phase-1d: jakarta migration (Spring 6, Jersey 3, Jetty 12)#759
Open
buggtb wants to merge 35 commits intodevelopmentfrom
Open
phase-1d: jakarta migration (Spring 6, Jersey 3, Jetty 12)#759buggtb wants to merge 35 commits intodevelopmentfrom
buggtb wants to merge 35 commits intodevelopmentfrom
Conversation
… namespaces Forks bumped: - mondrian: 4.8.0.0-SAIKU → 4.8.1.0-SAIKU-jakarta - olap4j-xmlaserver: 1.2.0-spicule-1 → 1.3.0-spicule-jakarta Dependency upgrades: - Spring 4.3.30 → 6.1.14 - Spring Security 4.2.20 → 6.3.4 - Jersey 1.19 → Jersey 3.1.9 (jakarta, glassfish) - javax.servlet-api 3.1 → jakarta.servlet-api 6.0 - jakarta.xml.bind-api 2.3 → 4.0; jaxb-runtime 4.0.5 - commons-io 2.4 → 2.17 - jetty maven plugin: 6.1 → jetty-ee10 12.0.16 Source migration: - javax.servlet.* → jakarta.servlet.* - javax.ws.rs.* → jakarta.ws.rs.* - javax.xml.bind.* → jakarta.xml.bind.* - com.sun.jersey.multipart → org.glassfish.jersey.media.multipart - Removed @component from Jersey resource classes (XML beans are source of truth) - web.xml: Jersey 1 SpringServlet → Jersey 3 ServletContainer with MultiPartFeature - web.xml: dropped dead SaikuWebdavServlet (Jackrabbit) + H2Console (javax.servlet) - Spring XSDs: pinned versions → unversioned - SessionService now explicitly saves SecurityContext via HttpSessionSecurityContextRepository (Spring Security 6 behavioural change) - users.properties passwords prefixed with {noop} for Spring Security 6 - BasicRepositoryResource2: finished VFS → java.io.File refactor - JdbcUserDAO: fixed classpath ../database-queries.properties lookup - Removed dead MondrianVFS bean from saiku-beans.xml Status: local build green, REST GET /info + POST /session (login) returning 200 with admin roles. Spring Security 6 session persistence on authenticated endpoints needs follow-up — GET /session works logged-in but hasRole-gated /rest/saiku/admin/* still returns 401.
- Add SaikuJerseyApplication ResourceConfig that pulls @Path-annotated Spring beans from root WebApplicationContext (skipping CGLIB scopedTarget.* entries) - web.xml: swap Jersey package-scan for jakarta.ws.rs.Application init-param - saiku-beans.xml: add <aop:scoped-proxy/> to all session/request scoped beans so singletons can reference them from outside an active scope - Make no-arg constructors public on resource + service classes CGLIB needs to subclass for scope proxies (OlapQueryService, ThinQueryService, OlapDiscoverService + dependent resources) - Exclude transitive javax.servlet / jersey 1.x / jetty 6 from hadoop-common - Drop jackson-jaxrs-json-provider (javax); jersey-media-json-jackson handles it Verified end-to-end: - GET /rest/saiku/info → 200 [] - POST /rest/saiku/session (admin/admin) → 200 + JSESSIONID - GET /rest/saiku/session → 200 with isadmin:true, roles [ROLE_ADMIN,ROLE_USER] - GET /rest/saiku/admin/datasources → 200 JSON array of cube connections
- New saiku-launcher module produces a single runnable jar (saiku-3.17.jar)
bundling the full Saiku webapp + embedded Jetty 12
- Picocli CLI with 'serve' subcommand: --port, --host, --context, --home
- Launcher creates saiku home directory structure (data/, repository/data/,
logs/, plugins/) and sets -Dsaiku.home for Spring placeholders
- saiku-beans.properties paths rewritten to ${saiku.home}/... tokens
- <context:property-placeholder> gets system-properties-mode=OVERRIDE so
-Dsaiku.home wins
- Database.initDB now expandSaikuHome()-s context-param URLs so the legacy
../../data relative paths resolve under the runtime home dir
- Foodmart/Earthquakes sample-data loaders catch missing files instead of
failing context init — allows shipping without the demo SQL/XML
- Make ClassPathRepositoryManager.bootstrap re-entrancy-safe
(bootstrapping flag) to prevent infinite recursion when data dir absent
- Make no-arg ctors public on resource + olap service classes so Spring CGLIB
scoped-proxy can subclass them
- sessionRepo demoted from session-scoped to singleton — legitimately a
container for the current HttpSession, not per-session state
- Add <aop:scoped-proxy/> to every session/request scoped bean so singletons
can hold a reference to them
- SaikuJerseyApplication filters scopedTarget.* beans so only the proxies
(not the real scoped beans) are registered with Jersey at init time
Verified:
- `mvn install` green across all modules (incl. saiku-launcher)
- `java -jar saiku-launcher/target/saiku-3.17.jar serve` boots cleanly in
an empty working dir
- `POST /rest/saiku/session` → 200 admin login, `GET /rest/saiku/session`
returns `{isadmin:true, roles:[ROLE_ADMIN, ROLE_USER], username:admin}`
- Multi-stage build: maven:3.9-temurin-21 → distroless java21 nonroot - Bundles saiku-launcher fat JAR as /app/saiku.jar, SAIKU_HOME volume - release.yml on tag: build JAR, build+push multi-arch image to GHCR, publish GitHub Release with JAR asset and generated notes - workflow_dispatch entrypoint for manual releases Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add saiku-native org.saiku.repository.RepositoryException and PathNotFoundException; swap all javax.jcr.* imports across IRepositoryManager, IDatasourceManager, ClassPathRepositoryManager, ClassPathResourceDatasourceManager, RepositoryDatasourceManager, AdminResource, MockRepositoryManager. - Strip Jackrabbit Node-based methods from Acl2; only File-backed variants were actually called from the repo manager. Removes javax.jcr.Node / PathNotFoundException / RepositoryException / jackrabbit-commons JcrUtils dependencies from the class. - Drop javax.jcr:jcr and org.apache.jackrabbit:jackrabbit-jcr-commons from the BOM and saiku-service pom. Fat JAR builds cleanly, REST interface verified end-to-end (GET /info 200, session login 200, session GET returns admin roles). FilesystemRepositoryManager/migrate CLI/roles.yml ACL/git-sync deferred to later iterations per the phase-2 plan's risk-ordered shipping plan. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ager Honest name for the manager that has always been filesystem-backed. Also drops the never-wired ClassPathResourceDatasourceManager (its only reference was a commented-out bean in saiku-beans.xml). Renames: - class ClassPathRepositoryManager -> FilesystemRepositoryManager - static accessor getClassPathRepositoryManager -> getInstance - delete ClassPathResourceDatasourceManager.java - remove stale classpathDsManager XML comment block Deferred Phase 2 tasks (plan 2026-04-19-phase-2-filesystem-repo.md): - 2.4 `saiku migrate jcr-to-fs` CLI: no Jackrabbit left to migrate from (purged in previous commit); skipped pending external demand. - 2.5 roles.yml ACL: existing JSON-backed Acl2 works; revisit when a real multi-role deployment needs it (Phase 7 OIDC). - 2.6 Optional git-sync: opt-in nice-to-have, no signal yet. Full `mvn clean package` green; REST verified end-to-end: GET /info 200, POST /session (admin) 200, GET /session returns admin roles, GET /admin/datasources 200. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New saiku-semantic module, a self-contained Java 21 / JUnit 5 slice: - IR records (CubeDefinition, Dimension, Level, Measure) as immutable Jackson-bound Java records. - CubeYamlParser reads YAML via jackson-dataformat-yaml, validates required fields, throws SemanticValidationException with the offending field name. - MondrianCompiler walks the IR and emits deterministic Mondrian Schema/Cube XML via XMLStreamWriter (byte-identical output for the same input — enables golden-file tests later). - Sample sales.yml fixture + 5 tests (parse, compile, determinism, validation failures). BOM gains jackson-dataformat-yaml and junit-jupiter 5.11.3. Module registered in saiku-core aggregator. Deferred: lint (JDBC introspection), infer, convert mondrian-to-yaml, hot reload, dbt/Cube importers, DuckDB example adapter, docs cookbook. Those layer on top of this core IR + compiler and can ship incrementally. Verified: mvn -pl saiku-core/saiku-semantic -am test (5/5 green), full clean package green, fat JAR REST smoke test passes (info, login, session, admin/datasources all 200). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Greenfield scaffold (legacy saiku-ui was deleted in 6bba36f): - Vite 6 + TypeScript 5.7 (strict) + React 18. - Design tokens in src/styles/tokens.css: spacing/radius/font/shadow scales plus light and dark colour scales. Respects prefers-color-scheme and a data-theme override persisted to localStorage. - Login form hits POST /rest/saiku/session, falls back to GET /session to read the canonical session JSON. Treats any response without username/roles as anonymous (the backend returns a stub session for unauthenticated GETs). - Workspace shell placeholder (Cubes sidebar + main pane) with clear next-slice markers for AG Grid pivot, Monaco editor, ECharts. - Vite dev server proxies /rest/* to SAIKU_API (default http://localhost:8080). npm run build: tsc --noEmit + vite build, 148 kB JS bundle (47 kB gzip). - README documents the dev flow. Verified end-to-end in Chrome against a live launcher (admin/admin -> Workspace with roles [ROLE_ADMIN, ROLE_USER]). Deferred: AG Grid pivot, Monaco, ECharts, Playwright, frontend-maven-plugin integration (so mvn package bundles dist/ into saiku-webapp). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The original Phase 4 (Vite/TS overlay on Backbone) and Phase 6 (SvelteKit rewrite) collapse into a single feature-complete SvelteKit port now that the legacy Backbone tree has already been deleted from the working tree. Changes: - Restore the legacy Backbone UI as `saiku-ui-legacy/` from commit 4fa2020^ as a read-only porting reference (399 files, 41 views, 24 modals, 16 models, 11 plugins). Deleted after the inventory hits 100%. - Wipe the React scaffold from 019d23d. - Scaffold `saiku-ui/` as SvelteKit 2 + Svelte 5 (runes) + TypeScript strict with @sveltejs/adapter-static. `npm run check` passes (0 errors, 0 warnings, 267 files). - Design tokens in `$lib/styles/tokens.css` — light/dark via prefers-color-scheme + data-theme override persisted to localStorage. - Session + theme stores using Svelte 5 runes ($state/$effect/$effect.root). - Reusable `Modal.svelte` primitive (backdrop + escape + snippets for body and footer, sizes sm/md/lg/xl). - LoginForm + Workspace shell + inline AboutModal ported. Login + session + logout + theme toggle + modal open/close all verified end-to-end in Chrome against a live launcher. - `docs/plans/2026-04-19-phase-4-sveltekit-port.md`: view-by-view inventory with ✅/❌ status and 12-slice ordering through full feature parity. Task #26 (Phase 6 SvelteKit rewrite) deleted — merged into this Phase 4. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ports legacy shape: cube <select> dropdown (not tree), measure/dimension
panels under it, workspace toolbar above the query canvas with the
action set from legacy WorkspaceToolbar.js + QueryToolbar.js, Columns /
Rows / Filter drop zones, grid placeholder.
- $lib/api/discover.ts: typed wrappers over GET /rest/saiku/{user}/
discover[/{connection}/{catalog}/{schema}/{cube}/{dimensions,measures}]
plus member/{member}/children.
- $lib/stores/datasources.svelte.ts: runes-based store with a `loaded`
one-shot latch so the $effect guard doesn't loop after empty
responses. metadata(cube) cache keyed by connection/catalog/schema/
cube signature.
- $lib/stores/selection.svelte.ts: currently-selected cube.
- session.logout() now clears datasources + selection.
- CubePicker.svelte: legacy-style optgroup-per-schema dropdown +
refresh button + empty state "No cubes available. Add a datasource
in the admin console."
- DimensionList.svelte: measures panel (grouped) + dimensions panel
with hierarchy/level children. Levels and measures marked draggable
(wiring in the query-execution slice).
- WorkspaceToolbar.svelte: legacy action set — New / Open / Save /
Save As / Run / Autorun / Non-empty / Swap / MDX / XLS / CSV / PDF.
Handlers log; hooked up to services in later slices.
- QueryCanvas.svelte: Columns / Rows / Filter dropzones + grid
placeholder that switches text based on cube selection.
Chrome-verified against the live launcher (no datasources attached):
login → dropdown resolves to "Select a cube", empty-state message
visible, toolbar + dropzones render in legacy layout. Metadata fetch
and actual population still need real datasources + query execution
in the next slice.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New reusable pieces:
- ConfirmModal.svelte — generic confirm with default/danger variants.
- Modal ports: WarningModal, OverwriteModal, AddFolderModal,
DeleteRepositoryObject, MoveRepositoryObject, ReportTitlesModal,
SessionErrorModal, SaveQueryModal, OpenDialogModal. Each cites its
legacy view source in a header comment.
- ToastStack.svelte + toasts runes store (info/success/warning/danger
with ttl + manual dismiss).
- repository REST client + runes store (list, get/save/delete/move
resource, flat + folders helpers).
WorkspaceToolbar now opens real modals instead of console.log:
- New — confirm-discard when dirty
- Open — loads repository tree, opens OpenDialogModal
- Save / Save As — opens SaveQueryModal preseeded with folders
- Run — opens WarningModal ("wires in next slice")
- Swap / MDX / XLS / CSV / PDF — toast placeholders
Chrome-verified against live launcher (no datasources): Save opens the
SaveQuery modal with the folder dropdown + name preset; Run opens the
warning modal; Cancel dismisses. Toast stack renders for Swap/MDX/Export.
Known svelte-check warnings (4, non-blocking): prop-capture
suggestions in SaveQueryModal/MoveRepositoryObject/ReportTitlesModal
— harmless given the $effect(open) resync pattern used.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- $lib/api/query.ts — ThinQuery / ThinQueryModel / ThinAxis /
ThinHierarchy / ThinMeasure types, newQuery() factory matching the
legacy SaikuOlapQueryTemplate shape, executeQuery() POST to
/rest/saiku/api/query/execute, cancelQuery().
- $lib/stores/query.svelte.ts — runes-backed query state:
initFor(cube), includeLevel(axis, drop) moving hierarchies between
axes, removeHierarchy, addMeasure / removeMeasure, swapAxes,
setNonEmpty, run() with hasRunnableShape guard.
- DimensionList draggable rows now set
application/x-saiku-level and application/x-saiku-measure MIME
payloads on dragstart.
- QueryCanvas replaces the static dropzones with real ondragover /
ondrop handlers that route payloads to query.includeLevel /
query.addMeasure. Placed hierarchies + measures render as
dismissible chips.
- CellsetTable.svelte renders QueryResult.cellset as a sticky-header
HTML table (row-header + column-header + data-cell styling).
AG Grid swap-in happens in a later slice.
- WorkspaceToolbar: Run now calls query.run() (falls back to a warning
modal when no cube is selected). Swap calls query.swapAxes() and
re-runs if Autorun is on. Non-empty toggle syncs onto ROWS and
COLUMNS nonEmpty flags. XLS/CSV/PDF buttons open
/rest/saiku/api/query/{name}/export/{format} in a new tab.
Type-check clean (0 errors; 4 advisory warnings about prop-capture
patterns in modals, harmless due to $effect(open) resync).
This slice cannot be fully Chrome-verified without a real OLAP
datasource — the fat JAR launcher boots with no datasources attached
by default. The shape + API wiring match the server contract; running
against an attached Mondrian datasource will exercise the full path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each modal carries a header comment citing its legacy view source under saiku-ui-legacy/js/saiku/views/. All use the Modal primitive and share the design tokens. Added: - MDXModal — textarea-backed MDX view/run (Monaco swap in a later slice). - CalculatedMemberModal — name / parent hierarchy / formula / format. - FilterModal — custom Order/Filter/TopCount/BottomCount/Limit MDX with ASC/BASC/DESC/BDESC sort on Order. - CustomFilterModal — measure filter by comparator + single or BETWEEN value pair. - DateFilterModal — YEAR/QUARTER/MONTH/WEEK to-date, LAST_N_*, CUSTOM_RANGE with date pickers. - MeasuresModal — search + checkbox picker with calc badge. - SelectionsModal — include/exclude members for a level with search, select-all / clear, member descriptions, open-date-filter hook. - ParentMemberSelectorModal — searchable member list. - DrillthroughModal — dimension + measure checkboxes + maxRows with Run and Export CSV actions. - DrillAcrossModal — target cube picker. - GrowthModal — compare against previous / first / specific member. - FormatAsPercentageModal — base axis + all/selected scope. - PermissionsModal — PRIVATE / SECURED / PUBLIC + per-role READ/WRITE/GRANT grid. - DataSourcesModal — connection/catalog/schema/cube table + refresh. These are UI shells wired against typed props + onSave/onApply/onRun hooks. Query-state and member-list integration lands per-modal in subsequent slices when the pieces they depend on exist (e.g. SelectionsModal needs live member fetching via /discover/cube/member/ children, which comes with drag-drop axis context). svelte-check 0 errors, 17 advisory warnings (prop-capture pattern, harmless given $effect(open) resync). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
/admin SvelteKit route with tabbed Users / Datasources / Schemas /
Logs panes. Admin-gated (isadmin check, fallback LoginForm or
forbidden banner).
- $lib/api/admin.ts — typed wrappers over /rest/saiku/admin/{users,
datasources,schema,log/{name},version}, full CRUD + refresh.
- UsersAdmin — list + modal create/edit with username/email/password
fields and ROLE_USER / ROLE_ADMIN role toggles + delete with
confirmation.
- DatasourcesAdmin — list + modal create/edit of name/driver/location/
type/schema/username/password + refresh + delete with confirmation.
- SchemasAdmin — list + XML file/textarea upload modal + delete with
confirmation.
- LogsAdmin — log-name picker + fetch + monospace viewer.
- Topbar gains Admin and Workspace nav buttons for admin users.
Chrome-verified: login → /admin renders the tabbed shell with a
graceful "users → 500" error callout (the launcher's H2-backed user
DAO is not seeded in the fresh-home case), + Add user button opens
the user modal, tab nav is active. The admin REST endpoints
themselves behave like the legacy UI (same API surface) so once the
deployment has a populated users DB and real datasources, the panes
populate.
svelte-check 0 errors; advisory warnings remain at 18 (prop-capture
pattern, harmless).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Install ag-grid-community ^33 and echarts ^5.
- $lib/views/cellsetUtils.ts: parseCellset(QueryResult) → headerRowCount,
rowHeaderColCount, row/column categories, per-body-row splits.
toNumber(cell) tolerant to saiku's raw-prop / comma formatting.
- CellsetTable.svelte rewired on top of AG Grid createGrid() with
pinned row-header columns, sticky column-header rows, numeric
right-align + tabular-nums for data cells, sort + filter enabled.
Grid is re-optioned on every QueryResult change via setGridOption.
Dark theme via legacy grid theme + local CSS overrides pulling
design tokens.
- ChartView.svelte: ECharts wrapper rendering ten chart types matching
the legacy CCC surface:
bar, stackedBar, line, stackedLine, area, stackedArea,
pie, heatmap, radar, scatter
Dark palette tuned to tokens. ResizeObserver keeps the chart sized
to its host.
- chartTypes.ts: ChartType enum + CHART_TYPES picker catalogue
(separate module so QueryCanvas can import the list without pulling
Svelte component exports).
- QueryCanvas gains a Grid / Chart view toggle and a chart-type <select>
when Chart is active.
- Delete saiku-ui-legacy/js/saiku/plugins/CCC_Chart and CCC_Chart2
(proto-vis / pvc / tipsy bundles). CCC is not ported; ECharts
replaces its surface.
svelte-check 0 errors, 19 advisory warnings (prop-capture patterns,
unchanged).
Chrome-verified: login, workspace renders with the new view-toggle
wired in, no console errors. Grid/Chart toggle and each ECharts
type will render against a live Mondrian datasource once one is
attached — the launcher ships without foodmart h2 data in the bundle
so the runtime end-to-end path isn't exercisable from this tree.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Save flow: - query store gains savedPath + loadFromJson(raw, path) + markSaved(path). - Toolbar Save posts ThinQuery JSON to POST /api/repository/resource via saveResource(path, content). Re-save on known savedPath skips the modal; Save As always reprompts. - SaveQueryModal defaults the folder + name from the current savedPath and appends .saiku to the file name if the user didn't type one. Repository tree refreshes after a successful save so the Open dialog picks up the new file immediately. Open flow: - Toolbar Open loads /api/repository (saiku type filter) if not cached, presents OpenDialogModal. Selecting a file GETs /api/repository/resource?file=..., feeds the JSON into query.loadFromJson(), triggers a re-run when Autorun is on. MDX: - Toolbar MDX button opens MDXModal seeded with the current query.mdx (if MDX-typed) or the server-enriched mdx on the last result. - Run MDX switches the query type to MDX, stores the text, and calls query.run() against /api/query/execute. Chrome-verified: login → Open loads the real repository tree (folders datasources / etc / theme / homes / legacyreports populate from the live launcher backend), filter + empty state render. Save round-trip against a populated datasource remains a backend concern (server currently 500s on /resource without an attached datasource — confirmed via curl; unrelated to the UI change). svelte-check 0 errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…gins
- discover.ts: listLevelMembers() + listRootMembers() wrappers.
- QueryCanvas chips now split into a label button (opens
SelectionsModal for that axis/hierarchy/level) and a separate
remove (×) button. Member list hydrates from
/discover/.../levels/{level}, falling back to
/hierarchies/{h}/rootmembers on error; toast on final failure.
- Selections save is wired at the UI level; persistence into the
ThinQueryModel's per-level `selection` field lands when the query
IR exposes the accessor (current shape supports it but the helper
isn't public yet).
- platform.svelte.ts store: fullscreen-change listener driving the
topbar toggle, loadVersion() against /admin/version, anonymous
info ping on mount (legacy plugins/Statistics equivalent).
- Topbar gains a fullscreen toggle button.
- UpgradeBanner.svelte renders a warning strip when
platform.newVersionAvailable is true (version comparison is a
follow-up; dismissal persisted to localStorage).
svelte-check 0 errors.
Verified the surface locally: rendered via `npm run build` — bundle
clean. Live SelectionsModal member fetch exercises the /discover
path; against a launcher without attached datasources the fetch
returns 404/500 and the toast + fallback fire as designed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- $lib/i18n/{en,de}.json bundles: 70 keyed strings covering login,
topbar, cubes panel, dimension/measure panels, workspace toolbar,
query canvas prompts, view toggle, modal verbs, admin tabs, toast
messages, and the upgrade banner.
- $lib/stores/i18n.svelte.ts runes store: locale persisted to
localStorage, initial pick from navigator.language, applies
<html lang> on change, t(key, fallback) lookup.
- LocalePicker.svelte drops into the topbar next to the theme toggle;
legacy parity for the ChangeLocale plugin without touching the
global bean factory model.
- Migrated visible strings in LoginForm, CubePicker, WorkspaceToolbar,
and the layout topbar to t(). Remaining modals + admin panes still
carry English literals (sweep layered in follow-up slices).
Chrome-verified: picking "Deutsch" swaps the login form, the topbar
labels, and the <html lang> attribute live without a reload. English
↔ German round-trip preserves all bindings.
svelte-check 0 errors, 19 advisory warnings (prop-capture pattern,
unchanged).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Install monaco-editor. $lib/monaco/worker-env.ts registers the base editor worker via Vite's ?worker import (no JSON/TS/CSS language services — we don't need them for MDX). - $lib/monaco/mdx-lang.ts registers a Monarch MDX tokenizer with ~50 OLAP keywords (SELECT, FROM, ON COLUMNS, CROSSJOIN, NON EMPTY, TOPCOUNT, HIERARCHIZE, CURRENTMEMBER, ...) + bracket-matched identifier tokens for [Dim].[Level] references. - MonacoEditor.svelte wrapper: dynamic-imports monaco only on mount (keeps the initial bundle lean), tracks data-theme for vs / vs-dark swap on theme toggle. - MDXModal drops the textarea and renders MonacoEditor when open. Strings routed through i18n. - Tour.svelte: 4-step interactive walkthrough highlighting #cubes-select → sidebar → dropzones → view toggle. Dismissal persisted to localStorage; restart() exported for future "Replay tour" action. - String sweep continues: OpenDialogModal, SaveQueryModal, Workspace/AboutModal, QueryCanvas labels + view toggle + prompts + empty states now resolve through i18n.t(). svelte-check 0 errors. Monaco splits into its own chunk (verified by build output). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…co, i18n sweep
- saiku-webapp pom.xml: wire frontend-maven-plugin to
install-node-and-npm (v22.11), run npm ci + npm run build in
saiku-ui/ during generate-resources, then maven-war-plugin
webResources copies saiku-ui/dist to /ui/ inside saiku.war. After
this slice `mvn package` produces a WAR that serves the new
SvelteKit UI at /ui/ alongside the JAX-RS /rest/saiku/* endpoints.
- Selection persistence lands in the query store:
setLevelSelection(hierarchy, level, members[], type) writes
level.selection = { type, members:[{uniqueName}] } into the
ThinHierarchy, and getLevelSelection() reads it back. Clearing
selections deletes the field (matches legacy behaviour).
- SelectionsModal is now fully wired: opening a chip loads existing
members from the query state, saving writes back through
setLevelSelection, optional auto-rerun when query has a runnable
shape.
- FilterModal + CalculatedMemberModal swap textareas for MonacoEditor
(mdx language). Autoloaded only when the modal is open.
- String sweep continues through WarningModal + AddFolderModal +
FilterModal + CalculatedMemberModal footers.
svelte-check 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…modal - Add saiku-ui/src/lib/i18n/es.json and fr.json (~70 keys each, mirroring en / de). LOCALES picker now lists English, Deutsch, Español, Français; locale detector accepts any of the four from navigator.language and localStorage. - $lib/api/http.ts: installAuthInterceptor() wraps globalThis.fetch so any /rest/saiku/* response with 401/403 (except /session itself) notifies registered listeners. Installed on layout mount. - Layout now listens for auth failure and pops SessionErrorModal with a Reload button. Matches legacy SessionErrorModal.js behaviour. svelte-check 0 errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- saiku-webapp pom passes SAIKU_BASE_PATH=/ui to npm run build so SvelteKit emits asset URLs rooted at /ui/; adapter-static's relative paths mode keeps links portable. - svelte.config.js reads SAIKU_BASE_PATH env var (defaults to empty for local npm run dev on :5173). - webapp index.jsp now renders as a minimal dark splash that immediately redirects to ui/ (instead of the stale Pentaho serverdocs link). - applicationContext-saiku.xml adds `<security:http pattern="..." security="none">` chains for /ui/**, /, /index.jsp, /style.css, /images/** so the static SPA assets load without Basic-auth pre-prompting the user. - SaikuLauncher prints Workspace + Admin + REST URLs on boot. - saiku-ui/.gitignore drops the frontend-maven-plugin-managed .node/ directory. Verified end-to-end: `mvn -pl saiku-launcher -am package` produces a self-contained fat JAR. Launching it: Workspace : http://0.0.0.0:8095/ui/ Admin : http://0.0.0.0:8095/ui/admin REST API : http://0.0.0.0:8095/rest/saiku/ Chrome loads /ui/ → renders SvelteKit login page (tokens, dark mode, Sign-in form); /rest/saiku/session 200s on admin/admin. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- i18n bundles gain 19 keys covering admin tabs' add / edit / delete / refresh / empty state / per-user column headers (username, email, roles, password), LogsAdmin fetch/loading/idle strings, and modal titles for Warning / Delete / Overwrite / AddFolder / SessionError + Reload button. - UsersAdmin, DatasourcesAdmin, SchemasAdmin, LogsAdmin + /admin page tabs + forbidden banner now resolve via i18n.t(). - WarningModal, AddFolderModal, SessionErrorModal titles + session reload button migrated. svelte-check 0 errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The legacy Backbone/gulp/jQuery UI tree (~380 files, 41 views, 24 modals, 16 models, 11 plugins) has been ported to SvelteKit across slices 1 through 15. Retaining it as a read-only reference is no longer useful — the history is in git (commit 4fa2020^ has the last intact copy) and the porting inventory doc cross-references every view that landed in the new tree. Summary of what the new tree covers: - Shell: login, topbar, fullscreen, theme, locale, upgrade banner, session-error interceptor, tour walkthrough. - Workspace: cube picker + measure/dimension panels with live fetch, drag-drop to Columns/Rows/Filter axes, chip-click member selections with persistence into ThinQueryModel, swap axes, non-empty, autorun. - Query execution: POST /rest/saiku/api/query/execute, cellset rendering via AG Grid Community, ECharts chart view with ten chart types (bar/stacked bar/line/stacked line/area/stacked area/pie/ heatmap/radar/scatter), Grid/Chart view toggle. - Modals: About, SaveQuery, OpenDialog, Warning, Confirm, Overwrite, AddFolder, Delete, Move, ReportTitles, SessionError, MDX (Monaco), Calculated member (Monaco), Filter (Monaco), CustomFilter, DateFilter, Measures, Selections (with live member fetch), ParentMember, Drillthrough, DrillAcross, Growth, FormatAsPercentage, Permissions, DataSources. - Repository: list, get, save, delete, move resources; query save/open round-trip through /api/repository. - Export: XLS / CSV / PDF wired to /rest/saiku/api/query/{name}/export. - Admin: Users / Datasources / Schemas / Logs with full CRUD + modals. - i18n: en, de, es, fr bundles; LocalePicker with navigator auto-detect + localStorage persistence; <html lang> sync. - Deployment: frontend-maven-plugin runs `npm ci && npm run build` during `mvn package`; maven-war-plugin bundles dist/ into the WAR at /ui/; saiku-launcher fat JAR serves it at http://host:port/ui/ and prints Workspace + Admin + REST URLs on boot. Deliberately not ported: - BIServer plugin (Pentaho integration — consumed only by the p7.1 plugin module which was deleted in 51ecff0 / 4fa2020). - CCC_Chart / CCC_Chart2 plugins — replaced wholesale by ECharts. - intro.js, qtip, tipsy, spectrum, fancybox, notify, ozpIwc vendor bundles — superseded by the Tour + Toast + Modal primitives and native browser UI. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Admin/Workspace topbar <a href> hardcoded "/" which escaped the /ui mount
on direct click, producing 403 from Jetty's root servlet. Use {base} so
hrefs resolve to /ui/admin and /ui/ when deployed under SAIKU_BASE_PATH.
- adapter-static was only emitting dist/index.html; direct navigation to
/ui/admin/ returned 404. Enable prerender + trailingSlash:"always" so
dist/admin/index.html is generated for hard loads and deep-links.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three regressions were hiding the foodmart cube from the workspace:
1. JdbcUserDAO loaded database-queries.properties from the classpath, but
the file only lived under WEB-INF/; nothing staged a classpath copy
after the Jackrabbit removal. Admin /users returned 500 ("SQL must
not be null"). Restore a classpath-root copy under
saiku-webapp/src/main/resources.
2. SaikuLauncher only mkdir'd saiku-home/data; it never staged the
bundled FoodMart4.xml or foodmart_h2.sql script. loadFoodmart
swallowed the resulting FileNotFoundException and the datasource
list stayed empty. Bundle both assets under
saiku-launcher/src/main/resources/seed/ and extract/unzip on boot.
3. commons-vfs2 2.10 transitively needs httpclient 4.3+ (TrustStrategy);
BOM pinned httpcore 4.3-alpha1 + httpclient 4.2.5 so Mondrian
ApacheVfs2VirtualFileHandler crashed on every schema URL. Bump to
httpclient 4.5.14 + httpcore 4.4.16.
Also replace mondrian:///datasources/foodmart4.xml with a plain
file:// URL pointing at the staged FoodMart4.xml, since the custom
MondrianVFS provider was deleted in Phase 2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- UI now sends ThinHierarchy.name as the hierarchy unique name (matching backend convention in Thin.java) and drops the non-existent `uniqueName` field that was failing Jackson deserialization with "Unrecognized field" on /rest/saiku/api/query/execute. - LevelDrop payload adds dimensionName so ThinHierarchy.dimension can be populated from drag-drop. - Guard against query.getAxis(PAGES) returning null in Fat.convertAxis so the PAGES axis (sent empty by the new UI) no longer NPEs at qaxis.setNonEmpty(). - Workspace layout/viewport + jersey tracing fix carry-overs from the same testing pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rips out @ag-grid-community (v35 theming API kept fighting the layout — 2px root wrapper nested inside a 538px host, plus no nested row semantics for OLAP cellsets) in favour of a Svelte-native HTML table that mirrors legacy Saiku's rendering and interactions: - Renders parseCellset output as a semantic <table> with th.row / th.row_null / th.col / td.data classes matching the legacy SaikuTableRenderer markup. - Nested row headers: repeated parent values collapse into row_null cells (Mondrian's empty-string convention + uniquename fallback for older cellsets) so USA/CA appear once across their children. - Sticky column header (thead) and sticky pinned first-column row headers for horizontal scrolling. - Custom right-click context menu on row/column header cells with Keep Only / Include Level ▸ / Remove Level ▸ / Filter Level… — drills the query store, disables already-used levels, and emits a saiku-filter-level CustomEvent that QueryCanvas wires to the SelectionsModal. - cellsetUtils.rowHeaderDisplay() computes the is-null/merged state per row-header cell up front, keeping the template simple. Removes @finos/perspective* deps and vite tweaks that were briefly trialled — WASM bundle + shadow-DOM theming didn't give us nested expand/collapse or a usable custom context menu without writing a bespoke plugin, so the pragmatic call was a thin native table. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lifts autorun state into the query store and routes every mutator (include/remove level, add/remove measure, set selection, swap axes, non-empty, setMeasures) through a markDirty() helper that fires the query when autorun is on — previously the toolbar owned local autorun state so removing a chip via the × button set dirty=true but never triggered a re-run. Adds sidebar affordances that mirror the legacy view: - Measures panel header gets a gear (bulk toggle via MeasuresModal) and a + button (opens the Calculated Measure wizard). Saving the wizard appends to queryModel.calculatedMeasures and adds the new [Measures].[<name>] member to the visible measure list. - Per-level gear (visible on hover) opens SelectionsModal pre- targeted to that hierarchy/level so filters can be set without dragging to an axis first. Turns the calculated-measure dialog into a proper wizard with name/parent/format fields, a preview of the generated MDX, and three palettes (measures to insert, operator chips, function templates with caret positions) that insert at the current textarea cursor. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Legacy Saiku treated every axis chip the same — tap the label to edit
member selections, click the × to remove. The rewrite had wired that
split for COLUMNS/ROWS but the FILTER drop-zone was still a single
button that removed on click, making members impossible to filter via
that axis.
Split the FILTER chip into the same label + × pattern so clicking the
label routes through openSelections("FILTER", hier).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- New chart types: donut, treemap, sunburst, bubble (sized scatter), waterfall (transparent-spacer stacked bar). CHART_TYPES now carries a `group` so the picker renders as grouped optgroups (Bars / Lines / Proportional / Matrix / Points). - ChartOptions (title, axis labels, legend on/off + position, trend line) threaded through ChartView with a new ChartEditorModal opened via the ⚙ button on the chart view toolbar. Trend line supports linear regression, moving average and weighted moving average with a configurable period; overlay on the first measure of line/stacked line/area charts. - Rebuilt WorkspaceToolbar into grouped buttons + Tools and Export dropdowns so the export triplet and the rarely-used tools live behind proper menus. Tools ▾ now wires MDX, Drill across (DrillAcrossModal populated from datasources.connections) and Report titles (persists into queryModel.properties). - Cellset data cells now fire saiku-drillthrough on right-click; QueryCanvas listens and opens DrillthroughModal (dimensions / measures / max rows). Run goes through the new drillthrough API helper and renders the result in a new DrillthroughResultModal. Export CSV opens the backend's export path. Backend currently throws a classloader error on the endpoint under the fat-jar launcher (see memory); UI round-trip is correct. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mondrian's mondrian.rolap.SqlStatement#getWrappedResultSet() calls
Proxy.newProxyInstance(null, new Class<?>[]{ResultSet.class}, …) which
fails on JDK 17+ with "java.sql.ResultSet referenced from a method is
not visible from class loader: null" because java.sql moved into its
own named module and can no longer be resolved through the bootstrap
loader.
Shadow the class inside saiku-webapp/src/main/java so Servlet-spec
loader precedence (WEB-INF/classes wins over WEB-INF/lib) pulls in the
patched copy. Only the loader argument changes — we pick the thread
context classloader and fall back to ResultSet.class.getClassLoader()
then ClassLoader.getSystemClassLoader().
Also sets saiku-webapp's maven-compiler-plugin to the parent pom's
source/target (21) so the shadowed source actually compiles; the
parent default of 1.6 is no longer accepted by modern javac.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Installs lucide-svelte and swaps the emoji/text icons across the workspace toolbar, the left sidebar panel actions, and the dimension/measure tree for consistent stroke-style SVG glyphs. - Toolbar: icon-only File buttons (new / open / save / save-as), primary Run button with Play icon + label, Swap icon, Tools menu (wrench), Export menu (download). Dropdown items get their own icons (MDX braces, Drill-across arrows, Report tags, XLS/CSV/PDF file-type icons). - Sidebar: Settings2 gear + Plus for the Measures header, ChevronDown/ Right for every tree twisty, Sigma for measure rows (coloured like the legacy UI), Folder for dimensions, GitFork for hierarchy nodes, Minus for leaf levels, Filter for the per-level gear (fades in on hover). - i18n strings stripped of inline emoji so the SVG is the icon and the label stays clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sparklines + split Run button with autorun/non-empty dropdown - Right-click a measure chip on the COLUMNS drop-zone for a context menu (CustomFilterModal for "Filter by value…", FormatAsPercentage for "Format as percentage…", GrowthModal for "Growth calculation…", plus Remove). Format % and Growth write a calculated measure into queryModel.calculatedMeasures and add the resulting member to the visible measures list. Filter by value wraps the ROWS axis in a FILTER() expression. - Right-click a hierarchy chip for Edit-selections + Remove. - Each axis drop-zone grows a "⋯" action button (visible on hover) that opens FilterModal pre-configured with the expression type — Order/Filter/TopCount/BottomCount/Limit — and writes the MDX onto the axis (or sortOrder / sortEvaluationLiteral for Order). - New view-toggle buttons: Grid · Chart · Stats · Sparkline · Sparkbar. StatsView computes Count/Min/Max/Sum/Avg/StdDev per data column. SparklineView renders per-row SVG sparklines or sparkbars with first/last/min/max summary columns. - Reworked the Run button into a split control: primary Run on the left, chevron caret on the right that drops down with Autorun and Non-empty as menu items. Click-outside collapses the run menu. - New lightweight ContextMenu component in $lib/components reused by measure/hierarchy/axis menus. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sparklines: sparkline/sparkbar are no longer a separate view. CellsetTable
now takes an optional \`spark\` prop ("line" | "bar") and appends a
Sparkline/Sparkbar column to the existing grid — every body row gets a
tiny inline SVG chart built from that row's data cells, matching the
legacy saiku-ui behaviour. The orphan SparklineView.svelte is removed.
Measure-chip + per-axis MDX: the first pass wrote Axis(0).Item(0)…
expressions which Mondrian rejects inside WITH MEMBER and FILTER. All
four handlers now dereference the primary ROWS/COLUMNS hierarchy
directly:
- Filter by value: FILTER(<primary rows level>.Members, measure op value)
- Format as percentage: IIF(([m], <hier>.CurrentMember.Parent) = 0, null,
[m] / ([m], <hier>.CurrentMember.Parent)) with ROWS / COLUMNS /
GRAND_TOTAL scopes. Warns when the chosen axis has no hierarchy.
- Growth calculation: (m - (m, <rowsHier>.CurrentMember.PrevMember)) /
prev with a null-guard. "first" uses FirstSibling, "specific" accepts
a user-supplied member unique name.
- Per-axis FilterModal (Order / Filter / TopCount / BottomCount /
Limit): builds the base set from the axis hierarchies' deepest level
and CROSSJOINs when multiple. Pre-seeds the expression field with a
per-type placeholder (e.g. "10, [Measures].[Unit Sales]" for
TopCount) instead of leaking the previous axis.mdx into a new type.
Verified end-to-end in-session: Filter-by-value narrowed 3 states to 2,
Format-as-% emitted CA=28.02% / WA=46.62%, Growth rendered
WA=83.81% (vs prev. member CA), and TopCount on State Province sorted
the three rows by Unit Sales DESC.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fork changes (installed locally)
jakarta-servletof~/Projects/saiku/mondrian-saiku)jakarta-servletof~/Projects/olap4j-xmlaserver)Source migration (sed-driven)
javax.servlet.*→jakarta.servlet.*javax.ws.rs.*→jakarta.ws.rs.*javax.xml.bind.*→jakarta.xml.bind.*com.sun.jersey.multipart→org.glassfish.jersey.media.multipartStatus
mvn -DskipTests installgreen across all modulesGET /rest/saiku/info→ 200POST /rest/saiku/session(login) → 200 with{"isadmin":true,"roles":["ROLE_ADMIN","ROLE_USER"]}GET /rest/saiku/admin/datasources→ 401 — Spring Security 6 session-context persistence on subsequent requests needs follow-up. TheHttpSessionSecurityContextRepository.saveContextis called on login but the filter chain isn't restoring it; likely a<security:http>namespace vs SecurityFilterChain-bean issue in Spring Security 6.Test plan
mvn installsucceedsmvn jetty:runboots without ContextLoader errors/inforeturns 200🤖 Generated with Claude Code