Version 7.13 Release #294

Merged
allan merged 125 commits from version7-13 into main 2026-09-03 14:16:03 +00:00
Owner

Highlights

Formula-based data quality rules

A major new capability: HARDFORMULA / SOFTFORMULA DQ rule types, backed by Handsontable's formulas plugin (HyperFormula).

  • Formula rules substitute column names with row-relative cell references and resolve DC.USER_NAME / DC.ORIG_VALUE to literal values; HARDFORMULA reuses the existing READONLY mechanism.
  • Live DC.ROW_STATUS via a hidden EDIT_STATUS column, so formulas can react to a row's edit state; the row-header +/-/~ indicator translates visual→physical row index so it stays correct when the grid is sorted.
  • Inserting rows uses hot.alter() so formula values realign instead of going stale.
  • UserService is now providedIn: 'root', fixing DC.USER_NAME (and other lazy-module readers) always seeing an unset user.
  • The column-info dropdown shows the applied formula.
  • Formula-overwritten cells are flagged and can be reverted; saveTable() submits the live computed value rather than the raw =... string.
  • Guards against EDIT_STATUS / dc.row_status colliding with a real column of the same name, and stops the client-only EDIT_STATUS column from being submitted to the backend during dynamic cell validation.

Cell revert

Revert one cells (or range of cells) to their original (current) value

Formatted / Unformatted toggle on the staging page

Mirrors the review page's toggle. getstagetable.sas now also returns SAS-formatted data (fmt_stagetable), and the UI picks between formatted and raw, falling back to raw when formatted data is absent. Added e2e coverage.

Viewbox resize on all edges

Viewboxes can now be dragged to resize from all four edges and corners (previously only the bottom-right corner). Resize handles sit above Handsontable's frozen header clones so clicks reach the handle rather than the table underneath.

Licence-key protocol mismatch warning

A licence key generated for one connection type (http/https) always fails to decrypt on the other. The mismatch is now detected client-side from the raw key text before submission, with a warning and the apply button disabled, instead of surfacing only as a generic "invalid key" error after a backend round trip.

Bug fixes

  • stage: ensure formats arrive from the backend in getstagetable.sas.
  • validations: bump core for the mp_validate fix.
  • CAS: support the REPLACE load type (plus docs).
  • startup: show the real startup-service response text when Viya returns a plain-text "Job error" body instead of JSON, instead of reporting every expected field as "not present".
  • editor: the column-info dropdown text can now be selected and copied.
  • editor: table-header buttons and title size to their content rather than fixed grid thirds, so the dataset name/row-count no longer wraps unnecessarily.
  • editor: dc.row_status / EDIT_STATUS is no longer submitted to the backend.

Dependencies / security

  • Resolved npm audit vulnerabilities in production dependencies (Angular HttpTransferCache cache-key ambiguity and i18n XSS advisories).
  • Aligned all @angular/* packages to the same lockstep version (20.3.27) so CI's strict npm ci passes.
  • Scoped the brace-expansion override to its actual vulnerable chain (fixes license-checker breakage).
  • Pinned babel-loader to resolve an unresolvable peer conflict; regenerated/patched the lockfile to fix npm ci sync errors.
  • Overrode nanoid to 3.3.18 in sas/ directly (rather than relying on @sasjs/cli's override, which doesn't propagate to consumers) so it survives future npm install runs.
## Highlights ### Formula-based data quality rules A major new capability: `HARDFORMULA` / `SOFTFORMULA` DQ rule types, backed by Handsontable's formulas plugin (HyperFormula). - Formula rules substitute column names with row-relative cell references and resolve `DC.USER_NAME` / `DC.ORIG_VALUE` to literal values; `HARDFORMULA` reuses the existing READONLY mechanism. - Live `DC.ROW_STATUS` via a hidden `EDIT_STATUS` column, so formulas can react to a row's edit state; the row-header +/-/~ indicator translates visual→physical row index so it stays correct when the grid is sorted. - Inserting rows uses `hot.alter()` so formula values realign instead of going stale. - `UserService` is now `providedIn: 'root'`, fixing `DC.USER_NAME` (and other lazy-module readers) always seeing an unset user. - The column-info dropdown shows the applied formula. - Formula-overwritten cells are flagged and can be reverted; `saveTable()` submits the live computed value rather than the raw `=...` string. - Guards against `EDIT_STATUS` / `dc.row_status` colliding with a real column of the same name, and stops the client-only `EDIT_STATUS` column from being submitted to the backend during dynamic cell validation. ### Cell revert Revert one cells (or range of cells) to their original (current) value ### Formatted / Unformatted toggle on the staging page Mirrors the review page's toggle. `getstagetable.sas` now also returns SAS-formatted data (`fmt_stagetable`), and the UI picks between formatted and raw, falling back to raw when formatted data is absent. Added e2e coverage. ### Viewbox resize on all edges Viewboxes can now be dragged to resize from all four edges and corners (previously only the bottom-right corner). Resize handles sit above Handsontable's frozen header clones so clicks reach the handle rather than the table underneath. ### Licence-key protocol mismatch warning A licence key generated for one connection type (http/https) always fails to decrypt on the other. The mismatch is now detected client-side from the raw key text before submission, with a warning and the apply button disabled, instead of surfacing only as a generic "invalid key" error after a backend round trip. ## Bug fixes - **stage:** ensure formats arrive from the backend in `getstagetable.sas`. - **validations:** bump core for the `mp_validate` fix. - **CAS:** support the REPLACE load type (plus docs). - **startup:** show the real startup-service response text when Viya returns a plain-text "Job error" body instead of JSON, instead of reporting every expected field as "not present". - **editor:** the column-info dropdown text can now be selected and copied. - **editor:** table-header buttons and title size to their content rather than fixed grid thirds, so the dataset name/row-count no longer wraps unnecessarily. - **editor:** `dc.row_status` / `EDIT_STATUS` is no longer submitted to the backend. ## Dependencies / security - Resolved npm audit vulnerabilities in production dependencies (Angular HttpTransferCache cache-key ambiguity and i18n XSS advisories). - Aligned all `@angular/*` packages to the same lockstep version (20.3.27) so CI's strict `npm ci` passes. - Scoped the `brace-expansion` override to its actual vulnerable chain (fixes license-checker breakage). - Pinned `babel-loader` to resolve an unresolvable peer conflict; regenerated/patched the lockfile to fix `npm ci` sync errors. - Overrode `nanoid` to 3.3.18 in `sas/` directly (rather than relying on `@sasjs/cli`'s override, which doesn't propagate to consumers) so it survives future `npm install` runs.
allan added 19 commits 2026-08-05 08:46:57 +00:00
Adds HARDFORMULA/SOFTFORMULA DQ rule types, backed by Handsontable's
formulas plugin (gated per-table via hasFormulaRules). parseFormulaRule
substitutes column names with row-relative cell references (quote-aware,
blank-boundary matching per spec) and resolves DC.USER_NAME/DC.ORIG_VALUE
to literal values; applyFormulaRules injects the computed formula per row,
reusing the existing READONLY mechanism for HARDFORMULA.

DC.ROW_STATUS/EDIT_STATUS deliberately deferred - would require prepending
COLHEADERS, which headerColumns has undocumented positional coupling to
elsewhere in editor.component.ts.
feat(formulas): live DC.ROW_STATUS, insert-row formula fixes, UserService singleton fix
Build / Build-and-ng-test (pull_request) Successful in 5m13s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m31s
Build / Build-and-test-development (pull_request) Successful in 21m56s
4a8c39b4c0
- addRow()/insertRowAtPosition() use hot.alter() so HARDFORMULA/SOFTFORMULA
  values realign on insert instead of going stale
- New hidden EDIT_STATUS column gives DC.ROW_STATUS a real, live cell
  reference; row-header +/-/~ indicator now translates visual->physical
  row index so it stays correct when the grid is sorted
- UserService is now providedIn: 'root' instead of module-scoped, fixing
  DC.USER_NAME (and any other lazy-module reader) always seeing an unset user
- Column-info dropdown shows the applied formula ("√x=<formula>")
chore: merge remote-tracking branch 'origin/main' into additional-validations-formulae
Build / Build-and-ng-test (pull_request) Successful in 5m7s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m44s
Build / Build-and-test-development (pull_request) Successful in 21m54s
bb808617f4
fix(formulas): avoid EDIT_STATUS colliding with a real column of that name
Build / Build-and-ng-test (pull_request) Successful in 5m15s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m48s
Build / Build-and-test-development (pull_request) Successful in 21m38s
acb97f4bfb
fix: CAS support for REPLACE type plus docs
Build / Build-and-test-development (pull_request) Successful in 22m24s
Build / Build-and-ng-test (pull_request) Successful in 5m50s
Lighthouse Checks / lighthouse (pull_request) Successful in 20m39s
ea05f07180
chore(test): test for mpe_targetloader update
Build / Build-and-ng-test (pull_request) Successful in 5m9s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m27s
Build / Build-and-test-development (pull_request) Successful in 22m4s
28104f83e1
feat(formulas): flag formula-overwritten cells with revert, harden dc.row_status against SAS name collisions
Build / Build-and-ng-test (pull_request) Failing after 1m49s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Successful in 21m51s
a8237b2881
- Formulas' HyperFormula sync resolves a dotted `data` key differently
  than getDataAtRowProp/datamap.get() - dataDotNotation: false is needed
  for the renamed dc.row_status column to work as a live cell reference
- saveTable() now submits the live computed value for a formula cell, not
  the raw '=...' string still sitting in dataSource
Merge branch 'version7-13' into additional-validations-formulae
Build / Build-and-ng-test (pull_request) Failing after 2m2s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Successful in 21m21s
b88b22684a
fix(deps): resolve npm audit vulnerabilities in production dependencies
Build / Build-and-ng-test (pull_request) Failing after 44s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 1m4s
257f69ccc6
- Angular packages resolve within their existing ^20.3.26 range up to
  20.3.27, which patches two advisories (HttpTransferCache cache-key
  ambiguity, i18n XSS via event-handler attributes)
- brace-expansion (via exceljs -> archiver -> readdir-glob -> minimatch)
  needed an override to ^5.0.9 - npm's resolver won't proactively bump a
  nested transitive dep that already satisfies its declared semver range,
  even when a patched version exists inside that same range
fix(deps): align @angular/* packages to the same lockstep version
Build / Build-and-ng-test (pull_request) Failing after 43s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 1m6s
24e6297187
npm audit fix only bumped @angular/core/common/compiler to 20.3.27 (the
packages with a direct advisory), leaving animations/forms/platform-browser*/
router at 20.3.26 - each peer-depends on the others' exact version, so the
mismatch passed a lenient `npm install` locally but failed CI's strict
`npm ci`. All seven now resolve to 20.3.27.
fix(deps): resolve npm audit findings and align @angular/* to the same lockstep version
Build / Build-and-ng-test (pull_request) Failing after 46s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 1m6s
b9e4b2733f
- Angular packages (core/common/compiler/animations/forms/
  platform-browser*/router/compiler-cli) all peer-depend on each other's
  exact version - npm audit fix only bumped the three with a direct
  advisory, leaving the rest mismatched; a plain `npm install` tolerated
  it but CI's strict `npm ci` didn't. All now resolve to 20.3.27.
- undici/fast-uri updated via npm audit fix; brace-expansion pinned to
  ^5.0.9 via overrides (npm's resolver wasn't proactively bumping a
  nested transitive dep that already satisfied its declared range)
fix(deps): regenerate package-lock.json to resolve npm ci sync errors
Build / Build-and-ng-test (pull_request) Failing after 49s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 1m6s
a501903e6d
Incremental `npm install <pkg>@version` patches left the lockfile
internally inconsistent enough that CI's `npm ci` rejected it as out of
sync with package.json (a long list of unrelated nested deps reported
missing). Regenerated from scratch instead of patching further; verified
with a clean `npm ci`, matching the resolved versions from the prior fix.
fix(deps): pin babel-loader to resolve an unresolvable peer conflict
Build / Build-and-ng-test (pull_request) Failing after 46s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 1m11s
3bf3bf0dde
chore: regenerated package-lock.json with correct env
Build / Build-and-ng-test (pull_request) Failing after 48s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 1m6s
44aac556b9
chore: trying package.json and package-lock.json from the main branch
Build / Build-and-ng-test (pull_request) Failing after 2m2s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Successful in 20m44s
d841163dd0
fix(deps): resolve npm audit findings via direct lockfile patch, not regeneration
Build / Build-and-ng-test (pull_request) Failing after 2m0s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 2m12s
4d1bfa6343
Fixed by patching only the 12 needed entries directly (9 @angular/*
packages to 20.3.27, brace-expansion to 5.0.9, fast-uri to 3.1.5, undici to 6.28.0) with exact registry resolved/integrity metadata, leaving the other 1925 packages untouched.
fix(deps): scope the brace-expansion override to its actual vulnerable chain
Build / Build-and-ng-test (pull_request) Successful in 5m39s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m16s
Build / Build-and-test-development (pull_request) Successful in 22m18s
f9d061c489
An unscoped "brace-expansion": "^5.0.9" override applies to every
occurrence in the tree, not just the vulnerable one - it was silently
replacing the 1.1.16 that glob's bundled minimatch@3.x actually needs,
breaking license-checker with "TypeError: expand is not a function".
Scoped it to the readdir-glob chain (exceljs -> archiver -> readdir-glob
-> minimatch@10.x), the same nested-override pattern already used here
for exceljs's own archiver/unzipper pins.
Merge pull request 'Additional validations formulae' (#290) from additional-validations-formulae into version7-13
Build / Build-and-ng-test (pull_request) Successful in 5m30s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m11s
Build / Build-and-test-development (pull_request) Successful in 23m19s
7d6dc652be
Reviewed-on: #290
allan added 4 commits 2026-08-05 14:36:27 +00:00
feat(stage): add Formatted/Unformatted toggle to the staging page
Build / Build-and-ng-test (pull_request) Successful in 4m53s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m0s
Build / Build-and-test-development (pull_request) Successful in 20m17s
74c38e1641
- Mirrors the review page's toggle: getstagetable.sas now also webouts
  fmt_stagetable (SAS-formatted), selectFormattedRows() picks between it
  and the raw stagetable, falling back to raw if fmt data is absent
- Adds stage.cy.ts e2e coverage and wires it into both build.yaml and
  release.yaml's cypress --spec lists (previously out of sync with
  each other on viewbox.cy.ts too)
Merge branch 'version7-13' into issue-148
Lighthouse Checks / lighthouse (pull_request) Successful in 21m27s
Build / Build-and-ng-test (pull_request) Failing after 1m48s
Build / Build-and-test-development (pull_request) Skipped
fc6c9f844f
Merge branch 'version7-13' into issue-148
Build / Build-and-ng-test (pull_request) Successful in 5m57s
Lighthouse Checks / lighthouse (pull_request) Successful in 25m2s
Build / Build-and-test-development (pull_request) Successful in 23m12s
48d06ec6a7
Merge pull request 'feat(stage): add Formatted/Unformatted toggle to the staging page' (#293) from issue-148 into version7-13
Build / Build-and-ng-test (pull_request) Successful in 5m35s
Lighthouse Checks / lighthouse (pull_request) Successful in 24m55s
Build / Build-and-test-development (pull_request) Successful in 23m59s
3c9eba4df4
Reviewed-on: #293
allan added 4 commits 2026-08-07 08:54:34 +00:00
Merge branch 'version7-13' into issue289
Build / Build-and-ng-test (pull_request) Successful in 6m35s
Lighthouse Checks / lighthouse (pull_request) Canceled after 15m33s
Build / Build-and-test-development (pull_request) Failing after 1m43s
ff8f10d533
Merge branch 'version7-13' into issue289
Build / Build-and-ng-test (pull_request) Successful in 5m27s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m8s
Build / Build-and-test-development (pull_request) Successful in 22m32s
93a513a15f
fix: adding formula types
Build / Build-and-test-development (pull_request) Canceled after 0s
Build / Build-and-ng-test (pull_request) Canceled after 2m17s
Lighthouse Checks / lighthouse (pull_request) Canceled after 4s
c07a01a38d
Merge pull request 'fix: CAS support for REPLACE type plus docs' (#291) from issue289 into version7-13
Build / Build-and-ng-test (pull_request) Canceled after 0s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Canceled after 0s
0815fcea2c
Reviewed-on: #291
allan added 6 commits 2026-08-07 08:54:58 +00:00
feat(editor): generalize cell revert to any overwritten value, not just formulas
Build / Build-and-ng-test (pull_request) Successful in 5m7s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m7s
Build / Build-and-test-development (pull_request) Successful in 23m29s
07d586da52
fix(editor): size table-header buttons and title to content, not fixed grid thirds
Build / Build-and-ng-test (pull_request) Successful in 7m24s
Lighthouse Checks / lighthouse (pull_request) Successful in 23m26s
Build / Build-and-test-development (pull_request) Successful in 22m52s
d0a7561f1a
The Filter/Edit/Upload buttons used btn-block (width: 100%), stretching
them wider than the Cancel/Add Row/Submit buttons shown during edit.
The back/viewboxes, title, and action-button columns were also locked to
equal 12-col grid thirds regardless of actual content, so the dataset
name/row-count wrapped even with visible free space on either side.
Switched the outer columns to size to their content (clr-col-*-auto) and
let the title column flex-grow into whatever space is left.
fix(editor): let the column info dropdown's text be selected and copied
Build / Build-and-ng-test (pull_request) Successful in 5m24s
Lighthouse Checks / lighthouse (pull_request) Successful in 23m1s
Build / Build-and-test-development (pull_request) Successful in 23m23s
1e516f4012
Handsontable's Menu widget closes on any mouseup inside an item and
unconditionally preventDefault()s contextmenu, even for the info item,
which has no callback and exists purely to show read-only column details.
That made its text impossible to select or right-click-copy. Marked the
item isCommand: false and stop mousedown/mouseup/contextmenu/selectstart
from bubbling past its rendered content to the menu's own listeners.
Merge branch 'version7-13' into editor-improvements
Build / Build-and-ng-test (pull_request) Successful in 5m9s
Build / Build-and-test-development (pull_request) Canceled after 4m18s
Lighthouse Checks / lighthouse (pull_request) Canceled after 9m32s
eea9fbd938
Merge branch 'version7-13' into editor-improvements
Lighthouse Checks / lighthouse (pull_request) Canceled after 0s
Build / Build-and-ng-test (pull_request) Canceled after 0s
Build / Build-and-test-development (pull_request) Canceled after 0s
72cae704df
Merge pull request 'Revert any overwritten cell' (#296) from editor-improvements into version7-13
Build / Build-and-ng-test (pull_request) Canceled after 0s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Canceled after 0s
aa4b3b94cd
Reviewed-on: #296
allan added 3 commits 2026-08-07 08:57:03 +00:00
fix(startup): show the real startupservice response text on a malformed reply
Build / Build-and-ng-test (pull_request) Successful in 5m17s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m4s
Build / Build-and-test-development (pull_request) Successful in 22m42s
9a1b7d0f52
A misconfigured Viya computeTasks deployment makes the Compute service
return a plain-text "Job error" body instead of JSON. @sasjs/adapter can't
parse it and resolves with that raw text as adapterResponse, so both
startup and manual deploy validation reported every expected field as
"not present" without ever showing the actual cause. Added
getMalformedAdapterResponseMessage to detect a non-object adapterResponse
and surface its real text instead.
Merge branch 'version7-13' into issue-277
Build / Build-and-test-development (pull_request) Canceled after 0s
Build / Build-and-ng-test (pull_request) Canceled after 4s
Lighthouse Checks / lighthouse (pull_request) Canceled after 0s
84cce5eb0f
Merge pull request 'Show the real startupservice response text on a malformed reply' (#297) from issue-277 into version7-13
Build / Build-and-ng-test (pull_request) Successful in 5m15s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m18s
Build / Build-and-test-development (pull_request) Successful in 23m14s
1807d66ea1
Reviewed-on: #297
allan added 1 commit 2026-08-07 11:45:39 +00:00
fix: ensuring formats arrive from backend in getstagetable.sas
Build / Build-and-ng-test (pull_request) Successful in 5m4s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m34s
Build / Build-and-test-development (pull_request) Successful in 23m26s
daacb49c8a
allan added 1 commit 2026-08-07 13:38:25 +00:00
fix: bump core for mp_validate fix
Build / Build-and-ng-test (pull_request) Successful in 5m8s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m43s
Build / Build-and-test-development (pull_request) Successful in 23m34s
5a44b2804f
allan added 1 commit 2026-08-09 18:56:09 +00:00
chore: updated release docs
Build / Build-and-ng-test (pull_request) Failing after 1m46s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Successful in 20m35s
76bb83d860
allan added 5 commits 2026-08-11 08:08:10 +00:00
feat(viewboxes): add drag-to-resize on all four edges and corners
Build / Build-and-ng-test (pull_request) Failing after 1m41s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Successful in 20m36s
e5a5bf2144
Adds right, left, bottom, and bottom-left-corner resize handles
alongside the existing bottom-right corner, and raises resize handle
z-index above Handsontable's frozen header clones so clicks reach the
handle instead of the table underneath it.
fix: fixed npm vuln
Build / Build-and-ng-test (pull_request) Successful in 5m16s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m7s
Build / Build-and-test-development (pull_request) Successful in 23m55s
26b55b1bde
chore: merge remote-tracking branch 'origin/version7-13' into issue-190
Build / Build-and-ng-test (pull_request) Failing after 2m17s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Successful in 21m8s
7c33839b99
fix(deps): override nanoid to 3.3.18 in sas/
Build / Build-and-ng-test (pull_request) Successful in 5m8s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m12s
Build / Build-and-test-development (pull_request) Successful in 24m44s
d57ae03fc4
@sasjs/cli's own nanoid override doesn't propagate to consumers - npm
only reads overrides from the root project being installed. Adding it
here directly, rather than a raw lockfile patch, so it survives future
npm install runs instead of silently reverting again.
Merge pull request 'feat(viewboxes): add drag-to-resize on all four edges and corners' (#298) from issue-190 into version7-13
Build / Build-and-ng-test (pull_request) Successful in 5m6s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m33s
Build / Build-and-test-development (pull_request) Successful in 25m28s
fabb9e5bfd
Reviewed-on: #298
allan added 3 commits 2026-08-11 09:34:35 +00:00
fix(editor): stop dc.row_status from being submitted to the backend
Build / Build-and-ng-test (pull_request) Failing after 1m54s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Successful in 22m34s
dc0f6a7baa
dynamicCellValidation() built its editors/getdynamiccolvals payload
from a raw dataSource row clone, which always carries the client-only
EDIT_STATUS column - unlike saveTable()'s submit path, nothing
stripped it before the request went out. Extracted the strip logic
(previously private to classifyRow) into a shared withoutEditStatus
util and applied it here too.
chore: merge remote-tracking branch 'origin/version7-13' into formula-status-fix
Build / Build-and-ng-test (pull_request) Successful in 5m30s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m5s
Build / Build-and-test-development (pull_request) Successful in 25m18s
5615e6b0db
Merge pull request 'fix(editor): stop dc.row_status from being submitted to the backend' (#299) from formula-status-fix into version7-13
Build / Build-and-ng-test (pull_request) Successful in 5m11s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m7s
Build / Build-and-test-development (pull_request) Successful in 25m0s
20a007622c
Reviewed-on: #299
allan added 2 commits 2026-08-11 17:33:05 +00:00
feat(licensing): warn and block applying a licence key generated for the wrong protocol
Build / Build-and-ng-test (pull_request) Successful in 5m16s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m18s
Build / Build-and-test-development (pull_request) Successful in 25m0s
41680e2ecc
A key generated for one connection type (http/https) always fails to
decrypt on the other, surfacing only as a generic "invalid key" error
after a round trip to the backend. Detects the mismatch client-side
from the raw key text before the user submits, and disables the apply
button while it's showing.
Merge pull request 'feat(licensing): warn and block applying a licence key generated for the wrong protocol' (#300) from issue-133 into version7-13
Build / Build-and-ng-test (pull_request) Successful in 5m22s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m23s
Build / Build-and-test-development (pull_request) Successful in 24m54s
c0fec25c4d
Reviewed-on: #300
allan added 3 commits 2026-08-12 11:55:15 +00:00
feat(editor): color row-header status cells and switch modified symbol to ±
Build / Build-and-ng-test (pull_request) Successful in 6m2s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m18s
Build / Build-and-test-development (pull_request) Successful in 26m6s
e15f2c2a34
Merge branch 'version7-13' into formula-status-fix
Build / Build-and-ng-test (pull_request) Successful in 5m58s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m2s
Build / Build-and-test-development (pull_request) Successful in 25m24s
8209c1cb49
Merge pull request 'feat(editor): colour row-header status cells and switch modified symbol to ±' (#301) from formula-status-fix into version7-13
Build / Build-and-ng-test (pull_request) Successful in 5m14s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m21s
Build / Build-and-test-development (pull_request) Successful in 25m11s
9f4b357108
Reviewed-on: #301
allan added 1 commit 2026-08-12 20:39:39 +00:00
chore(tidy up): remove trailing space and deprecated logic
Build / Build-and-ng-test (pull_request) Successful in 5m2s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m22s
Build / Build-and-test-development (pull_request) Successful in 24m50s
9cd976495b
hermes left a comment
Collaborator

Hermes Agent Code Review

Verdict: Comment — no blocking issues found; a few warnings and suggestions below.

This is a large, well-structured release PR (94 files, +7189/-3009). The new formula-based DQ rules feature is the core change, with supporting cell revert, formatted/unformatted staging toggle, viewbox resize on all edges, and licence-key protocol mismatch warning. The extraction of logic into small, well-tested utility functions is good practice.


Critical

None.


Warnings

W1 — quoteLiteral does not escape embedded double quotes (parseFormulaRule.ts:38-39)

const quoteLiteral = (value: string | number | undefined): string =>
  `"${value ?? ''}"`

When DC.USER_NAME or DC.ORIG_VALUE contains a double quote character (e.g. a username like john"doe or a cell value with "), the resulting formula string will have an unescaped quote inside a quoted literal, producing a malformed HyperFormula expression. This could cause the formula to silently fail to evaluate, or in a worst case, alter the formula's semantics. The values come from SAS data / user session, so while admin-controlled in most deployments, a user with a quote in their name is plausible.

Suggested fix:

const quoteLiteral = (value: string | number | undefined): string =>
  `"${String(value ?? '').replace(/"/g, '""')}"`

W2 — Number(rawValueText) can produce NaN in revert callback (editor.component.ts:96-100)

hot.setDataAtRowProp(
  row,
  prop,
  isNumericCol ? Number(rawValueText) : rawValueText
)

If a numeric column's original value was stored in the comment as a non-numeric string (unlikely but possible if the comment was somehow corrupted or the raw SAS value was a special missing like .S), Number(rawValueText) returns NaN, which would then be written into the cell. There's no guard against this. Consider falling back to the raw text or validating before conversion.

W3 — Missing mp_abort between dc_assignlib and mp_getcols in getstagetable.sas (lines 48-52)

%dc_assignlib(READ,&base_lib)
%mp_getcols(&base_lib..&base_ds,outds=work.basecols)

If mpe_submit has no row matching &table_id (e.g. stale table_id, race condition), &base_lib and &base_ds remain empty. dc_assignlib(READ,) and mp_getcols(..,.) will fail, but the mp_abort that catches this only runs after mp_applyformats — several steps later. While &syscc will be set and eventually caught, adding an explicit abort immediately after the data _null_ step (or after dc_assignlib) would provide a clearer error message and prevent confusing intermediate errors.

W4 — addRow() with empty dataSource passes index - 1 to hot.alter() (editor.component.ts:1444-1452)

const newIndex = this.dataSource.length  // 0 if empty
hot.alter('insert_row_below', newIndex - 1, 1)  // insert_row_below at -1
this.dataSource[newIndex].noLinkOption = true     // dataSource[0]

When dataSource.length === 0, newIndex - 1 is -1. Handsontable's alter() does treat -1 as "insert at the end" which works, but this.dataSource[newIndex] immediately after assumes alter() synchronously spliced into dataSource. If the formulas plugin's beforeCreateRow hook interferes or the data binding is async, this could throw. The beforeCreateRow hook does check addingNewRow (set to true before), so it should be fine in practice — but this is fragile and worth a defensive guard.


Suggestions

S1 — buildColInfoHtml does not HTML-escape formulaValue (col-info-html.ts:35)

html += `<br>√x=${formula}`

formulaValue comes from RULE_VALUE in MPE_VALIDATIONS, which is admin-controlled. However, the same function already interpolates colName, hardRegexValue, etc. without escaping, so this is consistent with existing behavior. Still, if any of these values contain < or >, they would be interpreted as HTML. Consider escaping all interpolated values in this function in a future cleanup.

S2 — getstagetable.sas — no mp_abort if base_lib/base_ds are empty after the DATA step

Related to W3. A simple guard would be:

%mp_abort(iftrue= (&base_lib= or &base_ds=)
  ,mac=&_program..sas
  ,msg=%str(No mpe_submit record found for table_id=&table_id)
)

S3 — Migration script 20260807_v7.13_release.sas — max(selectbox_rk) on empty table

select max(selectbox_rk) into: maxrk
  from &dclib..mpe_selectbox;

If mpe_selectbox were ever empty, &maxrk resolves to missing (.), and &maxrk+1 is also missing. The INSERT would then set selectbox_rk=.. This is unlikely in a running DC instance (the table is always populated), but mpe_makedata.sas uses %mf_increment(rk) which handles this more robustly. Not a real-world issue, just a consistency note.

S4 — detectLicenceKeyProtocolMismatch — heuristic-based detection is fragile

The protocol mismatch detection relies on the assumption that HTTPS-format keys always have different licence/activation values and HTTP-format keys always have identical values. This is a structural assumption about the key generation process. If the key generation logic ever changes (e.g. HTTP keys also get distinct values), this detection would silently produce false positives. Worth a comment in the key generation code pointing to this dependency, or a more robust detection method.

S5 — mpe_targetloader.sas CAS branch — dcsession is referenced without being established in this macro

data &libds (append=yes) / sessref=dcsession;
  set casuser.&tmpds;
run;

The dcsession CAS session is presumably established upstream (in the calling service). If it's not, this will fail with a confusing error. This is pre-existing convention, but a comment noting the prerequisite would help maintainers.


Looks Good

  • Utility extraction: classifyRow, expandCellRanges, findOverwrittenCells, getRevertableCols, getStableFormulaBaseCols, parseFormulaRule, etc. are all well-separated pure functions with thorough unit tests. The test coverage is excellent — each utility has spec files covering edge cases (composite keys, reverse drag selection, null values, loosely-equal comparisons).
  • EDIT_STATUS column design: Using dc.row_status (with a period) as a client-only column name that can never collide with a real SAS variable is a clever and safe approach. The dataDotNotation: false setting and the withoutEditStatus stripping before backend submission are correctly handled.
  • Cell revert feature: The comment-based "original value" tracking is well-thought-out. PK-matched comparison via findOverwrittenCells correctly handles row reordering. The live afterChange sync keeps comments and EDIT_STATUS up to date.
  • SAS changes: The mp_abort(mode=INCLUDE) additions in restore.sas, stagedata.sas, postdata.sas, getdata.sas, etc. correctly replace the deprecated mf_abort.error file-check pattern with the modern @sasjs/core approach. The CAS REPLACE branch in mpe_targetloader.sas with varchar casting and deleteRows truncation is well-structured with appropriate mp_abort guards before destructive operations.
  • Viewbox resize: The migration from CDK cdkDrag to native pointer events for resize handles is a solid improvement. The endActiveResize cleanup in ngOnDestroy prevents listener leaks. The resizeFromLeftEdge read-back of actual width (to respect CSS min-width) is a nice touch.
  • Licence protocol mismatch: Clean implementation with good unit test coverage. The use of window.crypto.subtle availability (rather than location.protocol) correctly handles localhost.
  • Cypress test coverage: 30+ new editor tests covering HARDFORMULA/SOFTFORMULA, row insert with formulas, DC.* variables, revert scenarios (single cell, range, column, mixed), row header symbols, sorting alignment, and submission. Stage and licensing tests also added.

Reviewed by Hermes Agent

## Hermes Agent Code Review **Verdict: Comment** — no blocking issues found; a few warnings and suggestions below. This is a large, well-structured release PR (94 files, +7189/-3009). The new formula-based DQ rules feature is the core change, with supporting cell revert, formatted/unformatted staging toggle, viewbox resize on all edges, and licence-key protocol mismatch warning. The extraction of logic into small, well-tested utility functions is good practice. --- ### Critical None. --- ### Warnings **W1 — `quoteLiteral` does not escape embedded double quotes (parseFormulaRule.ts:38-39)** ```ts const quoteLiteral = (value: string | number | undefined): string => `"${value ?? ''}"` ``` When `DC.USER_NAME` or `DC.ORIG_VALUE` contains a double quote character (e.g. a username like `john"doe` or a cell value with `"`), the resulting formula string will have an unescaped quote inside a quoted literal, producing a malformed HyperFormula expression. This could cause the formula to silently fail to evaluate, or in a worst case, alter the formula's semantics. The values come from SAS data / user session, so while admin-controlled in most deployments, a user with a quote in their name is plausible. **Suggested fix:** ```ts const quoteLiteral = (value: string | number | undefined): string => `"${String(value ?? '').replace(/"/g, '""')}"` ``` **W2 — `Number(rawValueText)` can produce `NaN` in revert callback (editor.component.ts:96-100)** ```ts hot.setDataAtRowProp( row, prop, isNumericCol ? Number(rawValueText) : rawValueText ) ``` If a numeric column's original value was stored in the comment as a non-numeric string (unlikely but possible if the comment was somehow corrupted or the raw SAS value was a special missing like `.S`), `Number(rawValueText)` returns `NaN`, which would then be written into the cell. There's no guard against this. Consider falling back to the raw text or validating before conversion. **W3 — Missing `mp_abort` between `dc_assignlib` and `mp_getcols` in getstagetable.sas (lines 48-52)** ```sas %dc_assignlib(READ,&base_lib) %mp_getcols(&base_lib..&base_ds,outds=work.basecols) ``` If `mpe_submit` has no row matching `&table_id` (e.g. stale table_id, race condition), `&base_lib` and `&base_ds` remain empty. `dc_assignlib(READ,)` and `mp_getcols(..,.)` will fail, but the `mp_abort` that catches this only runs *after* `mp_applyformats` — several steps later. While `&syscc` will be set and eventually caught, adding an explicit abort immediately after the `data _null_` step (or after `dc_assignlib`) would provide a clearer error message and prevent confusing intermediate errors. **W4 — `addRow()` with empty `dataSource` passes `index - 1` to `hot.alter()` (editor.component.ts:1444-1452)** ```ts const newIndex = this.dataSource.length // 0 if empty hot.alter('insert_row_below', newIndex - 1, 1) // insert_row_below at -1 this.dataSource[newIndex].noLinkOption = true // dataSource[0] ``` When `dataSource.length === 0`, `newIndex - 1` is `-1`. Handsontable's `alter()` does treat `-1` as "insert at the end" which works, but `this.dataSource[newIndex]` immediately after assumes `alter()` synchronously spliced into `dataSource`. If the formulas plugin's `beforeCreateRow` hook interferes or the data binding is async, this could throw. The `beforeCreateRow` hook does check `addingNewRow` (set to `true` before), so it should be fine in practice — but this is fragile and worth a defensive guard. --- ### Suggestions **S1 — `buildColInfoHtml` does not HTML-escape `formulaValue` (col-info-html.ts:35)** ```ts html += `<br>√x=${formula}` ``` `formulaValue` comes from `RULE_VALUE` in `MPE_VALIDATIONS`, which is admin-controlled. However, the same function already interpolates `colName`, `hardRegexValue`, etc. without escaping, so this is consistent with existing behavior. Still, if any of these values contain `<` or `>`, they would be interpreted as HTML. Consider escaping all interpolated values in this function in a future cleanup. **S2 — `getstagetable.sas` — no `mp_abort` if `base_lib`/`base_ds` are empty after the DATA step** Related to W3. A simple guard would be: ```sas %mp_abort(iftrue= (&base_lib= or &base_ds=) ,mac=&_program..sas ,msg=%str(No mpe_submit record found for table_id=&table_id) ) ``` **S3 — Migration script `20260807_v7.13_release.sas` — `max(selectbox_rk)` on empty table** ```sas select max(selectbox_rk) into: maxrk from &dclib..mpe_selectbox; ``` If `mpe_selectbox` were ever empty, `&maxrk` resolves to missing (`.`), and `&maxrk+1` is also missing. The INSERT would then set `selectbox_rk=.`. This is unlikely in a running DC instance (the table is always populated), but `mpe_makedata.sas` uses `%mf_increment(rk)` which handles this more robustly. Not a real-world issue, just a consistency note. **S4 — `detectLicenceKeyProtocolMismatch` — heuristic-based detection is fragile** The protocol mismatch detection relies on the assumption that HTTPS-format keys always have different licence/activation values and HTTP-format keys always have identical values. This is a structural assumption about the key generation process. If the key generation logic ever changes (e.g. HTTP keys also get distinct values), this detection would silently produce false positives. Worth a comment in the key generation code pointing to this dependency, or a more robust detection method. **S5 — `mpe_targetloader.sas` CAS branch — `dcsession` is referenced without being established in this macro** ```sas data &libds (append=yes) / sessref=dcsession; set casuser.&tmpds; run; ``` The `dcsession` CAS session is presumably established upstream (in the calling service). If it's not, this will fail with a confusing error. This is pre-existing convention, but a comment noting the prerequisite would help maintainers. --- ### Looks Good - **Utility extraction**: classifyRow, expandCellRanges, findOverwrittenCells, getRevertableCols, getStableFormulaBaseCols, parseFormulaRule, etc. are all well-separated pure functions with thorough unit tests. The test coverage is excellent — each utility has spec files covering edge cases (composite keys, reverse drag selection, null values, loosely-equal comparisons). - **EDIT_STATUS column design**: Using `dc.row_status` (with a period) as a client-only column name that can never collide with a real SAS variable is a clever and safe approach. The `dataDotNotation: false` setting and the `withoutEditStatus` stripping before backend submission are correctly handled. - **Cell revert feature**: The comment-based "original value" tracking is well-thought-out. PK-matched comparison via `findOverwrittenCells` correctly handles row reordering. The live `afterChange` sync keeps comments and EDIT_STATUS up to date. - **SAS changes**: The `mp_abort(mode=INCLUDE)` additions in restore.sas, stagedata.sas, postdata.sas, getdata.sas, etc. correctly replace the deprecated `mf_abort.error` file-check pattern with the modern `@sasjs/core` approach. The CAS REPLACE branch in `mpe_targetloader.sas` with varchar casting and `deleteRows` truncation is well-structured with appropriate `mp_abort` guards before destructive operations. - **Viewbox resize**: The migration from CDK `cdkDrag` to native pointer events for resize handles is a solid improvement. The `endActiveResize` cleanup in `ngOnDestroy` prevents listener leaks. The `resizeFromLeftEdge` read-back of actual width (to respect CSS `min-width`) is a nice touch. - **Licence protocol mismatch**: Clean implementation with good unit test coverage. The use of `window.crypto.subtle` availability (rather than `location.protocol`) correctly handles localhost. - **Cypress test coverage**: 30+ new editor tests covering HARDFORMULA/SOFTFORMULA, row insert with formulas, DC.* variables, revert scenarios (single cell, range, column, mixed), row header symbols, sorting alignment, and submission. Stage and licensing tests also added. --- Reviewed by Hermes Agent
Collaborator

W2 — Number(rawValueText) can produce NaN

If the comment text after the prefix is not a valid number (e.g. a SAS special missing like .S), Number() returns NaN which gets written into the cell. Consider a guard:

const numericValue = Number(rawValueText)
hot.setDataAtRowProp(
  row, prop,
  isNaN(numericValue) ? rawValueText : numericValue
)
**W2 — `Number(rawValueText)` can produce `NaN`** If the comment text after the prefix is not a valid number (e.g. a SAS special missing like `.S`), `Number()` returns `NaN` which gets written into the cell. Consider a guard: ```ts const numericValue = Number(rawValueText) hot.setDataAtRowProp( row, prop, isNaN(numericValue) ? rawValueText : numericValue ) ```
Collaborator

W4 — addRow() with empty dataSource

When dataSource.length === 0, newIndex - 1 is -1. Handsontable treats this as "insert at end" so it works, but this.dataSource[newIndex] immediately after assumes alter() synchronously spliced the row. A defensive guard (if (this.dataSource[newIndex])) would prevent a crash if the data binding ever becomes async.

**W4 — `addRow()` with empty `dataSource`** When `dataSource.length === 0`, `newIndex - 1` is `-1`. Handsontable treats this as "insert at end" so it works, but `this.dataSource[newIndex]` immediately after assumes `alter()` synchronously spliced the row. A defensive guard (`if (this.dataSource[newIndex])`) would prevent a crash if the data binding ever becomes async.
Collaborator

W1 — quoteLiteral does not escape embedded double quotes

If DC.USER_NAME or DC.ORIG_VALUE contains a " character, the resulting formula string will have an unescaped quote inside a quoted literal, producing a malformed HyperFormula expression.

Suggested fix:

const quoteLiteral = (value: string | number | undefined): string =>
  `"${String(value ?? '').replace(/"/g, '""')}"`
**W1 — `quoteLiteral` does not escape embedded double quotes** If `DC.USER_NAME` or `DC.ORIG_VALUE` contains a `"` character, the resulting formula string will have an unescaped quote inside a quoted literal, producing a malformed HyperFormula expression. **Suggested fix:** ```ts const quoteLiteral = (value: string | number | undefined): string => `"${String(value ?? '').replace(/"/g, '""')}"` ```
Collaborator

W3 — Missing mp_abort between dc_assignlib and mp_getcols

If mpe_submit has no row matching &table_id, &base_lib/&base_ds remain empty. The abort that catches this only runs after mp_applyformats — several steps later. Consider adding an explicit guard here:

%mp_abort(iftrue= (&base_lib= or &base_ds=)
  ,mac=&_program..sas
  ,msg=%str(No mpe_submit record found for table_id=&table_id)
)
**W3 — Missing `mp_abort` between `dc_assignlib` and `mp_getcols`** If `mpe_submit` has no row matching `&table_id`, `&base_lib`/`&base_ds` remain empty. The abort that catches this only runs after `mp_applyformats` — several steps later. Consider adding an explicit guard here: ```sas %mp_abort(iftrue= (&base_lib= or &base_ds=) ,mac=&_program..sas ,msg=%str(No mpe_submit record found for table_id=&table_id) ) ```
allan marked this conversation as resolved
allan added 1 commit 2026-08-14 07:19:23 +00:00
chore: adding additional mp_abort per per review
Build / Build-and-ng-test (pull_request) Successful in 5m43s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m38s
Build / Build-and-test-development (pull_request) Successful in 24m54s
067c08765b
hermes left a comment
Collaborator

Hermes Agent Code Review

Verdict: Approve

This commit (067c087 - "chore: adding additional mp_abort per per review") addresses prior review feedback: it adds a null-check guard to getstagetable.sas for the case where no mpe_submit record is found for the given table_id.

Critical

None.

Warnings

None.

Suggestions

getstagetable.sas lines 45-48 - guard placement and syscc interaction
The data step that reads mpe_submit (lines 39-44) could set &syscc to a non-zero value if it encounters an error (e.g. mpe_submit locked or missing), in which case the existing mp_abort at line 29 (checking &syscc ne 0 after the stagetable load) would already have fired. However, if mpe_submit exists but simply has no matching TABLE_ID row, &syscc stays 0 and base_lib/base_ds remain empty - which is exactly the scenario this new guard catches. The guard is correctly placed after the data step and before dc_assignlib(READ,&base_lib) (line 50), which would otherwise fail or behave unpredictably with an empty libref. Good defensive addition.

One minor consideration: the error message No mpe_submit record found for table_id=&table_id will surface to the end user via mp_abort. Confirm this is the desired UX (exposing the internal table_id value) vs. a more generic message. This is a stylistic choice, not a defect.

Looks Good

  • The guard prevents dc_assignlib from being called with an empty &base_lib, which would produce a confusing low-level SAS error rather than a clear abort message.
  • mp_abort is already in the file's documented @li macro list (line 10), so no doc header update is needed.
  • The mac=&_program..sas parameter correctly identifies the service in the abort message for debugging.

Reviewed by Hermes Agent

## Hermes Agent Code Review **Verdict: Approve** This commit (`067c087` - "chore: adding additional mp_abort per per review") addresses prior review feedback: it adds a null-check guard to `getstagetable.sas` for the case where no `mpe_submit` record is found for the given `table_id`. ### Critical None. ### Warnings None. ### Suggestions **getstagetable.sas lines 45-48 - guard placement and syscc interaction** The data step that reads `mpe_submit` (lines 39-44) could set `&syscc` to a non-zero value if it encounters an error (e.g. `mpe_submit` locked or missing), in which case the existing `mp_abort` at line 29 (checking `&syscc ne 0` after the `stagetable` load) would already have fired. However, if `mpe_submit` exists but simply has no matching `TABLE_ID` row, `&syscc` stays 0 and `base_lib`/`base_ds` remain empty - which is exactly the scenario this new guard catches. The guard is correctly placed *after* the data step and *before* `dc_assignlib(READ,&base_lib)` (line 50), which would otherwise fail or behave unpredictably with an empty libref. Good defensive addition. One minor consideration: the error message `No mpe_submit record found for table_id=&table_id` will surface to the end user via `mp_abort`. Confirm this is the desired UX (exposing the internal `table_id` value) vs. a more generic message. This is a stylistic choice, not a defect. ### Looks Good - The guard prevents `dc_assignlib` from being called with an empty `&base_lib`, which would produce a confusing low-level SAS error rather than a clear abort message. - `mp_abort` is already in the file's documented `@li` macro list (line 10), so no doc header update is needed. - The `mac=&_program..sas` parameter correctly identifies the service in the abort message for debugging. Reviewed by Hermes Agent
Collaborator

The error message exposes the internal table_id value to the end user via mp_abort. Consider whether a more generic message (e.g. "staged table could not be located") is preferable for end-user UX, or if exposing table_id is intentional for support/debugging purposes. Stylistic choice, not a defect.

The error message exposes the internal `table_id` value to the end user via `mp_abort`. Consider whether a more generic message (e.g. "staged table could not be located") is preferable for end-user UX, or if exposing `table_id` is intentional for support/debugging purposes. Stylistic choice, not a defect.
allan marked this conversation as resolved
Collaborator

Test Coverage Report

Coverage measured via npx @sasjs/cli c -t server-ci against the server-ci target.

Category Covered Total Percentage
Services 20 60 33%
Macros 12 42 29%
Overall 32 102 31%

The compile step completed successfully (server-ci target). A non-fatal warning was emitted about webSourcePath: client/dist not existing - expected in this SAS-only checkout context and does not affect coverage measurement.

Note: This PR is primarily a client-side (Angular) release. The SAS-side change in this commit is a single defensive mp_abort guard added to getstagetable.sas. The coverage figures above reflect the SAS test suite only; client-side Karma/Cypress coverage is run separately in CI.

Generated by Hermes Agent

## Test Coverage Report Coverage measured via `npx @sasjs/cli c -t server-ci` against the `server-ci` target. | Category | Covered | Total | Percentage | |----------|---------|-------|------------| | Services | 20 | 60 | 33% | | Macros | 12 | 42 | 29% | | **Overall** | **32** | **102** | **31%** | The compile step completed successfully (`server-ci` target). A non-fatal warning was emitted about `webSourcePath: client/dist` not existing - expected in this SAS-only checkout context and does not affect coverage measurement. Note: This PR is primarily a client-side (Angular) release. The SAS-side change in this commit is a single defensive `mp_abort` guard added to `getstagetable.sas`. The coverage figures above reflect the SAS test suite only; client-side Karma/Cypress coverage is run separately in CI. Generated by Hermes Agent
allan added 3 commits 2026-08-14 11:05:20 +00:00
fix(core): bump to v5 (breaking change)
Build / Build-and-ng-test (pull_request) Successful in 5m12s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m15s
Build / Build-and-test-development (pull_request) Successful in 24m56s
59e9e96f5a
fix: mp_execute dep
Build / Build-and-ng-test (pull_request) Successful in 5m6s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m24s
Build / Build-and-test-development (pull_request) Successful in 25m2s
023c29f00f
Merge pull request 'fix(core): bump to v5 (breaking change)' (#304) from corebump into version7-13
Build / Build-and-ng-test (pull_request) Successful in 5m6s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m22s
Build / Build-and-test-development (pull_request) Successful in 24m38s
9b2e0df2b0
Reviewed-on: #304
Collaborator

Test Coverage Report

Coverage measured via npx @sasjs/cli c -t server-ci against the server-ci target.

Category Covered Total Percentage
Services 22 60 37%
Macros 12 42 29%
Overall 34 102 33%

The compile completed successfully. The streamWeb web-app compile step errored (client/dist does not exist in this review environment — the Angular client was not built), but this does not affect the SAS service/macro coverage figures above.

Notes

  • Coverage increased slightly from the previous review (Services 33%→37%, Overall 31%→33%), reflecting the two new test files added in the core v5 bump: exportdb.test.sas and refreshcatalog.test.sas.
  • The core v5 migration renamed mp_testservice→mp_execute and mp_getddl→mp_ds2ddl; all test files were updated accordingly.

Generated by Hermes Agent

## Test Coverage Report Coverage measured via `npx @sasjs/cli c -t server-ci` against the `server-ci` target. | Category | Covered | Total | Percentage | |----------|---------|-------|------------| | Services | 22 | 60 | 37% | | Macros | 12 | 42 | 29% | | **Overall** | **34** | **102** | **33%** | The compile completed successfully. The `streamWeb` web-app compile step errored (`client/dist` does not exist in this review environment — the Angular client was not built), but this does not affect the SAS service/macro coverage figures above. ### Notes - Coverage increased slightly from the previous review (Services 33%→37%, Overall 31%→33%), reflecting the two new test files added in the core v5 bump: `exportdb.test.sas` and `refreshcatalog.test.sas`. - The core v5 migration renamed `mp_testservice`→`mp_execute` and `mp_getddl`→`mp_ds2ddl`; all test files were updated accordingly. Generated by Hermes Agent
hermes left a comment
Collaborator

Hermes Agent Code Review

Verdict: Approve

This review covers the new commits since the last review (067c087→9b2e0df): the @sasjs/core v4→v5 bump (a breaking-change migration) and two new SAS test files.

Summary of new changes

  • @sasjs/core 4.68.3 → 5.0.0: Updated package.json and package-lock.json.
  • mp_testservice → mp_execute: All 14 test files migrated. mp_execute is a thin wrapper around mx_testservice (verified in core v5 source) with an identical parameter signature — the calls work as drop-in replacements.
  • mp_getddl → mp_ds2ddl: Two service files (exportdb.sas, getddl.sas) updated, with the new showlog=NO parameter added to suppress log output in web-service context.
  • mp_coretable → mddl_dc_*: testsetup.sas now uses mddl_dc_locktable/mddl_dc_difftable (verified to exist in core v5 with the libds= parameter). Cleaned up testinit.sas (removed stale comments).
  • New test files: exportdb.test.sas (74 lines, tests PGSQL + SAS flavours) and refreshcatalog.test.sas (58 lines, tests catalog refresh + verification).

Checks performed

  • ✅ No stale mp_testservice, mp_getddl, or mp_coretable references remain anywhere in sas/sasjs/.
  • ✅ All replacement macros (mp_execute, mp_ds2ddl, mddl_dc_locktable, mddl_dc_difftable) exist in @sasjs/core@5.0.0.
  • ✅ mp_execute parameter signature matches the old mp_testservice calls — inputfiles, inputdatasets, outlib, outref, viyacontext, viyaresult, mdebug all carried over correctly.
  • ✅ mp_ds2ddl calls preserved the fref=, flavour=, schema=, applydttm= parameters and added showlog=NO.
  • ✅ Compile (npx @sasjs/cli c -t server-ci) succeeds; coverage increased from 31%→33% overall thanks to the two new test files.
  • ✅ The new test files follow the existing pattern (%mx_testservice + %mp_assertdsobs), are well-structured, and test both positive and edge cases.

No issues found

The migration is mechanical, complete, and correct. No security, correctness, or style concerns in the new commits.

Reviewed by Hermes Agent

## Hermes Agent Code Review **Verdict: Approve** This review covers the new commits since the last review (`067c087`→`9b2e0df`): the `@sasjs/core` v4→v5 bump (a breaking-change migration) and two new SAS test files. ### Summary of new changes - **`@sasjs/core` 4.68.3 → 5.0.0**: Updated `package.json` and `package-lock.json`. - **`mp_testservice` → `mp_execute`**: All 14 test files migrated. `mp_execute` is a thin wrapper around `mx_testservice` (verified in core v5 source) with an identical parameter signature — the calls work as drop-in replacements. - **`mp_getddl` → `mp_ds2ddl`**: Two service files (`exportdb.sas`, `getddl.sas`) updated, with the new `showlog=NO` parameter added to suppress log output in web-service context. - **`mp_coretable` → `mddl_dc_*`**: `testsetup.sas` now uses `mddl_dc_locktable`/`mddl_dc_difftable` (verified to exist in core v5 with the `libds=` parameter). Cleaned up `testinit.sas` (removed stale comments). - **New test files**: `exportdb.test.sas` (74 lines, tests PGSQL + SAS flavours) and `refreshcatalog.test.sas` (58 lines, tests catalog refresh + verification). ### Checks performed - ✅ No stale `mp_testservice`, `mp_getddl`, or `mp_coretable` references remain anywhere in `sas/sasjs/`. - ✅ All replacement macros (`mp_execute`, `mp_ds2ddl`, `mddl_dc_locktable`, `mddl_dc_difftable`) exist in `@sasjs/core@5.0.0`. - ✅ `mp_execute` parameter signature matches the old `mp_testservice` calls — `inputfiles`, `inputdatasets`, `outlib`, `outref`, `viyacontext`, `viyaresult`, `mdebug` all carried over correctly. - ✅ `mp_ds2ddl` calls preserved the `fref=`, `flavour=`, `schema=`, `applydttm=` parameters and added `showlog=NO`. - ✅ Compile (`npx @sasjs/cli c -t server-ci`) succeeds; coverage increased from 31%→33% overall thanks to the two new test files. - ✅ The new test files follow the existing pattern (`%mx_testservice` + `%mp_assertdsobs`), are well-structured, and test both positive and edge cases. ### No issues found The migration is mechanical, complete, and correct. No security, correctness, or style concerns in the new commits. Reviewed by Hermes Agent
allan added 2 commits 2026-08-17 12:43:29 +00:00
fix: address hermes review feedback (formula quoting, cell revert, addRow guard)
Build / Build-and-ng-test (pull_request) Successful in 5m41s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m42s
Build / Build-and-test-development (pull_request) Successful in 24m59s
36963aa746
- Escape embedded double quotes in DC.USER_NAME/DC.ORIG_VALUE formula
  literals, preventing malformed HyperFormula expressions
- Extract cell-revert numeric parsing into resolveRevertedCellValue,
  falling back to the raw text instead of writing NaN
- Guard addRow() against an empty dataSource before indexing into it
- Document the cross-repo assumption behind licence protocol-mismatch
  detection
Merge pull request 'fix: address hermes review feedback (formula quoting, cell revert, addRow guard)' (#305) from pr-feedback into version7-13
Build / Build-and-ng-test (pull_request) Successful in 5m11s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m1s
Build / Build-and-test-development (pull_request) Successful in 25m9s
bf54a589f5
Reviewed-on: #305
hermes left a comment
Collaborator

Hermes Agent Code Review

Verdict: Approve

This review covers the new commits since the last review (9b2e0df→bf54a58): commit 36963aa "fix: address hermes review feedback (formula quoting, cell revert, addRow guard)" and its merge.

Summary of new changes

The incremental commits address three pieces of prior review feedback:

  1. Formula literal quoting fix (parseFormulaRule.ts): quoteLiteral now escapes embedded double-quotes (" → "") so a DC.USER_NAME/DC.ORIG_VALUE containing a " produces a valid spreadsheet literal instead of a broken formula. Two new spec cases cover john"doe and a "quoted" value. Correct and well-tested.

  2. Cell revert special-missing handling (resolveRevertedCellValue.ts, new): Reverting a numeric column now falls back to the raw text when Number(rawValueText) is NaN (e.g. a SAS special missing like .S), instead of silently writing NaN into the cell. The editor's revert callback now calls this helper instead of the inline isNumericCol ? Number(rawValueText) : rawValueText. Three new spec cases cover non-numeric, valid numeric, and special-missing. Clean extraction.

  3. addRow guard (editor.component.ts): After hot.alter('insert_row_below', ...) the this.dataSource[newIndex].noLinkOption = true assignment is now guarded with if (this.dataSource[newIndex]), defending against the data binding ever becoming async. Defensive and correct.

  4. Doc note (detectLicenceKeyProtocolMismatch.ts): Added a comment documenting that the HTTP/HTTPS detection is a structural assumption about the (separate) key-generation repo. Good documentation of a cross-repo coupling.

Checks performed

  • ✅ quoteLiteral escaping is correct: String(value ?? '').replace(/"/g,'""') produces valid ""-escaped literals, matching the two new specs.
  • ✅ resolveRevertedCellValue uses Number.isNaN() (not loose isNaN) — correctly distinguishes NaN from numeric coercion quirks.
  • ✅ The revert path in editor.component.ts passes the same rawValueText/isNumericCol the inline code did — no behavioural regression.
  • ✅ The addRow guard preserves the existing seedFormulaValuesForRow/updateEditStatusForRow calls that follow it.
  • ✅ SAS compile (npx @sasjs/cli c -t server-ci) succeeds; overall coverage holds at 33% (34/102). See the separate coverage comment.

Observations (non-blocking)

  • One low-probability regex edge case in parseFormulaRule's quoted-span splitter (see inline comment) — only relevant if a rule author hand-types "" escapes in RULE_VALUE.
  • The PK-matching pattern (find inside forEach) in applyFormulaRules, findOverwrittenCells, and classifyRow is O(rows²) per call. Fine for typical editor row counts; worth a Map-based lookup if very large staging tables ever become a supported use case.

No blocking issues

The feedback-fix commits are correct, tested, and don't introduce regressions. Approving.

Reviewed by Hermes Agent

## Hermes Agent Code Review **Verdict: Approve** This review covers the new commits since the last review (`9b2e0df`→`bf54a58`): commit `36963aa` "fix: address hermes review feedback (formula quoting, cell revert, addRow guard)" and its merge. ### Summary of new changes The incremental commits address three pieces of prior review feedback: 1. **Formula literal quoting fix** (`parseFormulaRule.ts`): `quoteLiteral` now escapes embedded double-quotes (`"` → `""`) so a `DC.USER_NAME`/`DC.ORIG_VALUE` containing a `"` produces a valid spreadsheet literal instead of a broken formula. Two new spec cases cover `john"doe` and `a "quoted" value`. Correct and well-tested. 2. **Cell revert special-missing handling** (`resolveRevertedCellValue.ts`, new): Reverting a numeric column now falls back to the raw text when `Number(rawValueText)` is `NaN` (e.g. a SAS special missing like `.S`), instead of silently writing `NaN` into the cell. The editor's revert callback now calls this helper instead of the inline `isNumericCol ? Number(rawValueText) : rawValueText`. Three new spec cases cover non-numeric, valid numeric, and special-missing. Clean extraction. 3. **addRow guard** (`editor.component.ts`): After `hot.alter('insert_row_below', ...)` the `this.dataSource[newIndex].noLinkOption = true` assignment is now guarded with `if (this.dataSource[newIndex])`, defending against the data binding ever becoming async. Defensive and correct. 4. **Doc note** (`detectLicenceKeyProtocolMismatch.ts`): Added a comment documenting that the HTTP/HTTPS detection is a structural assumption about the (separate) key-generation repo. Good documentation of a cross-repo coupling. ### Checks performed - ✅ `quoteLiteral` escaping is correct: `String(value ?? '').replace(/"/g,'""')` produces valid `""`-escaped literals, matching the two new specs. - ✅ `resolveRevertedCellValue` uses `Number.isNaN()` (not loose `isNaN`) — correctly distinguishes `NaN` from numeric coercion quirks. - ✅ The revert path in `editor.component.ts` passes the same `rawValueText`/`isNumericCol` the inline code did — no behavioural regression. - ✅ The addRow guard preserves the existing `seedFormulaValuesForRow`/`updateEditStatusForRow` calls that follow it. - ✅ SAS compile (`npx @sasjs/cli c -t server-ci`) succeeds; overall coverage holds at 33% (34/102). See the separate coverage comment. ### Observations (non-blocking) - One low-probability regex edge case in `parseFormulaRule`'s quoted-span splitter (see inline comment) — only relevant if a rule author hand-types `""` escapes in `RULE_VALUE`. - The PK-matching pattern (`find` inside `forEach`) in `applyFormulaRules`, `findOverwrittenCells`, and `classifyRow` is O(rows²) per call. Fine for typical editor row counts; worth a Map-based lookup if very large staging tables ever become a supported use case. ### No blocking issues The feedback-fix commits are correct, tested, and don't introduce regressions. Approving. Reviewed by Hermes Agent
Collaborator

Performance note (non-blocking): the dataSourceUnchanged.find(...) inside the dataSource.forEach + for (const formulaRule ...) loop is O(rows × formulaRules × unchangedRows). The same PK-matching find-inside-forEach pattern appears in findOverwrittenCells.ts and classifyRow.ts. Fine for typical editor row counts; if very large staging tables become a supported use case, a one-time Map keyed by the joined PK values would drop this to O(rows × formulaRules).

Performance note (non-blocking): the `dataSourceUnchanged.find(...)` inside the `dataSource.forEach` + `for (const formulaRule ...)` loop is O(rows × formulaRules × unchangedRows). The same PK-matching `find`-inside-`forEach` pattern appears in `findOverwrittenCells.ts` and `classifyRow.ts`. Fine for typical editor row counts; if very large staging tables become a supported use case, a one-time `Map` keyed by the joined PK values would drop this to O(rows × formulaRules).
Collaborator

Low-priority edge case: the quotedSpanPattern regex "[^"]*"|'[^']*' does not handle escaped double-quotes ("") inside a double-quoted span — it stops at the first inner ", so a literal like ="john""doe" is split into two matches ("john" and "doe") rather than one span.

This is low-impact because the splitter runs on the ORIGINAL rule text (before quoteLiteral produces "" escapes), so it only bites if a rule author literally types a "" escape in RULE_VALUE alongside a DC.* token. If it ever matters, /(?:"(?:""|[^"])*"|'[^']*')/g would handle the escape.

Low-priority edge case: the `quotedSpanPattern` regex `"[^"]*"|'[^']*'` does not handle escaped double-quotes (`""`) inside a double-quoted span — it stops at the first inner `"`, so a literal like `="john""doe"` is split into two matches (`"john"` and `"doe"`) rather than one span. This is low-impact because the splitter runs on the ORIGINAL rule text (before `quoteLiteral` produces `""` escapes), so it only bites if a rule author literally types a `""` escape in `RULE_VALUE` alongside a `DC.*` token. If it ever matters, `/(?:"(?:""|[^"])*"|'[^']*')/g` would handle the escape.
hermes left a comment
Collaborator

Hermes Agent Code Review

Verdict: Approve

This review covers the new commits since the last review (9b2e0df→bf54a58): commit 36963aa "fix: address hermes review feedback (formula quoting, cell revert, addRow guard)" and its merge.

Summary of new changes

The incremental commits address three pieces of prior review feedback:

  1. Formula literal quoting fix (parseFormulaRule.ts): quoteLiteral now escapes embedded double-quotes (" → "") so a DC.USER_NAME/DC.ORIG_VALUE containing a " produces a valid spreadsheet literal instead of a broken formula. Two new spec cases cover john"doe and a "quoted" value. Correct and well-tested.

  2. Cell revert special-missing handling (resolveRevertedCellValue.ts, new): Reverting a numeric column now falls back to the raw text when Number(rawValueText) is NaN (e.g. a SAS special missing like .S), instead of silently writing NaN into the cell. The editor's revert callback now calls this helper instead of the inline isNumericCol ? Number(rawValueText) : rawValueText. Three new spec cases cover non-numeric, valid numeric, and special-missing. Clean extraction.

  3. addRow guard (editor.component.ts): After hot.alter('insert_row_below', ...) the this.dataSource[newIndex].noLinkOption = true assignment is now guarded with if (this.dataSource[newIndex]), defending against the data binding ever becoming async. Defensive and correct.

  4. Doc note (detectLicenceKeyProtocolMismatch.ts): Added a comment documenting that the HTTP/HTTPS detection is a structural assumption about the (separate) key-generation repo. Good documentation of a cross-repo coupling.

Checks performed

  • ✅ quoteLiteral escaping is correct: String(value ?? '').replace(/"/g,'""') produces valid ""-escaped literals, matching the two new specs.
  • ✅ resolveRevertedCellValue uses Number.isNaN() (not loose isNaN) — correctly distinguishes NaN from numeric coercion quirks.
  • ✅ The revert path in editor.component.ts passes the same rawValueText/isNumericCol the inline code did — no behavioural regression.
  • ✅ The addRow guard preserves the existing seedFormulaValuesForRow/updateEditStatusForRow calls that follow it.
  • ✅ SAS compile (npx @sasjs/cli c -t server-ci) succeeds; overall coverage holds at 33% (34/102). See the separate coverage comment.

Observations (non-blocking)

  • One low-probability regex edge case in parseFormulaRule's quoted-span splitter (see inline comment) — only relevant if a rule author hand-types "" escapes in RULE_VALUE.
  • The PK-matching pattern (find inside forEach) in applyFormulaRules, findOverwrittenCells, and classifyRow is O(rows²) per call. Fine for typical editor row counts; worth a Map-based lookup if very large staging tables ever become a supported use case.

No blocking issues

The feedback-fix commits are correct, tested, and don't introduce regressions. Approving.

Reviewed by Hermes Agent

## Hermes Agent Code Review **Verdict: Approve** This review covers the new commits since the last review (`9b2e0df`→`bf54a58`): commit `36963aa` "fix: address hermes review feedback (formula quoting, cell revert, addRow guard)" and its merge. ### Summary of new changes The incremental commits address three pieces of prior review feedback: 1. **Formula literal quoting fix** (`parseFormulaRule.ts`): `quoteLiteral` now escapes embedded double-quotes (`"` → `""`) so a `DC.USER_NAME`/`DC.ORIG_VALUE` containing a `"` produces a valid spreadsheet literal instead of a broken formula. Two new spec cases cover `john"doe` and `a "quoted" value`. Correct and well-tested. 2. **Cell revert special-missing handling** (`resolveRevertedCellValue.ts`, new): Reverting a numeric column now falls back to the raw text when `Number(rawValueText)` is `NaN` (e.g. a SAS special missing like `.S`), instead of silently writing `NaN` into the cell. The editor's revert callback now calls this helper instead of the inline `isNumericCol ? Number(rawValueText) : rawValueText`. Three new spec cases cover non-numeric, valid numeric, and special-missing. Clean extraction. 3. **addRow guard** (`editor.component.ts`): After `hot.alter('insert_row_below', ...)` the `this.dataSource[newIndex].noLinkOption = true` assignment is now guarded with `if (this.dataSource[newIndex])`, defending against the data binding ever becoming async. Defensive and correct. 4. **Doc note** (`detectLicenceKeyProtocolMismatch.ts`): Added a comment documenting that the HTTP/HTTPS detection is a structural assumption about the (separate) key-generation repo. Good documentation of a cross-repo coupling. ### Checks performed - ✅ `quoteLiteral` escaping is correct: `String(value ?? '').replace(/"/g,'""')` produces valid `""`-escaped literals, matching the two new specs. - ✅ `resolveRevertedCellValue` uses `Number.isNaN()` (not loose `isNaN`) — correctly distinguishes `NaN` from numeric coercion quirks. - ✅ The revert path in `editor.component.ts` passes the same `rawValueText`/`isNumericCol` the inline code did — no behavioural regression. - ✅ The addRow guard preserves the existing `seedFormulaValuesForRow`/`updateEditStatusForRow` calls that follow it. - ✅ SAS compile (`npx @sasjs/cli c -t server-ci`) succeeds; overall coverage holds at 33% (34/102). See the separate coverage comment. ### Observations (non-blocking) - One low-probability regex edge case in `parseFormulaRule`'s quoted-span splitter (see inline comment) — only relevant if a rule author hand-types `""` escapes in `RULE_VALUE`. - The PK-matching pattern (`find` inside `forEach`) in `applyFormulaRules`, `findOverwrittenCells`, and `classifyRow` is O(rows²) per call. Fine for typical editor row counts; worth a Map-based lookup if very large staging tables ever become a supported use case. ### No blocking issues The feedback-fix commits are correct, tested, and don't introduce regressions. Approving. Reviewed by Hermes Agent
Collaborator

Performance note (non-blocking): the dataSourceUnchanged.find(...) inside the dataSource.forEach + for (const formulaRule ...) loop is O(rows × formulaRules × unchangedRows). The same PK-matching find-inside-forEach pattern appears in findOverwrittenCells.ts and classifyRow.ts. Fine for typical editor row counts; if very large staging tables become a supported use case, a one-time Map keyed by the joined PK values would drop this to O(rows × formulaRules).

Performance note (non-blocking): the `dataSourceUnchanged.find(...)` inside the `dataSource.forEach` + `for (const formulaRule ...)` loop is O(rows × formulaRules × unchangedRows). The same PK-matching `find`-inside-`forEach` pattern appears in `findOverwrittenCells.ts` and `classifyRow.ts`. Fine for typical editor row counts; if very large staging tables become a supported use case, a one-time `Map` keyed by the joined PK values would drop this to O(rows × formulaRules).
Collaborator

Low-priority edge case: the quotedSpanPattern regex "[^"]*"|'[^']*' does not handle escaped double-quotes ("") inside a double-quoted span — it stops at the first inner ", so a literal like ="john""doe" is split into two matches ("john" and "doe") rather than one span.

This is low-impact because the splitter runs on the ORIGINAL rule text (before quoteLiteral produces "" escapes), so it only bites if a rule author literally types a "" escape in RULE_VALUE alongside a DC.* token. If it ever matters, /(?:"(?:""|[^"])*"|'[^']*')/g would handle the escape.

Low-priority edge case: the `quotedSpanPattern` regex `"[^"]*"|'[^']*'` does not handle escaped double-quotes (`""`) inside a double-quoted span — it stops at the first inner `"`, so a literal like `="john""doe"` is split into two matches (`"john"` and `"doe"`) rather than one span. This is low-impact because the splitter runs on the ORIGINAL rule text (before `quoteLiteral` produces `""` escapes), so it only bites if a rule author literally types a `""` escape in `RULE_VALUE` alongside a `DC.*` token. If it ever matters, `/(?:"(?:""|[^"])*"|'[^']*')/g` would handle the escape.
Collaborator

Test Coverage Report

SASjs compile/coverage run via npx @sasjs/cli c -t server-ci (local compile only — no server required).

Scope Coverage
Overall 34 / 102 (33%)
Services 22 / 60 (37%)
Macros 12 / 42 (29%)

Notes

  • ✔ The project compiled successfully for SASJS using target server-ci (output in sas/sasjsbuild).
  • The compile reported one unrelated ERROR about streamConfig.webSourcePath: '.../client/dist' not existing — this is expected, since the Angular client was not built in this review environment, and does not affect the coverage figures above.
  • Overall coverage holds at 33% (34/102), consistent with the previous review. The two new test files added earlier in this PR (exportdb.test.sas, refreshcatalog.test.sas) are reflected in the 22 covered services.
  • Newly added mpe_targetloader.test.sas (REPLACE loadtype) is registered as a test macro; the CAS-specific branch of mpe_targetloader.sas is explicitly noted in that test as not exercised (BASE engine only).

Generated by Hermes Agent

## Test Coverage Report SASjs compile/coverage run via `npx @sasjs/cli c -t server-ci` (local compile only — no server required). | Scope | Coverage | |---|---| | **Overall** | **34 / 102 (33%)** | | Services | 22 / 60 (37%) | | Macros | 12 / 42 (29%) | ### Notes - ✔ The project compiled successfully for SASJS using target `server-ci` (output in `sas/sasjsbuild`). - The compile reported one unrelated `ERROR` about `streamConfig.webSourcePath: '.../client/dist'` not existing — this is expected, since the Angular client was not built in this review environment, and does not affect the coverage figures above. - Overall coverage holds at 33% (34/102), consistent with the previous review. The two new test files added earlier in this PR (`exportdb.test.sas`, `refreshcatalog.test.sas`) are reflected in the 22 covered services. - Newly added `mpe_targetloader.test.sas` (REPLACE loadtype) is registered as a test macro; the CAS-specific branch of `mpe_targetloader.sas` is explicitly noted in that test as not exercised (BASE engine only). Generated by Hermes Agent
Yury added 7 commits 2026-08-19 09:40:23 +00:00
fix: core major bump plus autofix of viya context on deploy
Build / Build-and-ng-test (pull_request) Successful in 5m8s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m36s
Build / Build-and-test-development (pull_request) Successful in 25m4s
6527c10f26
feat: updated configurator for Viya deploy
Build / Build-and-ng-test (pull_request) Successful in 5m34s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m2s
Build / Build-and-test-development (pull_request) Successful in 24m52s
df2027dab6
- Add explanatory comments for deliberately enabled debug mode in configurator
- Fix deploy-field-description font-size from 0.55rem to 0.85rem (was unreadably small)
chore: reference configurator improvements for Viya deploy
Build / Build-and-ng-test (pull_request) Canceled after 0s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Canceled after 0s
003550b965
fixes #303
fix: clarify debug comment is permanent, extend ComputeContextDetails with Viya response fields
Build / Build-and-ng-test (pull_request) Successful in 5m30s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m2s
Build / Build-and-test-development (pull_request) Successful in 26m15s
72484ae844
- sasjs-configurator: rewrite _debug=131 comment to make explicit that
  debug is ALWAYS enabled for the makedata service (runs once during
  deployment, debug output always wanted for diagnostics). Remove the
  commented-out debug-off line that caused reviewer confusion.
- viya-compute-context-details.model: extend Attributes with
  allowXCMD, sessionInactiveTimeout (number), and make all attribute
  fields optional. Add Environment interface with autoExecLines string
  array. Add environment? to ComputeContextDetails (optional, not all
  contexts expose it).
- automatic.component: rewrite extractRunAs to use
  attributes.runServerAs directly per the actual Viya API response
  shape. Remove all any casts and the non-existent runAsUserId /
  environment.runAsUserId field probes.
fix: escape regex metacharacters in context name before prxchange
Build / Build-and-ng-test (pull_request) Successful in 5m30s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m5s
Build / Build-and-test-development (pull_request) Successful in 25m28s
9023eb2a39
symget('_contextname') is concatenated directly into the PRX replacement
string in makedata.sas. A context name containing \ or $ would be
interpreted as a backreference or variable interpolation in the Perl
regex replacement. Escape both characters with a leading backslash in a
data step before embedding the value in the prxchange call.
Merge pull request 'fix: core major bump plus autofix of viya context on deploy' (#306) from sasfixes into version7-13
Build / Build-and-ng-test (pull_request) Successful in 5m39s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m13s
Build / Build-and-test-development (pull_request) Successful in 26m26s
9deccd072d
Reviewed-on: #306
allan added 1 commit 2026-08-19 09:46:04 +00:00
chore: packages / deploy
Build / Build-and-ng-test (pull_request) Successful in 5m43s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m48s
Build / Build-and-test-development (pull_request) Successful in 26m22s
2dfeb02faf
hermes left a comment
Collaborator

Hermes Agent Code Review

Verdict: Approve

This review covers the 8 new commits since the last review (bf54a58→2dfeb02): the Viya deploy configurator rewrite (df2027d), the regex-metacharacter escaping fix in makedata.sas (9023eb2), the @sasjs/core major bump with mx_testservice/mp_execute→mx_execute test renames (6527c10 + merge), debug-comment clarification and model extension (72484ae), and the packages/deploy chore (2dfeb02).

Critical

None.

Warnings / Suggestions

  • prxchange delimiter not escaped — makedata.sas:236. The \ and $ escaping is correct and closes the backreference/interpolation gap, but the substitution uses | as its delimiter and a context name containing a literal | would terminate the replacement early. Low probability (Viya context names rarely contain |), but see the inline comment for a one-line fix.
  • Method call in @for — automatic.component.html:218. getFilteredComputeContexts() is invoked on every change-detection cycle. Binding to a cached filteredComputeContexts array (updated in the existing handler methods) avoids a fresh filter allocation each tick. See inline comment.
  • Internal host in config — sasjsconfig.json:146. The new nextviya target hardcodes https://nextviya.emea.sas.com and /Users/viyademo18/dc. Other targets already hardcode sas.4gl.io, so this follows the existing pattern, but it does expose an internal SAS demo host. Confirm this target is intended to ship in the public repo (it's referenced by a nextviya npm script in package.json).

Looks Good

  • Function-replacement for $ (automatic.component.ts:643): String.replace now uses a replacer function () => \contextname="${computeContextName}"`soin the context name is treated literally. Correct fix for the prior string-replacement bug where&/$1/$`patterns would mangle names containing$`.
  • getComputeContextDetails parallel fetch (automatic.component.ts:167): fetches each context's full details via Promise.all, with a graceful error branch that resolves runAs: null, reusable: false instead of rejecting. Good resilience — one unfetchable context won't block the whole dropdown.
  • getMemberAdminGroups fallback (automatic.component.ts:351): on memberships-fetch failure, falls back to showing all groups. Sensible degraded behaviour.
  • onComputeContextChange ordering note (automatic.component.ts:405): correctly synchronises selectedComputeContext before deriving runningAsUser, with a clear comment explaining the ngModelChange timing. The runningAsUser will now match the dropdown on the same change, not lag by one.
  • ComputeContextDetails model (viya-compute-context-details.model.ts): attributes fields made optional with ? and allowXCMD/sessionInactiveTimeout added, plus a new Environment.autoExecLines. Aligns with the real Viya API which doesn't always populate every attribute.
  • getCurrentUserGroupMemberships (sas-viya.service.ts:174): clean addition following the existing getAdminGroups pattern; limit is a number default, not user input, so no injection surface.
  • Test macro renames (~30 .test.sas files + testsetup.sas + buildtermviya.sas): mechanical mx_testservice/mp_execute→mx_execute rename driven by the @sasjs/core 5.0→5.1 bump. All renames are consistent.
  • SAS compile (npx @sasjs/cli c -t server-ci) succeeds; overall coverage holds at 33% (34/102). See the separate coverage comment.

Reviewed by Hermes Agent

## Hermes Agent Code Review **Verdict: Approve** This review covers the 8 new commits since the last review (`bf54a58`→`2dfeb02`): the Viya deploy configurator rewrite (`df2027d`), the regex-metacharacter escaping fix in `makedata.sas` (`9023eb2`), the `@sasjs/core` major bump with `mx_testservice`/`mp_execute`→`mx_execute` test renames (`6527c10` + merge), debug-comment clarification and model extension (`72484ae`), and the packages/deploy chore (`2dfeb02`). ### Critical None. ### Warnings / Suggestions - **`prxchange` delimiter not escaped** — `makedata.sas:236`. The `\` and `$` escaping is correct and closes the backreference/interpolation gap, but the substitution uses `|` as its delimiter and a context name containing a literal `|` would terminate the replacement early. Low probability (Viya context names rarely contain `|`), but see the inline comment for a one-line fix. - **Method call in `@for`** — `automatic.component.html:218`. `getFilteredComputeContexts()` is invoked on every change-detection cycle. Binding to a cached `filteredComputeContexts` array (updated in the existing handler methods) avoids a fresh `filter` allocation each tick. See inline comment. - **Internal host in config** — `sasjsconfig.json:146`. The new `nextviya` target hardcodes `https://nextviya.emea.sas.com` and `/Users/viyademo18/dc`. Other targets already hardcode `sas.4gl.io`, so this follows the existing pattern, but it does expose an internal SAS demo host. Confirm this target is intended to ship in the public repo (it's referenced by a `nextviya` npm script in `package.json`). ### Looks Good - **Function-replacement for `$`** (`automatic.component.ts:643`): `String.replace` now uses a replacer function `() => \`contextname="${computeContextName}"\`` so `$` in the context name is treated literally. Correct fix for the prior string-replacement bug where `$&`/`$1`/`$\`` patterns would mangle names containing `$`. - **`getComputeContextDetails` parallel fetch** (`automatic.component.ts:167`): fetches each context's full details via `Promise.all`, with a graceful error branch that resolves `runAs: null, reusable: false` instead of rejecting. Good resilience — one unfetchable context won't block the whole dropdown. - **`getMemberAdminGroups` fallback** (`automatic.component.ts:351`): on memberships-fetch failure, falls back to showing all groups. Sensible degraded behaviour. - **`onComputeContextChange` ordering note** (`automatic.component.ts:405`): correctly synchronises `selectedComputeContext` before deriving `runningAsUser`, with a clear comment explaining the `ngModelChange` timing. The `runningAsUser` will now match the dropdown on the same change, not lag by one. - **`ComputeContextDetails` model** (`viya-compute-context-details.model.ts`): `attributes` fields made optional with `?` and `allowXCMD`/`sessionInactiveTimeout` added, plus a new `Environment.autoExecLines`. Aligns with the real Viya API which doesn't always populate every attribute. - **`getCurrentUserGroupMemberships`** (`sas-viya.service.ts:174`): clean addition following the existing `getAdminGroups` pattern; `limit` is a number default, not user input, so no injection surface. - **Test macro renames** (~30 `.test.sas` files + `testsetup.sas` + `buildtermviya.sas`): mechanical `mx_testservice`/`mp_execute`→`mx_execute` rename driven by the `@sasjs/core` 5.0→5.1 bump. All renames are consistent. - **SAS compile** (`npx @sasjs/cli c -t server-ci`) succeeds; overall coverage holds at 33% (34/102). See the separate coverage comment. Reviewed by Hermes Agent
Collaborator

Calling getFilteredComputeContexts() directly in the @for expression means Angular re-invokes the method on every change-detection cycle (it can't know the return value is stable). For a small list this is harmless, but the idiomatic and cheaper approach is to bind to a cached array property (e.g. update this.filteredComputeContexts inside ensureSelectedComputeContext() / onShowAllContextsChange() and iterate over that). This avoids a fresh filter allocation on every CD tick.

Calling `getFilteredComputeContexts()` directly in the `@for` expression means Angular re-invokes the method on every change-detection cycle (it can't know the return value is stable). For a small list this is harmless, but the idiomatic and cheaper approach is to bind to a cached array property (e.g. update `this.filteredComputeContexts` inside `ensureSelectedComputeContext()` / `onShowAllContextsChange()` and iterate over that). This avoids a fresh `filter` allocation on every CD tick.
Collaborator

This target hardcodes an internal SAS demo host (nextviya.emea.sas.com) and a demo user path (/Users/viyademo18/dc). It's referenced by the nextviya npm script in sas/package.json. Other targets already hardcode sas.4gl.io, so this follows the existing pattern, but confirm this internal host is intended to ship in the public repo.

This target hardcodes an internal SAS demo host (`nextviya.emea.sas.com`) and a demo user path (`/Users/viyademo18/dc`). It's referenced by the `nextviya` npm script in `sas/package.json`. Other targets already hardcode `sas.4gl.io`, so this follows the existing pattern, but confirm this internal host is intended to ship in the public repo.
Collaborator

The escaping covers \ (backreference) and $ (interpolation), but the prxchange substitution uses | as the delimiter:

s|contextname="[^"]*"|contextname="<escaped>"|

A context name containing a literal | would prematurely terminate the replacement string and shift the remainder into the flags section, either erroring or producing a malformed DC.html. Viya context names rarely contain |, so this is low-risk, but for completeness the delimiter itself should also be quoted in _ctxesc (e.g. add a tranwrd(...,'|','\\|')), or switch the delimiter to a character unlikely to appear in a name.

The escaping covers `\` (backreference) and `$` (interpolation), but the `prxchange` substitution uses `|` as the delimiter: ``` s|contextname="[^"]*"|contextname="<escaped>"| ``` A context name containing a literal `|` would prematurely terminate the replacement string and shift the remainder into the flags section, either erroring or producing a malformed `DC.html`. Viya context names rarely contain `|`, so this is low-risk, but for completeness the delimiter itself should also be quoted in `_ctxesc` (e.g. add a `tranwrd(...,'|','\\|')`), or switch the delimiter to a character unlikely to appear in a name.
Collaborator

Test Coverage

Services: 22/60 (37%) | Macros: 12/42 (29%) | Overall: 34/102 (33%) — run 2026-08-19T10:14Z on 2dfeb02faf

PR diff


Generated by Hermes Agent

## Test Coverage **Services: 22/60 (37%) | Macros: 12/42 (29%) | Overall: 34/102 (33%)** — run `2026-08-19T10:14Z` on `2dfeb02faf` [PR diff](https://git.datacontroller.io/dc/dc/pulls/294/files) --- *Generated by Hermes Agent*
allan added 11 commits 2026-08-21 17:14:46 +00:00
feat(editor): translate column names to cell references on formula paste
Build / Build-and-ng-test (pull_request) Successful in 5m48s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m28s
Build / Build-and-test-development (pull_request) Successful in 26m35s
1031ea7ed7
- Paste a formula using column names (e.g. "=A_COL * B_COL") into any
  cell and have it translated to that row's cell references
  ("=B4 * C4") so HyperFormula can evaluate it - covers both a
  grid-level paste and pasting directly into an open cell editor
- Only applies on tables that already have formulas enabled (an
  existing HARDFORMULA/SOFTFORMULA column); otherwise the pasted text
  is left untouched rather than becoming inert translated text
- New substituteColumnReferences() reuses parseFormulaRule's
  boundary-matching helpers but deliberately skips DC.* variable
  substitution, which only makes sense for admin-defined rule values
Merge branch 'version7-13' into process-formula
Build / Build-and-ng-test (pull_request) Successful in 5m40s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m30s
Build / Build-and-test-development (pull_request) Successful in 26m26s
772a359da7
fix: agent skills and nextviya deploys
Build / Build-and-ng-test (pull_request) Successful in 5m8s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m24s
Build / Build-and-test-development (pull_request) Successful in 25m20s
f7db8719f5
chore: sample record in mpe_x_test
Build / Build-and-ng-test (pull_request) Successful in 5m29s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m51s
Build / Build-and-test-development (pull_request) Successful in 25m32s
9dab3d50ef
Only backend-sourced `=`-led values are auto-escaped now; anything the
user types or pastes into a character column evaluates as a real
formula, and "Apply as formula" lets a user promote an auto-escaped
cell explicitly. Formulas are enabled on every table instead of only
ones with formula rules.
chore: merge branch 'process-formula' of https://git.datacontroller.io/dc/dc into process-formula
Build / Build-and-ng-test (pull_request) Successful in 5m18s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m47s
Build / Build-and-test-development (pull_request) Failing after 26m20s
d0d17995d3
fix(editor): escape formula-looking values in uploaded Excel data
Build / Build-and-ng-test (pull_request) Successful in 5m47s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m15s
Build / Build-and-test-development (pull_request) Successful in 26m20s
e69df3deb2
Uploaded file content is bulk external data, not user-typed input, so
a `=`-led value in a character column must be escaped the same way a
fresh backend load is - otherwise it silently evaluates as a live
formula now that formulas are enabled for every table.
fix(editor): re-mark a reverted cell as auto-escaped so "Apply as formula" works again
Build / Build-and-ng-test (pull_request) Successful in 5m19s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m37s
Build / Build-and-test-development (pull_request) Successful in 26m33s
12091c4044
dataSourceRaw is now escaped the same way dataSource is, so Revert
restores the escaped text a user actually saw instead of the raw
backend string (which would otherwise evaluate live again).
beforeChange re-marks a cell revert_cells restores to its escaped
form, distinguishing that from a user typing a literal leading `'`
themselves via the write's source tag.
fix(deploy): Viya deploy checks, startup diagnostics, and chunked deploy script
Build / Build-and-ng-test (pull_request) Failing after 2m43s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Successful in 21m6s
917925f553
- Add StartupCheckService to show deploy check progress on loading screen
  with step-by-step status (appLoc check, Viya deploy, startup service)
- Add console logging throughout checkViyaDeploy and viyaMakedataSuccessfull
  to diagnose deploy flow issues
- Fix missing return after resolve(false) in viyaMakedataSuccessfull when
  folderId is undefined, which caused fall-through to getFolderMembers
  with an undefined ID
- Fix Viya Folders API pagination: getFolderMembers now requests limit=500
  so all members are returned (admin folder has 32 members, default page
  size was hiding makedata)
- Fix error handler in viyaMakedataSuccessfull to resolve(false) instead of
  reject() so the app falls back to setup screen on API errors
- Fix licence key whitespace: SAS makedata initialises keys as a single
  space which is truthy in JS; trim keys before checking so empty keys
  correctly fall back to free tier instead of triggering decryption errors
- Always set _debug=131 on makedata URLs (automatic and manual deploy)
  so full debug output is available for the one-time setup service
- Add makedata completion polling after runMakedataInNewWindow so the app
  auto-reloads when the makedata job self-deletes, instead of hanging
- Fix loading screen slider position from absolute to relative so it does
  not obscure the startup check steps
- Add chunk_deploy.py to sasjs/utils for chunked Viya deploys, splitting
  viya.sas by service and web file boundaries with correct %let path=
  tracking per chunk

Closes #303, #200, #125
fix: spy for startup
Build / Build-and-ng-test (pull_request) Successful in 5m18s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m27s
Build / Build-and-test-development (pull_request) Successful in 26m24s
69b41559f7
Merge pull request 'feat(editor): translate column names to cell references on formula paste' (#308) from process-formula into version7-13
Build / Build-and-ng-test (pull_request) Successful in 5m15s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m31s
Build / Build-and-test-development (pull_request) Successful in 26m27s
a7bb13c3ae
Reviewed-on: #308
Collaborator

PR #294 Review — Version 7.13 Release

Security

1. getstagetable.sas (lines 24, 26, 41) — &table_id used in libname and set statements without validation

table_id comes from mf_getvalue(work.iwant,table_id) (user-supplied via the web request). It's used directly in a libname path, a set statement, and a SQL where clause — all classic SAS macro injection vectors. If table_id contains spaces, quotes, or %-led macro triggers, this is exploitable. This pattern pre-exists in the file but the new code (lines 37-48) adds another unvalidated use of &table_id in a where clause and passes &base_lib/&base_ds (also from mpe_submit, user-influenced) into %dc_assignlib and %mp_getcols. Recommend validating table_id against a whitelist (e.g. exists in mpe_submit) before any use, or at minimum wrapping with appropriate sanitisation.

2. makedata.sas (lines 254-257) — prxchange regex substitution of _contextname into DC.html

The escaping on line 236 handles \ and $ but not | — the delimiter used in the prxchange s|...|...| pattern. A context name containing | would break the regex or inject a replacement. Low likelihood (context names are admin-controlled) but worth noting.

Correctness

3. sas.service.ts (lines 442, 460-465, 477-483, 526-529) — ~10 console.log statements added to production code

Multiple verbose console.log calls were added to checkViyaDeploy and viyaMakedataSuccessfull. These will spam the browser console in production. Some log internal config objects (sasjsConfig). Should be removed or gated behind a debug flag / the existing LoggerService.

4. sas.service.ts (line ~560) — error handler now silently resolves false instead of rejecting

The previous code called reject() on folder-members fetch error. Now it resolves false (redirects to /deploy). The comment says "assume setup is needed" but this masks real API failures (auth, network, permissions) as "needs setup" — the user gets sent to a deploy screen for an unrelated error.

5. editor.component.ts — dataSourceRaw uses array-index alignment with dataSource

overlayFormulaRawValuesOnUnchanged and the escape loop in initSetup both access this.dataSourceRaw[rowIndex] by index. But dataSource can be reordered by sort/insert/delete after load. The PK-matched lookups in findOverwrittenCells and syncOverwrittenCommentForCell are correct, but these index-aligned accesses will drift if any reordering happens between dataSourceRaw capture and their use. The escape loop runs immediately after capture so is likely safe, but overlayFormulaRawValuesOnUnchanged is called from editTable() which can run after sorts.

6. chunk_deploy.py (line 21) — header = lines[:HEADER_END] with no None check

If HEADER_END stays None (no %let path= or %let service= line found), lines[:None] returns the entire file as "header", and i = HEADER_END on line 29 sets i = None which throws TypeError in the while loop. Should add a guard: if HEADER_END is None: sys.exit("No header marker found").

7. chunk_deploy.py (line 63) — web_sections computed but never used

web_sections is computed on line 63 but the actual filtering on lines 67-73 recomputes web_file_sections and images_sections from scratch. web_sections is dead code.

Tests

8. No test for chunk_deploy.py — the Python deploy script has no tests and has at least two bugs (above). Given it's a deploy-time utility, at minimum a smoke test that it handles a missing header gracefully would help.

Observations

  • The mp_abort(mode=INCLUDE) pattern replacing the old mf_abort.error file check across 6 services is a clean simplification.
  • The EDIT_STATUS column approach (dc.row_status with a period to prevent SAS name collision) is well-reasoned, as is the dataDotNotation: false setting to make it work with HyperFormula.
  • The migration script correctly uses max(selectbox_rk)+1 rather than %mf_increment, appropriate for a standalone migration.

Coverage

Services: 22/60 (37%)  |  Macros: 12/42 (29%)  |  Overall: 34/102 (33%)
SHA: a7bb13c3 | 2026-08-21T19:35Z
## PR #294 Review — Version 7.13 Release ### Security **1. `getstagetable.sas` (lines 24, 26, 41) — `&table_id` used in `libname` and `set` statements without validation** `table_id` comes from `mf_getvalue(work.iwant,table_id)` (user-supplied via the web request). It's used directly in a `libname` path, a `set` statement, and a SQL `where` clause — all classic SAS macro injection vectors. If `table_id` contains spaces, quotes, or `%`-led macro triggers, this is exploitable. This pattern pre-exists in the file but the new code (lines 37-48) adds another unvalidated use of `&table_id` in a `where` clause and passes `&base_lib`/`&base_ds` (also from `mpe_submit`, user-influenced) into `%dc_assignlib` and `%mp_getcols`. Recommend validating `table_id` against a whitelist (e.g. exists in `mpe_submit`) before any use, or at minimum wrapping with appropriate sanitisation. **2. `makedata.sas` (lines 254-257) — `prxchange` regex substitution of `_contextname` into DC.html** The escaping on line 236 handles `\` and `$` but not `|` — the delimiter used in the `prxchange` `s|...|...|` pattern. A context name containing `|` would break the regex or inject a replacement. Low likelihood (context names are admin-controlled) but worth noting. ### Correctness **3. `sas.service.ts` (lines 442, 460-465, 477-483, 526-529) — ~10 `console.log` statements added to production code** Multiple verbose `console.log` calls were added to `checkViyaDeploy` and `viyaMakedataSuccessfull`. These will spam the browser console in production. Some log internal config objects (`sasjsConfig`). Should be removed or gated behind a debug flag / the existing `LoggerService`. **4. `sas.service.ts` (line ~560) — error handler now silently resolves `false` instead of rejecting** The previous code called `reject()` on folder-members fetch error. Now it resolves `false` (redirects to /deploy). The comment says "assume setup is needed" but this masks real API failures (auth, network, permissions) as "needs setup" — the user gets sent to a deploy screen for an unrelated error. **5. `editor.component.ts` — `dataSourceRaw` uses array-index alignment with `dataSource`** `overlayFormulaRawValuesOnUnchanged` and the escape loop in `initSetup` both access `this.dataSourceRaw[rowIndex]` by index. But `dataSource` can be reordered by sort/insert/delete after load. The PK-matched lookups in `findOverwrittenCells` and `syncOverwrittenCommentForCell` are correct, but these index-aligned accesses will drift if any reordering happens between `dataSourceRaw` capture and their use. The escape loop runs immediately after capture so is likely safe, but `overlayFormulaRawValuesOnUnchanged` is called from `editTable()` which can run after sorts. **6. `chunk_deploy.py` (line 21) — `header = lines[:HEADER_END]` with no None check** If `HEADER_END` stays `None` (no `%let path=` or `%let service=` line found), `lines[:None]` returns the entire file as "header", and `i = HEADER_END` on line 29 sets `i = None` which throws `TypeError` in the `while` loop. Should add a guard: `if HEADER_END is None: sys.exit("No header marker found")`. **7. `chunk_deploy.py` (line 63) — `web_sections` computed but never used** `web_sections` is computed on line 63 but the actual filtering on lines 67-73 recomputes `web_file_sections` and `images_sections` from scratch. `web_sections` is dead code. ### Tests **8. No test for `chunk_deploy.py`** — the Python deploy script has no tests and has at least two bugs (above). Given it's a deploy-time utility, at minimum a smoke test that it handles a missing header gracefully would help. ### Observations - The `mp_abort(mode=INCLUDE)` pattern replacing the old `mf_abort.error` file check across 6 services is a clean simplification. - The `EDIT_STATUS` column approach (`dc.row_status` with a period to prevent SAS name collision) is well-reasoned, as is the `dataDotNotation: false` setting to make it work with HyperFormula. - The migration script correctly uses `max(selectbox_rk)+1` rather than `%mf_increment`, appropriate for a standalone migration. ### Coverage ``` Services: 22/60 (37%) | Macros: 12/42 (29%) | Overall: 34/102 (33%) SHA: a7bb13c3 | 2026-08-21T19:35Z ```
hermes left a comment
Collaborator

See review comment above.

See review comment above.
hermes added 1 commit 2026-08-24 14:12:01 +00:00
fix: hardening following PR review feedback
Build / Build-and-ng-test (pull_request) Successful in 5m44s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m52s
Build / Build-and-test-development (pull_request) Successful in 26m20s
1f4aa6fcea
hermes left a comment
Collaborator

Code Review

Verdict: Approve — no changes needed.

Reviewed the incremental commit 1f4aa6f (hardening following PR review feedback). The getstagetable.sas input validation is well implemented — table_id is validated with mp_validatecol(…,ISNAME,…) and base_libds with LIBDS before any use in libname/set/where statements. The @sasjs/core bump to 5.2.0 adds the ISNAME rule and the lockfile is correctly updated. The chunk_deploy.py HEADER_END is None guard and dead web_sections removal are clean. The new getstagetable.test.sas covers the end-to-end stage→fetch flow.


Reviewed by Hermes Agent

## Code Review **Verdict: Approve** — no changes needed. Reviewed the incremental commit `1f4aa6f` (hardening following PR review feedback). The `getstagetable.sas` input validation is well implemented — `table_id` is validated with `mp_validatecol(…,ISNAME,…)` and `base_libds` with `LIBDS` before any use in `libname`/`set`/`where` statements. The `@sasjs/core` bump to 5.2.0 adds the `ISNAME` rule and the lockfile is correctly updated. The `chunk_deploy.py` `HEADER_END is None` guard and dead `web_sections` removal are clean. The new `getstagetable.test.sas` covers the end-to-end stage→fetch flow. --- *Reviewed by Hermes Agent*
Collaborator

Test Coverage

Services: 23/60 (38%) | Macros: 12/42 (29%) | Overall: 35/102 (34%) — 2026-08-25T15:38:18Z on 1f4aa6fce

PR diff


Generated by Hermes Agent

## Test Coverage **Services: 23/60 (38%) | Macros: 12/42 (29%) | Overall: 35/102 (34%)** — `2026-08-25T15:38:18Z` on `1f4aa6fce` [PR diff](https://git.datacontroller.io/dc/dc/pulls/294/files) --- *Generated by Hermes Agent*
allan added 31 commits 2026-08-30 22:00:08 +00:00
fix(editor): submit a live formula's computed value, not its raw text
Build / Build-and-ng-test (pull_request) Successful in 5m47s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m27s
Build / Build-and-test-development (pull_request) Successful in 27m6s
b7d31066ae
saveTable() only resolved computed values for HARDFORMULA/SOFTFORMULA
columns - a formula typed, pasted, or "Apply as formula"'d into any
other character column still had its raw `=...` text submitted,
since HyperFormula recomputes the display without ever mutating the
underlying dataSource string.
Merge branch 'version7-13' into process-formula
Build / Build-and-ng-test (pull_request) Successful in 6m0s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m29s
Build / Build-and-test-development (pull_request) Successful in 27m4s
1b1937bad8
fix(editor): resolve computed value for a live formula in numeric columns too
Build / Build-and-ng-test (pull_request) Successful in 5m46s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m55s
Build / Build-and-test-development (pull_request) Successful in 26m47s
2cc8489892
saveTable() only resolved a live formula's computed value for character columns - coerceNumericRow (beforePaste/beforeAutofill) deliberately leaves a non-numeric, formula-looking value alone rather than forcing it to NaN, so a numeric column can hold a live formula too. Its raw `=...` text was reaching the backend, which a numeric SAS field can't parse.
fix(editor): resolve a primary key's live formula to its computed value, not the raw formula text
Build / Build-and-ng-test (pull_request) Successful in 5m12s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m40s
Build / Build-and-test-development (pull_request) Successful in 27m13s
d72e19a308
chore: lint fix
Build / Build-and-ng-test (pull_request) Successful in 5m53s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m3s
Build / Build-and-test-development (pull_request) Failing after 33m36s
18a16fcb06
feat: functional JS mocks
Build / Build-and-ng-test (pull_request) Successful in 5m28s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m25s
Build / Build-and-test-development (pull_request) Failing after 33m34s
ee52bb8967
fix: using utility macro for webout
Build / Build-and-ng-test (pull_request) Successful in 5m52s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m26s
Build / Build-and-test-development (pull_request) Failing after 33m42s
3595bb9499
fix: history mock
Build / Build-and-ng-test (pull_request) Successful in 5m16s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m32s
Build / Build-and-test-development (pull_request) Failing after 33m38s
0082828543
fix: dynamic js mocks
Build / Build-and-ng-test (pull_request) Successful in 5m16s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m35s
Build / Build-and-test-development (pull_request) Failing after 33m28s
3b9344cb60
fix(editor): stop turning missing columns into undefined, and fix column lookup for sparse rows
Build / Build-and-ng-test (pull_request) Successful in 5m36s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m58s
Build / Build-and-test-development (pull_request) Failing after 33m41s
a104f78645
Writing back an unescaped cell's value unconditionally materialized a column the backend never populated as an explicit `undefined`, which the SASjs adapter rejects at submit time. dynamicCellValidation's column-name lookup (Object.keys(row)[column]) only worked by coincidence when every row had a full key set - fixed to use hot.colToProp instead.
fix: unnecessary getsubmits call removed
Build / Build-and-ng-test (pull_request) Successful in 5m19s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m40s
Build / Build-and-test-development (pull_request) Failing after 33m35s
cc2ff873fa
fix: more mock improvements
Build / Build-and-ng-test (pull_request) Successful in 5m19s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m34s
Build / Build-and-test-development (pull_request) Failing after 33m31s
a80927cbb6
- Accept a single pasted/uploaded combined licence key (base64+gzip of
  licence+activation key), auto-detected in either paste mode, alongside
  the legacy two-field format
- Warn and block applying a key generated for the wrong protocol before
  it ever reaches the backend
- Decode both the legacy positional features string and a new named
  features object, so already-issued keys keep working unchanged
- Show a live "Key Details" preview (validity, users, site IDs, enabled
  features) as a key is typed, pasted, or uploaded
- Fix cypress test setup broken by the paste-format default change, and
  an unreliable FileReader-based base64 conversion in the test helpers
fix: failing test
Build / Build-and-ng-test (pull_request) Successful in 5m24s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m25s
Build / Build-and-test-development (pull_request) Failing after 33m33s
f213d24f09
fix: dynamic getdynamiccolvals.js
Build / Build-and-ng-test (pull_request) Successful in 5m22s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m27s
Build / Build-and-test-development (pull_request) Failing after 33m37s
d59ef44226
fix(tests): new libref
Build / Build-and-ng-test (pull_request) Successful in 5m27s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m39s
Build / Build-and-test-development (pull_request) Failing after 33m47s
c0bc87d331
fix: tests and realistic getcolvals.js
Build / Build-and-ng-test (pull_request) Successful in 5m49s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m56s
Build / Build-and-test-development (pull_request) Failing after 33m48s
5d03164b6a
fix(cypress): make licensing combined-key tests order-independent
Build / Build-and-ng-test (pull_request) Successful in 5m16s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m48s
Build / Build-and-test-development (pull_request) Failing after 33m43s
4db8d62dca
- Tests 7-10 only acted when already on the licensing page, silently
  no-oping otherwise; now they navigate to licensing/update explicitly
  when needed, chained via .then() to avoid Cypress conflating a raw
  async function's promise with the preceding cy.visit()/cy.wait()
- Replace Response(readable).arrayBuffer() with a direct
  ReadableStream reader loop in generateCombinedKey - the Response-based
  read never settled under Cypress, likely due to its fetch/Response
  patching for network interception
fix(licensing): bridge async proceed() through cy.then and replace Response.arrayBuffer with manual stream reader
Build / Build-and-ng-test (pull_request) Successful in 5m9s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m31s
Build / Build-and-test-development (pull_request) Failing after 33m53s
8e51fde524
- licensing.cy.ts: call proceed() unconditionally via cy.then() instead of conditionally chaining .then() after cy.wait(), so the command queue stays open across the async gap and enqueued commands always run
- combinedLicenceKey.ts: replace new Response(stream).arrayBuffer() with an explicit streamToArrayBuffer helper for broader browser support
- remove debug-license.cy.ts, an untracked throwaway repro for the promise-handling question
fix(cypress): correct mock data, test expectations, and abort modal
Build / Build-and-ng-test (pull_request) Successful in 5m20s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m39s
Build / Build-and-test-development (pull_request) Failing after 34m1s
13c10be30f
- editor.cy.ts: assert DC.USER_NAME is non-empty instead of hardcoded
  'sasdemo' (CI runs as a different OS user)
- editor.cy.ts: use =SOME_BESTNUM * 0 + 1936 for a deterministic formula
  result regardless of the column's actual value
- excel.cy.ts: expect computed value '2' instead of formula text '=1+1'
  (no XL_RULE=FORMULA configured on that column)
- excel-multi-load.cy.ts: use DC996664 libref to match the actual fixture
  sheet names
- makedata.js: add labels to DC_JSLIB.MPE_X_TEST schema (SOME_CHAR,
  SOME_DATE, SOME_DATETIME, SOME_TIME) for the viewer-labels tests
- makedata.js: register DC996664 libref with MPE_X_TEST/MPE_TABLES/
  MPE_VALIDATIONS/MPE_ALERTS and copy table files for multi-load fixtures
- makedata.js: change SOME_SHORTNUM HARDREGEX from [1-5] to [0-2]
- loadfile.js: exclude VAR_TXFROM/VAR_TXTO/VAR_PROCESSED columns from the
  missing-cols check for TXTEMPORAL tables (computed at load time)
- InfoModal.ts: add _PROGRAM to AbortDetails
- info-modal.component.html: display _PROGRAM in the abort modal
- 4 call sites: pass abortRes._PROGRAM when building AbortDetails
fix(mocks): remove MPE_VALIDATIONS and MPE_ALERTS from DC996664 libref
Build / Build-and-ng-test (pull_request) Successful in 5m20s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m59s
Build / Build-and-test-development (pull_request) Failing after 8h28m57s
4f7b845390
The multi-load fixture sheets for DC996664.MPE_VALIDATIONS had data
misaligned with the schema, corrupting the shared submit state. The
tests only need MPE_X_TEST and MPE_TABLES, so only register those two.
rejection.js defined its own local saveTableData(tableName, tableData)
with 2 args, but dcMockUtils.js (loaded via eval) also defines
saveTableData(dataDir, tableName, tableData) with 3 args. The
dcMockUtils version was shadowing the local one, causing
saveTableData('MPE_SUBMIT', submitData) to pass the table name as
dataDir and the data object as tableName, crashing with
'TypeError: tableName.toLowerCase is not a function'.

Removed the local saveTableData and updated calls to use the
dcMockUtils 3-arg signature: saveTableData(dataDir, tableName, data).
Same shadowing issue as rejection.js: postdata.js defined its own
saveTableData(tableName, tableData, tableLibref) which was shadowed
by dcMockUtils.saveTableData(dataDir, tableName, tableData) from the
eval. Renamed to saveTableToLib to eliminate the collision.
fix(mocks): remove SOME_SHORTNUM HARDREGEX from MPE_VALIDATIONS
Build / Build-and-ng-test (pull_request) Successful in 5m51s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m6s
Build / Build-and-test-development (pull_request) Failing after 31m25s
34bd6607c5
The Excel fixture files (7th_tab, extra_column, duplicate_column,
duplicate_row, regular_xls, regular_with_delete, password, leading
whitespace) contain SOME_SHORTNUM values of 0, 1, and 2. The
HARDREGEX /^(?![0-2](\.\d+)?$).*/ blocks those values, aborting
the upload before it reaches the submit/approve flow. Removing the
rule allows all 23 excel.cy.ts tests and both liveness.cy.ts tests
to pass.
fix(mocks): add LIBRARYNAME to startupservice, fix viewbox column order
Build / Build-and-ng-test (pull_request) Successful in 5m19s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m27s
Build / Build-and-test-development (pull_request) Failing after 27m3s
c0af86ef29
startupservice.js only returned LIBREF in saslibs, but the dc-tree
component renders LIBRARYNAME — without it the nav tree node text was
empty, so libraryToOpenIncludes could not match. Added LIBRARYNAME,
LIBRARYID, and ENGINE to match the viewlibs service format.

Changed libraryToOpenIncludes_SASJS from 'dc' to 'dc_jslib' to avoid
matching multiple libraries (DC_JSLIB and DC996664 both contain 'dc').

viewdata.js: removed alphabetical .sort() on cols (SAS preserves
physical column order via mp_getcols); added empty row with correct
SAS missing values ('.' for numeric, '' for character) when a table
has no data, so the viewbox grid can derive column headers.
fix(mocks): look up PK_FIELDS from MPE_TABLES in viewdata, fallback for MPE_AUDIT
Build / Build-and-ng-test (pull_request) Successful in 5m52s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m54s
Build / Build-and-test-development (pull_request) Failing after 25m39s
94d2a7970e
viewdata.js hardcoded PK_FIELDS as empty, so the viewbox grid treated
all columns as non-PK. removeAllColumns() could delete every column
including the ones the tests expect to be protected.

Now reads MPE_TABLES buskey (mirrors mp_getpk) and falls back to
LOAD_REF LIBREF DSN KEY_HASH TGTVAR_NM for MPE_AUDIT (which has a
known index-based PK in real SAS but isn't registered in MPE_TABLES).

All 12 viewbox.cy.ts tests now pass.
fix(mocks): add fixture staged data and MPE_SUBMIT row for stage.cy.ts
Build / Build-and-ng-test (pull_request) Successful in 5m44s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m25s
Build / Build-and-test-development (pull_request) Failing after 25m9s
6e2548d78c
stage.cy.ts navigates directly to /stage/DC20221007T122326121_612316_7259
and expects a hot table with MPE_X_TEST data. The mock had no fixture
staged.json or MPE_SUBMIT row for this table ID, so getstagetable
returned 0 rows and getchangeinfo returned empty jsparams.

Added a fixture staged.json with MPE_X_TEST rows (SOME_DATE=42) and
a matching MPE_SUBMIT row (SUBMITTED status) in makedata.js.
getchangeinfo.js: use SAS datetime19. format (DDMMMYYYY:HH:MM:SS)
for SUBMITTED_ON_DTTM and REVIEWED_ON_DTTM, matching the real SAS
output. Use SUBMIT_STATUS_CD field name (not REVIEW_STATUS_ID)
to match the MPE_SUBMIT column name and getchangeinfo.sas output.

stage.cy.ts: add login step in beforeEach (the beforeAll logs out
after uploading the license, so each test needs to re-login).
Use loginAndUpdateValidKey(true) to force license upload.
fix(mocks): limit stage fixture to 1 row, fix column order
Build / Build-and-ng-test (pull_request) Successful in 5m12s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m49s
Build / Build-and-test-development (pull_request) Successful in 24m6s
f25ecd8828
The stage.cy.ts fixture had 7 rows of MPE_X_TEST data with wide string
values, causing Handsontable to auto-size columns wider so only 4 of
10 fit in the 1280px viewport. The test checks .ht_clone_top headers
which only render visible columns.

Limited to 1 row (matching the original sas9 mock) and moved
_____DELETE__THIS__RECORD_____ to the end of the column order.
fix(stage): submit data via editor flow, use dynamic date assertion
Build / Build-and-ng-test (pull_request) Successful in 5m14s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m30s
Build / Build-and-test-development (pull_request) Successful in 24m11s
490d0137b9
Rewrite stage.cy.ts to follow the same pattern as excel.cy.ts:
open MPE_X_TEST, attach regular_excel.xlsx, submit to create staged
data, then test the formatted/unformatted toggle on the stage page.

The date assertion is now dynamic: reads the raw SOME_DATE value
from the unformatted view and computes the expected SAS date9.
formatted value, rather than hardcoding 12FEB1960.

Remove the fixture stageSubmission code from makedata.js — staged
data is created at runtime by stagedata.js, not by makedata.
Merge pull request 'fix(editor): submit a live formula's computed value, not its raw text' (#310) from process-formula into version7-13
Build / Build-and-ng-test (pull_request) Successful in 5m11s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m34s
Build / Build-and-test-development (pull_request) Successful in 24m12s
25434b35b9
Reviewed-on: #310
hermes added 1 commit 2026-09-01 09:33:05 +00:00
chore: docs
Build / Build-and-ng-test (pull_request) Successful in 5m27s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m52s
Build / Build-and-test-development (pull_request) Successful in 24m31s
1ed31f2a49
allan added 1 commit 2026-09-02 13:07:23 +00:00
fix: ensure cleanup of casuser temp table in error condition
Build / Build-and-ng-test (pull_request) Successful in 5m26s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m58s
Build / Build-and-test-development (pull_request) Successful in 24m16s
c4123e5c96
hermes left a comment
Collaborator

Code Review — 4gl-reviewer

Summary: Large v7.13 release PR (236 files) covering licence key refactoring, editor formula rules, viewbox resize handles, CAS REPLACE load type, and getstagetable input validation. The security hardening on getstagetable.sas and the CAS-aware REPLACE path in mpe_targetloader.sas are well done. A few issues below.

Issues:

  • [sas.service.ts:449] — Multiple console.log statements left in production code (checkViyaDeploy: checking appLoc, appLoc exists?, makedata job already run?, etc.). These should use loggerService or be removed before release — they'll spam the browser console for every Viya deployment check.
  • [sas.service.ts:593] — The getFolderMembers error handler changed from reject() to resolve(false). While "assume setup is needed on error" is reasonable, this silently swallows API errors — if the Viya folder API is down, the user is redirected to /deploy with no indication of the real problem. Consider at least logging the error via loggerService.error() before resolving.
  • [combinedLicenceKey.ts:54] — DecompressionStream is a relatively new Web API (Chrome 80+, not available in older browsers or Node < 18). If this code runs in any non-modern-browser context, gzip decompression will throw. Consider a feature check or polyfill fallback.
  • [mpe_targetloader.sas — CAS REPLACE path] — The temp table CASUSER.&tmpds is created and loaded before the syscc>0 check, but if the data step that populates it fails mid-way, the proc sql; drop table CASUSER.&tmpds;quit; in the error path runs — which is good. However, the non-CAS REPLACE path's error check (syscc>0) only unlocks the table; it doesn't clean up any WORK. temp datasets created earlier in the macro. Verify that WORK.&STAGING_DS and related temps are cleaned up by SAS session termination or explicit drops elsewhere.

Suggestions:

  • [decodeLicenceFeatures.ts:35-46] — The decodeFeaturesObject uses ?? to default numeric fields to Infinity. This is correct, but the boolean fields (vb, fu, er, ar) are passed through without coercion — if the object has undefined for these, the licence state would have undefined booleans rather than false. Consider !!features.vb for safety.
  • [automatic.component.ts:167] — getComputeContextDetails() fires N parallel API calls (one per compute context). For servers with many contexts, this could be slow or hit rate limits. Consider whether the batch identity is needed for all contexts or just the selected one.
  • [getstagetable.sas] — The table_id validation with mp_validatecol(ISNAME) is excellent and prevents code injection. Consider also validating base_lib and base_ds output from mpe_submit with the same macro before using them in %dc_assignlib and %mp_getcols (the LIBDS check is used, which is good, but double-check it covers all injection vectors for librefs/dsnames used in macro variable resolution).

Looks good:

  • getstagetable.sas input validation is a genuine security improvement — table_id was previously used unsanitized in libname and set statements.
  • The CAS REPLACE path in mpe_targetloader.sas correctly uses table.deleteRows (CAS doesn't support SQL deletes) and places the error check before the destructive truncate.
  • The licence feature decoding refactoring cleanly separates the two format variants (positional string vs named object) with clear documentation of the append-only contract.
  • Extensive test coverage added across dc-validator/utils/, licensing/utils/, and viewboxes.component.spec.ts.
## Code Review — 4gl-reviewer **Summary:** Large v7.13 release PR (236 files) covering licence key refactoring, editor formula rules, viewbox resize handles, CAS REPLACE load type, and `getstagetable` input validation. The security hardening on `getstagetable.sas` and the CAS-aware REPLACE path in `mpe_targetloader.sas` are well done. A few issues below. **Issues:** - [`sas.service.ts:449`] — Multiple `console.log` statements left in production code (`checkViyaDeploy: checking appLoc`, `appLoc exists?`, `makedata job already run?`, etc.). These should use `loggerService` or be removed before release — they'll spam the browser console for every Viya deployment check. - [`sas.service.ts:593`] — The `getFolderMembers` error handler changed from `reject()` to `resolve(false)`. While "assume setup is needed on error" is reasonable, this silently swallows API errors — if the Viya folder API is down, the user is redirected to `/deploy` with no indication of the real problem. Consider at least logging the error via `loggerService.error()` before resolving. - [`combinedLicenceKey.ts:54`] — `DecompressionStream` is a relatively new Web API (Chrome 80+, not available in older browsers or Node < 18). If this code runs in any non-modern-browser context, gzip decompression will throw. Consider a feature check or polyfill fallback. - [`mpe_targetloader.sas` — CAS REPLACE path] — The temp table `CASUSER.&tmpds` is created and loaded before the `syscc>0` check, but if the `data` step that populates it fails mid-way, the `proc sql; drop table CASUSER.&tmpds;quit;` in the error path runs — which is good. However, the non-CAS REPLACE path's error check (`syscc>0`) only unlocks the table; it doesn't clean up any `WORK.` temp datasets created earlier in the macro. Verify that `WORK.&STAGING_DS` and related temps are cleaned up by SAS session termination or explicit drops elsewhere. **Suggestions:** - [`decodeLicenceFeatures.ts:35-46`] — The `decodeFeaturesObject` uses `??` to default numeric fields to `Infinity`. This is correct, but the boolean fields (`vb`, `fu`, `er`, `ar`) are passed through without coercion — if the object has `undefined` for these, the licence state would have `undefined` booleans rather than `false`. Consider `!!features.vb` for safety. - [`automatic.component.ts:167`] — `getComputeContextDetails()` fires N parallel API calls (one per compute context). For servers with many contexts, this could be slow or hit rate limits. Consider whether the batch identity is needed for all contexts or just the selected one. - [`getstagetable.sas`] — The `table_id` validation with `mp_validatecol(ISNAME)` is excellent and prevents code injection. Consider also validating `base_lib` and `base_ds` output from `mpe_submit` with the same macro before using them in `%dc_assignlib` and `%mp_getcols` (the `LIBDS` check is used, which is good, but double-check it covers all injection vectors for librefs/dsnames used in macro variable resolution). **Looks good:** - `getstagetable.sas` input validation is a genuine security improvement — `table_id` was previously used unsanitized in `libname` and `set` statements. - The CAS REPLACE path in `mpe_targetloader.sas` correctly uses `table.deleteRows` (CAS doesn't support SQL deletes) and places the error check before the destructive truncate. - The licence feature decoding refactoring cleanly separates the two format variants (positional string vs named object) with clear documentation of the append-only contract. - Extensive test coverage added across `dc-validator/utils/`, `licensing/utils/`, and `viewboxes.component.spec.ts`.
allan added 11 commits 2026-09-03 09:21:58 +00:00
fix(editor): fix formula $-substitution corruption and sorted-grid row/formula desync
Build / Build-and-ng-test (pull_request) Successful in 5m23s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m6s
Build / Build-and-test-development (pull_request) Successful in 25m7s
e139c37fbf
String.replace interpreted $&/$`/$'/$<digit> specially in DC.ORIG_VALUE/DC.USER_NAME substitutions, corrupting formulas whose cell data happened to contain a literal $. Separately, afterChange indexed dataSource with Handsontable's visual row instead of translating to physical (as beforeChange already does), desyncing EDIT_STATUS/overwritten-comment writes on a sorted grid. Investigating that surfaced a third, unrelated Handsontable bug: updateSettings() corrupts formula cell references whenever a sort is active, regardless of what it changes - fixed by clearing the sort around every updateSettings() call.
Merge branch 'version7-13' into version7-13-fix
Build / Build-and-ng-test (pull_request) Successful in 5m55s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m23s
Build / Build-and-test-development (pull_request) Successful in 25m15s
d3b881fa85
refactor(editor): document updateSettingsSortSafe's exceptions and dedupe sort-config normalization
Build / Build-and-ng-test (pull_request) Failing after 2m7s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Successful in 21m46s
cc68c218d8
fix: pin fast-uri to 3.1.7 to close high-severity SSRF/host-confusion advisories
Build / Build-and-ng-test (pull_request) Failing after 53s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 1m5s
f4325b2056
chore: regenerated client/package-lock.json
Build / Build-and-ng-test (pull_request) Failing after 54s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 1m12s
8f12fe74a6
fix(deps): regenerate lockfile with Node 24 and pin handsontable to 18.0.0
Lighthouse Checks / lighthouse (pull_request) Failing after 1m3s
Build / Build-and-ng-test (pull_request) Failing after 45s
Build / Build-and-test-development (pull_request) Skipped
7da29bdb11
fix(deps): regenerate lockfile with strict peer-dep resolution to fix npm ci in CI
Build / Build-and-ng-test (pull_request) Failing after 1m51s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 2m0s
efd2e18c7b
fix(deps): pin @handsontable/angular-wrapper to 18.0.0
Build / Build-and-ng-test (pull_request) Successful in 5m24s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m35s
Build / Build-and-test-development (pull_request) Successful in 25m4s
2d7df66fc2
18.1.0 has a malformed license field (points at a logo SVG instead of a real identifier), failing the license checker's --onlyAllow gate.
refactor(editor): harden pasteListener cleanup and document handsontable version pin
Build / Build-and-ng-test (pull_request) Successful in 5m58s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m39s
Build / Build-and-test-development (pull_request) Successful in 25m27s
3380150d1c
fix(editor): clear sort before reading cells to preserve on cancel
Build / Build-and-ng-test (pull_request) Successful in 5m16s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m2s
Build / Build-and-test-development (pull_request) Successful in 24m42s
4137ebdf6b
cancelEdit()'s getFormulaCellsToPreserveOnCancel callbacks passed a
physical rowIndex straight into visual-row-expecting Handsontable APIs
(getCommentAtCell, getDataAtRowProp) while the grid could still be
sorted, since clearSort() only ran later, right before updateSettings().
On a sorted grid this read the wrong row's comment/value, corrupting
which formula cells got preserved across cancel. Move clearSort() (via
getCurrentSortConfigs()) ahead of those callbacks instead.
Merge pull request 'fix(editor): fix formula $-substitution corruption and sorted-grid row/formula desync' (#311) from version7-13-fix into version7-13
Build / Build-and-ng-test (pull_request) Successful in 5m30s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m1s
Build / Build-and-test-development (pull_request) Successful in 24m32s
f797b1130a
Reviewed-on: #311
hermes left a comment
Collaborator

Code Review — 4gl-reviewer

Summary: Large v7.13 release PR adding formula column support (HARDFORMULA/SOFTFORMULA), combined licence keys, CAS engine REPLACE load type, editor row edit-status tracking, and substantial refactoring with well-tested extractions. Overall quality is high with strong test coverage, but a few items should be addressed before merge.

Issues:

  • [client/src/app/services/sas.service.ts:449-520] — Extensive console.log statements were left in checkViyaDeploy() and viyaMakedataSuccessfull(). These are debug-level logs (checkViyaDeploy: checking appLoc, viyaMakedataSuccessfull: checking folder, etc.) that will spam the browser console in production. Remove them or gate behind a debug flag.
  • [sas/sasjs/utils/chunk_deploy.py:152] — subprocess.run(..., timeout=timeout) can raise subprocess.TimeoutExpired which is not caught. An uncaught exception will crash the deploy script with a traceback instead of a clean failure message. Wrap in try/except to print a meaningful timeout error.
  • [sas/sasjs/macros/mpe_targetloader.sas:175-182] — In the CAS REPLACE path, the &syscc>0 check drops CASUSER.&tmpds between the UNLOCK and the %mp_abort call. If the drop fails (e.g. session state issues), &syscc could escalate, but %mp_abort is already conditioned on the prior &syscc value so this is low-risk. Consider moving the cleanup to after the abort guard for clarity.

Suggestions:

  • [client/src/app/licensing/licensing.component.ts:84-101] — bypassSecurityTrustHtml is used for all error messages. The comment correctly notes the content is hardcoded (no user input), so this is safe today, but consider keeping a code comment or lint rule to prevent future developers from interpolating user-supplied strings into these messages.
  • [client/src/app/editor/utils/classifyRow.ts:33] — JSON.stringify comparison for change detection is fragile (key ordering, undefined vs missing properties). It matches the existing inline logic it was extracted from, but consider a deep-equals utility for future iterations.
  • [client/src/app/editor/utils/normalizeSortConfig.ts:12] — The any[] return type is acknowledged in the doc comment (Handsontable's SortConfig isn't publicly typed). Acceptable pragmatic choice; add a // TODO: type when HT exports SortConfig for tracking.

Looks good:

  • Excellent test coverage on new extractions — classifyRow, expandCellRanges, combinedLicenceKey, decodeLicenceFeatures, syncOverwrittenCellComment, and the integration spec for sorted-grid row sync are all well-tested with edge cases.
  • The CAS REPLACE implementation in mpe_targetloader.sas mirrors the established pattern from bitemporal_dataloader.sas, including the pre-destructive abort check and varchar casting.
  • The mp_abort(mode=INCLUDE) additions across postdata.sas, getdata.sas, getdynamiccolvals.sas, loadfile.sas, and restore.sas are a clean fix for hook-script abort propagation.
  • The decodeLicenceFeatures refactor cleanly supports both legacy positional strings and new named objects without a version flag.
## Code Review — 4gl-reviewer **Summary:** Large v7.13 release PR adding formula column support (HARDFORMULA/SOFTFORMULA), combined licence keys, CAS engine REPLACE load type, editor row edit-status tracking, and substantial refactoring with well-tested extractions. Overall quality is high with strong test coverage, but a few items should be addressed before merge. **Issues:** - [client/src/app/services/sas.service.ts:449-520] — Extensive `console.log` statements were left in `checkViyaDeploy()` and `viyaMakedataSuccessfull()`. These are debug-level logs (`checkViyaDeploy: checking appLoc`, `viyaMakedataSuccessfull: checking folder`, etc.) that will spam the browser console in production. Remove them or gate behind a debug flag. - [sas/sasjs/utils/chunk_deploy.py:152] — `subprocess.run(..., timeout=timeout)` can raise `subprocess.TimeoutExpired` which is not caught. An uncaught exception will crash the deploy script with a traceback instead of a clean failure message. Wrap in try/except to print a meaningful timeout error. - [sas/sasjs/macros/mpe_targetloader.sas:175-182] — In the CAS REPLACE path, the `&syscc>0` check drops `CASUSER.&tmpds` between the UNLOCK and the `%mp_abort` call. If the drop fails (e.g. session state issues), `&syscc` could escalate, but `%mp_abort` is already conditioned on the prior `&syscc` value so this is low-risk. Consider moving the cleanup to after the abort guard for clarity. **Suggestions:** - [client/src/app/licensing/licensing.component.ts:84-101] — `bypassSecurityTrustHtml` is used for all error messages. The comment correctly notes the content is hardcoded (no user input), so this is safe today, but consider keeping a code comment or lint rule to prevent future developers from interpolating user-supplied strings into these messages. - [client/src/app/editor/utils/classifyRow.ts:33] — `JSON.stringify` comparison for change detection is fragile (key ordering, `undefined` vs missing properties). It matches the existing inline logic it was extracted from, but consider a deep-equals utility for future iterations. - [client/src/app/editor/utils/normalizeSortConfig.ts:12] — The `any[]` return type is acknowledged in the doc comment (Handsontable's SortConfig isn't publicly typed). Acceptable pragmatic choice; add a `// TODO: type when HT exports SortConfig` for tracking. **Looks good:** - Excellent test coverage on new extractions — `classifyRow`, `expandCellRanges`, `combinedLicenceKey`, `decodeLicenceFeatures`, `syncOverwrittenCellComment`, and the integration spec for sorted-grid row sync are all well-tested with edge cases. - The CAS REPLACE implementation in `mpe_targetloader.sas` mirrors the established pattern from `bitemporal_dataloader.sas`, including the pre-destructive abort check and varchar casting. - The `mp_abort(mode=INCLUDE)` additions across `postdata.sas`, `getdata.sas`, `getdynamiccolvals.sas`, `loadfile.sas`, and `restore.sas` are a clean fix for hook-script abort propagation. - The `decodeLicenceFeatures` refactor cleanly supports both legacy positional strings and new named objects without a version flag.
allan added 2 commits 2026-09-03 10:12:22 +00:00
fix(editor): restore multi-column sort with one call, not a loop
Build / Build-and-ng-test (pull_request) Successful in 5m23s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m1s
Build / Build-and-test-development (pull_request) Successful in 25m4s
8fafeb7923
multiColumnSorting's sort() replaces the entire sort state on every call rather than accumulating, so the restore loop after updateSettings()/editTable()/cancelEdit() silently dropped every sorted column but the last. Pass the captured configs as a single array instead. Adds a Karma integration test proving the loop
loses columns, and a Cypress test verifying cancelEdit() keeps a real shift-click multi-column sort intact end-to-end.
Merge pull request 'fix(editor): restore multi-column sort with one call, not a loop' (#312) from version7-13-fix into version7-13
Build / Build-and-ng-test (pull_request) Successful in 6m12s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m40s
Build / Build-and-test-development (pull_request) Successful in 24m57s
1ad27d55f1
Reviewed-on: #312
hermes left a comment
Collaborator

Code Review — 4gl-reviewer

Summary: Large v7.13 release introducing formula validation rules (HARDFORMULA/SOFTFORMULA) with HyperFormula integration, combined licence key support, CAS table handling in mpe_targetloader, security hardening in getstagetable.sas, and extensive utility extraction with good test coverage. Overall well-structured with thoughtful documentation.

Issues:

  • [sas/sasjs/macros/mpe_targetloader.sas:~180] — CAS branch does table.deleteRows where="1=1" (full truncate) before the data ... append=yes step. If the append fails, the target table is left empty with no rollback. Consider wrapping in a transaction or staging a backup that can be restored on failure.
  • [client/src/app/services/sas.service.ts:449-525] — Many verbose console.log statements (e.g. 'checkViyaDeploy: checking appLoc', 'viyaMakedataSuccessfull: folder members:') appear to be debug logging left in production code. These should use loggerService or be removed/gated behind a debug flag.
  • [client/src/app/shared/dc-validator/utils/applyFormulaRules.ts:30] and [findOverwrittenCells.ts:31] — Both do dataSourceUnchanged.find(...) inside a forEach loop, giving O(n×m) complexity for PK matching. On large tables this could be slow. A Map keyed by PK values would make each lookup O(1).
  • [client/src/app/licensing/licensing.component.ts:70-90] — bypassSecurityTrustHtml is used for error messages. The comment correctly notes the content is hardcoded, but this pattern bypasses Angular's XSS protection — if any of these strings ever incorporate user-derived data in the future, it becomes an injection vector. Worth a follow-up to render <clr-icon> via Angular templates instead.
  • [sas/sasjs/db/migrations/20260807_v7.13_release.sas:17] — select max(selectbox_rk) into: maxrk then &maxrk+1 / &maxrk+2 will produce .+1 (a SAS error) if mpe_selectbox is empty. The makedata equivalent uses %mf_increment(rk) which is safer. Consider adding a %let maxrk=0; default before the select.

Suggestions:

  • [client/src/app/services/utils/decodeLicenceFeatures.ts:64] — parseInt(codeBit) is called without a radix argument. While moved unchanged from the original, parseInt(codeBit, 10) would be more defensive against leading-zero strings.
  • [sas/sasjs/utils/chunk_deploy.py:140] — sys.exit(1) on the first chunk failure means a partial deployment with no rollback. Consider logging which chunks succeeded so the operator knows where to resume.
  • [client/src/app/deploy/sections/automatic/automatic.component.ts:165] — getComputeContextDetails fires one API call per context in parallel. Environments with many contexts could hit rate limits; consider batching or at least logging a count of contexts being fetched.

Looks good:

  • Excellent test coverage — nearly every new utility has a corresponding .spec.ts file with meaningful test cases.
  • getstagetable.sas security hardening (table_id validation via mp_validatecol + whitelist check against mpe_submit) is a solid injection-prevention pattern.
  • parseFormulaRule.ts correctly skips quoted string spans during variable substitution and uses function replacements to avoid $ interpretation — well thought out.
  • classifyRow.ts correctly excludes EDIT_STATUS from the diff comparison to avoid self-perpetuating 'M' state.
  • resizeFromLeftEdge in viewboxes.component.ts has an excellent explanation of why raw pointer coordinates are used instead of getBoundingClientRect.
  • Combined licence key format detection (DCKEY1: prefix, unambiguous with base64 alphabet) is clean and well-documented.
## Code Review — 4gl-reviewer **Summary:** Large v7.13 release introducing formula validation rules (HARDFORMULA/SOFTFORMULA) with HyperFormula integration, combined licence key support, CAS table handling in mpe_targetloader, security hardening in getstagetable.sas, and extensive utility extraction with good test coverage. Overall well-structured with thoughtful documentation. **Issues:** - [`sas/sasjs/macros/mpe_targetloader.sas:~180`] — CAS branch does `table.deleteRows where="1=1"` (full truncate) before the `data ... append=yes` step. If the append fails, the target table is left empty with no rollback. Consider wrapping in a transaction or staging a backup that can be restored on failure. - [`client/src/app/services/sas.service.ts:449-525`] — Many verbose `console.log` statements (e.g. `'checkViyaDeploy: checking appLoc'`, `'viyaMakedataSuccessfull: folder members:'`) appear to be debug logging left in production code. These should use `loggerService` or be removed/gated behind a debug flag. - [`client/src/app/shared/dc-validator/utils/applyFormulaRules.ts:30`] and [`findOverwrittenCells.ts:31`] — Both do `dataSourceUnchanged.find(...)` inside a `forEach` loop, giving O(n×m) complexity for PK matching. On large tables this could be slow. A `Map` keyed by PK values would make each lookup O(1). - [`client/src/app/licensing/licensing.component.ts:70-90`] — `bypassSecurityTrustHtml` is used for error messages. The comment correctly notes the content is hardcoded, but this pattern bypasses Angular's XSS protection — if any of these strings ever incorporate user-derived data in the future, it becomes an injection vector. Worth a follow-up to render `<clr-icon>` via Angular templates instead. - [`sas/sasjs/db/migrations/20260807_v7.13_release.sas:17`] — `select max(selectbox_rk) into: maxrk` then `&maxrk+1` / `&maxrk+2` will produce `.+1` (a SAS error) if `mpe_selectbox` is empty. The makedata equivalent uses `%mf_increment(rk)` which is safer. Consider adding a `%let maxrk=0;` default before the select. **Suggestions:** - [`client/src/app/services/utils/decodeLicenceFeatures.ts:64`] — `parseInt(codeBit)` is called without a radix argument. While moved unchanged from the original, `parseInt(codeBit, 10)` would be more defensive against leading-zero strings. - [`sas/sasjs/utils/chunk_deploy.py:140`] — `sys.exit(1)` on the first chunk failure means a partial deployment with no rollback. Consider logging which chunks succeeded so the operator knows where to resume. - [`client/src/app/deploy/sections/automatic/automatic.component.ts:165`] — `getComputeContextDetails` fires one API call per context in parallel. Environments with many contexts could hit rate limits; consider batching or at least logging a count of contexts being fetched. **Looks good:** - Excellent test coverage — nearly every new utility has a corresponding `.spec.ts` file with meaningful test cases. - `getstagetable.sas` security hardening (table_id validation via `mp_validatecol` + whitelist check against `mpe_submit`) is a solid injection-prevention pattern. - `parseFormulaRule.ts` correctly skips quoted string spans during variable substitution and uses function replacements to avoid `$` interpretation — well thought out. - `classifyRow.ts` correctly excludes `EDIT_STATUS` from the diff comparison to avoid self-perpetuating 'M' state. - `resizeFromLeftEdge` in viewboxes.component.ts has an excellent explanation of why raw pointer coordinates are used instead of getBoundingClientRect. - Combined licence key format detection (`DCKEY1:` prefix, unambiguous with base64 alphabet) is clean and well-documented.
allan merged commit 34ff2ac8d0 into main 2026-09-03 14:16:03 +00:00
allan deleted branch version7-13 2026-09-03 14:16:03 +00:00
Sign in to join this conversation.
No Reviewers
3 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dc/dc#294