Compare commits

...
Author SHA1 Message Date
semantic-release-bot e07246ac68 chore(release): 7.22.0 [skip ci]
# [7.22.0](https://git.datacontroller.io/dc/dc/compare/v7.21.0...v7.22.0) (2026-11-05)

### Bug Fixes

* **browse:** accept legal folder names, cap the walk, and lock the read ([3572d56](3572d56cfb))
* **build:** build the mock sites with a CLI whose runtime provides webout() ([b1eab98](b1eab98200))
* **build:** publish the changed-path decision and pin the matcher with tests ([ecc8702](ecc87021c3))
* **build:** treat a sas/package.json bump as a Cypress input ([48df795](48df795202))
* **client:** cancel a queued dropdown reposition on destroy ([590b841](590b841f24))
* **client:** drop the synthetic window resize dispatches ([2535baa](2535baa5f7))
* **client:** keep the ARIA pass on the viewbox and multi-dataset grids too ([cb30469](cb30469e79))
* **client:** restrict the table pass to Handsontable's own tables, add the viewer fallback ([46c6aec](46c6aec37a))
* **client:** scope the ARIA pass to each grid and drive it from the render hooks ([bf1e7d6](bf1e7d6b15))
* **client:** stop synthesising aria-labels; name the filter modal's submit in the test ([32d3d49](32d3d49b59))
* **client:** strip only this grid's own wrapper, and pin the re-strip after a data load ([f282d1a](f282d1a44b))
* **deploy:** ACTION cannot seed by default, and the probe stops overwriting ([e84cfaa](e84cfaaabd))
* **deploy:** ask for the admin group before the staging directory on SASjs ([5c64691](5c646913e4))
* **deploy:** await nothing, and encode the values in the makedata URL ([d67a0e5](d67a0e5d5f))
* **deploy:** drop the deprecated service-pack deploy from the setup screen ([59e09e2](59e09e26a5))
* **deploy:** gate the browse actions on the settings file ([5eb8492](5eb8492742))
* **deploy:** make the folder picker work on both estates ([fb38309](fb38309e98))
* **deploy:** match the documented SYSSCPL set and stop guessing at the cap ([5f338ff](5f338ff437))
* **deploy:** read a URL-supplied ACTION before defaulting, and show locked folders ([addb4a5](addb4a56ea))
* **deploy:** stop the makedata poll outliving its screen, and align the release gates ([c808c41](c808c41333)), closes [#407](#407)
* **deploy:** unclamp the folder picker row and probe in the chosen context ([1330925](133092502c))
* **deps:** bump @sasjs/core to 7.0.1 so special missings can be restored ([3d2eefd](3d2eefdd7b)), closes [sasjs/core#499](sasjs/core#499)
* **deps:** bump @sasjs/core to 7.1.3 so a filter abort names the clause ([c7c1815](c7c181518c))
* **editor:** a container resize must not clear an active sort ([5bd7b5d](5bd7b5d724)), closes [#246](#246) [#246](#246)
* **editor:** drop the stale case hint, and narrow the strip to our own form ([bd2739c](bd2739cde4))
* **editor:** one predicate for the CASE marker, and no stacking in the tooltip ([18b0546](18b0546dcb))
* **embed:** a rejected mapping must not block the page ([d845b9e](d845b9e9b7)), closes [dc/dc#404](#404)
* **embed:** read a month-name date from VA as a date, not a character value ([30cefcf](30cefcf2dc))
* **getgroupmembers:** widen membername to $256 for long names ([196d6c9](196d6c931d))
* **lint:** enable noSingleAsteriskComments and noUnusedLibnames ([9071801](9071801ac5)), closes [#376](#376)
* **lint:** make noBannedStrings an error and clear its 13 findings ([0b85f7b](0b85f7bf1e)), closes [#376](#376)
* **lint:** make noUndeclaredMacros an error, and fix what it found ([af28a79](af28a79bd5)), closes [#376](#376)
* **lint:** make noUnusedMacros an error and clear its 54 findings ([c8c0858](c8c0858295)), closes [#376](#376)
* **lint:** rename the short and mixed-case helper macros, and clear the last warnings ([7da67eb](7da67eb17b)), closes [#376](#376)
* **makedata:** let the browse actions live as long as the service does ([912fae4](912fae448b))
* **mocks:** 0 is a closed TX_TO, a libref boundary, and the union of the row keys ([67d9cfd](67d9cfd1bd))
* **mocks:** abort like mp_abort, lock like mp_lockanytable, derive saslibs ([ca60208](ca602082ff))
* **mocks:** answer the deploy screen's DIRLIST and WRITECHECK actions ([e1c3c6e](e1c3c6e872))
* **mocks:** derive startupservice's maps and timezone instead of hardcoding ([87fd46c](87fd46c825))
* **mocks:** fold the audit window, and append MPE_AUDIT under its lock ([5ecc7be](5ecc7bebec))
* **mocks:** keep the OMS and path separators the captures lost ([7d9d466](7d9d466c3e))
* **mocks:** one sasDatetimeNow, a documented TX_TO=0, and a shaped fallback export ([e53c50d](e53c50d4e2))
* **mocks:** read hook columns in either case, and guard duplicates first ([349a2f3](349a2f314c))
* **mocks:** read the metadata member from the response, not the function's arity ([09e9f19](09e9f192c5))
* **mocks:** refuse a runtime whose webout() cannot carry column metadata ([482435f](482435f318))
* **mocks:** resolve the audit table once, record lower-case md5, and report a failed audit write ([1a9f594](1a9f59478b))
* **mocks:** run approval hooks, as mpe_runhook does ([f0208bb](f0208bb0b0))
* **mocks:** separate a body read failure from a missing showmeta floor ([744c89b](744c89b919))
* **mpe_abortmessage:** decide once whether the payload was escaped ([af89126](af89126acf))
* **mpe_abortmessage:** read program and state by name, and trim the prefix ([68b89ca](68b89caf63))
* **mpe_assert_abort:** anchor the cut on the whole escaped separator ([e74a7ba](e74a7ba115))
* **mpe_assert_abort:** compare the hook's message, not the log extract ([8b426c2](8b426c27fd))
* **mpe_assert_abort:** compare the unescaped message, cut at the last marker ([e68d221](e68d221354))
* **mpe_assert_abort:** cut at the separator, not the last marker ([4c7e869](4c7e869369))
* **mpe_assert_abort:** name the separator and tolerate a carriage return ([7b09ade](7b09ade97c))
* **mpe_assert_abort:** size the capture buffer for the whole message ([5d32de9](5d32de9df9))
* **mpe_assert_abort:** strip the separator tail, not every trailing escape ([b29bc1e](b29bc1e7ae))
* **mpe_execute:** assemble the abort text in a macro that can be tested ([e2e9103](e2e91039cb))
* **mpe_execute:** keep a child abort message intact when reporting it ([ebfd395](ebfd39582f))
* **mpe_execute:** keep the child message in the fallback, and test the doubled separator ([11587e2](11587e2f24))
* **mpe_execute:** make the abort-text fallback reachable ([24b8cbe](24b8cbe9dc))
* **mpe_execute:** mask a child abort message before it aborts the caller ([20dbf00](20dbf00fb0))
* **mpe_execute:** one symbol for the abort text, and no values in the call ([700b906](700b906d69))
* **multi-dataset:** a sheet that failed to extract is not a clean finish ([3129648](312964864e))
* **multi-dataset:** show the file size and loading stage while the file loads ([45d6c02](45d6c025c9)), closes [#121](#121)
* **report:** treat a blank PDF footer parameter as its default ([0f729d7](0f729d7101))
* **test:** let the dirlist and writecheck results reach the caller on Viya ([ed678f0](ed678f0b46))
* **test:** run metanav.cy.ts in CI and open a metadata object ([02a8590](02a8590310))
* **va-host:** bound the early replay even when the frame is not postable ([f6d5a04](f6d5a0495e))
* **va-host:** deliver in load mode, honour dataType, and survive odd prompt names ([7d4155d](7d4155dc6c))
* **va-host:** deliver the load-mount message once, and document report.json from the file ([90784b3](90784b342f))
* **va-host:** keep the deploy password off argv, and verify the app is served ([7c63a5c](7c63a5c93f))
* **va-host:** post to the frame's own origin, and key the replay to the document ([9cec292](9cec292681))
* **viewer:** clamp over-tall PDF rows, always emit an otherwise-empty page ([10229d5](10229d5efe))
* **viewer:** defer the blob URL revoke, reject a selection inside skipped columns ([b5bb704](b5bb704a4f))
* **viewer:** keep the PDF export working where hiddenColumns is not enabled ([f84d4ca](f84d4ca7af))
* **viewer:** review findings - CSV skipped column, PDF row cap before reading ([2c4be5e](2c4be5e94b))

### Features

* **admin:** find and test a writeable location during deploy ([442c595](442c595c2b))
* **deploy:** pick and validate the DC location from the deploy screens ([6eeab2e](6eeab2ed16))
* **editor:** convert CASE rule values on entry ([c4eef49](c4eef498b2))
* **logs:** read the logs modal with the shared log parser ([aabc578](aabc578d5e))
* **mocks:** add the control-table services the screens call ([a1615ee](a1615ee8a7))
* **mocks:** build every JS stored program's response with webout() ([ce6bb5d](ce6bb5d4cd))
* **mocks:** emit the lineage, usernav and libinfo responses through webout() ([1f54fad](1f54fad3fe))
* **mocks:** emit the metanav responses through webout() ([e1d877f](e1d877f21d))
* **mocks:** emit the sas9 services through webout() ([7be3f84](7be3f84073)), closes [#461](#461)
* **mocks:** restore a load, and record the diffs it needs ([1a60c68](1a60c689f4))
* **mocks:** use the server's webout() in the JS stored programs ([ccb67ed](ccb67ed5a0))
* **va-host:** emulate a VA report that embeds DC, as a deployable AppStream app ([2185db9](2185db911d))
* **viewer:** download a server-side PDF report ([67ca129](67ca129c4d))
* **viewer:** export the grid as PDF from the context menu ([e58e735](e58e735610))

### Reverts

* keep the helpers nested inside makedata_body ([4424171](4424171b2f))
2026-11-05 21:11:44 +00:00
allan 39e8ffdbdd Merge pull request 'feat: Release/7.22' (#407) from release/7.22 into main
Release / Build-production-and-ng-test (push) Successful in 4m25s
Release / Build-and-test-development (push) Successful in 21m8s
Release / release (push) Successful in 4m37s
Reviewed-on: #407
2026-11-05 20:42:41 +00:00
allan 6f7f2d189f Merge pull request 'fix(deploy): drop the browse gate, and ask for the admin group before the directory' (#478) from fix/7.22-browse-window-and-order into release/7.22
Sites / deploy (push) Successful in 3m6s
Build / Build-and-ng-test (pull_request) Successful in 3m46s
Lighthouse Checks / lighthouse (pull_request) Successful in 8m4s
Build / Build-and-test-development (pull_request) Successful in 29m18s
Reviewed-on: #478
2026-11-05 18:54:47 +00:00
dc 5c646913e4 fix(deploy): ask for the admin group before the staging directory on SASjs
Build / Build-and-ng-test (pull_request) Successful in 4m15s
Lighthouse Checks / lighthouse (pull_request) Successful in 8m31s
Build / Build-and-test-development (pull_request) Successful in 33m3s
The SASjs Server / SAS9 screen put the DC Staging Directory first and the admin
group second, the reverse of the Viya screen, so the operator was asked to pick
a location before being told who administers DC. It now asks for the admin group
first and the directory last, with the copy reordered to match.

A rendered-order spec covers it: it reads the labels off the rendered template
and fails if the directory comes first again.
2026-11-05 18:21:54 +00:00
dc 912fae448b fix(makedata): let the browse actions live as long as the service does
DIRLIST and WRITECHECK were refused once the appLoc had a settings.sas, on the
argument that a failed run could leave that file behind. That is what makes the
gate wrong: a failed run is exactly when the deploy screen still needs to list
and probe directories, and any appLoc configured earlier refuses them for good.
The window needs no separate clock - a successful run deletes this program, so
once DC is configured there is nothing left here to call.

Removed from both variants (server checks the drive over HTTP, Viya checks the
filesystem; neither is needed), and the two tests that asserted the refusal go
with it. The macros themselves are covered by mpe_dirlist.test.sas and
mpe_writecheck.test.sas.

Seen on sas.4gl.io: a deployment to /Public/app/dc477 answered BROWSE with
"DIRLIST and WRITECHECK are only available until DC is configured".
2026-11-05 18:21:54 +00:00
allan eef91ce989 Merge pull request 'fix(7.22): clear the makedata poll on destroy, and close the release-gate findings' (#477) from fix/7.22-release-followups into release/7.22
Sites / deploy (push) Successful in 3m6s
Build / Build-and-ng-test (pull_request) Successful in 6m18s
Lighthouse Checks / lighthouse (pull_request) Successful in 8m47s
Build / Build-and-test-development (pull_request) Successful in 27m24s
Reviewed-on: #477
2026-11-05 17:58:28 +00:00
7 changed files with 232 additions and 112 deletions

No files matched your search

+106
View File
@@ -1,3 +1,109 @@
# [7.22.0](https://git.datacontroller.io/dc/dc/compare/v7.21.0...v7.22.0) (2026-11-05)
### Bug Fixes
* **browse:** accept legal folder names, cap the walk, and lock the read ([3572d56](https://git.datacontroller.io/dc/dc/commit/3572d56cfb0e438512fac202829c8409388b1dac))
* **build:** build the mock sites with a CLI whose runtime provides webout() ([b1eab98](https://git.datacontroller.io/dc/dc/commit/b1eab982006393bb23f8d19076a715055087d4fc))
* **build:** publish the changed-path decision and pin the matcher with tests ([ecc8702](https://git.datacontroller.io/dc/dc/commit/ecc87021c3e398f7d2b451971e9d618d3dd33d04))
* **build:** treat a sas/package.json bump as a Cypress input ([48df795](https://git.datacontroller.io/dc/dc/commit/48df79520217afbc55a5b7f6a67a500efc70db07))
* **client:** cancel a queued dropdown reposition on destroy ([590b841](https://git.datacontroller.io/dc/dc/commit/590b841f24329e6d1aefab881895f794fc3738ed))
* **client:** drop the synthetic window resize dispatches ([2535baa](https://git.datacontroller.io/dc/dc/commit/2535baa5f7ad169f48678df3f0f55ca18b5eaa9a))
* **client:** keep the ARIA pass on the viewbox and multi-dataset grids too ([cb30469](https://git.datacontroller.io/dc/dc/commit/cb30469e794b4fb4c87f5c382f36236b837e1855))
* **client:** restrict the table pass to Handsontable's own tables, add the viewer fallback ([46c6aec](https://git.datacontroller.io/dc/dc/commit/46c6aec37a836722fff61337fde4cab0d97723d8))
* **client:** scope the ARIA pass to each grid and drive it from the render hooks ([bf1e7d6](https://git.datacontroller.io/dc/dc/commit/bf1e7d6b1583ef32d492840c508d2a4b0fb1a1df))
* **client:** stop synthesising aria-labels; name the filter modal's submit in the test ([32d3d49](https://git.datacontroller.io/dc/dc/commit/32d3d49b59fd158c5790c9e1a4dd3cfc4f532877))
* **client:** strip only this grid's own wrapper, and pin the re-strip after a data load ([f282d1a](https://git.datacontroller.io/dc/dc/commit/f282d1a44bfe458c1e17415e1e3ba8b484f61518))
* **deploy:** ACTION cannot seed by default, and the probe stops overwriting ([e84cfaa](https://git.datacontroller.io/dc/dc/commit/e84cfaaabdf6fb91b0d58524f8a1ad7a4d2673f3))
* **deploy:** ask for the admin group before the staging directory on SASjs ([5c64691](https://git.datacontroller.io/dc/dc/commit/5c646913e453699ee8d5e7aedcae8b92b125ebde))
* **deploy:** await nothing, and encode the values in the makedata URL ([d67a0e5](https://git.datacontroller.io/dc/dc/commit/d67a0e5d5f06eac2d3aba220f43eb0c7f9a2e4af))
* **deploy:** drop the deprecated service-pack deploy from the setup screen ([59e09e2](https://git.datacontroller.io/dc/dc/commit/59e09e26a5f4432fbfb397287f1fdd4f656d761e))
* **deploy:** gate the browse actions on the settings file ([5eb8492](https://git.datacontroller.io/dc/dc/commit/5eb8492742a6ede252bc4f40dd33932700792718))
* **deploy:** make the folder picker work on both estates ([fb38309](https://git.datacontroller.io/dc/dc/commit/fb38309e98a6455ffe1d2cc501d5c1f9e0a694ba))
* **deploy:** match the documented SYSSCPL set and stop guessing at the cap ([5f338ff](https://git.datacontroller.io/dc/dc/commit/5f338ff43754ef58ba45c81d9929115c3f4715a3))
* **deploy:** read a URL-supplied ACTION before defaulting, and show locked folders ([addb4a5](https://git.datacontroller.io/dc/dc/commit/addb4a56ea4e7cac2bf92956fcc30b593bba7eee))
* **deploy:** stop the makedata poll outliving its screen, and align the release gates ([c808c41](https://git.datacontroller.io/dc/dc/commit/c808c41333a1dd46008c38fe60169585e03bfe68)), closes [#407](https://git.datacontroller.io/dc/dc/issues/407)
* **deploy:** unclamp the folder picker row and probe in the chosen context ([1330925](https://git.datacontroller.io/dc/dc/commit/133092502cf4832ab9df7472b690e646ed0765ff))
* **deps:** bump @sasjs/core to 7.0.1 so special missings can be restored ([3d2eefd](https://git.datacontroller.io/dc/dc/commit/3d2eefdd7b815e8dcff7989393a6055468859bbd)), closes [sasjs/core#499](https://git.datacontroller.io/sasjs/core/issues/499)
* **deps:** bump @sasjs/core to 7.1.3 so a filter abort names the clause ([c7c1815](https://git.datacontroller.io/dc/dc/commit/c7c181518cbdb209fc73830bb791e87186bcfc20))
* **editor:** a container resize must not clear an active sort ([5bd7b5d](https://git.datacontroller.io/dc/dc/commit/5bd7b5d724db1590d211c25fa6b5cd3568ead36c)), closes [#246](https://git.datacontroller.io/dc/dc/issues/246) [#246](https://git.datacontroller.io/dc/dc/issues/246)
* **editor:** drop the stale case hint, and narrow the strip to our own form ([bd2739c](https://git.datacontroller.io/dc/dc/commit/bd2739cde41f4e2132059d04b75f0e9ed4cf33d9))
* **editor:** one predicate for the CASE marker, and no stacking in the tooltip ([18b0546](https://git.datacontroller.io/dc/dc/commit/18b0546dcbe647a5a64209535c04132c4c68bd17))
* **embed:** a rejected mapping must not block the page ([d845b9e](https://git.datacontroller.io/dc/dc/commit/d845b9e9b74a87be907f2455e55dbf21c7dfdd36)), closes [dc/dc#404](https://git.datacontroller.io/dc/dc/issues/404)
* **embed:** read a month-name date from VA as a date, not a character value ([30cefcf](https://git.datacontroller.io/dc/dc/commit/30cefcf2dcfd78ccb963d86b36668248fec9fa1e))
* **getgroupmembers:** widen membername to $256 for long names ([196d6c9](https://git.datacontroller.io/dc/dc/commit/196d6c931d44527250f59dd1f89db259a24774ad))
* **lint:** enable noSingleAsteriskComments and noUnusedLibnames ([9071801](https://git.datacontroller.io/dc/dc/commit/9071801ac51ac1bc726c9430389d34bddc7cd1c0)), closes [#376](https://git.datacontroller.io/dc/dc/issues/376)
* **lint:** make noBannedStrings an error and clear its 13 findings ([0b85f7b](https://git.datacontroller.io/dc/dc/commit/0b85f7bf1e59543ac195e37dd0a9e4c58b6befb6)), closes [#376](https://git.datacontroller.io/dc/dc/issues/376)
* **lint:** make noUndeclaredMacros an error, and fix what it found ([af28a79](https://git.datacontroller.io/dc/dc/commit/af28a79bd56234f17545c5bc3df4969464779264)), closes [#376](https://git.datacontroller.io/dc/dc/issues/376)
* **lint:** make noUnusedMacros an error and clear its 54 findings ([c8c0858](https://git.datacontroller.io/dc/dc/commit/c8c0858295f40ba734403ba24a4cae02b5464554)), closes [#376](https://git.datacontroller.io/dc/dc/issues/376)
* **lint:** rename the short and mixed-case helper macros, and clear the last warnings ([7da67eb](https://git.datacontroller.io/dc/dc/commit/7da67eb17bccf2e6635d01a007872294b5d1708a)), closes [#376](https://git.datacontroller.io/dc/dc/issues/376)
* **makedata:** let the browse actions live as long as the service does ([912fae4](https://git.datacontroller.io/dc/dc/commit/912fae448b55130db13b3e684ea66b846d3d51c7))
* **mocks:** 0 is a closed TX_TO, a libref boundary, and the union of the row keys ([67d9cfd](https://git.datacontroller.io/dc/dc/commit/67d9cfd1bd9eb9512ddd9d222221dcf674552d9f))
* **mocks:** abort like mp_abort, lock like mp_lockanytable, derive saslibs ([ca60208](https://git.datacontroller.io/dc/dc/commit/ca602082ffc69f4cfca2de878fbf842a57efc194))
* **mocks:** answer the deploy screen's DIRLIST and WRITECHECK actions ([e1c3c6e](https://git.datacontroller.io/dc/dc/commit/e1c3c6e8727d928a10ddd2838563d13efcf33ddc))
* **mocks:** derive startupservice's maps and timezone instead of hardcoding ([87fd46c](https://git.datacontroller.io/dc/dc/commit/87fd46c825f592927df0e172bdfc91f0d13f81ef))
* **mocks:** fold the audit window, and append MPE_AUDIT under its lock ([5ecc7be](https://git.datacontroller.io/dc/dc/commit/5ecc7bebec104ef451e3d405c4c1728e39d8c2b0))
* **mocks:** keep the OMS and path separators the captures lost ([7d9d466](https://git.datacontroller.io/dc/dc/commit/7d9d466c3e9e9863e897bd1f663664274e660153))
* **mocks:** one sasDatetimeNow, a documented TX_TO=0, and a shaped fallback export ([e53c50d](https://git.datacontroller.io/dc/dc/commit/e53c50d4e2b4b8e3b3097bd415f2e2bbf940793d))
* **mocks:** read hook columns in either case, and guard duplicates first ([349a2f3](https://git.datacontroller.io/dc/dc/commit/349a2f314cbcbf6c706e201ccb5faa3412f934f4))
* **mocks:** read the metadata member from the response, not the function's arity ([09e9f19](https://git.datacontroller.io/dc/dc/commit/09e9f192c5dbc64f2812c2bac90b61d3e1e7346a))
* **mocks:** refuse a runtime whose webout() cannot carry column metadata ([482435f](https://git.datacontroller.io/dc/dc/commit/482435f318a3bd0dd33df02c6f4dc6335d7a1a6e))
* **mocks:** resolve the audit table once, record lower-case md5, and report a failed audit write ([1a9f594](https://git.datacontroller.io/dc/dc/commit/1a9f59478b059ac59c284f45cdd08fa6428e430c))
* **mocks:** run approval hooks, as mpe_runhook does ([f0208bb](https://git.datacontroller.io/dc/dc/commit/f0208bb0b00cce7e6fa1e5015aa95b4286509bdb))
* **mocks:** separate a body read failure from a missing showmeta floor ([744c89b](https://git.datacontroller.io/dc/dc/commit/744c89b91972f08130797a2650daf49cce0e6eb7))
* **mpe_abortmessage:** decide once whether the payload was escaped ([af89126](https://git.datacontroller.io/dc/dc/commit/af89126acf38d9aeee320b5784df87cdd48148e8))
* **mpe_abortmessage:** read program and state by name, and trim the prefix ([68b89ca](https://git.datacontroller.io/dc/dc/commit/68b89caf63678ace2bde61414b5b4bac2d419a9d))
* **mpe_assert_abort:** anchor the cut on the whole escaped separator ([e74a7ba](https://git.datacontroller.io/dc/dc/commit/e74a7ba1153f83eb436235fa17a90826e09af81f))
* **mpe_assert_abort:** compare the hook's message, not the log extract ([8b426c2](https://git.datacontroller.io/dc/dc/commit/8b426c27fd37a071a3df0417bbda3e1ec2edc291))
* **mpe_assert_abort:** compare the unescaped message, cut at the last marker ([e68d221](https://git.datacontroller.io/dc/dc/commit/e68d221354a3ab94d2975ca0949dee7b4f0c4137))
* **mpe_assert_abort:** cut at the separator, not the last marker ([4c7e869](https://git.datacontroller.io/dc/dc/commit/4c7e8693698ef7da6dd94e053cb6183a536004a3))
* **mpe_assert_abort:** name the separator and tolerate a carriage return ([7b09ade](https://git.datacontroller.io/dc/dc/commit/7b09ade97c4283fcff49a3e3d3b42a88085e575d))
* **mpe_assert_abort:** size the capture buffer for the whole message ([5d32de9](https://git.datacontroller.io/dc/dc/commit/5d32de9df97208e87e9baf7b60113ea155264510))
* **mpe_assert_abort:** strip the separator tail, not every trailing escape ([b29bc1e](https://git.datacontroller.io/dc/dc/commit/b29bc1e7ae6fa0c166e0649d96df1bc9aed9d54c))
* **mpe_execute:** assemble the abort text in a macro that can be tested ([e2e9103](https://git.datacontroller.io/dc/dc/commit/e2e91039cbf67cdc4306d4abb9d1db000c8ca1a3))
* **mpe_execute:** keep a child abort message intact when reporting it ([ebfd395](https://git.datacontroller.io/dc/dc/commit/ebfd39582fe6abe7249ce696cd79ff2a8962d53b))
* **mpe_execute:** keep the child message in the fallback, and test the doubled separator ([11587e2](https://git.datacontroller.io/dc/dc/commit/11587e2f2433fc68fc9e382b7fd3c3136a4edd34))
* **mpe_execute:** make the abort-text fallback reachable ([24b8cbe](https://git.datacontroller.io/dc/dc/commit/24b8cbe9dc3dc41a7fdd75d36de09dc7d7e4ca17))
* **mpe_execute:** mask a child abort message before it aborts the caller ([20dbf00](https://git.datacontroller.io/dc/dc/commit/20dbf00fb0b468549330cee049ff17bcdd99d5ea))
* **mpe_execute:** one symbol for the abort text, and no values in the call ([700b906](https://git.datacontroller.io/dc/dc/commit/700b906d69a20a73165335c8acbeb95f70dc1fea))
* **multi-dataset:** a sheet that failed to extract is not a clean finish ([3129648](https://git.datacontroller.io/dc/dc/commit/312964864ef5d908e181610245c46cd2e78b86cb))
* **multi-dataset:** show the file size and loading stage while the file loads ([45d6c02](https://git.datacontroller.io/dc/dc/commit/45d6c025c94946b7543f88ebaf92baae341d8a10)), closes [#121](https://git.datacontroller.io/dc/dc/issues/121)
* **report:** treat a blank PDF footer parameter as its default ([0f729d7](https://git.datacontroller.io/dc/dc/commit/0f729d7101ad14d556f7d356ffeb5366072e6be5))
* **test:** let the dirlist and writecheck results reach the caller on Viya ([ed678f0](https://git.datacontroller.io/dc/dc/commit/ed678f0b4616da0cef7f61b5e8b890cbdc26e912))
* **test:** run metanav.cy.ts in CI and open a metadata object ([02a8590](https://git.datacontroller.io/dc/dc/commit/02a8590310afce6c43a9218b67824cc6ac988710))
* **va-host:** bound the early replay even when the frame is not postable ([f6d5a04](https://git.datacontroller.io/dc/dc/commit/f6d5a0495e922ef3f3c14b0c291e4f8301a865ba))
* **va-host:** deliver in load mode, honour dataType, and survive odd prompt names ([7d4155d](https://git.datacontroller.io/dc/dc/commit/7d4155dc6ca0f3ec788a9e33719e9e29fe82d470))
* **va-host:** deliver the load-mount message once, and document report.json from the file ([90784b3](https://git.datacontroller.io/dc/dc/commit/90784b342f82d480fd67fdbcad5f65e8cd9c59e0))
* **va-host:** keep the deploy password off argv, and verify the app is served ([7c63a5c](https://git.datacontroller.io/dc/dc/commit/7c63a5c93f6efb64afa13e183fd2e8e91ff42679))
* **va-host:** post to the frame's own origin, and key the replay to the document ([9cec292](https://git.datacontroller.io/dc/dc/commit/9cec292681fbc8d331dc7e9ad063c350529de1a3))
* **viewer:** clamp over-tall PDF rows, always emit an otherwise-empty page ([10229d5](https://git.datacontroller.io/dc/dc/commit/10229d5efe3f1fa652cb4c168d40045bd3788771))
* **viewer:** defer the blob URL revoke, reject a selection inside skipped columns ([b5bb704](https://git.datacontroller.io/dc/dc/commit/b5bb704a4fb346c71c7159814b7e9bdc246e80a0))
* **viewer:** keep the PDF export working where hiddenColumns is not enabled ([f84d4ca](https://git.datacontroller.io/dc/dc/commit/f84d4ca7af55b577c644db28b0c375cc0ea4e9b5))
* **viewer:** review findings - CSV skipped column, PDF row cap before reading ([2c4be5e](https://git.datacontroller.io/dc/dc/commit/2c4be5e94b0f4590e96deac72bbaac36d5297784))
### Features
* **admin:** find and test a writeable location during deploy ([442c595](https://git.datacontroller.io/dc/dc/commit/442c595c2bb5983ba177d35b49d37c31a8a433d9))
* **deploy:** pick and validate the DC location from the deploy screens ([6eeab2e](https://git.datacontroller.io/dc/dc/commit/6eeab2ed16655c7e57ad4b8d3d678832fd225d14))
* **editor:** convert CASE rule values on entry ([c4eef49](https://git.datacontroller.io/dc/dc/commit/c4eef498b22af47258cd5cfac317e2020c8b7e31))
* **logs:** read the logs modal with the shared log parser ([aabc578](https://git.datacontroller.io/dc/dc/commit/aabc578d5ec787837450e4a12ade307b7cda08fc))
* **mocks:** add the control-table services the screens call ([a1615ee](https://git.datacontroller.io/dc/dc/commit/a1615ee8a711ccc4a4ad8a7b7638b3fd6a26b18e))
* **mocks:** build every JS stored program's response with webout() ([ce6bb5d](https://git.datacontroller.io/dc/dc/commit/ce6bb5d4cde50d47d2a5f7d2d3066ff086172603))
* **mocks:** emit the lineage, usernav and libinfo responses through webout() ([1f54fad](https://git.datacontroller.io/dc/dc/commit/1f54fad3fe7d0a42387f7e5e9fa661409f892a86))
* **mocks:** emit the metanav responses through webout() ([e1d877f](https://git.datacontroller.io/dc/dc/commit/e1d877f21d0882ee2220ce143583d2d6fb080cf8))
* **mocks:** emit the sas9 services through webout() ([7be3f84](https://git.datacontroller.io/dc/dc/commit/7be3f8407384babdf59979e91f706b5e8025579a)), closes [#461](https://git.datacontroller.io/dc/dc/issues/461)
* **mocks:** restore a load, and record the diffs it needs ([1a60c68](https://git.datacontroller.io/dc/dc/commit/1a60c689f4a1b7b3b374829a669638fc493a899f))
* **mocks:** use the server's webout() in the JS stored programs ([ccb67ed](https://git.datacontroller.io/dc/dc/commit/ccb67ed5a0981a9fb36f44214e364c08b3384e5c))
* **va-host:** emulate a VA report that embeds DC, as a deployable AppStream app ([2185db9](https://git.datacontroller.io/dc/dc/commit/2185db911d0333ef006cca7dc7a0f3d6aa7884a6))
* **viewer:** download a server-side PDF report ([67ca129](https://git.datacontroller.io/dc/dc/commit/67ca129c4db3eaead56a287811c578634ff59b19))
* **viewer:** export the grid as PDF from the context menu ([e58e735](https://git.datacontroller.io/dc/dc/commit/e58e7356103df350cf8ee1dd6e9eb59c8db57dd4))
### Reverts
* keep the helpers nested inside makedata_body ([4424171](https://git.datacontroller.io/dc/dc/commit/4424171b2f1cc89a7235c415557c72e0b9e7148a))
# [7.21.0](https://git.datacontroller.io/dc/dc/compare/v7.20.0...v7.21.0) (2026-10-29)
@@ -11,23 +11,6 @@
To configure Data Controller for SAS©, please provide the following details:
</p>
<p class="m-0 mt-10">
Please specify a physical directory (on the
<strong> {{ SYSHOSTNAME }}</strong>
compute server) below, to which user
<strong>{{ SYSUSERID }}</strong> can write, on behalf of Data Controller.
</p>
<label for="dcDirectory" class="mt-20 clr-control-label"
>DC Staging Directory</label
>
<div class="mb-10 clr-control-container">
<app-dc-location
[(path)]="dcDirectory"
inputId="dcDirectory"
></app-dc-location>
</div>
<p class="m-0 mt-10">
Below are the list of groups to which you belong. The group you select will
become the Data Controller Admin Group, and everyone in it will have
@@ -45,6 +28,23 @@
</select>
</clr-select-container>
<p class="m-0 mt-10">
Now specify a physical directory (on the
<strong> {{ SYSHOSTNAME }}</strong>
compute server) below, to which user
<strong>{{ SYSUSERID }}</strong> can write, on behalf of Data Controller.
</p>
<label for="dcDirectory" class="mt-20 clr-control-label"
>DC Staging Directory</label
>
<div class="mb-10 clr-control-container">
<app-dc-location
[(path)]="dcDirectory"
inputId="dcDirectory"
></app-dc-location>
</div>
<p class="m-0 mt-10">
Now, create the target directory and deploy the control library using the
admin group selected above:
@@ -1,4 +1,10 @@
import { Component, NO_ERRORS_SCHEMA } from '@angular/core'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { FormsModule } from '@angular/forms'
import { ServerType } from '@sasjs/utils/types/serverType'
import { SasjsService } from 'src/app/services/sasjs.service'
import { SasService } from 'src/app/services/sas.service'
import { DcLocationComponent } from '../dc-location/dc-location.component'
import { SasjsConfiguratorComponent } from './sasjs-configurator.component'
/**
@@ -148,3 +154,78 @@ describe('SasjsConfiguratorComponent directory default', () => {
expect(await directoryFor('DOCKER')).toBe('/tmp/DataController')
})
})
/**
* The rendered order. DC on a SASjs Server or SAS9 estate is configured by the
* admin group selected here, and the staging directory is the last question
* because it is the one the picker has to prove - the same order the Viya
* screen asks them in. A template edit that put the directory back on top
* would compile and deploy fine and ask the operator to choose a location
* before telling DC who administers it.
*/
describe('SasjsConfiguratorComponent field order', () => {
@Component({
selector: 'app-sasjs-configurator-host',
template: '<app-sasjs-configurator></app-sasjs-configurator>',
standalone: false
})
class HostComponent {}
let fixture: ComponentFixture<HostComponent>
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
SasjsConfiguratorComponent,
HostComponent,
DcLocationComponent
],
imports: [FormsModule],
providers: [
{
provide: SasService,
useValue: {
getExecutionPath: () => '/SASStoredProcess/do',
request: () =>
Promise.resolve({ adapterResponse: { SYSSCPL: 'Linux' } }),
getSasjsConfig: () => ({}),
sasjsMakedataChecking: () => Promise.resolve(false),
getUserGroups: () => ({ subscribe: () => undefined }),
logout: () => undefined
}
},
{
provide: SasjsService,
useValue: {
getServerInfo: () => ({ subscribe: () => undefined })
}
}
],
schemas: [NO_ERRORS_SCHEMA]
})
fixture = TestBed.createComponent(HostComponent)
fixture.detectChanges()
})
afterEach(() => {
TestBed.resetTestingModule()
})
it('asks for the admin group before the staging directory', () => {
const labels = Array.from(
fixture.nativeElement.querySelectorAll('.clr-control-label')
).map((label: unknown) => ((label as HTMLElement).textContent || '').trim())
const admin = labels.findIndex((l) =>
l.toLowerCase().includes('admin group')
)
const directory = labels.findIndex((l) =>
l.toLowerCase().includes('staging directory')
)
expect(admin).toBeGreaterThanOrEqual(0)
expect(directory).toBeGreaterThanOrEqual(0)
expect(admin).toBeLessThan(directory)
})
})
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "dcfrontend",
"version": "7.21.0",
"version": "7.22.0",
"description": "Data Controller",
"devDependencies": {
"@nogoo9/gitleaks": "8.30.1-post.2",
@@ -30,7 +30,6 @@
@li mf_mkdir.sas
@li mp_abort.sas
@li mpe_action.sas
@li ms_getfile.sas
@li mpe_dirlist.sas
@li mpe_writecheck.sas
@li mpe_makedata.sas
@@ -274,37 +273,15 @@ run;
%mend makedata_body;
/**
* Refuses the browse actions once this appLoc has a settings.sas.
* The browse actions need no gate of their own.
*
* The deploy window closes when MAKEDATA writes that file, and a run that
* failed after writing it leaves the file behind - so its presence, not the
* survival of this STP, is what says the system is configured. Without this
* a failed or abandoned install leaves a caller with a directory listing of
* any path the SAS user can read.
*
* A probe that cannot answer is treated as configured: an unreachable drive
* is not evidence that the deployment window is still open.
* DIRLIST and WRITECHECK exist for the deploy screen, and the deploy window
* closes by itself: a successful run deletes this program, so once DC is
* configured there is nothing here left to call. A gate keyed off
* settings.sas closed the window on the runs that still needed it - a failed
* install leaves that file behind, and so does any earlier configuration of
* the same appLoc. That gate closed the very window it was meant to protect.
*/
%macro browse_gate(iftrue=0);
%local status;
%if not(&iftrue) %then %return;
%let status=0;
%ms_getfile(%mf_getapploc()/services/public/settings.sas,outref=dcstngs)
%if %symexist(SYS_PROCHTTP_STATUS_CODE) %then
%let status=&SYS_PROCHTTP_STATUS_CODE;
/* the probe is allowed to 404, so the status is recorded and cleared */
%let syscc=0;
%mp_abort(iftrue= (&status=200)
,mac=&_program
,msg=%str(DIRLIST and WRITECHECK are only available until DC is configured)
)
%mp_abort(iftrue= (&status ne 200 and &status ne 404)
,mac=&_program
,msg=%str(Could not check whether this appLoc is configured - probe &status)
)
%mend browse_gate;
%browse_gate(iftrue= (&action=DIRLIST or &action=WRITECHECK))
/*
Three invocations rather than one branch: each macro takes `iftrue=` and
@@ -8,10 +8,10 @@
is asserted against the macros directly (macros/mpe_dirlist.test.sas,
macros/mpe_writecheck.test.sas); this file covers the dispatch around them.
The browse actions are only reachable while the appLoc has no settings.sas,
and any appLoc running this suite has one - so here the two actions are
expected to be refused by the gate. What that proves is that the action
reached the gate at all, which is the part the dispatch owns.
The browse actions carry no gate of their own: a successful run deletes this
program, so the deploy window closes by itself. One of the tests below calls
DIRLIST on an appLoc that already has a settings.sas, which is what a retry
after a failed install looks like.
<h4> SAS Macros </h4>
@li mpe_assert_abort.sas
@@ -82,49 +82,16 @@ run;
outds=work.test_results
)
/**
* Test 4 - DIRLIST is refused once the appLoc is configured. A run that
failed after writing settings.sas leaves it behind, so the file is the
test, not the survival of this STP.
*/
data work.fromjs;
length action $10 parent $1000;
action='DIRLIST';
parent='/tmp';
run;
%mpe_execute(&_program,
viyacontext=&defaultcontext,
inputdatasets=work.fromjs,
outlib=web4,
failfast=0
)
%mpe_assert_abort(
desc=DIRLIST is refused once the appLoc is configured,
outstatus=work.dc_childstatus,
msg=%str(DIRLIST and WRITECHECK are only available until DC is configured),
outds=work.test_results
)
/*
There is no test here for the browse actions themselves.
/**
* Test 5 - and so is WRITECHECK
*/
data work.fromjs;
length action $10 path $1000;
action='WRITECHECK';
path='/tmp';
run;
%mpe_execute(&_program,
viyacontext=&defaultcontext,
inputdatasets=work.fromjs,
outlib=web5,
failfast=0
)
%mpe_assert_abort(
desc=WRITECHECK is refused once the appLoc is configured,
outstatus=work.dc_childstatus,
msg=%str(DIRLIST and WRITECHECK are only available until DC is configured),
outds=work.test_results
)
They used to be refused once settings.sas existed, and that refusal is what
these two used to assert. The gate is gone: the deploy window closes when a
successful run deletes this program, so the window and the program have the
same lifetime. DIRLIST and WRITECHECK are asserted against the macros that
implement them (macros/mpe_dirlist.test.sas, macros/mpe_writecheck.test.sas);
the dispatch around them is what tests 1-3 cover.
*/
/**
* dump results to the log for offline inspection
@@ -24,7 +24,6 @@
@li mf_trimstr.sas
@li mp_abort.sas
@li mpe_action.sas
@li mfv_existfile.sas
@li mpe_dirlist.sas
@li mpe_writecheck.sas
@li mpe_makedata.sas
@@ -366,25 +365,15 @@ run;
%mend makedata_body;
/**
* Refuses the browse actions once this appLoc has a settings.sas.
* The browse actions need no gate of their own.
*
* The deploy window closes when MAKEDATA writes that file, and a run that
* failed after writing it leaves the file behind - so its presence, not the
* survival of this STP, is what says the system is configured. Without this
* a failed or abandoned install leaves a caller with a directory listing of
* any path the SAS user can read.
* DIRLIST and WRITECHECK exist for the deploy screen, and the deploy window
* closes by itself: a successful run deletes this program, so once DC is
* configured there is nothing here left to call. A gate keyed off
* settings.sas closed the window on the runs that still needed it - a failed
* install leaves that file behind, and so does any earlier configuration of
* the same appLoc. That gate closed the very window it was meant to protect.
*/
%macro browse_gate(iftrue=0);
%local configured;
%if not(&iftrue) %then %return;
%let configured=%mfv_existfile(%mf_getapploc()/services/settings.sas);
%mp_abort(iftrue= (&configured=1)
,mac=&_program
,msg=%str(DIRLIST and WRITECHECK are only available until DC is configured)
)
%mend browse_gate;
%browse_gate(iftrue= (&action=DIRLIST or &action=WRITECHECK))
/*
Three invocations rather than one branch: each macro takes `iftrue=` and