Compare commits

...

354 Commits

Author SHA1 Message Date
sead 0b7b51046b fix(ci): update lighthouse appLoc
Build / Build-and-ng-test (pull_request) Successful in 3m43s
Build / Build-and-test-development (pull_request) Successful in 9m43s
Lighthouse Checks / lighthouse (pull_request) Failing after 17m18s
2026-06-16 23:51:10 +02:00
sead 77b4bb310e fix: build issues (#245)
Build / Build-and-ng-test (pull_request) Successful in 3m40s
Build / Build-and-test-development (pull_request) Successful in 9m36s
Lighthouse Checks / lighthouse (pull_request) Successful in 16m45s
- resolve HOT v17 render loops caused by unstable settings bind
- tests: pin locale to en-GB
- improve accessibility
- improve initial bundle size, split and lazy-load feature modules

Reviewed-on: #245
Co-authored-by: s <sead@4gl.io>
Co-committed-by: s <sead@4gl.io>
2026-06-16 21:41:24 +00:00
sead 47ba03ec63 fix(ci): bump Lighthouse to 13.4.0
Build / Build-and-ng-test (pull_request) Successful in 4m16s
Lighthouse Checks / lighthouse (pull_request) Failing after 18m34s
Build / Build-and-test-development (pull_request) Failing after 2h32m47s
2026-06-15 14:42:18 +02:00
sead e331e1bb21 ci(lighthouse): skip Chrome install when already present
Build / Build-and-ng-test (pull_request) Successful in 3m42s
Lighthouse Checks / lighthouse (pull_request) Failing after 5m2s
Build / Build-and-test-development (pull_request) Has been cancelled
2026-06-15 13:42:57 +02:00
sead 40f285f295 test(mocks): pin e2e locale and add lazy MPE_X_NEW demo table 2026-06-15 13:40:38 +02:00
sead 2d29346cbf feat(edit-record): use native date/time/datetime pickers 2026-06-15 13:10:13 +02:00
sead f70ea2fe71 feat(editor): migrate date/time/datetime to HOT 17 Intl cell types 2026-06-15 11:30:17 +02:00
sead 29aaa72c60 feat(handsontable): enable export plugin, add export menu items
Build / Build-and-ng-test (pull_request) Successful in 4m10s
Lighthouse Checks / lighthouse (pull_request) Failing after 7m20s
Build / Build-and-test-development (pull_request) Failing after 21m27s
2026-06-10 11:19:34 +02:00
sead 12c7d30894 fix(handsontable): update licenseChecker exclude buffers - exceljs dep 2026-06-09 13:56:30 +02:00
sead 38cb1e207b fix(handsontable): add exceljs
override uuid version - exceljs dep
2026-06-09 13:55:42 +02:00
sead 4c879c614b refactor(editor): replace numbro with native Intl numeric formatting 2026-06-08 19:08:25 +02:00
sead 8e012f760a refactor(sas): drop DISPLAY_VALUE from getdynamiccolvals payload
Lighthouse Checks / lighthouse (pull_request) Failing after 5m44s
Build / Build-and-ng-test (pull_request) Failing after 14m0s
Build / Build-and-test-development (pull_request) Has been skipped
2026-06-08 13:10:46 +02:00
sead 8c09befd74 refactor(editor): drop unused DISPLAY_VALUE from dropdown payload 2026-06-08 13:10:19 +02:00
sead 6af49cf1fd fix(editor): preserve commas in dropdown source values 2026-06-08 11:45:35 +02:00
sead 4ea604f9fb feat(editor): add READONLY, HIDDEN, ROUND and NUMBER_FORMAT validation rules
Build / Build-and-ng-test (pull_request) Failing after 15m15s
Build / Build-and-test-development (pull_request) Has been skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 20m15s
2026-06-05 21:25:13 +02:00
sead 9d97bf7ea1 fix(handsontable): restore dark mode for v17
Lighthouse Checks / lighthouse (pull_request) Failing after 17m59s
Build / Build-and-ng-test (pull_request) Failing after 17m59s
Build / Build-and-test-development (pull_request) Has been skipped
2026-06-05 14:12:11 +02:00
sead eb015d712b revert(editor): DISPLAY_VALUE change
Build / Build-and-ng-test (pull_request) Failing after 16m30s
Build / Build-and-test-development (pull_request) Has been skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 31m28s
Partially revert changes from 51071b463b
2026-05-26 22:08:20 +02:00
sead 1d04f4a42c refactor(editor): add bulk validation only on HARDSELECT_HOOK; skip SOFTSELECT_HOOK
Build / Build-and-ng-test (pull_request) Failing after 17m35s
Build / Build-and-test-development (pull_request) Has been skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 32m35s
2026-05-26 21:57:30 +02:00
sead 11ee49a57a feat(editor): validate autofilled cells; fix paste validation lag
Build / Build-and-ng-test (pull_request) Successful in 3m33s
Lighthouse Checks / lighthouse (pull_request) Failing after 27m56s
Build / Build-and-test-development (pull_request) Failing after 34m22s
2026-05-26 17:09:35 +02:00
sead 609731ff99 feat(editor): paste-validation overlay with cancel and confirm
Build / Build-and-ng-test (pull_request) Failing after 16m23s
Build / Build-and-test-development (pull_request) Has been skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 31m23s
2026-05-26 14:38:41 +02:00
sead d6cb32ed25 refactor(licensing): expand protocl text
Build / Build-and-ng-test (pull_request) Failing after 17m14s
Build / Build-and-test-development (pull_request) Has been skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 32m12s
2026-05-26 11:47:52 +02:00
sead 3668a7426f fix(licensing): add protocol info
Build / Build-and-ng-test (pull_request) Successful in 3m32s
Build / Build-and-test-development (pull_request) Failing after 20m50s
Lighthouse Checks / lighthouse (pull_request) Failing after 29m24s
Close #178
2026-05-26 11:40:40 +02:00
sead cc82dcaafe refactor(mocks): replace string webouts with JS objects
Build / Build-and-ng-test (pull_request) Failing after 17m42s
Build / Build-and-test-development (pull_request) Has been skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 28m24s
2026-05-26 11:06:50 +02:00
sead ea03bdecc5 fix(editor): await dynamic validation on paste; defer spinner 2026-05-26 10:48:13 +02:00
sead 51071b463b fix(editor): cancelEdit will reset cell's valid state 2026-05-25 16:55:18 +02:00
sead ac0bd10212 fix(handsontable): horizontal scrollbar in dropdown 2026-05-25 13:59:24 +02:00
sead 1b73e355b7 fix: migrate handsontables to v17 2026-05-25 13:29:27 +02:00
sead b661580c60 fix: validate pasted values 2026-05-22 22:10:58 +02:00
sead dc4e07a692 fix: viewLibs now fires only once, libPromise shared between the calls 2026-05-22 21:57:53 +02:00
semantic-release-bot f2313b31f1 chore(release): 7.8.2 [skip ci]
## [7.8.2](https://git.datacontroller.io/dc/dc/compare/v7.8.1...v7.8.2) (2026-05-20)

### Bug Fixes

* bumping ws package ([2382a55](2382a559a5))
* enabling version restore for non admin users ([5d889d8](5d889d824c))
2026-05-20 13:20:34 +00:00
allan f8810ee7e9 Merge pull request 'fix: bumping ws package' (#234) from customerfeedback into main
Release / Build-production-and-ng-test (push) Successful in 3m17s
Release / Build-and-test-development (push) Successful in 8m27s
Release / release (push) Successful in 7m4s
Reviewed-on: #234
2026-05-20 13:05:56 +00:00
allan 8ab4af8397 Merge branch 'main' into customerfeedback
Build / Build-and-ng-test (pull_request) Successful in 3m29s
Build / Build-and-test-development (pull_request) Successful in 8m29s
Lighthouse Checks / lighthouse (pull_request) Successful in 17m59s
2026-05-20 12:45:51 +00:00
4gl 2382a559a5 fix: bumping ws package
Build / Build-and-ng-test (pull_request) Failing after 3m30s
Build / Build-and-test-development (pull_request) Has been skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 3m5s
2026-05-20 13:11:13 +01:00
Unknown 5d889d824c fix: enabling version restore for non admin users
Release / Build-production-and-ng-test (push) Failing after 1m11s
Release / Build-and-test-development (push) Has been skipped
Release / release (push) Has been skipped
2026-05-20 12:38:21 +01:00
semantic-release-bot bed21122ce chore(release): 7.8.1 [skip ci]
## [7.8.1](https://git.datacontroller.io/dc/dc/compare/v7.8.0...v7.8.1) (2026-05-15)

### Bug Fixes

* **sasjs:** enable runAsTask ([f1a26e1](f1a26e132e))
2026-05-15 11:29:08 +00:00
allan ea8cf71101 Merge pull request 'fix(sasjs): enable runAsTask' (#233) from hotfix-sasjs-attributes into main
Release / Build-production-and-ng-test (push) Successful in 3m38s
Release / Build-and-test-development (push) Successful in 8m56s
Release / release (push) Successful in 8m0s
Reviewed-on: #233
2026-05-15 11:13:21 +00:00
sead f1a26e132e fix(sasjs): enable runAsTask
Build / Build-and-ng-test (pull_request) Successful in 3m57s
Build / Build-and-test-development (pull_request) Successful in 9m29s
Lighthouse Checks / lighthouse (pull_request) Successful in 18m28s
2026-05-15 12:53:04 +02:00
semantic-release-bot 1db6984de3 chore(release): 7.8.0 [skip ci]
# [7.8.0](https://git.datacontroller.io/dc/dc/compare/v7.7.3...v7.8.0) (2026-05-15)

### Bug Fixes

* enabling DSN=*ALL* in MPE_SECURITY ([7d94cb2](7d94cb2ae4))
* providing default values for RULE_ACTIVE on MPE_VALIDATIONS ([f031b4e](f031b4eb89))
* switch away from api usage for CASLIB metadata ([ce921a0](ce921a032a))
* use correct debug param for runAsTask ([bb80476](bb80476767))

### Features

* add runAsTask config attribute parser ([1635bc9](1635bc9c45))
* enabling *ALL* option by default in MPE_SECURITY (DSN col) ([93d4ab6](93d4ab65ac))
2026-05-15 09:07:49 +00:00
allan 636ff237dd Merge pull request 'Updates following customer session' (#231) from customerfeedback into main
Release / Build-production-and-ng-test (push) Successful in 3m39s
Release / Build-and-test-development (push) Successful in 8m50s
Release / release (push) Successful in 7m39s
Reviewed-on: #231
2026-05-15 08:52:14 +00:00
sead 02963ab6d5 chore: bump adapter
Build / Build-and-ng-test (pull_request) Successful in 3m49s
Build / Build-and-test-development (pull_request) Successful in 9m2s
Lighthouse Checks / lighthouse (pull_request) Successful in 18m12s
2026-05-15 10:29:54 +02:00
allan d40f61292a Merge pull request 'feat: add runAsTask config attribute parser' (#232) from feat/execution-tasks-flag into customerfeedback
Lighthouse Checks / lighthouse (pull_request) Failing after 3m31s
Build / Build-and-ng-test (pull_request) Failing after 3m51s
Build / Build-and-test-development (pull_request) Has been skipped
Reviewed-on: #232
2026-05-15 08:08:39 +00:00
4gl 7d94cb2ae4 fix: enabling DSN=*ALL* in MPE_SECURITY
Build / Build-and-ng-test (pull_request) Successful in 3m48s
Build / Build-and-test-development (pull_request) Successful in 8m59s
Lighthouse Checks / lighthouse (pull_request) Successful in 18m8s
2026-05-14 22:47:17 +01:00
sead bb80476767 fix: use correct debug param for runAsTask
Lighthouse Checks / lighthouse (pull_request) Failing after 3m28s
Build / Build-and-ng-test (pull_request) Failing after 3m51s
Build / Build-and-test-development (pull_request) Has been skipped
2026-05-14 11:21:01 +02:00
sead 1635bc9c45 feat: add runAsTask config attribute parser 2026-05-14 11:19:32 +02:00
4gl f031b4eb89 fix: providing default values for RULE_ACTIVE on MPE_VALIDATIONS
Lighthouse Checks / lighthouse (pull_request) Successful in 18m12s
Build / Build-and-ng-test (pull_request) Successful in 3m40s
Build / Build-and-test-development (pull_request) Successful in 8m58s
2026-05-13 19:02:09 +01:00
4gl 93d4ab65ac feat: enabling *ALL* option by default in MPE_SECURITY (DSN col)
Build / Build-and-ng-test (pull_request) Successful in 3m53s
Build / Build-and-test-development (pull_request) Successful in 9m12s
Lighthouse Checks / lighthouse (pull_request) Successful in 18m15s
2026-05-13 18:47:19 +01:00
4gl ce921a032a fix: switch away from api usage for CASLIB metadata 2026-05-13 18:46:35 +01:00
semantic-release-bot 322f904b4b chore(release): 7.7.3 [skip ci]
## [7.7.3](https://git.datacontroller.io/dc/dc/compare/v7.7.2...v7.7.3) (2026-05-12)

### Bug Fixes

* move cas session assign to settings.sas and abort when lib is unassigned ([65f0b97](65f0b979a4))
2026-05-12 18:43:04 +00:00
allan 982eeac58c Merge pull request 'fix: move cas session assign to settings.sas and abort when lib is unassigned' (#230) from viyaux into main
Release / Build-production-and-ng-test (push) Successful in 3m49s
Release / Build-and-test-development (push) Successful in 9m10s
Release / release (push) Successful in 8m2s
Reviewed-on: #230
2026-05-12 18:26:47 +00:00
allan 0ab9717556 Merge branch 'main' into viyaux
Build / Build-and-test-development (pull_request) Has been cancelled
Build / Build-and-ng-test (pull_request) Has been cancelled
Lighthouse Checks / lighthouse (pull_request) Successful in 18m27s
2026-05-12 18:26:35 +00:00
allan 24a85de8e1 Merge pull request 'chore(client): bump fast-uri' (#229) from audit-20260511 into main
Release / Build-production-and-ng-test (push) Successful in 3m40s
Release / Build-and-test-development (push) Successful in 8m50s
Release / release (push) Failing after 3m10s
Reviewed-on: #229
2026-05-11 17:38:14 +00:00
4gl 65f0b979a4 fix: move cas session assign to settings.sas and abort when lib is unassigned
Build / Build-and-ng-test (pull_request) Successful in 3m55s
Build / Build-and-test-development (pull_request) Successful in 9m13s
Lighthouse Checks / lighthouse (pull_request) Successful in 18m48s
2026-05-11 15:08:20 +01:00
sead 947f34a0ad chore(client): bump fast-uri
Build / Build-and-ng-test (pull_request) Successful in 3m50s
Build / Build-and-test-development (pull_request) Successful in 9m6s
Lighthouse Checks / lighthouse (pull_request) Successful in 18m19s
Resolve GHSA-q3j6-qgpj-74h6, GHSA-v39h-62p7-jpjc
2026-05-11 09:39:36 +02:00
semantic-release-bot 0f60fd7181 chore(release): 7.7.2 [skip ci]
## [7.7.2](https://git.datacontroller.io/dc/dc/compare/v7.7.1...v7.7.2) (2026-05-07)

### Bug Fixes

* **client:** bundle Metropolis font locally to satisfy CSP ([9546fcd](9546fcd631))
* **client:** clear angular build cache on font strip to avoid stale dist ([503cb08](503cb08b2f))
* **client:** postinstall removal of Metropolis [@font-face](https://git.datacontroller.io/font-face) from @clr/ui ([e6397ce](e6397cecc1))
* **client:** serve text-security-disc font locally ([80ce80e](80ce80ece4))
* **editor:** preserve numeric type for SAS num cols with static SOFTSELECT/HARDSELECT ([05a3289](05a328976e))
2026-05-07 15:18:44 +00:00
allan 251062e42e Merge pull request 'Multiple frontend client issues' (#228) from 227-csp-issues-20260507 into main
Release / Build-production-and-ng-test (push) Successful in 3m38s
Release / Build-and-test-development (push) Successful in 8m50s
Release / release (push) Successful in 7m35s
Reviewed-on: #228
2026-05-07 15:03:08 +00:00
sead 05a328976e fix(editor): preserve numeric type for SAS num cols with static SOFTSELECT/HARDSELECT
Build / Build-and-ng-test (pull_request) Successful in 3m43s
Build / Build-and-test-development (pull_request) Successful in 9m21s
Lighthouse Checks / lighthouse (pull_request) Successful in 18m25s
2026-05-07 15:02:06 +02:00
sead 503cb08b2f fix(client): clear angular build cache on font strip to avoid stale dist 2026-05-07 13:43:57 +02:00
sead f71be20476 build(client): drop duplicate asset emits for fonts and CSS-referenced svgs 2026-05-07 13:43:49 +02:00
sead e6397cecc1 fix(client): postinstall removal of Metropolis @font-face from @clr/ui 2026-05-07 13:43:37 +02:00
sead 80ce80ece4 fix(client): serve text-security-disc font locally 2026-05-07 13:43:21 +02:00
sead 9546fcd631 fix(client): bundle Metropolis font locally to satisfy CSP 2026-05-07 13:43:05 +02:00
semantic-release-bot b79aaf4327 chore(release): 7.7.1 [skip ci]
## [7.7.1](https://git.datacontroller.io/dc/dc/compare/v7.7.0...v7.7.1) (2026-05-05)

### Bug Fixes

* **client:** bump adapter ([d26f7d2](d26f7d2511))
* **sas:** bump cli ([d60029d](d60029deae))
2026-05-05 20:04:33 +00:00
allan 76f9198f73 Merge pull request 'fix(client): bump adapter' (#226) from fix/adapter-20260505 into main
Release / Build-production-and-ng-test (push) Successful in 3m28s
Release / Build-and-test-development (push) Successful in 8m44s
Release / release (push) Successful in 7m33s
Reviewed-on: #226
2026-05-05 19:49:17 +00:00
4gl d60029deae fix(sas): bump cli
Build / Build-and-ng-test (pull_request) Successful in 3m57s
Build / Build-and-test-development (pull_request) Successful in 9m9s
Lighthouse Checks / lighthouse (pull_request) Successful in 18m10s
2026-05-05 18:48:15 +01:00
sead d26f7d2511 fix(client): bump adapter
Build / Build-and-ng-test (pull_request) Successful in 4m21s
Build / Build-and-test-development (pull_request) Successful in 10m34s
Lighthouse Checks / lighthouse (pull_request) Successful in 19m37s
2026-05-05 17:16:55 +02:00
semantic-release-bot 33efe09b50 chore(release): 7.7.0 [skip ci]
# [7.7.0](https://git.datacontroller.io/dc/dc/compare/v7.6.0...v7.7.0) (2026-05-04)

### Bug Fixes

* bump adapter to 4.16.6 ([1707f38](1707f3802a))
* remove data:image/svg+xml CSP violation, use class instead changing style directly ([d66eb5d](d66eb5dfc2))
* remove WORK, SASUSER and CASUSER as library options.  [#224](#224) ([ec66631](ec66631a33))

### Features

* auto-save CAS tables [#224](#224) ([40d04a5](40d04a53c4))
* autoload CAS tables. [#224](#224) ([d5ebb01](d5ebb01ce3))
2026-05-04 23:24:36 +00:00
4gl e0aef9bf00 chore: pin got lib to enable release flow
Release / Build-production-and-ng-test (push) Successful in 11m47s
Release / Build-and-test-development (push) Successful in 17m37s
Release / release (push) Successful in 8m18s
2026-05-04 23:51:19 +01:00
allan 02d1a2e0b1 Merge pull request 'fix: resolve CSP violation and update dependancies' (#223) from fix/audit-20260413 into main
Release / Build-production-and-ng-test (push) Successful in 8m48s
Release / Build-and-test-development (push) Successful in 17m50s
Release / release (push) Failing after 5m52s
Reviewed-on: #223
Reviewed-by: allan <allan@4gl.io>
2026-05-04 16:50:52 +00:00
sead 4e3154e929 chore(cypress): enable e2e video, folder guards
Build / Build-and-ng-test (pull_request) Successful in 3m58s
Build / Build-and-test-development (pull_request) Successful in 9m6s
Lighthouse Checks / lighthouse (pull_request) Successful in 18m21s
2026-05-04 12:57:31 +02:00
sead 32c0713256 chore: add hyperformula license exception
Build / Build-and-ng-test (pull_request) Successful in 3m44s
Build / Build-and-test-development (pull_request) Failing after 8m45s
Lighthouse Checks / lighthouse (pull_request) Successful in 18m29s
2026-05-04 11:05:00 +02:00
sead defe15bcec chore: bump client eslint and sas sasjs packages
Build / Build-and-ng-test (pull_request) Failing after 1m34s
Build / Build-and-test-development (pull_request) Has been skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 1m47s
2026-05-04 10:42:31 +02:00
4gl 6f8e471f16 chore: dep man
Build / Build-and-ng-test (pull_request) Failing after 40s
Build / Build-and-test-development (pull_request) Has been skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 55s
2026-05-01 12:53:07 +01:00
4gl dc35abfd85 chore: dependency bumps
Build / Build-and-ng-test (pull_request) Failing after 42s
Build / Build-and-test-development (pull_request) Has been skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 1m0s
2026-05-01 12:36:39 +01:00
4gl 04a8c5d52a chore: bumping node
Build / Build-and-ng-test (pull_request) Failing after 50s
Build / Build-and-test-development (pull_request) Has been skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 57s
2026-05-01 11:46:10 +01:00
4gl 2cb370053d chore: rebuilt package lock
Build / Build-and-ng-test (pull_request) Failing after 42s
Build / Build-and-test-development (pull_request) Has been skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 1m3s
2026-05-01 11:34:30 +01:00
4gl 1707f3802a fix: bump adapter to 4.16.6
Lighthouse Checks / lighthouse (pull_request) Failing after 1m7s
Build / Build-and-ng-test (pull_request) Failing after 1m44s
Build / Build-and-test-development (pull_request) Has been skipped
2026-05-01 10:59:59 +01:00
allan c87ba660ca Merge branch 'main' into fix/audit-20260413
Build / Build-and-ng-test (pull_request) Successful in 3m58s
Build / Build-and-test-development (pull_request) Successful in 9m56s
Lighthouse Checks / lighthouse (pull_request) Successful in 18m30s
2026-04-30 16:44:46 +00:00
allan ef8a2dbc38 Merge pull request 'fix: remove WORK, SASUSER and CASUSER as library options, plus auto CAS table load' (#225) from issue224 into main
Release / Build-production-and-ng-test (push) Failing after 1m28s
Release / Build-and-test-development (push) Has been skipped
Release / release (push) Has been skipped
Reviewed-on: #225
2026-04-30 15:28:50 +00:00
4gl 40d04a53c4 feat: auto-save CAS tables #224
Build / Build-and-ng-test (pull_request) Successful in 4m2s
Build / Build-and-test-development (pull_request) Successful in 10m6s
Lighthouse Checks / lighthouse (pull_request) Successful in 19m5s
2026-04-30 16:04:31 +01:00
4gl d5ebb01ce3 feat: autoload CAS tables. #224
Build / Build-and-ng-test (pull_request) Successful in 4m6s
Build / Build-and-test-development (pull_request) Successful in 10m9s
Lighthouse Checks / lighthouse (pull_request) Successful in 18m54s
2026-04-30 15:41:20 +01:00
allan ec66631a33 fix: remove WORK, SASUSER and CASUSER as library options. #224
Build / Build-and-ng-test (pull_request) Successful in 4m15s
Build / Build-and-test-development (pull_request) Successful in 10m28s
Lighthouse Checks / lighthouse (pull_request) Successful in 18m47s
2026-04-17 14:21:24 +01:00
sead d66eb5dfc2 fix: remove data:image/svg+xml CSP violation, use class instead changing style directly
Build / Build-and-ng-test (pull_request) Failing after 1m16s
Build / Build-and-test-development (pull_request) Has been skipped
Lighthouse Checks / lighthouse (pull_request) Successful in 18m7s
2026-04-13 10:29:54 +02:00
sead 731b589ed8 chore: override ajv and regenrate lock file 2026-04-13 09:23:13 +02:00
sead fe92d5fc36 chore: bump angular to latest 19 2026-04-13 08:58:54 +02:00
sead a335b400f1 chore: bump @sasjs/adapter 2026-04-13 08:55:48 +02:00
semantic-release-bot f63e507ddf chore(release): 7.6.0 [skip ci]
# [7.6.0](https://git.datacontroller.io/dc/dc/compare/v7.5.0...v7.6.0) (2026-04-03)

### Bug Fixes

* add label and tooltip for libref download, sanitise input ([52d5803](52d58036a4))

### Features

* configurable email alerts.  Closes [#217](#217) ([2ccf0d1](2ccf0d1100))
2026-04-03 22:17:14 +00:00
allan 991cc0567d Merge pull request 'feat: configurable email alerts. Closes #217' (#222) from issue217 into main
Release / Build-production-and-ng-test (push) Successful in 3m42s
Release / Build-and-test-development (push) Successful in 10m10s
Release / release (push) Successful in 7m48s
Reviewed-on: #222
2026-04-03 21:09:11 +00:00
sead 52d58036a4 fix: add label and tooltip for libref download, sanitise input
Build / Build-and-ng-test (pull_request) Successful in 4m6s
Build / Build-and-test-development (pull_request) Successful in 10m13s
Lighthouse Checks / lighthouse (pull_request) Successful in 18m37s
2026-04-03 19:55:42 +02:00
allan 26bff85792 chore: fix debug line
Build / Build-and-ng-test (pull_request) Successful in 4m47s
Build / Build-and-test-development (pull_request) Successful in 10m16s
Lighthouse Checks / lighthouse (pull_request) Successful in 19m41s
2026-04-03 18:35:48 +01:00
allan 2ccf0d1100 feat: configurable email alerts. Closes #217
Build / Build-and-ng-test (pull_request) Successful in 4m42s
Build / Build-and-test-development (pull_request) Has been cancelled
Lighthouse Checks / lighthouse (pull_request) Has been cancelled
2026-04-03 18:34:23 +01:00
semantic-release-bot 3be33186bc chore(release): 7.5.0 [skip ci]
# [7.5.0](https://git.datacontroller.io/dc/dc/compare/v7.4.1...v7.5.0) (2026-04-03)

### Bug Fixes

* add workflow audits, update deps ([66e98a9](66e98a96cb))
* allow CSV uploads with licence row limit ([5b260e4](5b260e4915)), closes [#213](#213)
* bumping cli and pinning versions in .npmrc ([80039f4](80039f4876))
* guard CSV upload with fileUpload licence flag ([ed40df6](ed40df6295))
* parse embed param from window.location.hash for hash router compatibility ([0269c24](0269c2421d))
* quote CSV char values.  Closes [#215](#215) ([d9980e8](d9980e866d))
* resolve outer promise in parseCsvFile for non-WLATIN1 path ([4ee15e1](4ee15e1b6e))
* use XLSX for CSV row truncation to handle new lines in values ([6d590c0](6d590c050d))

### Features

* add embed URL parameter to hide header and back button ([b0dc441](b0dc441d68)), closes [#214](#214)
* add target libref input to config download ([a89657b](a89657b0b8)), closes [#212](#212)
* export config service to allow dclib swapping.  Closes [#212](#212) ([326c26f](326c26fddf))
2026-04-03 11:06:36 +00:00
allan 1a7f950ae2 Merge pull request 'feat: enabling dclib switching when exporting config' (#220) from issue212 into main
Release / Build-production-and-ng-test (push) Successful in 3m39s
Release / Build-and-test-development (push) Successful in 9m55s
Release / release (push) Successful in 7m46s
Reviewed-on: #220
2026-04-03 10:49:43 +00:00
allan 8924dc8ab1 chore: merge buid.yaml
Build / Build-and-ng-test (pull_request) Successful in 3m58s
Build / Build-and-test-development (pull_request) Successful in 10m3s
Lighthouse Checks / lighthouse (pull_request) Successful in 18m46s
2026-04-03 10:30:05 +00:00
sead 2c2901b537 chore: rever upload artifacts actions version
Build / Build-and-ng-test (pull_request) Successful in 4m3s
Build / Build-and-test-development (pull_request) Successful in 10m0s
Lighthouse Checks / lighthouse (pull_request) Successful in 18m24s
2026-04-03 10:32:28 +02:00
sead 2cae7ea638 chore: improve CI workflows
Build / Build-and-ng-test (pull_request) Successful in 4m3s
Build / Build-and-test-development (pull_request) Failing after 10m17s
Lighthouse Checks / lighthouse (pull_request) Failing after 18m31s
2026-04-03 09:36:39 +02:00
sead 66e98a96cb fix: add workflow audits, update deps
Build / Build-and-ng-test (pull_request) Successful in 4m2s
Build / Build-and-test-development (pull_request) Successful in 10m19s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m53s
2026-04-03 09:10:49 +02:00
allan 0b0db1c543 chore: run audit check in build.yaml as well as release.yaml
Build / Build-and-ng-test (pull_request) Failing after 1m31s
Build / Build-and-test-development (pull_request) Successful in 10m23s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 19m32s
2026-04-03 01:18:54 +00:00
allan 80039f4876 fix: bumping cli and pinning versions in .npmrc
Build / Build-and-ng-test (pull_request) Successful in 3m51s
Build / Build-and-test-development (pull_request) Successful in 10m9s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 19m11s
2026-04-03 02:02:05 +01:00
allan 326c26fddf feat: export config service to allow dclib swapping. Closes #212 2026-04-03 02:01:44 +01:00
allan e7b2ead0e2 Merge pull request 'fix: allow CSV uploads with licence row limit' (#219) from fix/213-csv-license-row-limit into main
Release / Build-production-and-ng-test (push) Failing after 1m25s
Release / Build-and-test-development (push) Has been skipped
Release / release (push) Has been skipped
Reviewed-on: #219
2026-04-02 19:08:18 +00:00
sead a89657b0b8 feat: add target libref input to config download
Build / Build-and-ng-test (pull_request) Successful in 4m5s
Build / Build-and-test-development (pull_request) Successful in 10m16s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m30s
Closes #212
2026-04-02 19:37:55 +02:00
sead 4ee15e1b6e fix: resolve outer promise in parseCsvFile for non-WLATIN1 path
Build / Build-and-ng-test (pull_request) Successful in 3m55s
Build / Build-and-test-development (pull_request) Successful in 10m21s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m49s
2026-04-02 18:48:27 +02:00
sead ed40df6295 fix: guard CSV upload with fileUpload licence flag
Build / Build-and-ng-test (pull_request) Successful in 4m3s
Build / Build-and-test-development (pull_request) Failing after 11m54s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m35s
2026-04-02 17:40:16 +02:00
sead 6d590c050d fix: use XLSX for CSV row truncation to handle new lines in values
Build / Build-and-ng-test (pull_request) Successful in 3m53s
Build / Build-and-test-development (pull_request) Successful in 10m25s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m29s
2026-04-02 17:03:16 +02:00
allan 47f9a54f97 Merge pull request 'feat: add embed URL parameter to hide header and back button' (#218) from feat/214-hide-titlebar-embed into fix/213-csv-license-row-limit
Build / Build-and-ng-test (pull_request) Successful in 4m0s
Build / Build-and-test-development (pull_request) Successful in 10m18s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m56s
Reviewed-on: #218
2026-04-02 14:37:06 +00:00
sead 17b0d72fbf test: add csv-limited spec to cypress workflow
Build / Build-and-ng-test (pull_request) Successful in 4m1s
Build / Build-and-test-development (pull_request) Successful in 10m23s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 19m1s
2026-04-02 16:13:35 +02:00
sead 0269c2421d fix: parse embed param from window.location.hash for hash router compatibility
Build / Build-and-ng-test (pull_request) Successful in 4m9s
Build / Build-and-test-development (pull_request) Successful in 10m9s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 19m8s
2026-04-02 14:57:16 +02:00
sead 5b260e4915 fix: allow CSV uploads with licence row limit
Build / Build-and-ng-test (pull_request) Successful in 3m56s
Build / Build-and-test-development (pull_request) Successful in 10m3s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m49s
Fixes #213
2026-04-02 14:34:58 +02:00
allan 5290410a17 Merge branch 'main' into feat/214-hide-titlebar-embed
Build / Build-and-ng-test (pull_request) Successful in 3m56s
Build / Build-and-test-development (pull_request) Successful in 10m11s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m31s
2026-04-02 11:13:49 +00:00
allan dc9041aaec Merge pull request 'fix: quote CSV char values. Closes #215' (#216) from issue215 into main
Release / Build-production-and-ng-test (push) Failing after 1m25s
Release / Build-and-test-development (push) Has been skipped
Release / release (push) Has been skipped
Reviewed-on: #216
2026-04-02 11:12:38 +00:00
sead b0dc441d68 feat: add embed URL parameter to hide header and back button
Build / Build-and-ng-test (pull_request) Successful in 4m3s
Build / Build-and-test-development (pull_request) Successful in 10m6s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m39s
Closes #214
2026-04-02 11:26:28 +02:00
allan b0fc3eb5af chore: update comment
Build / Build-and-ng-test (pull_request) Successful in 4m23s
Build / Build-and-test-development (pull_request) Successful in 10m7s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 19m13s
2026-03-31 17:09:17 +01:00
allan d9980e866d fix: quote CSV char values. Closes #215
Build / Build-and-ng-test (pull_request) Successful in 4m7s
Build / Build-and-test-development (pull_request) Has been cancelled
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Has been cancelled
2026-03-31 17:04:46 +01:00
semantic-release-bot 52ae3404ee chore(release): 7.4.1 [skip ci]
## [7.4.1](https://git.datacontroller.io/dc/dc/compare/v7.4.0...v7.4.1) (2026-03-12)

### Bug Fixes

* support for SASIOSNF engine (SNOW alias) plus meta assignment ([7694d1b](7694d1b0fb))
2026-03-12 00:52:17 +00:00
allan eecb4f4f53 Merge pull request 'fix: support for SASIOSNF engine (SNOW alias) plus meta assignment' (#209) from snowfixes into main
Release / Build-production-and-ng-test (push) Successful in 3m41s
Release / Build-and-test-development (push) Successful in 9m52s
Release / release (push) Successful in 7m57s
Reviewed-on: #209
2026-03-12 00:35:16 +00:00
allan 744345af81 chore: bump sasjs/cli
Build / Build-and-ng-test (pull_request) Successful in 3m51s
Build / Build-and-test-development (pull_request) Successful in 9m58s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m48s
2026-03-12 00:16:10 +00:00
_ 7694d1b0fb fix: support for SASIOSNF engine (SNOW alias) plus meta assignment
Build / Build-and-ng-test (pull_request) Successful in 3m46s
Build / Build-and-test-development (pull_request) Successful in 9m38s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m18s
2026-03-10 23:50:57 +00:00
semantic-release-bot d8010d4c0c chore(release): 7.4.0 [skip ci]
# [7.4.0](https://git.datacontroller.io/dc/dc/compare/v7.3.0...v7.4.0) (2026-02-20)

### Bug Fixes

* cli bump for mf_getscheme support ([a84ba41](a84ba41ea9))
* missing upcase on SNOW section, plus local sasjs target ([dc20064](dc200646f7))

### Features

* SAS code changes for snowflake support ([e273e87](e273e870ef))
2026-02-20 18:53:31 +00:00
allan a57b49c936 Merge pull request 'feat: SAS code changes for snowflake support' (#208) from sf into main
Release / Build-production-and-ng-test (push) Successful in 4m0s
Release / Build-and-test-development (push) Successful in 9m57s
Release / release (push) Successful in 7m57s
Reviewed-on: #208
2026-02-20 18:36:00 +00:00
allan a84ba41ea9 fix: cli bump for mf_getscheme support
Build / Build-and-ng-test (pull_request) Successful in 4m10s
Build / Build-and-test-development (pull_request) Successful in 10m4s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m58s
2026-02-20 18:15:31 +00:00
allan dc200646f7 fix: missing upcase on SNOW section, plus local sasjs target
Build / Build-and-ng-test (pull_request) Successful in 4m11s
Build / Build-and-test-development (pull_request) Successful in 9m55s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m25s
2026-02-20 01:08:55 +00:00
allan e273e870ef feat: SAS code changes for snowflake support
Build / Build-and-ng-test (pull_request) Successful in 4m9s
Build / Build-and-test-development (pull_request) Successful in 10m1s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m57s
2026-02-20 00:15:23 +00:00
semantic-release-bot 6fc34aca00 chore(release): 7.3.0 [skip ci]
# [7.3.0](https://git.datacontroller.io/dc/dc/compare/v7.2.8...v7.3.0) (2026-02-10)

### Bug Fixes

* bump xlsx, add crypto-shim ([8dc18b1](8dc18b155a))
* correctly applying deletes on viya, also ([46cdeb0](46cdeb0bab))
* crypto module requirement for sheetjs/crypto package ([505d0af](505d0af2b3))
* disable parsing excel in web worker beacuse it breaks in the stream apps ([280bdee](280bdeeb1b))
* Display all contexts when installing DC on Viya ([d41f88f](d41f88f8bf))
* **edit:** use cellValidation keys and hotDataSchema to fill in defaults on add row ([4957548](495754816c))
* enabling closeouts for UPDATE in CAS tables ([8b8e8ae](8b8e8aec15))
* enabling rollback when the table has formatted values ([815d6e9](815d6e97a8))
* improvements to validations ([6ceb681](6ceb681463))
* remove IE checks and conditions ([ece6bd1](ece6bd1d78))
* updates to demodata to enable auto CAS promote ([7740d2a](7740d2ac86))
* upgrade angular core and compiler ([aecd597](aecd597687))
* using fcopy instead of binary copy for file upload, for Viya 2026 compatibility ([716ee6e](716ee6eba0))
* **viewer:** search causing blank Handsontable ([338c7a2](338c7a2e41)), closes [#206](#206)

### Features

* adding demo data job ([8c2aeac](8c2aeacc85))
* **dq rules:** notnull validation when invalid cell, will auto populate a default value ([96f2518](96f2518af9))
2026-02-10 19:17:29 +00:00
allan f97ac70678 Merge pull request 'demodata' (#203) from demodata into main
Release / Build-production-and-ng-test (push) Successful in 3m27s
Release / Build-and-test-development (push) Successful in 9m22s
Release / release (push) Successful in 7m53s
Reviewed-on: #203
2026-02-10 19:01:17 +00:00
allan 6ceb681463 fix: improvements to validations
Build / Build-and-ng-test (pull_request) Successful in 3m47s
Build / Build-and-test-development (pull_request) Successful in 9m38s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m21s
2026-02-10 18:26:02 +00:00
allan 716ee6eba0 fix: using fcopy instead of binary copy for file upload, for Viya 2026 compatibility
Build / Build-and-ng-test (pull_request) Successful in 3m42s
Build / Build-and-test-development (pull_request) Successful in 9m33s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m44s
2026-02-10 16:34:09 +00:00
allan f6b0f6b0cd Merge pull request 'fix(viewer): search causing blank Handsontable' (#207) from fix/206-search-issue into demodata
Build / Build-and-ng-test (pull_request) Successful in 3m39s
Build / Build-and-test-development (pull_request) Successful in 9m37s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m31s
Reviewed-on: #207
2026-02-10 15:50:42 +00:00
sead 737a652ff0 refactor(viewer): use drop instead of debounce
Build / Build-and-ng-test (pull_request) Successful in 3m40s
Build / Build-and-test-development (pull_request) Successful in 9m31s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m32s
2026-02-10 15:36:20 +01:00
sead 2995e5c9dc chore: restore comments and condition
Build / Build-and-ng-test (pull_request) Successful in 3m52s
Build / Build-and-test-development (pull_request) Successful in 9m39s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 19m8s
2026-02-10 14:37:56 +01:00
sead 338c7a2e41 fix(viewer): search causing blank Handsontable
Build / Build-and-ng-test (pull_request) Successful in 3m46s
Build / Build-and-test-development (pull_request) Successful in 9m26s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m52s
Closes #206
2026-02-10 14:24:06 +01:00
allan ad27358deb Merge pull request 'fix(edit): use cellValidation keys and hotDataSchema to fill in defaults on add row' (#205) from fix/204-default-value into demodata
Build / Build-and-ng-test (pull_request) Successful in 4m15s
Build / Build-and-test-development (pull_request) Successful in 10m18s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m50s
Reviewed-on: #205
2026-02-10 12:58:15 +00:00
sead 495754816c fix(edit): use cellValidation keys and hotDataSchema to fill in defaults on add row
Build / Build-and-ng-test (pull_request) Successful in 3m40s
Build / Build-and-test-development (pull_request) Successful in 10m33s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 26m51s
2026-02-10 13:53:29 +01:00
M 96f2518af9 feat(dq rules): notnull validation when invalid cell, will auto populate a default value
Build / Build-and-ng-test (pull_request) Successful in 3m41s
Build / Build-and-test-development (pull_request) Successful in 9m30s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m50s
2026-02-10 12:29:15 +01:00
M 280bdeeb1b fix: disable parsing excel in web worker beacuse it breaks in the stream apps
Build / Build-and-ng-test (pull_request) Successful in 3m41s
Build / Build-and-test-development (pull_request) Successful in 9m41s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m0s
2026-02-10 11:58:00 +01:00
allan 46cdeb0bab fix: correctly applying deletes on viya, also
Build / Build-and-ng-test (pull_request) Successful in 3m32s
Build / Build-and-test-development (pull_request) Successful in 9m14s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m18s
adding more info to the staged directory in relation to deletes.  Also
refactoring the demo data.
2026-02-09 23:23:08 +00:00
allan d41f88f8bf fix: Display all contexts when installing DC on Viya
Build / Build-and-ng-test (pull_request) Successful in 3m58s
Build / Build-and-test-development (pull_request) Successful in 9m48s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m29s
2026-02-09 18:22:06 +00:00
allan 815d6e97a8 fix: enabling rollback when the table has formatted values
Build / Build-and-ng-test (pull_request) Successful in 3m35s
Build / Build-and-test-development (pull_request) Successful in 9m16s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m4s
2026-02-08 02:40:07 +00:00
allan 4e35aefe41 Merge pull request 'Upgrade angular core and compiler' (#199) from fix/audit-20260112 into demodata
Build / Build-and-ng-test (pull_request) Successful in 3m59s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 19m16s
Build / Build-and-test-development (pull_request) Successful in 9m48s
Reviewed-on: #199
Reviewed-by: mihajlo <mihajlo@4gl.io>
2026-02-07 23:46:19 +00:00
allan ca84915e43 Merge branch 'demodata' into fix/audit-20260112
Build / Build-and-ng-test (pull_request) Successful in 4m15s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 19m43s
Build / Build-and-test-development (pull_request) Successful in 9m56s
2026-02-07 23:46:10 +00:00
zver 31cc7e9e4d chore: server release and more demodata
Build / Build-and-ng-test (pull_request) Successful in 4m3s
Build / Build-and-test-development (pull_request) Successful in 9m46s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 19m52s
2026-02-07 23:44:37 +00:00
allan 4ec107705e Merge branch 'main' into demodata
Build / Build-and-ng-test (pull_request) Successful in 3m57s
Build / Build-and-test-development (pull_request) Successful in 9m9s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Failing after 3h13m38s
2026-02-07 20:04:16 +00:00
zver 7740d2ac86 fix: updates to demodata to enable auto CAS promote
Build / Build-and-ng-test (pull_request) Successful in 3m36s
Build / Build-and-test-development (pull_request) Successful in 9m2s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Failing after 3h13m50s
2026-02-07 20:02:14 +00:00
zver 8c2aeacc85 feat: adding demo data job 2026-02-06 20:50:55 +00:00
zver 8b8e8aec15 fix: enabling closeouts for UPDATE in CAS tables 2026-02-06 20:50:38 +00:00
semantic-release-bot 6ac3f660e9 chore(release): 7.2.8 [skip ci]
## [7.2.8](https://git.datacontroller.io/dc/dc/compare/v7.2.7...v7.2.8) (2026-02-06)

### Bug Fixes

* bump adapter version ([f4c8699](f4c8699aaf))
2026-02-06 12:51:50 +00:00
sead 7ee576a9c1 Merge pull request 'Update @sasjs/adapter version' (#202) from fix/bump-adapter-20260206 into main
Release / Build-production-and-ng-test (push) Successful in 3m32s
Release / Build-and-test-development (push) Successful in 9m7s
Release / release (push) Successful in 7m39s
Reviewed-on: #202
2026-02-06 12:35:55 +00:00
sead f4c8699aaf fix: bump adapter version
Build / Build-and-ng-test (pull_request) Successful in 3m48s
Build / Build-and-test-development (pull_request) Successful in 9m12s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m14s
2026-02-06 13:12:51 +01:00
zver 4273ca6e5c chore: demo data job 2026-02-06 02:13:23 +00:00
semantic-release-bot 4ba043b77e chore(release): 7.2.7 [skip ci]
## [7.2.7](https://git.datacontroller.io/dc/dc/compare/v7.2.6...v7.2.7) (2026-02-05)

### Bug Fixes

* dclib not found error in getchangeinfo job ([86791db](86791dbaca))
2026-02-05 19:15:36 +00:00
allan 0169415ea2 Merge pull request 'fix: dclib not found error in getchangeinfo job' (#201) from dclibfix into main
Release / Build-production-and-ng-test (push) Successful in 3m32s
Release / Build-and-test-development (push) Successful in 9m15s
Release / release (push) Successful in 7m56s
Reviewed-on: #201
2026-02-05 18:59:19 +00:00
allan 86791dbaca fix: dclib not found error in getchangeinfo job
Build / Build-and-ng-test (pull_request) Successful in 3m48s
Build / Build-and-test-development (pull_request) Successful in 9m21s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m34s
2026-02-05 16:53:23 +00:00
sead d5b58a3cbd test(excel): password tests - click away overlay modal
Build / Build-and-ng-test (pull_request) Successful in 3m39s
Build / Build-and-test-development (pull_request) Successful in 9m28s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 17m38s
2026-01-14 17:38:36 +01:00
sead 3d8281d27e test(excel): fix attachFile upload for password protected tests
Build / Build-and-ng-test (pull_request) Successful in 3m43s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 19m3s
Build / Build-and-test-development (pull_request) Failing after 11m3s
2026-01-14 15:47:10 +01:00
sead b1a014c7bc test(excel): add password protected excels tests
Build / Build-and-ng-test (pull_request) Successful in 3m36s
Build / Build-and-test-development (pull_request) Failing after 10m17s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m22s
2026-01-13 18:50:17 +01:00
sead 505d0af2b3 fix: crypto module requirement for sheetjs/crypto package
Undid the crypto-shim changes from 8dc18b155a
2026-01-13 18:41:56 +01:00
sead ece6bd1d78 fix: remove IE checks and conditions 2026-01-13 17:00:56 +01:00
sead 8dc18b155a fix: bump xlsx, add crypto-shim
Build / Build-and-ng-test (pull_request) Successful in 3m29s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m24s
Build / Build-and-test-development (pull_request) Successful in 9m33s
crypto-shim fixes vulnerable crypto-browserify package used by sheetjs/crypto, shim is based on crypto-js
2026-01-13 15:04:17 +01:00
sead aecd597687 fix: upgrade angular core and compiler
Build / Build-and-ng-test (pull_request) Successful in 3m50s
Build / Build-and-test-development (pull_request) Successful in 9m8s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m27s
2026-01-13 08:38:29 +01:00
allan c99f106bae Merge pull request 'Fix lighthouse ci save path' (#198) from fix/lighthouse-20251230 into main
Release / Build-production-and-ng-test (push) Successful in 3m31s
Release / Build-and-test-development (push) Successful in 9m14s
Release / release (push) Failing after 3m16s
Reviewed-on: #198
Reviewed-by: mihajlo <mihajlo@4gl.io>
2026-01-08 11:38:32 +00:00
allan d2fc7ae6fe Merge branch 'main' into fix/lighthouse-20251230
Build / Build-and-ng-test (pull_request) Successful in 4m15s
Build / Build-and-test-development (pull_request) Successful in 9m42s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 19m16s
2026-01-08 11:38:25 +00:00
semantic-release-bot c1c1d0055a chore(release): 7.2.6 [skip ci]
## [7.2.6](https://git.datacontroller.io/dc/dc/compare/v7.2.5...v7.2.6) (2026-01-05)

### Bug Fixes

* **deps:** update angular and moment ([8c5b357](8c5b357dd2))
2026-01-05 13:56:25 +00:00
sead f37ec82d39 Merge branch 'main' into fix/lighthouse-20251230
Build / Build-and-ng-test (pull_request) Successful in 4m4s
Build / Build-and-test-development (pull_request) Successful in 10m9s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 19m4s
2026-01-05 13:39:20 +00:00
sead 6e9e30e0f0 Merge pull request 'npm audit vulnerabilities' (#197) from fix/audit-20251229 into main
Release / Build-production-and-ng-test (push) Successful in 3m54s
Release / Build-and-test-development (push) Successful in 10m5s
Release / release (push) Successful in 7m57s
Reviewed-on: #197
Reviewed-by: mihajlo <mihajlo@4gl.io>
2026-01-05 13:39:02 +00:00
sead 990ddb5cd3 chore: update .gitignore
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Has been cancelled
Build / Build-and-test-development (pull_request) Has been cancelled
Build / Build-and-ng-test (pull_request) Has been cancelled
2025-12-30 14:55:33 +01:00
sead c6ebbb48bb ci: fix lighthouse artifacts save path
Build / Build-and-ng-test (pull_request) Successful in 3m55s
Build / Build-and-test-development (pull_request) Successful in 8m9s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m30s
2025-12-30 14:49:01 +01:00
sead 95cc0b1c91 chore: undo unnecessary changes
Build / Build-and-ng-test (pull_request) Successful in 3m49s
Build / Build-and-test-development (pull_request) Successful in 9m19s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m34s
2025-12-30 13:42:20 +01:00
sead 81b282f1f1 chore: revert from esbuild back to webpack
Build / Build-and-ng-test (pull_request) Successful in 3m41s
Build / Build-and-test-development (pull_request) Successful in 9m3s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m31s
2025-12-29 23:14:51 +01:00
sead 8c5b357dd2 fix(deps): update angular and moment
Build / Build-and-ng-test (pull_request) Successful in 2m28s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Has been cancelled
Build / Build-and-test-development (pull_request) Has been cancelled
2025-12-29 22:15:47 +01:00
sead a13b2cbfd2 chore: angular update from v18 to v19 2025-12-29 18:23:21 +01:00
sead 19617c2285 chore: ng update cdk 2025-12-29 17:01:48 +01:00
sead f9794a973f chore: ng update cli, schematics, and core 2025-12-29 16:53:33 +01:00
sead 65efe62d19 chore: prettier 2025-12-29 16:46:40 +01:00
sead 683ddcaf53 chore: add ng command 2025-12-29 16:46:11 +01:00
semantic-release-bot 113e0bbc3c chore(release): 7.2.5 [skip ci]
## [7.2.5](https://git.datacontroller.io/dc/dc/compare/v7.2.4...v7.2.5) (2025-12-09)

### Bug Fixes

* (build) rebuilt package-lock files ([bfbfd55](bfbfd55fe7))
* (deps) bump @sasjs/cli and @sasjs/core ([d7c7302](d7c7302c12))
* (deps) bumped @sasjs/core, @sasjs/cli, @sasjs/utils and @sasjs/adapter ([af1657e](af1657e226))
* configurable audit table on restore check ([26ce95f](26ce95f7c1)), closes [#193](#193)
* improved testing ([fb3c49a](fb3c49aa8b))
* output values to intended macro variables ([43ae73c](43ae73c5f3))
2025-12-09 12:27:01 +00:00
allan 2af97e40bf Merge pull request 'fix: (build) bump node version for pipelines' (#196) from node_version_bump_for_pipelines into main
Release / Build-production-and-ng-test (push) Successful in 3m23s
Release / Build-and-test-development (push) Successful in 7m59s
Release / release (push) Successful in 7m3s
Reviewed-on: #196
2025-12-09 12:12:31 +00:00
Trevor Moody 83cbe3aece chore: (build) bump node version for pipelines
Build / Build-and-ng-test (pull_request) Successful in 3m36s
Build / Build-and-test-development (pull_request) Successful in 8m10s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 18m11s
2025-12-09 11:49:56 +00:00
allan ceac1ba614 Merge pull request 'bumpCoreAndCli_20251125' (#195) from bumpCoreAndCli_20251125 into main
Release / Build-production-and-ng-test (push) Successful in 3m39s
Release / Build-and-test-development (push) Successful in 8m11s
Release / release (push) Failing after 3m19s
Reviewed-on: #195
2025-12-08 19:02:14 +00:00
allan 765fdbdf9d chore: merge
Build / Build-and-ng-test (pull_request) Successful in 3m33s
Build / Build-and-test-development (pull_request) Successful in 8m16s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Successful in 18m46s
2025-12-08 17:41:07 +00:00
Trevor Moody 43ae73c5f3 fix: output values to intended macro variables
Build / Build-and-ng-test (pull_request) Successful in 3m33s
Build / Build-and-test-development (pull_request) Successful in 8m19s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Successful in 19m13s
2025-12-08 16:55:22 +00:00
Trevor Moody e57a0de8a9 chore: Re-basing file due to merge conflict 2025-12-08 16:53:03 +00:00
Trevor Moody 3de491105b chore: (build) amended licenseChecker.js exclusions
Build / Build-and-ng-test (pull_request) Successful in 3m35s
Build / Build-and-test-development (pull_request) Successful in 8m11s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Successful in 19m7s
2025-12-08 16:04:41 +00:00
M 2fe690e962 chore: package-lock
Build / Build-and-ng-test (pull_request) Failing after 1m27s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Failing after 1m46s
Build / Build-and-test-development (pull_request) Successful in 8m6s
2025-12-08 15:49:46 +01:00
M b826d37086 chore: package-lock
Build / Build-and-ng-test (pull_request) Failing after 39s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Failing after 53s
Build / Build-and-test-development (pull_request) Failing after 42s
2025-12-08 15:42:03 +01:00
Trevor Moody bfbfd55fe7 fix: (build) rebuilt package-lock files
Build / Build-and-ng-test (pull_request) Failing after 38s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Failing after 58s
Build / Build-and-test-development (pull_request) Failing after 44s
2025-12-08 14:16:56 +00:00
Trevor Moody 15f38efd52 chore: (build) use node 24.5.0
Build / Build-and-ng-test (pull_request) Failing after 45s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Failing after 56s
Build / Build-and-test-development (pull_request) Failing after 41s
2025-12-08 13:02:50 +00:00
Trevor Moody 5d25681485 chore: lint fixes
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Failing after 57s
Build / Build-and-test-development (pull_request) Failing after 42s
Build / Build-and-ng-test (pull_request) Failing after 41s
2025-12-08 12:30:00 +00:00
Trevor Moody d26df376f8 chore: CRLF to LF
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Failing after 56s
Build / Build-and-test-development (pull_request) Failing after 41s
Build / Build-and-ng-test (pull_request) Failing after 37s
2025-12-08 11:41:12 +00:00
allan cff3fb3bad Merge pull request 'fix: configurable audit table on restore check' (#194) from issue193 into main
Release / Build-production-and-ng-test (push) Successful in 3m36s
Release / Build-and-test-development (push) Successful in 8m15s
Release / release (push) Failing after 3m20s
Reviewed-on: #194
2025-12-08 11:25:44 +00:00
Trevor Moody fb3c49aa8b fix: improved testing
Build / Build-and-ng-test (pull_request) Failing after 39s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Failing after 57s
Build / Build-and-test-development (pull_request) Failing after 58s
2025-12-08 10:21:37 +00:00
Trevor Moody af1657e226 fix: (deps) bumped @sasjs/core, @sasjs/cli, @sasjs/utils and @sasjs/adapter 2025-11-28 04:11:46 +00:00
Trevor Moody d7c7302c12 fix: (deps) bump @sasjs/cli and @sasjs/core 2025-11-25 14:24:36 +00:00
allan 26ce95f7c1 fix: configurable audit table on restore check
Build / Build-and-ng-test (pull_request) Successful in 5m41s
Build / Build-and-test-development (pull_request) Successful in 9m29s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Successful in 19m20s
closes #193
2025-10-27 20:13:49 +00:00
semantic-release-bot 4924df2ef3 chore(release): 7.2.4 [skip ci]
## [7.2.4](https://git.datacontroller.io/dc/dc/compare/v7.2.3...v7.2.4) (2025-10-14)

### Bug Fixes

* ensure reload after applying licence key ([cb1978b](cb1978bcaf))
* snyk report security patches ([387f512](387f5122f1))
2025-10-14 11:44:18 +00:00
sead 2e141a5d52 chore(git): merge pull request 'fix: snyk report security patches' (#192) from dc_snyk into main
Release / Build-production-and-ng-test (push) Successful in 3m42s
Release / Build-and-test-development (push) Successful in 8m52s
Release / release (push) Successful in 8m13s
Reviewed-on: #192
Reviewed-by: mihajlo <mihajlo@4gl.io>
Reviewed-by: sead <sead@noreply.git.datacontroller.io>
2025-10-14 11:28:05 +00:00
Trevor Moody cb1978bcaf fix: ensure reload after applying licence key
Build / Build-and-ng-test (pull_request) Successful in 4m2s
Build / Build-and-test-development (pull_request) Successful in 8m49s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Successful in 18m48s
2025-10-14 12:05:27 +01:00
Trevor Moody 387f5122f1 fix: snyk report security patches
Build / Build-and-ng-test (pull_request) Successful in 4m0s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Successful in 19m23s
Build / Build-and-test-development (pull_request) Failing after 11m1s
2025-10-10 12:54:22 +01:00
semantic-release-bot db5887de21 chore(release): 7.2.3 [skip ci]
## [7.2.3](https://git.datacontroller.io/dc/dc/compare/v7.2.2...v7.2.3) (2025-10-02)

### Bug Fixes

* opening second table in viewer throws an error ([6c6b1cb](6c6b1cbf46))
2025-10-02 15:10:30 +00:00
allan fe24d9bcbd Merge pull request 'opening second table in viewer throws an error' (#191) from issue-189 into main
Release / Build-production-and-ng-test (push) Successful in 3m47s
Release / Build-and-test-development (push) Successful in 8m52s
Release / release (push) Successful in 8m25s
Reviewed-on: #191
Reviewed-by: allan <allan@4gl.io>
2025-10-02 14:54:13 +00:00
M 6c6b1cbf46 fix: opening second table in viewer throws an error
Build / Build-and-ng-test (pull_request) Successful in 4m59s
Build / Build-and-test-development (pull_request) Successful in 9m5s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Successful in 19m55s
2025-10-02 16:23:00 +02:00
semantic-release-bot 4d65c9c999 chore(release): 7.2.2 [skip ci]
## [7.2.2](https://git.datacontroller.io/dc/dc/compare/v7.2.1...v7.2.2) (2025-09-23)

### Bug Fixes

* jsrsasign,  @sasjs/cli bump ([365f129](365f12996d))
2025-09-23 21:50:46 +00:00
allan 4417279275 Merge pull request 'Addressing production vulnerabilities' (#188) from vulnerabilities into main
Release / Build-production-and-ng-test (push) Successful in 3m35s
Release / Build-and-test-development (push) Successful in 8m7s
Release / release (push) Successful in 7m48s
Reviewed-on: #188
2025-09-23 21:35:40 +00:00
M 365f12996d fix: jsrsasign, @sasjs/cli bump
Build / Build-and-ng-test (pull_request) Successful in 3m52s
Build / Build-and-test-development (pull_request) Successful in 8m14s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Successful in 18m20s
2025-09-23 16:44:55 +02:00
semantic-release-bot ef1015f33b chore(release): 7.2.1 [skip ci]
## [7.2.1](https://git.datacontroller.io/dc/dc/compare/v7.2.0...v7.2.1) (2025-08-08)

### Bug Fixes

* removing localhost from index.html ([225e693](225e693d1f))
2025-08-08 17:35:02 +00:00
allan b43dfb5cf4 Merge pull request 'fix: removing localhost from index.html' (#187) from localhostfix into main
Release / Build-production-and-ng-test (push) Successful in 3m54s
Release / Build-and-test-development (push) Successful in 8m25s
Release / release (push) Successful in 7m50s
Reviewed-on: #187
2025-08-08 17:19:17 +00:00
allan 225e693d1f fix: removing localhost from index.html
Build / Build-and-ng-test (pull_request) Successful in 3m47s
Build / Build-and-test-development (pull_request) Successful in 8m27s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Successful in 18m41s
2025-08-08 18:08:49 +01:00
semantic-release-bot fda91770be chore(release): 7.2.0 [skip ci]
# [7.2.0](https://git.datacontroller.io/dc/dc/compare/v7.1.1...v7.2.0) (2025-08-08)

### Bug Fixes

* **ci:** cypress dependency package not available anymore ([26cdd73](26cdd73331))
* **hot v16 migration:** multi dataset fixed issues, and cypress tests adapted ([712b384](712b384848))
* obsolete cypress deps ([2ba4b53](2ba4b5383e))
* remaining hot migrations - handsontable/angular-wrapper ([b419cd5](b419cd5078))

### Features

* lighthouse accessibility check pipeline ([670ec2c](670ec2c71c))
2025-08-08 11:51:38 +00:00
allan d512876e0b Merge pull request 'fix: obsolete cypress deps' (#186) from cypress-deps into main
Release / Build-production-and-ng-test (push) Successful in 3m42s
Release / Build-and-test-development (push) Successful in 8m24s
Release / release (push) Successful in 7m50s
Reviewed-on: #186
2025-08-08 11:36:06 +00:00
M 2ba4b5383e fix: obsolete cypress deps
Build / Build-and-ng-test (pull_request) Successful in 3m46s
Build / Build-and-test-development (pull_request) Successful in 8m17s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Successful in 18m35s
2025-08-08 13:25:11 +02:00
allan ecc3184609 Merge pull request 'fix: remaining hot migrations - handsontable/angular-wrapper' (#185) from remaining-hot-migration into main
Release / Build-production-and-ng-test (push) Successful in 3m32s
Release / Build-and-test-development (push) Failing after 38s
Release / release (push) Has been skipped
Reviewed-on: #185
Reviewed-by: allan <allan@4gl.io>
2025-08-08 08:28:16 +00:00
M 712b384848 fix(hot v16 migration): multi dataset fixed issues, and cypress tests adapted
Build / Build-and-ng-test (pull_request) Successful in 3m44s
Build / Build-and-test-development (pull_request) Successful in 8m24s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Successful in 18m5s
2025-08-07 16:58:53 +02:00
M 26cdd73331 fix(ci): cypress dependency package not available anymore
Build / Build-and-ng-test (pull_request) Successful in 3m46s
Build / Build-and-test-development (pull_request) Failing after 11m42s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Successful in 18m1s
2025-08-07 11:15:00 +02:00
M 919aa6dcfe ci: lighthouse
Build / Build-and-ng-test (pull_request) Successful in 3m46s
Build / Build-and-test-development (pull_request) Failing after 33s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Successful in 18m1s
2025-08-07 10:22:10 +02:00
M 3bb3093b49 ci: lighthouse
Build / Build-and-ng-test (pull_request) Successful in 3m54s
Build / Build-and-test-development (pull_request) Failing after 33s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Has been cancelled
2025-08-07 10:14:24 +02:00
M 9c12250558 ci: lighthouse
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Failing after 3m32s
Build / Build-and-ng-test (pull_request) Successful in 3m49s
Build / Build-and-test-development (pull_request) Failing after 35s
2025-08-07 10:09:19 +02:00
M 6c843f64fb chore: install wait-on
Build / Build-and-ng-test (pull_request) Successful in 3m45s
Build / Build-and-test-development (pull_request) Failing after 33s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Has been cancelled
2025-08-07 09:54:27 +02:00
M 3fda7dc5b0 chore: installed wait-on
Build / Build-and-ng-test (pull_request) Successful in 3m49s
Build / Build-and-test-development (pull_request) Failing after 37s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Has been cancelled
2025-08-07 09:48:31 +02:00
M 22ec7f0340 ci: lighthouse
Build / Build-and-ng-test (pull_request) Successful in 4m30s
Build / Build-and-test-development (pull_request) Failing after 38s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Failing after 3h12m40s
2025-08-06 16:15:45 +02:00
M 378461dcbb chore: licence checker
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Failing after 3m31s
Build / Build-and-ng-test (pull_request) Successful in 3m49s
Build / Build-and-test-development (pull_request) Failing after 54s
2025-08-06 16:08:50 +02:00
M 905c7b9d3c chore: remove doxy
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Failing after 1m26s
Build / Build-and-ng-test (pull_request) Failing after 2m6s
Build / Build-and-test-development (pull_request) Failing after 53s
2025-08-06 16:05:07 +02:00
M 670ec2c71c feat: lighthouse accessibility check pipeline
Build / Build-and-ng-test (pull_request) Failing after 1m20s
Build / Build-and-test-development (pull_request) Failing after 57s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Failing after 2m24s
2025-08-06 15:52:58 +02:00
M b419cd5078 fix: remaining hot migrations - handsontable/angular-wrapper
Build / Build-and-ng-test (pull_request) Failing after 2m3s
Build / Build-and-test-development (pull_request) Failing after 1m34s
2025-08-06 14:06:07 +02:00
semantic-release-bot b1db4ea590 chore(release): 7.1.1 [skip ci]
## [7.1.1](https://git.datacontroller.io/dc/dc/compare/v7.1.0...v7.1.1) (2025-07-24)

### Bug Fixes

* **viewboxes:** hot v16 fails to load because of relative height `100%` ([672dd6d](672dd6d4f1))
2025-07-24 11:46:09 +00:00
mihajlo 822ddb1274 Merge pull request 'fix(viewboxes): hot v16 fails to load because of relative height 100%' (#183) from hot16-viewboxes into main
Release / Build-production-and-ng-test (push) Successful in 3m50s
Release / Build-and-test-development (push) Successful in 9m17s
Release / release (push) Successful in 8m4s
Reviewed-on: #183
2025-07-24 11:29:33 +00:00
M 672dd6d4f1 fix(viewboxes): hot v16 fails to load because of relative height 100%
Build / Build-and-ng-test (pull_request) Successful in 3m55s
Build / Build-and-test-development (pull_request) Successful in 8m27s
2025-07-24 13:12:15 +02:00
semantic-release-bot b3ac73d903 chore(release): 7.1.0 [skip ci]
# [7.1.0](https://git.datacontroller.io/dc/dc/compare/v7.0.3...v7.1.0) (2025-07-23)

### Bug Fixes

* adapter bump ([b495c41](b495c41626))
* bumping CLI to 4.12.10 ([a08a717](a08a717ca8))
* bumping sasjs/core and sasjs/cli ([63e9af4](63e9af402e))

### Features

* improving accessibility score up to 100, hot update to v16.0.1 ([71c308d](71c308d052))
2025-07-23 19:46:56 +00:00
M f8554dd5e7 ci: cypress tests, ng server 'wait on' fix
Release / Build-production-and-ng-test (push) Successful in 3m36s
Release / Build-and-test-development (push) Successful in 8m12s
Release / release (push) Successful in 7m58s
2025-07-23 21:31:29 +02:00
allan 88679c0c9a Merge pull request 'fix: bumping CLI to 4.12.10' (#181) from clibump into main
Release / Build-production-and-ng-test (push) Successful in 3m35s
Release / Build-and-test-development (push) Failing after 3h10m30s
Release / release (push) Has been cancelled
Reviewed-on: #181
2025-07-23 15:04:30 +00:00
allan a08a717ca8 fix: bumping CLI to 4.12.10
Build / Build-and-ng-test (pull_request) Successful in 3m43s
Build / Build-and-test-development (pull_request) Successful in 8m11s
2025-07-23 16:03:57 +01:00
allan c8b6fdbfdb Merge pull request 'Improving accessibility score up to 100, hot update to v16.0.1' (#180) from accessibility-maxing into main
Release / Build-production-and-ng-test (push) Failing after 1m13s
Release / Build-and-test-development (push) Has been skipped
Release / release (push) Has been skipped
Reviewed-on: #180
2025-07-23 13:21:40 +00:00
M b495c41626 fix: adapter bump
Build / Build-and-ng-test (pull_request) Successful in 3m39s
Build / Build-and-test-development (pull_request) Successful in 8m9s
2025-07-23 14:59:45 +02:00
M 7f4be474c6 chore(git): Merge branch 'main' into accessibility-maxing
Build / Build-and-ng-test (pull_request) Successful in 3m48s
Build / Build-and-test-development (pull_request) Successful in 8m12s
2025-07-23 13:23:53 +02:00
allan 7f6f68fcbb Merge pull request 'fix: bumping sasjs/core and sasjs/cli' (#177) from issue157 into main
Release / Build-production-and-ng-test (push) Failing after 1m13s
Release / Build-and-test-development (push) Has been skipped
Release / release (push) Has been skipped
Reviewed-on: #177
2025-07-23 11:14:37 +00:00
M 03fd7db033 chore: licence checker
Build / Build-and-ng-test (pull_request) Successful in 3m48s
Build / Build-and-test-development (pull_request) Successful in 8m13s
2025-07-23 12:22:04 +02:00
M 9dc5c66f7b chore: package-lock
Build / Build-and-ng-test (pull_request) Failing after 1m18s
Build / Build-and-test-development (pull_request) Successful in 8m12s
2025-07-23 12:16:25 +02:00
M aa1b08632e chore: package-lock
Build / Build-and-ng-test (pull_request) Successful in 3m42s
Build / Build-and-test-development (pull_request) Successful in 7m59s
2025-07-23 11:52:56 +02:00
M 6bbe354c9e ci: fix
Build / Build-and-ng-test (pull_request) Failing after 45s
Build / Build-and-test-development (pull_request) Failing after 46s
2025-07-23 11:51:40 +02:00
M 8ff429793b style: lint
Build / Build-and-ng-test (pull_request) Failing after 45s
Build / Build-and-test-development (pull_request) Failing after 46s
2025-07-23 11:50:35 +02:00
M 70d010127a style: lint
Build / Build-and-ng-test (pull_request) Failing after 43s
Build / Build-and-test-development (pull_request) Failing after 44s
2025-07-23 11:49:34 +02:00
M 696717c509 ci: script
Build / Build-and-ng-test (pull_request) Failing after 41s
Build / Build-and-test-development (pull_request) Successful in 8m1s
2025-07-23 11:38:24 +02:00
M 71c308d052 feat: improving accessibility score up to 100, hot update to v16.0.1
Build / Build-and-ng-test (pull_request) Failing after 41s
Build / Build-and-test-development (pull_request) Has been cancelled
2025-07-23 11:17:57 +02:00
allan bed5b320ad chore: lint fix
Build / Build-and-ng-test (pull_request) Successful in 3m48s
Build / Build-and-test-development (pull_request) Has been cancelled
2025-07-07 14:51:04 +01:00
allan b0e827412e Merge branch 'main' into issue157
Build / Build-and-ng-test (pull_request) Failing after 45s
Build / Build-and-test-development (pull_request) Failing after 3h1m39s
2025-07-07 13:34:38 +00:00
allan 63e9af402e fix: bumping sasjs/core and sasjs/cli
Build / Build-and-ng-test (pull_request) Failing after 41s
Build / Build-and-test-development (pull_request) Failing after 3h11m34s
2025-07-07 14:34:03 +01:00
allan fd55105f62 chore(git): adding size check in precommit hook
Release / Build-production-and-ng-test (push) Successful in 4m20s
Release / release (push) Has been cancelled
Release / Build-and-test-development (push) Has been cancelled
2025-06-27 16:42:13 +01:00
semantic-release-bot c2e3b362e7 chore(release): 7.0.3 [skip ci]
## [7.0.3](https://git.datacontroller.io/dc/dc/compare/v7.0.2...v7.0.3) (2025-06-26)

### Bug Fixes

* makedata vars ([e7cb471](e7cb471c0b))
* viya deploy makedata missing params ([7a82316](7a8231615c))
2025-06-26 16:29:21 +00:00
Mihajlo Medjedovic 5d2d60d040 chore: npm audit fix for client folder, lint fix
Release / Build-production-and-ng-test (push) Successful in 4m5s
Release / Build-and-test-development (push) Successful in 9m9s
Release / release (push) Successful in 9m18s
2025-06-26 17:53:22 +02:00
allan 0e59f5406f Merge pull request 'fix: viya deploy makedata missing params' (#176) from viya-deploy-params into main
Release / Build-production-and-ng-test (push) Failing after 1m18s
Release / Build-and-test-development (push) Has been skipped
Release / release (push) Has been skipped
Reviewed-on: #176
2025-06-25 21:51:31 +00:00
Medjedovic e7cb471c0b fix: makedata vars
Build / Build-and-ng-test (pull_request) Successful in 4m5s
Build / Build-and-test-development (pull_request) Successful in 8m39s
2025-06-25 23:29:42 +02:00
Medjedovic 0465089207 style: lint
Build / Build-and-ng-test (pull_request) Successful in 4m4s
Build / Build-and-test-development (pull_request) Successful in 8m36s
2025-06-25 23:17:48 +02:00
Medjedovic 4f2f59907c Merge branch 'viya-deploy-params' of ssh://git.datacontroller.io:29419/dc/dc into viya-deploy-params
Build / Build-and-ng-test (pull_request) Failing after 47s
Build / Build-and-test-development (pull_request) Successful in 8m37s
2025-06-25 23:04:38 +02:00
Medjedovic 7d85328d41 style: lint 2025-06-25 23:04:19 +02:00
allan f2a9329196 chore: bumping node v
Build / Build-and-ng-test (pull_request) Failing after 49s
Build / Build-and-test-development (pull_request) Successful in 8m39s
2025-06-25 20:20:19 +01:00
Medjedovic 7a8231615c fix: viya deploy makedata missing params
Build / Build-and-ng-test (pull_request) Failing after 49s
Build / Build-and-test-development (pull_request) Successful in 9m5s
2025-06-25 15:41:43 +02:00
semantic-release-bot 0db6b25327 chore(release): 7.0.2 [skip ci]
## [7.0.2](https://git.datacontroller.io/dc/dc/compare/v7.0.1...v7.0.2) (2025-06-21)

### Bug Fixes

* **viya deploy:** run makedata in new window to ensure logs are available for the user ([0b4042a](0b4042af60))
2025-06-21 09:30:41 +00:00
allan e91f6f01a6 Merge pull request 'fix(viya deploy): run makedata in new window to ensure logs are available for the user' (#175) from issue154 into main
Release / Build-production-and-ng-test (push) Successful in 4m4s
Release / Build-and-test-development (push) Successful in 8m40s
Release / release (push) Successful in 8m30s
Reviewed-on: #175
2025-06-21 09:14:11 +00:00
Mihajlo Medjedovic 0b4042af60 fix(viya deploy): run makedata in new window to ensure logs are available for the user
Build / Build-and-ng-test (pull_request) Successful in 4m12s
Build / Build-and-test-development (pull_request) Successful in 8m42s
2025-06-17 15:33:11 +02:00
semantic-release-bot 519d8953b5 chore(release): 7.0.1 [skip ci]
## [7.0.1](https://git.datacontroller.io/dc/dc/compare/v7.0.0...v7.0.1) (2025-06-11)

### Bug Fixes

* refresh process ([4ecd186](4ecd186e5c))
2025-06-11 18:47:43 +00:00
allan 14a616fc1b Merge pull request 'fix: refresh process' (#173) from issue157 into main
Release / Build-production-and-ng-test (push) Successful in 4m9s
Release / Build-and-test-development (push) Successful in 8m45s
Release / release (push) Successful in 8m47s
Reviewed-on: #173
2025-06-11 18:05:06 +00:00
allan bfe5a8626f Merge branch 'main' into issue157
Build / Build-and-ng-test (pull_request) Successful in 4m18s
Build / Build-and-test-development (pull_request) Successful in 8m43s
2025-06-11 18:04:48 +00:00
allan 4ecd186e5c fix: refresh process
Build / Build-and-ng-test (pull_request) Successful in 4m15s
Build / Build-and-test-development (pull_request) Successful in 8m44s
2025-06-11 19:04:21 +01:00
semantic-release-bot 8c60473c15 chore(release): 7.0.0 [skip ci]
# [7.0.0](https://git.datacontroller.io/dc/dc/compare/v6.16.2...v7.0.0) (2025-06-11)

### Bug Fixes

* bumping adapter to re-enable JES API method ([e874143](e874143a95))
* commit git hooks checking lint ([69f687a](69f687a85f))
* ensuring apploc is not case sensitive. Closes [#171](#171) ([24545f2](24545f2acd))
* export unregistered formats ([f6d7d6f](f6d7d6f90c)), closes [#158](#158)
* reload startupservice after user approves the MPE_TABLES page ([e5f8e50](e5f8e500c1))
* showing catalog_cnt in libinfo ([e44a25d](e44a25dcc3)), closes [#160](#160)

### Features

* adding 4 new tables for catalogs ([e4dbab8](e4dbab8b16))
* capturing catalog specific information, closes [#159](#159) ([b4c586a](b4c586a859))
* viewer added catalog_cnt ([2aa19d1](2aa19d1dca))

### BREAKING CHANGES

* Introduction of 4 new tables for capturing information related to catalogs and their objects.  Migration script prepared and available in the DB folder (usual place)
2025-06-11 13:46:10 +00:00
allan bb126eba5b Merge pull request 'Reload startupservice after user approves the MPE_TABLES changes' (#170) from issue157 into main
Release / Build-production-and-ng-test (push) Successful in 4m8s
Release / Build-and-test-development (push) Successful in 8m47s
Release / release (push) Successful in 8m50s
Reviewed-on: #170
2025-06-11 13:16:22 +00:00
allan d1998422d2 chore: fix for mpe_datastatus_libs
Build / Build-and-ng-test (pull_request) Successful in 4m19s
Build / Build-and-test-development (pull_request) Successful in 8m45s
2025-06-11 14:15:50 +01:00
Mihajlo Medjedovic 69f687a85f fix: commit git hooks checking lint
Build / Build-and-ng-test (pull_request) Successful in 4m11s
Build / Build-and-test-development (pull_request) Successful in 8m34s
2025-06-11 13:11:30 +02:00
Mihajlo Medjedovic 2aa19d1dca feat: viewer added catalog_cnt
Build / Build-and-ng-test (pull_request) Failing after 48s
Build / Build-and-test-development (pull_request) Successful in 8m49s
2025-06-11 12:57:10 +02:00
allan e44a25dcc3 fix: showing catalog_cnt in libinfo
Build / Build-and-ng-test (pull_request) Successful in 4m19s
Build / Build-and-test-development (pull_request) Successful in 8m53s
Closes #160
2025-06-11 10:06:52 +01:00
allan efb5ffa906 chore: reverting accidental change
Build / Build-and-ng-test (pull_request) Successful in 4m4s
Build / Build-and-test-development (pull_request) Successful in 8m33s
2025-06-10 22:45:39 +01:00
allan b4c586a859 feat: capturing catalog specific information, closes #159
Build / Build-and-ng-test (pull_request) Successful in 4m12s
Build / Build-and-test-development (pull_request) Successful in 8m35s
BREAKING CHANGE:  Introduction of 4 new tables for capturing information related to catalogs and their objects.  Migration script prepared and available in the DB folder (usual place)
2025-06-10 22:40:09 +01:00
allan e874143a95 fix: bumping adapter to re-enable JES API method 2025-06-10 16:18:56 +01:00
allan e4dbab8b16 feat: adding 4 new tables for catalogs 2025-06-10 16:18:30 +01:00
allan f6d7d6f90c fix: export unregistered formats
Build / Build-and-ng-test (pull_request) Successful in 4m14s
Build / Build-and-test-development (pull_request) Successful in 9m0s
Closes #158
2025-06-10 09:41:18 +01:00
allan 063c90caf4 chore(docs): readme fix
Build / Build-and-ng-test (pull_request) Successful in 4m3s
Build / Build-and-test-development (pull_request) Successful in 8m38s
2025-06-06 23:24:35 +01:00
allan 2011c2eee7 chore(docs): updating README with viya deploy details
Build / Build-and-ng-test (pull_request) Successful in 4m0s
Build / Build-and-test-development (pull_request) Successful in 8m28s
2025-06-06 23:23:47 +01:00
allan 24545f2acd fix: ensuring apploc is not case sensitive. Closes #171
Build / Build-and-ng-test (pull_request) Successful in 4m6s
Build / Build-and-test-development (pull_request) Successful in 8m38s
2025-06-06 21:05:08 +01:00
mihajlo a7c81245ff Merge branch 'main' into issue157
Build / Build-and-ng-test (pull_request) Successful in 4m6s
Build / Build-and-test-development (pull_request) Successful in 8m45s
2025-06-06 13:17:17 +00:00
Mihajlo Medjedovic 4f2c993b2d style: lint
Build / Build-and-ng-test (pull_request) Successful in 4m3s
Build / Build-and-test-development (pull_request) Successful in 8m35s
2025-06-06 15:03:51 +02:00
Mihajlo Medjedovic e5f8e500c1 fix: reload startupservice after user approves the MPE_TABLES page 2025-06-06 15:03:36 +02:00
semantic-release-bot a61e2de140 chore(release): 6.16.2 [skip ci]
## [6.16.2](https://git.datacontroller.io/dc/dc/compare/v6.16.1...v6.16.2) (2025-06-06)

### Bug Fixes

* streaming viya deploy `isStreaming` function stability fix ([4830c6d](4830c6d219))
2025-06-06 11:36:00 +00:00
allan 881d2b060e Merge pull request 'fix: streaming viya deploy isStreaming function stability fix' (#169) from viya-deploy into main
Release / Build-production-and-ng-test (push) Successful in 3m53s
Release / Build-and-test-development (push) Successful in 8m36s
Release / release (push) Successful in 8m18s
Reviewed-on: #169
2025-06-06 11:08:03 +00:00
Mihajlo Medjedovic 4830c6d219 fix: streaming viya deploy isStreaming function stability fix
Build / Build-and-ng-test (pull_request) Successful in 3m59s
Build / Build-and-test-development (pull_request) Successful in 8m30s
2025-06-06 13:06:55 +02:00
semantic-release-bot 4c3c9ac88c chore(release): 6.16.1 [skip ci]
## [6.16.1](https://git.datacontroller.io/dc/dc/compare/v6.16.0...v6.16.1) (2025-06-06)

### Bug Fixes

* viya deploy updating index html based on URL ([86134f4](86134f478a))
* viya streamed app deploy page flow fix ([89ab296](89ab296151))
2025-06-06 10:35:30 +00:00
allan 7e1c610a4d Merge pull request 'Viya deploy, updating the compute context in index.html file, found based on URL' (#168) from viya-deploy into main
Release / Build-production-and-ng-test (push) Successful in 3m54s
Release / Build-and-test-development (push) Successful in 8m30s
Release / release (push) Successful in 8m19s
Reviewed-on: #168
2025-06-06 10:19:20 +00:00
Mihajlo Medjedovic 8139f495ce style: lint
Build / Build-and-ng-test (pull_request) Successful in 4m4s
Build / Build-and-test-development (pull_request) Successful in 8m33s
2025-06-06 12:06:10 +02:00
Mihajlo Medjedovic 89ab296151 fix: viya streamed app deploy page flow fix 2025-06-06 12:05:52 +02:00
Mihajlo Medjedovic a0dc92c403 style: lint
Build / Build-and-ng-test (pull_request) Successful in 4m15s
Build / Build-and-test-development (pull_request) Successful in 8m34s
2025-06-06 10:58:01 +02:00
Mihajlo Medjedovic 86134f478a fix: viya deploy updating index html based on URL 2025-06-06 10:57:41 +02:00
semantic-release-bot 9a2addc18e chore(release): 6.16.0 [skip ci]
# [6.16.0](https://git.datacontroller.io/dc/dc/compare/v6.15.2...v6.16.0) (2025-06-05)

### Bug Fixes

* adapter bump ([ca7caa2](ca7caa25b6))
* automatic viya deploy timing issue ([037a97b](037a97b6ff))
* bump core to ensure ff works on viya streaming deploy ([cbd69df](cbd69df708)), closes [#156](#156)
* viya deploy load data timing ([abdbb67](abdbb67471))

### Features

* viya deploy, update the index.html contextname ([7223955](72239558af))
2025-06-05 13:53:03 +00:00
allan 9264ce2a60 Merge pull request 'Adapter bump, including getFileContent and updateFileContent' (#167) from adapter-bump into main
Release / Build-production-and-ng-test (push) Successful in 4m4s
Release / Build-and-test-development (push) Successful in 9m9s
Release / release (push) Successful in 8m19s
Reviewed-on: #167
2025-06-05 13:17:06 +00:00
allan cbd69df708 fix: bump core to ensure ff works on viya streaming deploy
Build / Build-and-ng-test (pull_request) Successful in 4m3s
Build / Build-and-test-development (pull_request) Successful in 8m39s
closes #156 (along with previous releases)
2025-06-05 14:16:44 +01:00
Mihajlo Medjedovic ca7caa25b6 fix: adapter bump
Build / Build-and-ng-test (pull_request) Successful in 4m9s
Build / Build-and-test-development (pull_request) Successful in 8m45s
2025-06-05 14:49:21 +02:00
allan c10330627f Merge pull request 'Viya smooth deploy' (#166) from deploy-context into main
Release / Build-production-and-ng-test (push) Has been cancelled
Release / Build-and-test-development (push) Has been cancelled
Release / release (push) Has been cancelled
Reviewed-on: #166
2025-06-05 12:30:29 +00:00
allan d80c59afce Merge branch 'main' into deploy-context
Build / Build-and-ng-test (pull_request) Has been cancelled
Build / Build-and-test-development (pull_request) Has been cancelled
2025-06-05 12:30:19 +00:00
Mihajlo Medjedovic abdbb67471 fix: viya deploy load data timing
Build / Build-and-ng-test (pull_request) Failing after 4m0s
Build / Build-and-test-development (pull_request) Failing after 2h56m12s
2025-06-05 12:07:26 +02:00
Mihajlo Medjedovic 037a97b6ff fix: automatic viya deploy timing issue 2025-06-04 17:37:44 +02:00
Mihajlo Medjedovic a0a529ad38 style: lint 2025-06-04 17:36:13 +02:00
Mihajlo Medjedovic 72239558af feat: viya deploy, update the index.html contextname 2025-06-04 17:35:15 +02:00
semantic-release-bot d2097ad6dd chore(release): 6.15.2 [skip ci]
## [6.15.2](https://git.datacontroller.io/dc/dc/compare/v6.15.1...v6.15.2) (2025-06-04)

### Bug Fixes

* pipeline updates for DC.html ([624a7a8](624a7a8f37))
2025-06-04 14:26:44 +00:00
allan 1bd542cddb Merge pull request 'fix: pipeline updates for DC.html' (#165) from issue156b into main
Release / Build-production-and-ng-test (push) Successful in 4m29s
Release / Build-and-test-development (push) Successful in 8m37s
Release / release (push) Successful in 8m17s
Reviewed-on: #165
2025-06-04 13:53:24 +00:00
allan fb1c1ee874 Merge branch 'main' into issue156b
Build / Build-and-ng-test (pull_request) Successful in 4m1s
Build / Build-and-test-development (pull_request) Successful in 8m34s
2025-06-04 13:48:58 +00:00
allan 624a7a8f37 fix: pipeline updates for DC.html
Build / Build-and-ng-test (pull_request) Successful in 4m5s
Build / Build-and-test-development (pull_request) Successful in 8m42s
2025-06-04 14:41:36 +01:00
semantic-release-bot 381378f532 chore(release): 6.15.1 [skip ci]
## [6.15.1](https://git.datacontroller.io/dc/dc/compare/v6.15.0...v6.15.1) (2025-06-04)

### Bug Fixes

* updating pipeline to default to streaming on viya ([4b55894](4b558948d9))
2025-06-04 12:57:08 +00:00
allan af05486c0e Merge pull request 'fix: updating pipeline to default to streaming on viya' (#164) from issue156b into main
Release / Build-production-and-ng-test (push) Successful in 3m57s
Release / Build-and-test-development (push) Successful in 8m41s
Release / release (push) Failing after 7m35s
Reviewed-on: #164
2025-06-04 12:28:12 +00:00
allan 4b558948d9 fix: updating pipeline to default to streaming on viya
Build / Build-and-ng-test (pull_request) Successful in 4m4s
Build / Build-and-test-development (pull_request) Successful in 8m33s
Also added error captures on makedata
2025-06-04 13:27:28 +01:00
semantic-release-bot d9cff42f5c chore(release): 6.15.0 [skip ci]
# [6.15.0](https://git.datacontroller.io/dc/dc/compare/v6.14.10...v6.15.0) (2025-06-04)

### Bug Fixes

* makedata with context name ([da4d0b2](da4d0b28c7))

### Features

* viya deploy context ([6c96ef7](6c96ef7fb0))
2025-06-04 09:26:02 +00:00
allan 2a3d2b8d0d Merge pull request 'feat: viya deploy context' (#163) from deploy-context into main
Release / Build-production-and-ng-test (push) Successful in 4m3s
Release / Build-and-test-development (push) Successful in 8m39s
Release / release (push) Successful in 8m30s
Reviewed-on: #163
2025-06-04 09:09:28 +00:00
Mihajlo Medjedovic d0f453d291 chore: typo
Build / Build-and-ng-test (pull_request) Successful in 3m59s
Build / Build-and-test-development (pull_request) Successful in 8m31s
2025-06-04 09:41:06 +02:00
Mihajlo Medjedovic 8e65dd0eae style: lint
Build / Build-and-ng-test (pull_request) Successful in 3m59s
Build / Build-and-test-development (pull_request) Successful in 8m25s
2025-06-03 21:06:48 +02:00
Mihajlo Medjedovic da4d0b28c7 fix: makedata with context name
Build / Build-and-ng-test (pull_request) Failing after 49s
Build / Build-and-test-development (pull_request) Successful in 8m32s
2025-06-03 21:00:54 +02:00
Mihajlo Medjedovic 6c96ef7fb0 feat: viya deploy context
Build / Build-and-ng-test (pull_request) Failing after 47s
Build / Build-and-test-development (pull_request) Successful in 8m35s
2025-06-03 20:22:39 +02:00
semantic-release-bot 997f09adde chore(release): 6.14.10 [skip ci]
## [6.14.10](https://git.datacontroller.io/dc/dc/compare/v6.14.9...v6.14.10) (2025-06-02)

### Bug Fixes

* bump core ([0e8503e](0e8503ed2b))
* default to home directory for SAS Drive in Viya ([9682b54](9682b548e6))
2025-06-02 17:51:20 +00:00
allan 0e8503ed2b fix: bump core
Release / Build-production-and-ng-test (push) Successful in 4m31s
Release / Build-and-test-development (push) Successful in 8m40s
Release / release (push) Successful in 8m20s
2025-06-02 18:32:33 +01:00
allan 97dfcd79b1 Merge pull request 'issue156' (#161) from issue156 into main
Release / Build-and-test-development (push) Has been cancelled
Release / release (push) Has been cancelled
Release / Build-production-and-ng-test (push) Has been cancelled
Reviewed-on: #161
2025-06-02 17:29:18 +00:00
allan 9a12a2e41f Merge branch 'main' into issue156
Build / Build-and-ng-test (pull_request) Successful in 4m9s
Build / Build-and-test-development (pull_request) Has been cancelled
2025-06-02 17:27:14 +00:00
semantic-release-bot 5c114e562b chore(release): 6.14.9 [skip ci]
## [6.14.9](https://git.datacontroller.io/dc/dc/compare/v6.14.8...v6.14.9) (2025-06-02)

### Bug Fixes

* default DC path for viya ([f3125ff](f3125ff464))
2025-06-02 10:36:05 +00:00
allan ae696a0be0 Merge pull request 'Viya default DC Path by user name' (#162) from viya-smooth-deploy into main
Release / Build-production-and-ng-test (push) Successful in 3m56s
Release / Build-and-test-development (push) Successful in 8m33s
Release / release (push) Successful in 8m22s
Reviewed-on: #162
2025-06-02 10:19:51 +00:00
Mihajlo Medjedovic 22d46a5dcc style: lint
Build / Build-and-ng-test (pull_request) Successful in 4m8s
Build / Build-and-test-development (pull_request) Successful in 8m34s
2025-06-02 11:24:30 +02:00
Mihajlo Medjedovic f3125ff464 fix: default DC path for viya 2025-06-02 11:24:18 +02:00
allan 9682b548e6 fix: default to home directory for SAS Drive in Viya
Build / Build-and-ng-test (pull_request) Successful in 4m10s
Build / Build-and-test-development (pull_request) Successful in 8m35s
2025-05-29 15:05:18 +01:00
semantic-release-bot ec11a74265 chore(release): 6.14.8 [skip ci]
## [6.14.8](https://git.datacontroller.io/dc/dc/compare/v6.14.7...v6.14.8) (2025-05-28)

### Bug Fixes

* CSP issues, clarity local library build, fixed some style issues ([841201a](841201adab))
* deploy page, makedata error handling, added local build of clarity, to address clr-stack-view CSP issues (inline styles) ([7b5e7ae](7b5e7ae184))
* improved deploy flow for Viya ([9604661](9604661f3b))
* requests modal causing VIYA CSP errors ([1dc6934](1dc69341ca))
* sas viya service init timing issue ([9de04e9](9de04e9a0c))
* scss of components transferred to the global styles.scss so we do not cause CSP (inline styles) issues when streaming to Viya ([6c171a6](6c171a6394))
* viya deploy page improved flow ([4bd2154](4bd215491f))
2025-05-28 18:13:32 +00:00
allan 4be0614604 Merge pull request 'chore: package-lock' (#155) from lockfile-fix into main
Release / Build-production-and-ng-test (push) Successful in 4m5s
Release / Build-and-test-development (push) Successful in 8m48s
Release / release (push) Successful in 8m32s
Reviewed-on: #155
2025-05-28 17:56:46 +00:00
Mihajlo Medjedovic 27cbff2bc5 chore: remove package.json comment
Build / Build-and-ng-test (pull_request) Successful in 4m13s
Build / Build-and-test-development (pull_request) Successful in 8m49s
2025-05-28 19:18:32 +02:00
Mihajlo Medjedovic c41c8963f2 chore: package.json comments
Build / Build-and-ng-test (pull_request) Failing after 49s
Build / Build-and-test-development (pull_request) Failing after 50s
2025-05-28 19:03:54 +02:00
Mihajlo Medjedovic 7249d4fa29 chore: package-lock
Build / Build-and-ng-test (pull_request) Successful in 4m14s
Build / Build-and-test-development (pull_request) Successful in 8m50s
2025-05-28 19:01:16 +02:00
Mihajlo Medjedovic 2e0c60cc0d chore: fix package-lock 2025-05-28 18:57:43 +02:00
allan 7c5e47f5e4 Merge pull request 'Scss of components transferred to the global styles.scss so we do not cause CSP (inline styles) issues when streaming to Viya' (#153) from css-refactor into main
Release / Build-production-and-ng-test (push) Failing after 43s
Release / Build-and-test-development (push) Has been skipped
Release / release (push) Has been skipped
Reviewed-on: #153
2025-05-28 16:57:20 +00:00
Mihajlo Medjedovic f9decbd366 style: lint
Build / Build-and-ng-test (pull_request) Failing after 53s
Build / Build-and-test-development (pull_request) Failing after 50s
2025-05-26 15:39:48 +02:00
Mihajlo Medjedovic 1dc69341ca fix: requests modal causing VIYA CSP errors 2025-05-26 15:39:28 +02:00
Mihajlo Medjedovic 75ae19fa8e style: lint
Build / Build-and-ng-test (pull_request) Failing after 1m11s
Build / Build-and-test-development (pull_request) Failing after 1m18s
2025-05-23 13:35:58 +02:00
Mihajlo Medjedovic 9de04e9a0c fix: sas viya service init timing issue
Build / Build-and-ng-test (pull_request) Failing after 48s
Build / Build-and-test-development (pull_request) Failing after 1m15s
2025-05-23 13:35:37 +02:00
Mihajlo Medjedovic 983f59cd51 style: lint
Build / Build-and-ng-test (pull_request) Failing after 1m11s
Build / Build-and-test-development (pull_request) Failing after 1m11s
2025-05-23 11:32:27 +02:00
Mihajlo Medjedovic 7b5e7ae184 fix: deploy page, makedata error handling, added local build of clarity, to address clr-stack-view CSP issues (inline styles) 2025-05-23 11:30:15 +02:00
Mihajlo Medjedovic 6e96b1daec style: lint
Build / Build-and-ng-test (pull_request) Successful in 3m57s
Build / Build-and-test-development (pull_request) Successful in 8m28s
2025-05-22 10:47:46 +02:00
Mihajlo Medjedovic 9604661f3b fix: improved deploy flow for Viya 2025-05-22 10:47:15 +02:00
Mihajlo Medjedovic 4bd215491f fix: viya deploy page improved flow
Build / Build-and-ng-test (pull_request) Successful in 4m10s
Build / Build-and-test-development (pull_request) Successful in 8m43s
2025-05-21 14:13:03 +02:00
Mihajlo Medjedovic 6a7dd451b5 style: lint
Build / Build-and-ng-test (pull_request) Failing after 49s
Build / Build-and-test-development (pull_request) Failing after 47s
2025-05-21 09:55:05 +02:00
Mihajlo Medjedovic 841201adab fix: CSP issues, clarity local library build, fixed some style issues
Build / Build-and-ng-test (pull_request) Failing after 49s
Build / Build-and-test-development (pull_request) Failing after 49s
2025-05-21 09:36:36 +02:00
Mihajlo Medjedovic e013e62776 chore(git): Merge branch 'main' into css-refactor
Build / Build-and-ng-test (pull_request) Failing after 50s
Build / Build-and-test-development (pull_request) Successful in 8m48s
2025-05-13 15:59:08 +02:00
Mihajlo Medjedovic 6c171a6394 fix: scss of components transferred to the global styles.scss so we do not cause CSP (inline styles) issues when streaming to Viya 2025-05-13 15:54:57 +02:00
semantic-release-bot a377f6e8d6 chore(release): 6.14.7 [skip ci]
## [6.14.7](https://git.datacontroller.io/dc/dc/compare/v6.14.6...v6.14.7) (2025-05-08)

### Bug Fixes

* updated hot, clarity and improved accessibility score. ([2844c70](2844c70f95))
2025-05-08 11:54:20 +00:00
allan 0337318e0b Merge pull request 'Updated hot, clarity and improved accessibility score.' (#152) from hot-clarity-accessiblity-update into main
Release / Build-production-and-ng-test (push) Successful in 4m2s
Release / Build-and-test-development (push) Successful in 8m36s
Release / release (push) Successful in 8m19s
Reviewed-on: #152
Reviewed-by: allan <allan@4gl.io>
2025-05-08 11:37:55 +00:00
Mihajlo Medjedovic 2844c70f95 fix: updated hot, clarity and improved accessibility score.
Build / Build-and-ng-test (pull_request) Successful in 4m10s
Build / Build-and-test-development (pull_request) Successful in 8m41s
2025-05-06 15:58:30 +02:00
semantic-release-bot 7e11c8f375 chore(release): 6.14.6 [skip ci]
## [6.14.6](https://git.datacontroller.io/dc/dc/compare/v6.14.5...v6.14.6) (2025-04-03)

### Bug Fixes

* history table modal links styling ([c63fcdd](c63fcdd465))
2025-04-03 08:32:36 +00:00
allan 23cbbce964 Merge pull request 'History table modal links styling' (#151) from history-links into main
Release / Build-production-and-ng-test (push) Successful in 3m47s
Release / Build-and-test-development (push) Successful in 8m19s
Release / release (push) Successful in 7m55s
Reviewed-on: #151
2025-04-03 08:16:57 +00:00
Mihajlo Medjedovic c63fcdd465 fix: history table modal links styling
Build / Build-and-ng-test (pull_request) Successful in 3m58s
Build / Build-and-test-development (pull_request) Successful in 8m24s
2025-04-02 19:15:29 +02:00
semantic-release-bot 82412b2659 chore(release): 6.14.5 [skip ci]
## [6.14.5](https://git.datacontroller.io/dc/dc/compare/v6.14.4...v6.14.5) (2025-03-24)

### Bug Fixes

* improving accessibility lighthouse score ([7f3577c](7f3577c3ef))
* prevent errors when using sqlrc in a DI job in a HOOK ([d1f0879](d1f0879f0a))
* user profile style fix, new select library and table icons ([69f8830](69f883034f))
2025-03-24 23:10:35 +00:00
allan eef3832e40 Merge pull request 'User profile style fix, new select library and table icons, improved accessibility score' (#150) from styling into main
Release / Build-production-and-ng-test (push) Successful in 3m53s
Release / Build-and-test-development (push) Successful in 8m14s
Release / release (push) Successful in 8m14s
Reviewed-on: #150
2025-03-24 22:42:40 +00:00
allan 63b75a1c61 Merge branch 'main' into styling
Build / Build-and-ng-test (pull_request) Successful in 3m51s
Build / Build-and-test-development (pull_request) Successful in 8m14s
2025-03-24 22:42:31 +00:00
allan d1f0879f0a fix: prevent errors when using sqlrc in a DI job in a HOOK
Release / release (push) Blocked by required conditions
Release / Build-production-and-ng-test (push) Successful in 3m54s
Release / Build-and-test-development (push) Has been cancelled
2025-03-24 21:35:17 +00:00
Mihajlo Medjedovic 36416aab2e style: lint
Build / Build-and-ng-test (pull_request) Successful in 3m53s
Build / Build-and-test-development (pull_request) Successful in 8m16s
2025-03-21 14:08:57 +01:00
Mihajlo Medjedovic 7f3577c3ef fix: improving accessibility lighthouse score 2025-03-21 14:08:24 +01:00
Mihajlo Medjedovic 69f883034f fix: user profile style fix, new select library and table icons
Build / Build-and-ng-test (pull_request) Failing after 49s
Build / Build-and-test-development (pull_request) Successful in 8m21s
2025-03-20 17:33:35 +01:00
semantic-release-bot 1c56af01d0 chore(release): 6.14.4 [skip ci]
## [6.14.4](https://git.datacontroller.io/dc/dc/compare/v6.14.3...v6.14.4) (2025-03-18)

### Bug Fixes

* removing cli dependency warnings2 ([43c0f73](43c0f73c21))
2025-03-18 14:11:11 +00:00
Allan 43c0f73c21 fix: removing cli dependency warnings2
Release / Build-production-and-ng-test (push) Successful in 3m57s
Release / Build-and-test-development (push) Successful in 8m16s
Release / release (push) Successful in 8m11s
2025-03-18 13:45:55 +00:00
Allan e8cd3d63da chore: bumping CLI and core
Release / Build-production-and-ng-test (push) Successful in 3m55s
Release / Build-and-test-development (push) Successful in 8m14s
Release / release (push) Failing after 3m39s
2025-03-18 11:38:16 +00:00
338 changed files with 33024 additions and 26550 deletions
+19 -7
View File
@@ -1,11 +1,23 @@
#!/bin/sh
# Avoid commits to the master branch
BRANCH=`git rev-parse --abbrev-ref HEAD`
REGEX="^(master|development)$"
# Using `--silent` helps for showing any errs in the first line of the response
# The first line is picked up by the VS Code GIT UI popup when rc is not 0
if [[ "$BRANCH" =~ $REGEX ]]; then
echo "You are on branch $BRANCH. Are you sure you want to commit to this branch?"
echo "If so, commit with -n to bypass the pre-commit hook."
exit 1
if npm run --silent lint:check:silent ; then
exit 0
else
npm run --silent lint:fix:silent
echo "❌ Prettier check failed! We ran lint:fix for you. Please add & commit again."
exit 1
fi
## Avoid large commits
# https://www.backblaze.com/blog/how-many-bytes-are-in-a-megabyte-really/
size_limit=$((2 * 2**20)) # 2mbs
# https://git-scm.com/docs/git-rev-list#Documentation/git-rev-list.txt---disk-usage
commit_size=$(git rev-list --disk-usage HEAD^..HEAD)
test "$commit_size" -lt "$size_limit" || (
echo "Commit size is too large: $commit_size > $size_limit"
echo "Force commit using --no-verify"
exit 1
)
+1
View File
@@ -0,0 +1 @@
* text=auto eol=lf
+58 -37
View File
@@ -2,39 +2,53 @@ name: Build
run-name: Running Lint Check and Licence checker on Pull Request
on: [pull_request]
env:
NODE_VERSION: '24.15.0'
jobs:
Build-and-ng-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.14.0
node-version: ${{ env.NODE_VERSION }}
- name: Install Google Chrome
run: |
wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
echo "deb http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list
apt-get update
apt-get install -y google-chrome-stable xvfb
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
apt install -y ./google-chrome*.deb
- name: Write .npmrc file
run: echo "$NPMRC" > client/.npmrc
run: echo "$NPMRC" >> client/.npmrc
shell: bash
env:
NPMRC: ${{ secrets.NPMRC}}
- name: Lint check
run: npm run lint:check
- name: Install dependencies
run: |
cd client
# Decrypt and Install sheet
echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg
echo "${{ secrets.SHEET_PWD }}" | \
gpg --batch --yes --passphrase-fd 0 \
--output ./libraries/sheet-crypto.tgz \
--decrypt ./libraries/sheet-crypto.tgz.gpg
npm ci
- name: Check audit
# Audit should fail and stop the CI if critical vulnerability found
run: |
npm audit --audit-level=critical --omit=dev
cd ./sas
npm audit --audit-level=critical --omit=dev
cd ../client
npm audit --audit-level=critical --omit=dev
- name: Lint check
run: npm run lint:check
- name: Licence checker
run: |
cd client
@@ -52,26 +66,33 @@ jobs:
Build-and-test-development:
runs-on: ubuntu-latest
needs: Build-production-and-ng-test
needs: Build-and-ng-test
env:
CHROME_BIN: /usr/bin/google-chrome
# Pin OS locale
LANG: en_GB.UTF-8
LC_ALL: en_GB.UTF-8
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.14.0
node-version: ${{ env.NODE_VERSION }}
- name: Write .npmrc file
run: |
touch client/.npmrc
echo '${{ secrets.NPMRC}}' > client/.npmrc
- run: apt-get update
- run: wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
- run: apt install -y ./google-chrome*.deb;
- run: export CHROME_BIN=/usr/bin/google-chrome
- run: apt-get update -y
- run: apt-get -y install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb
- run: apt -y install jq
- name: Install system dependencies
run: |
apt-get update
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
apt install -y ./google-chrome*.deb
apt-get -y install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2t64 libxtst6 xauth xvfb jq zip locales
# Generate the en_GB.UTF-8 locale referenced by LANG/LC_ALL
locale-gen en_GB.UTF-8
update-locale LANG=en_GB.UTF-8 LC_ALL=en_GB.UTF-8
- name: Write cypress credentials
run: echo "$CYPRESS_CREDS" > ./client/cypress.env.json
@@ -86,17 +107,18 @@ jobs:
echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg
npm ci
# Install pm2 and prepare SASJS server
- run: npm i -g pm2
- run: curl -L https://github.com/sasjs/server/releases/latest/download/linux.zip > linux.zip
- run: unzip linux.zip
- run: touch .env
- run: echo RUN_TIMES=js >> .env
- run: echo NODE_PATH=node >> .env
- run: echo CORS=enable >> .env
- run: echo WHITELIST=http://localhost:4200 >> .env
- run: cat .env
- run: pm2 start api-linux --wait-ready
- name: Setup and start SASjs server
run: |
npm i -g pm2
curl -L https://github.com/sasjs/server/releases/latest/download/linux.zip > linux.zip
unzip linux.zip
touch .env
echo RUN_TIMES=js >> .env
echo NODE_PATH=node >> .env
echo CORS=enable >> .env
echo WHITELIST=http://localhost:4200 >> .env
cat .env
pm2 start api-linux --wait-ready
- name: Deploy mocked services
run: |
@@ -106,12 +128,8 @@ jobs:
sasjs cbd -t server-ci
# sasjs request services/admin/makedata -t server-ci -d ./deploy/makeData4GL.json -c ./deploy/requestConfig.json -o ./output.json
- name: Install ZIP
run: |
apt-get update
apt-get install zip
- name: Prepare and run frontend and cypress
timeout-minutes: 35
run: |
cd ./client
mv ./cypress.env.example.json ./cypress.env.json
@@ -126,11 +144,14 @@ jobs:
replace-in-files --regex='"hosturl".*' --replacement='hosturl:"http://localhost:4200",' ./cypress.config.ts
cat ./cypress.config.ts
# Start frontend and run cypress
npm start & npx wait-on http://localhost:4200 && npx cypress run --browser chrome --spec "cypress/e2e/liveness.cy.ts,cypress/e2e/editor.cy.ts,cypress/e2e/excel-multi-load.cy.ts,cypress/e2e/excel.cy.ts,cypress/e2e/csv.cy.ts,cypress/e2e/filtering.cy.ts,cypress/e2e/licensing.cy.ts"
# timeout 1800: SIGTERM after 30 min so Cypress can flush video/screenshots
# before the outer timeout-minutes hard-kills the step (avoids silent multi-hour hangs)
npx ng serve --host 0.0.0.0 --port 4200 & npx wait-on http://localhost:4200 && timeout 1800 npx cypress run --browser chrome --spec "cypress/e2e/csv-limited.cy.ts,cypress/e2e/liveness.cy.ts,cypress/e2e/editor.cy.ts,cypress/e2e/excel-multi-load.cy.ts,cypress/e2e/excel.cy.ts,cypress/e2e/csv.cy.ts,cypress/e2e/filtering.cy.ts,cypress/e2e/licensing.cy.ts"
- name: Zip Cypress videos
if: always()
run: |
mkdir -p ./client/cypress/videos
zip -r cypress-videos ./client/cypress/videos
- name: Add cypress videos artifacts
+91
View File
@@ -0,0 +1,91 @@
name: Lighthouse Checks
run-name: Running Lighthouse Performance and Accessibility Checks on Pull Request
on: [pull_request]
env:
NODE_VERSION: '24.15.0'
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install Google Chrome
run: |
apt-get update
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
apt install -y ./google-chrome*.deb
- name: Install global packages
run: npm i -g pm2 @sasjs/cli wait-on
- name: Setup and start SASjs server
run: |
touch .env
echo RUN_TIMES=js >> .env
echo NODE_PATH=node >> .env
echo CORS=enable >> .env
echo WHITELIST=http://localhost:4200 >> .env
cat .env
curl -L https://github.com/sasjs/server/releases/latest/download/linux.zip > linux.zip
unzip linux.zip
pm2 start api-linux --wait-ready
- name: Write .npmrc file
run: echo "$NPMRC" > client/.npmrc
shell: bash
env:
NPMRC: ${{ secrets.NPMRC}}
- name: Install npm dependencies
run: |
cd client
# Decrypt and Install sheet
echo "${{ secrets.SHEET_PWD }}" | \
gpg --batch --yes --passphrase-fd 0 \
--output ./libraries/sheet-crypto.tgz \
--decrypt ./libraries/sheet-crypto.tgz.gpg
npm ci
npm install -g replace-in-files-cli
- name: Update appLoc in index.html
run: |
cd client
replace-in-files --regex='appLoc=".*?"' --replacement='appLoc="/Public/app/devtest"' ./src/index.html
- name: Build Frontend
run: |
cd client
npm run build
- name: Deploy JS mocked services and frontend to the local SASjs Server instance
run: |
cd sas/mocks
npm ci
sasjs cbd -t server-ci
- name: Start frontend server
run: |
cd client
npx ng serve --host 0.0.0.0 --port 4200 &
wait-on http://localhost:4200
- name: Run Lighthouse CI
run: |
cd client
npx lhci autorun
- name: Lighthouse Result Artifacts
if: always()
uses: actions/upload-artifact@v3
with:
name: Lighthouse results
path: client/lighthouse-reports
include-hidden-files: true
+62 -58
View File
@@ -5,27 +5,31 @@ on:
branches:
- main
env:
NODE_VERSION: '24.5.0'
jobs:
Build-production-and-ng-test:
runs-on: ubuntu-latest
env:
CHROME_BIN: /usr/bin/google-chrome
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.14.0
node-version: ${{ env.NODE_VERSION }}
- name: Write .npmrc file
run: |
touch client/.npmrc
echo '${{ secrets.NPMRC}}' > client/.npmrc
touch client/.npmrc
echo '${{ secrets.NPMRC}}' > client/.npmrc
- name: Install Chrome for Angular tests
run: |
apt-get update
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
apt install -y ./google-chrome*.deb;
export CHROME_BIN=/usr/bin/google-chrome
apt install -y ./google-chrome*.deb
- name: Write cypress credentials
run: echo "$CYPRESS_CREDS" > ./client/cypress.env.json
@@ -41,11 +45,11 @@ jobs:
npm ci
- name: Check audit
# Audit should fail and stop the CI if critical vulnerability found
# Audit should fail and stop the CI if critical vulnerability found
run: |
npm audit --audit-level=critical --omit=dev
npm audit --omit=dev
cd ./sas
npm audit --audit-level=critical --omit=dev
npm audit --omit=dev
cd ../client
npm audit --audit-level=critical --omit=dev
@@ -63,25 +67,32 @@ jobs:
Build-and-test-development:
runs-on: ubuntu-latest
needs: Build-production-and-ng-test
env:
CHROME_BIN: /usr/bin/google-chrome
# Pin OS locale
LANG: en_GB.UTF-8
LC_ALL: en_GB.UTF-8
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.14.0
node-version: ${{ env.NODE_VERSION }}
- name: Write .npmrc file
run: |
touch client/.npmrc
echo '${{ secrets.NPMRC}}' > client/.npmrc
touch client/.npmrc
echo '${{ secrets.NPMRC}}' > client/.npmrc
- run: apt-get update
- run: wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
- run: apt install -y ./google-chrome*.deb;
- run: export CHROME_BIN=/usr/bin/google-chrome
- run: apt-get update -y
- run: apt-get -y install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb
- run: apt -y install jq
- name: Install system dependencies
run: |
apt-get update
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
apt install -y ./google-chrome*.deb
apt-get -y install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2t64 libxtst6 xauth xvfb jq zip locales
# Generate the en_GB.UTF-8 locale referenced by LANG/LC_ALL
locale-gen en_GB.UTF-8
update-locale LANG=en_GB.UTF-8 LC_ALL=en_GB.UTF-8
- name: Write cypress credentials
run: echo "$CYPRESS_CREDS" > ./client/cypress.env.json
@@ -96,17 +107,18 @@ jobs:
echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg
npm ci
# Install pm2 and prepare SASJS server
- run: npm i -g pm2
- run: curl -L https://github.com/sasjs/server/releases/latest/download/linux.zip > linux.zip
- run: unzip linux.zip
- run: touch .env
- run: echo RUN_TIMES=js >> .env
- run: echo NODE_PATH=node >> .env
- run: echo CORS=enable >> .env
- run: echo WHITELIST=http://localhost:4200 >> .env
- run: cat .env
- run: pm2 start api-linux --wait-ready
- name: Setup and start SASjs server
run: |
npm i -g pm2
curl -L https://github.com/sasjs/server/releases/latest/download/linux.zip > linux.zip
unzip linux.zip
touch .env
echo RUN_TIMES=js >> .env
echo NODE_PATH=node >> .env
echo CORS=enable >> .env
echo WHITELIST=http://localhost:4200 >> .env
cat .env
pm2 start api-linux --wait-ready
- name: Deploy mocked services
run: |
@@ -116,11 +128,6 @@ jobs:
sasjs cbd -t server-ci
# sasjs request services/admin/makedata -t server-ci -d ./deploy/makeData4GL.json -c ./deploy/requestConfig.json -o ./output.json
- name: Install ZIP
run: |
apt-get update
apt-get install zip
- name: Prepare and run frontend and cypress
run: |
cd ./client
@@ -136,11 +143,12 @@ jobs:
replace-in-files --regex='"hosturl".*' --replacement='hosturl:"http://localhost:4200",' ./cypress.config.ts
cat ./cypress.config.ts
# Start frontend and run cypress
npm start & npx wait-on http://localhost:4200 && npx cypress run --browser chrome --spec "cypress/e2e/liveness.cy.ts,cypress/e2e/editor.cy.ts,cypress/e2e/excel-multi-load.cy.ts,cypress/e2e/excel.cy.ts,cypress/e2e/csv.cy.ts,cypress/e2e/filtering.cy.ts,cypress/e2e/licensing.cy.ts"
npx ng serve --host 0.0.0.0 --port 4200 & npx wait-on http://localhost:4200 && npx cypress run --browser chrome --spec "cypress/e2e/csv-limited.cy.ts,cypress/e2e/liveness.cy.ts,cypress/e2e/editor.cy.ts,cypress/e2e/excel-multi-load.cy.ts,cypress/e2e/excel.cy.ts,cypress/e2e/csv.cy.ts,cypress/e2e/filtering.cy.ts,cypress/e2e/licensing.cy.ts"
- name: Zip Cypress videos
if: always()
run: |
mkdir -p ./client/cypress/videos
zip -r cypress-videos ./client/cypress/videos
- name: Add cypress videos artifacts
@@ -155,10 +163,10 @@ jobs:
needs: [Build-production-and-ng-test, Build-and-test-development]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.14.0
node-version: ${{ env.NODE_VERSION }}
- name: Write .npmrc file
run: |
@@ -168,17 +176,11 @@ jobs:
env:
NPMRC: ${{ secrets.NPMRC}}
- name: Install packages
- name: Install system packages
run: |
apt-get update
apt-get install zip -y
# sasjs cli is used to compile & build the SAS services
apt-get install -y zip jq doxygen
npm i -g @sasjs/cli
# jq is used to parse the release JSON
apt-get install jq -y
# doxygen is used for the SASJS docs
apt-get update
apt-get install doxygen -y
- name: Frontend Preliminary Build
description: We want to prevent creating empty release if frontend fails
@@ -228,6 +230,8 @@ jobs:
cp sasjs/utils/favicon.ico ../client/dist/favicon.ico
sasjs c -t server
rm -rf sasjsbuild/tests
server_apploc="/Public/app/dc"
sed -i "s|apploc=\"[^\"]*\"|apploc=\"${server_apploc}\"|g" sasjsbuild/services/web/index.html
sasjs b -t server
cp sasjsbuild/server.json.zip ./sasjs_server.json.zip
@@ -237,20 +241,20 @@ jobs:
cd sas
sasjs c -t viya
rm -rf sasjsbuild/tests
sed -i -e 's/servertype="SASJS"/servertype="SASVIYA"/g' sasjsbuild/services/clickme.html
sasjs b -t viya
cp sasjsbuild/viya.sas ./demostream_viya.sas
# compile Viya Full deploy (without web)
rm -rf sasjsbuild/services/web
rm sasjsbuild/services/clickme.html
sed -i -e 's/servertype="SASJS"/servertype="SASVIYA"/g' sasjsbuild/services/DC.html
sasjs b -t viya
cp sasjsbuild/viya.sas ./viya.sas
cp sasjsbuild/viya.json ./viya.json
# compile Viya Full deploy (without web)
rm -rf sasjsbuild/services/web
rm sasjsbuild/services/DC.html
sasjs b -t viya
cp sasjsbuild/viya.sas ./viya_noweb.sas
cp sasjsbuild/viya.json ./viya_noweb.json
- name: Zip Frontend (including viya.json for full viya deploy)
run: |
cd sas
cp sasjsbuild/viya.json ../client/dist
cp sasjsbuild/viya.json ../client/dist/viya.json
cd ..
zip -r frontend.zip ./client/dist
@@ -277,8 +281,8 @@ jobs:
URL="https://git.datacontroller.io/api/v1/repos/dc/dc/releases/$RELEASE_ID/assets?access_token=${{ secrets.RELEASE_TOKEN }}"
curl -k $URL -F attachment=@frontend.zip
curl -k $URL -F attachment=@sas/demostream_sas9.sas
curl -k $URL -F attachment=@sas/demostream_viya.sas
curl -k $URL -F attachment=@sas/viya.sas
curl -k $URL -F attachment=@sas/sasjs_server.json.zip
curl -k $URL -F attachment=@sas/sas9.sas
curl -k $URL -F attachment=@sas/viya.sas
curl -k $URL -F attachment=@sas/viya.json
curl -k $URL -F attachment=@sas/viya_noweb.sas
curl -k $URL -F attachment=@sas/viya_noweb.json
+2
View File
@@ -14,6 +14,7 @@ client/documentation
client/**/sheet-crypto.tgz
client/.nx
client/libraries/sheet-crypto.tgz
client/lighthouse-reports
cypress.env.json
sasjsbuild
sasjsresults
@@ -21,3 +22,4 @@ sasjsresults
.sasjsrc
client/.npmrc
*~
.lighthouseci
+3
View File
@@ -1 +1,4 @@
legacy-peer-deps=true
ignore-scripts=true
save-exact=true
fund=false
+409
View File
@@ -1,3 +1,412 @@
## [7.8.2](https://git.datacontroller.io/dc/dc/compare/v7.8.1...v7.8.2) (2026-05-20)
### Bug Fixes
* bumping ws package ([2382a55](https://git.datacontroller.io/dc/dc/commit/2382a559a5ac32b0f815776a90207650d5809ba6))
* enabling version restore for non admin users ([5d889d8](https://git.datacontroller.io/dc/dc/commit/5d889d824cc2f8e4ea089cbb578453125dc4ba6c))
## [7.8.1](https://git.datacontroller.io/dc/dc/compare/v7.8.0...v7.8.1) (2026-05-15)
### Bug Fixes
* **sasjs:** enable runAsTask ([f1a26e1](https://git.datacontroller.io/dc/dc/commit/f1a26e132eba7fa2ac64754940b52ea46c6619b3))
# [7.8.0](https://git.datacontroller.io/dc/dc/compare/v7.7.3...v7.8.0) (2026-05-15)
### Bug Fixes
* enabling DSN=*ALL* in MPE_SECURITY ([7d94cb2](https://git.datacontroller.io/dc/dc/commit/7d94cb2ae4a3f6c1fa1011ae0fced7083a2f2793))
* providing default values for RULE_ACTIVE on MPE_VALIDATIONS ([f031b4e](https://git.datacontroller.io/dc/dc/commit/f031b4eb8925397e60dcc739a721cfbbb6da8dff))
* switch away from api usage for CASLIB metadata ([ce921a0](https://git.datacontroller.io/dc/dc/commit/ce921a032a8970b8078a463a41da884e1fa71bc3))
* use correct debug param for runAsTask ([bb80476](https://git.datacontroller.io/dc/dc/commit/bb8047676749814d3b86eea666726dbe4bf5f270))
### Features
* add runAsTask config attribute parser ([1635bc9](https://git.datacontroller.io/dc/dc/commit/1635bc9c451bc221f386241007f594096f114b4f))
* enabling *ALL* option by default in MPE_SECURITY (DSN col) ([93d4ab6](https://git.datacontroller.io/dc/dc/commit/93d4ab65acce7b5b35e448146f9893964ad2cca3))
## [7.7.3](https://git.datacontroller.io/dc/dc/compare/v7.7.2...v7.7.3) (2026-05-12)
### Bug Fixes
* move cas session assign to settings.sas and abort when lib is unassigned ([65f0b97](https://git.datacontroller.io/dc/dc/commit/65f0b979a401277b3e070d409659ae3fae2ff8c0))
## [7.7.2](https://git.datacontroller.io/dc/dc/compare/v7.7.1...v7.7.2) (2026-05-07)
### Bug Fixes
* **client:** bundle Metropolis font locally to satisfy CSP ([9546fcd](https://git.datacontroller.io/dc/dc/commit/9546fcd6312f3e81f746ef6e32ef398810ed434a))
* **client:** clear angular build cache on font strip to avoid stale dist ([503cb08](https://git.datacontroller.io/dc/dc/commit/503cb08b2fa40397434189f9c20eff3358eb7010))
* **client:** postinstall removal of Metropolis [@font-face](https://git.datacontroller.io/font-face) from @clr/ui ([e6397ce](https://git.datacontroller.io/dc/dc/commit/e6397cecc13afe2a9238bdfb2b4b9b81f38d055c))
* **client:** serve text-security-disc font locally ([80ce80e](https://git.datacontroller.io/dc/dc/commit/80ce80ece40012e59c7cd0340b4aa9a9aca46443))
* **editor:** preserve numeric type for SAS num cols with static SOFTSELECT/HARDSELECT ([05a3289](https://git.datacontroller.io/dc/dc/commit/05a328976ea3d1d6ef7559850369aa580f0d067f))
## [7.7.1](https://git.datacontroller.io/dc/dc/compare/v7.7.0...v7.7.1) (2026-05-05)
### Bug Fixes
* **client:** bump adapter ([d26f7d2](https://git.datacontroller.io/dc/dc/commit/d26f7d2511008634124c7d6fde115abb43db9c43))
* **sas:** bump cli ([d60029d](https://git.datacontroller.io/dc/dc/commit/d60029deae0ec21f3b8570461e2a4ca041d58f72))
# [7.7.0](https://git.datacontroller.io/dc/dc/compare/v7.6.0...v7.7.0) (2026-05-04)
### Bug Fixes
* bump adapter to 4.16.6 ([1707f38](https://git.datacontroller.io/dc/dc/commit/1707f3802a97de8c659f1a88c92fc917e8a30615))
* remove data:image/svg+xml CSP violation, use class instead changing style directly ([d66eb5d](https://git.datacontroller.io/dc/dc/commit/d66eb5dfc2dbb01f1e6c0c7d15fc2ad2a39dd829))
* remove WORK, SASUSER and CASUSER as library options. [#224](https://git.datacontroller.io/dc/dc/issues/224) ([ec66631](https://git.datacontroller.io/dc/dc/commit/ec66631a33aabb8ab2f92fe22c15440127085782))
### Features
* auto-save CAS tables [#224](https://git.datacontroller.io/dc/dc/issues/224) ([40d04a5](https://git.datacontroller.io/dc/dc/commit/40d04a53c4c00183116bdbd08397e0f2ffb1f578))
* autoload CAS tables. [#224](https://git.datacontroller.io/dc/dc/issues/224) ([d5ebb01](https://git.datacontroller.io/dc/dc/commit/d5ebb01ce381f5f4ec06de041f3ab9e632c02e43))
# [7.6.0](https://git.datacontroller.io/dc/dc/compare/v7.5.0...v7.6.0) (2026-04-03)
### Bug Fixes
* add label and tooltip for libref download, sanitise input ([52d5803](https://git.datacontroller.io/dc/dc/commit/52d58036a40e25847e900f9b04a77dbcc409c12b))
### Features
* configurable email alerts. Closes [#217](https://git.datacontroller.io/dc/dc/issues/217) ([2ccf0d1](https://git.datacontroller.io/dc/dc/commit/2ccf0d11000129629a0665421135b7530af9892f))
# [7.5.0](https://git.datacontroller.io/dc/dc/compare/v7.4.1...v7.5.0) (2026-04-03)
### Bug Fixes
* add workflow audits, update deps ([66e98a9](https://git.datacontroller.io/dc/dc/commit/66e98a96cbd092e762b94a04660f8e17ca003ceb))
* allow CSV uploads with licence row limit ([5b260e4](https://git.datacontroller.io/dc/dc/commit/5b260e49153dd85bc0023ad94d8a5f57b8ffa6dc)), closes [#213](https://git.datacontroller.io/dc/dc/issues/213)
* bumping cli and pinning versions in .npmrc ([80039f4](https://git.datacontroller.io/dc/dc/commit/80039f4876c8e09dc477678e1eff58329094c9e9))
* guard CSV upload with fileUpload licence flag ([ed40df6](https://git.datacontroller.io/dc/dc/commit/ed40df62953c3055770b5cbf50738f4a48b943cd))
* parse embed param from window.location.hash for hash router compatibility ([0269c24](https://git.datacontroller.io/dc/dc/commit/0269c2421db245f7f5405678605cb4d4587e2a67))
* quote CSV char values. Closes [#215](https://git.datacontroller.io/dc/dc/issues/215) ([d9980e8](https://git.datacontroller.io/dc/dc/commit/d9980e866d1a2fe7a731ff279d73accd35003e67))
* resolve outer promise in parseCsvFile for non-WLATIN1 path ([4ee15e1](https://git.datacontroller.io/dc/dc/commit/4ee15e1b6e83f27f279fc345e6998452a8f64d7e))
* use XLSX for CSV row truncation to handle new lines in values ([6d590c0](https://git.datacontroller.io/dc/dc/commit/6d590c050dcd593a73464fae5604f774f016b10d))
### Features
* add embed URL parameter to hide header and back button ([b0dc441](https://git.datacontroller.io/dc/dc/commit/b0dc441d681369e06eee58288dbdbb236f930bdc)), closes [#214](https://git.datacontroller.io/dc/dc/issues/214)
* add target libref input to config download ([a89657b](https://git.datacontroller.io/dc/dc/commit/a89657b0b81b9c531f64c0dda2714b4eb16c4bc9)), closes [#212](https://git.datacontroller.io/dc/dc/issues/212)
* export config service to allow dclib swapping. Closes [#212](https://git.datacontroller.io/dc/dc/issues/212) ([326c26f](https://git.datacontroller.io/dc/dc/commit/326c26fddfa88a0dc4ca79d3bd0c77c4d807f37c))
## [7.4.1](https://git.datacontroller.io/dc/dc/compare/v7.4.0...v7.4.1) (2026-03-12)
### Bug Fixes
* support for SASIOSNF engine (SNOW alias) plus meta assignment ([7694d1b](https://git.datacontroller.io/dc/dc/commit/7694d1b0fb2bd0407c8598147fbae87a00d889a8))
# [7.4.0](https://git.datacontroller.io/dc/dc/compare/v7.3.0...v7.4.0) (2026-02-20)
### Bug Fixes
* cli bump for mf_getscheme support ([a84ba41](https://git.datacontroller.io/dc/dc/commit/a84ba41ea9f0c97ae24f0a572b8cf5ec200f2132))
* missing upcase on SNOW section, plus local sasjs target ([dc20064](https://git.datacontroller.io/dc/dc/commit/dc200646f7df2fd1910841f392c314532aae7581))
### Features
* SAS code changes for snowflake support ([e273e87](https://git.datacontroller.io/dc/dc/commit/e273e870efbf7875db869b760f2c7b1f39d571ae))
# [7.3.0](https://git.datacontroller.io/dc/dc/compare/v7.2.8...v7.3.0) (2026-02-10)
### Bug Fixes
* bump xlsx, add crypto-shim ([8dc18b1](https://git.datacontroller.io/dc/dc/commit/8dc18b155abfc20fd0b043e0d70bbbc17e6b6811))
* correctly applying deletes on viya, also ([46cdeb0](https://git.datacontroller.io/dc/dc/commit/46cdeb0babee6870553a41877cbe85204e7099c4))
* crypto module requirement for sheetjs/crypto package ([505d0af](https://git.datacontroller.io/dc/dc/commit/505d0af2b3b3c1c79c65045dcaffc263e0f8e796))
* disable parsing excel in web worker beacuse it breaks in the stream apps ([280bdee](https://git.datacontroller.io/dc/dc/commit/280bdeeb1b82f00689f46c68a3cde3f2d24bc18f))
* Display all contexts when installing DC on Viya ([d41f88f](https://git.datacontroller.io/dc/dc/commit/d41f88f8bf5bb2c725ee3edba085e8961ab8c727))
* **edit:** use cellValidation keys and hotDataSchema to fill in defaults on add row ([4957548](https://git.datacontroller.io/dc/dc/commit/495754816c0e757b8f8b1c0ad51246dc7b65d957))
* enabling closeouts for UPDATE in CAS tables ([8b8e8ae](https://git.datacontroller.io/dc/dc/commit/8b8e8aec159ff2f50cfa4683bcd7a25aabb75bf8))
* enabling rollback when the table has formatted values ([815d6e9](https://git.datacontroller.io/dc/dc/commit/815d6e97a8e304d79d48cc949ba126e02a318dc1))
* improvements to validations ([6ceb681](https://git.datacontroller.io/dc/dc/commit/6ceb6814633691b6d4ac2cb898cfb75e9d609102))
* remove IE checks and conditions ([ece6bd1](https://git.datacontroller.io/dc/dc/commit/ece6bd1d787d722531334fc4f1396a94cf6d92ec))
* updates to demodata to enable auto CAS promote ([7740d2a](https://git.datacontroller.io/dc/dc/commit/7740d2ac8694295b33b40a30603d8239818896f5))
* upgrade angular core and compiler ([aecd597](https://git.datacontroller.io/dc/dc/commit/aecd5976875a7c01189248c5f5aa3478b28c1ab2))
* using fcopy instead of binary copy for file upload, for Viya 2026 compatibility ([716ee6e](https://git.datacontroller.io/dc/dc/commit/716ee6eba0a28f4f0a7a96b0719caf15da9b6e78))
* **viewer:** search causing blank Handsontable ([338c7a2](https://git.datacontroller.io/dc/dc/commit/338c7a2e418c47e34331bd04718cd816f978837c)), closes [#206](https://git.datacontroller.io/dc/dc/issues/206)
### Features
* adding demo data job ([8c2aeac](https://git.datacontroller.io/dc/dc/commit/8c2aeacc85da5c106c356709cefcb412ed0a71db))
* **dq rules:** notnull validation when invalid cell, will auto populate a default value ([96f2518](https://git.datacontroller.io/dc/dc/commit/96f2518af9e547956be5862a1322d9ab8e07369b))
## [7.2.8](https://git.datacontroller.io/dc/dc/compare/v7.2.7...v7.2.8) (2026-02-06)
### Bug Fixes
* bump adapter version ([f4c8699](https://git.datacontroller.io/dc/dc/commit/f4c8699aaf0b1e01b447296978a4f6dedc8903f9))
## [7.2.7](https://git.datacontroller.io/dc/dc/compare/v7.2.6...v7.2.7) (2026-02-05)
### Bug Fixes
* dclib not found error in getchangeinfo job ([86791db](https://git.datacontroller.io/dc/dc/commit/86791dbaca39034a19bf8f34efbddf898c57f2f7))
## [7.2.6](https://git.datacontroller.io/dc/dc/compare/v7.2.5...v7.2.6) (2026-01-05)
### Bug Fixes
* **deps:** update angular and moment ([8c5b357](https://git.datacontroller.io/dc/dc/commit/8c5b357dd286db331a6dcdeb3fd499fe3b634288))
## [7.2.5](https://git.datacontroller.io/dc/dc/compare/v7.2.4...v7.2.5) (2025-12-09)
### Bug Fixes
* (build) rebuilt package-lock files ([bfbfd55](https://git.datacontroller.io/dc/dc/commit/bfbfd55fe7e2dff3ce707763a2c7939ff365318b))
* (deps) bump @sasjs/cli and @sasjs/core ([d7c7302](https://git.datacontroller.io/dc/dc/commit/d7c7302c12ac60f355ab9b3b1b461fcf7d0719b8))
* (deps) bumped @sasjs/core, @sasjs/cli, @sasjs/utils and @sasjs/adapter ([af1657e](https://git.datacontroller.io/dc/dc/commit/af1657e226a4efd22cc87401a3850c4a665c2680))
* configurable audit table on restore check ([26ce95f](https://git.datacontroller.io/dc/dc/commit/26ce95f7c1d2260f81c240cd6b058db154d997e4)), closes [#193](https://git.datacontroller.io/dc/dc/issues/193)
* improved testing ([fb3c49a](https://git.datacontroller.io/dc/dc/commit/fb3c49aa8bfdc6acf2ae3034b885010dcdce32a6))
* output values to intended macro variables ([43ae73c](https://git.datacontroller.io/dc/dc/commit/43ae73c5f3ad919394201f54984b61bb2a52fcfe))
## [7.2.4](https://git.datacontroller.io/dc/dc/compare/v7.2.3...v7.2.4) (2025-10-14)
### Bug Fixes
* ensure reload after applying licence key ([cb1978b](https://git.datacontroller.io/dc/dc/commit/cb1978bcaf23b0bf45b5d3b78b9707fd4e48a5f4))
* snyk report security patches ([387f512](https://git.datacontroller.io/dc/dc/commit/387f5122f1ea6dff55d23c9223f17737283a94d3))
## [7.2.3](https://git.datacontroller.io/dc/dc/compare/v7.2.2...v7.2.3) (2025-10-02)
### Bug Fixes
* opening second table in viewer throws an error ([6c6b1cb](https://git.datacontroller.io/dc/dc/commit/6c6b1cbf460e5291ec746af017e764b894fff8d5))
## [7.2.2](https://git.datacontroller.io/dc/dc/compare/v7.2.1...v7.2.2) (2025-09-23)
### Bug Fixes
* jsrsasign, @sasjs/cli bump ([365f129](https://git.datacontroller.io/dc/dc/commit/365f12996db3ef50a4f4f099d5af15696c43bb42))
## [7.2.1](https://git.datacontroller.io/dc/dc/compare/v7.2.0...v7.2.1) (2025-08-08)
### Bug Fixes
* removing localhost from index.html ([225e693](https://git.datacontroller.io/dc/dc/commit/225e693d1fd4381f2b8ce42fecb508f0a9e9dad8))
# [7.2.0](https://git.datacontroller.io/dc/dc/compare/v7.1.1...v7.2.0) (2025-08-08)
### Bug Fixes
* **ci:** cypress dependency package not available anymore ([26cdd73](https://git.datacontroller.io/dc/dc/commit/26cdd733315ef8babe9498ce93f6eb29c587dabd))
* **hot v16 migration:** multi dataset fixed issues, and cypress tests adapted ([712b384](https://git.datacontroller.io/dc/dc/commit/712b3848480a8769d149e00b0d2de91396022b66))
* obsolete cypress deps ([2ba4b53](https://git.datacontroller.io/dc/dc/commit/2ba4b5383e23bff8dfeb82b0ef473e5871c94709))
* remaining hot migrations - handsontable/angular-wrapper ([b419cd5](https://git.datacontroller.io/dc/dc/commit/b419cd507837e846e9dfcc6b729254d56cc196e6))
### Features
* lighthouse accessibility check pipeline ([670ec2c](https://git.datacontroller.io/dc/dc/commit/670ec2c71cb2d24e9d79e297a8cbc6136aa315c8))
## [7.1.1](https://git.datacontroller.io/dc/dc/compare/v7.1.0...v7.1.1) (2025-07-24)
### Bug Fixes
* **viewboxes:** hot v16 fails to load because of relative height `100%` ([672dd6d](https://git.datacontroller.io/dc/dc/commit/672dd6d4f1fda27e3706dd7caa42b45922319497))
# [7.1.0](https://git.datacontroller.io/dc/dc/compare/v7.0.3...v7.1.0) (2025-07-23)
### Bug Fixes
* adapter bump ([b495c41](https://git.datacontroller.io/dc/dc/commit/b495c41626c85b7c4141d9361e4d3a826efd6c05))
* bumping CLI to 4.12.10 ([a08a717](https://git.datacontroller.io/dc/dc/commit/a08a717ca8d49e8a7d63f3fd91c6a7d42a1d6d8b))
* bumping sasjs/core and sasjs/cli ([63e9af4](https://git.datacontroller.io/dc/dc/commit/63e9af402ed65f6be4426e76ee1376a40e6ed097))
### Features
* improving accessibility score up to 100, hot update to v16.0.1 ([71c308d](https://git.datacontroller.io/dc/dc/commit/71c308d052400ecedc03f8020a5a69471ac6b116))
## [7.0.3](https://git.datacontroller.io/dc/dc/compare/v7.0.2...v7.0.3) (2025-06-26)
### Bug Fixes
* makedata vars ([e7cb471](https://git.datacontroller.io/dc/dc/commit/e7cb471c0b60058b03fe8cbed5e3e2e70dd72e26))
* viya deploy makedata missing params ([7a82316](https://git.datacontroller.io/dc/dc/commit/7a8231615cb56710351fae5868e8fdeed54d180c))
## [7.0.2](https://git.datacontroller.io/dc/dc/compare/v7.0.1...v7.0.2) (2025-06-21)
### Bug Fixes
* **viya deploy:** run makedata in new window to ensure logs are available for the user ([0b4042a](https://git.datacontroller.io/dc/dc/commit/0b4042af6011fdc65cfaaa5d4b1d8f48cd67f3b3))
## [7.0.1](https://git.datacontroller.io/dc/dc/compare/v7.0.0...v7.0.1) (2025-06-11)
### Bug Fixes
* refresh process ([4ecd186](https://git.datacontroller.io/dc/dc/commit/4ecd186e5cb22dd436f2d7f1200956f4e3f27425))
# [7.0.0](https://git.datacontroller.io/dc/dc/compare/v6.16.2...v7.0.0) (2025-06-11)
### Bug Fixes
* bumping adapter to re-enable JES API method ([e874143](https://git.datacontroller.io/dc/dc/commit/e874143a95d0ac2e56c0793e04b979c27f96d74b))
* commit git hooks checking lint ([69f687a](https://git.datacontroller.io/dc/dc/commit/69f687a85f1cc562346b6167813d617cb9bd3404))
* ensuring apploc is not case sensitive. Closes [#171](https://git.datacontroller.io/dc/dc/issues/171) ([24545f2](https://git.datacontroller.io/dc/dc/commit/24545f2acdd5bd73cbe062526f2bd043269cc6a3))
* export unregistered formats ([f6d7d6f](https://git.datacontroller.io/dc/dc/commit/f6d7d6f90c978ac8c071471dfb67a60834424de5)), closes [#158](https://git.datacontroller.io/dc/dc/issues/158)
* reload startupservice after user approves the MPE_TABLES page ([e5f8e50](https://git.datacontroller.io/dc/dc/commit/e5f8e500c125ee233c6f7af5ad0077c0ed6abfcb))
* showing catalog_cnt in libinfo ([e44a25d](https://git.datacontroller.io/dc/dc/commit/e44a25dcc39ba4b9714257c60da84c2dfa613a85)), closes [#160](https://git.datacontroller.io/dc/dc/issues/160)
### Features
* adding 4 new tables for catalogs ([e4dbab8](https://git.datacontroller.io/dc/dc/commit/e4dbab8b1654b24e610e4b0603d1cf2b02a451e2))
* capturing catalog specific information, closes [#159](https://git.datacontroller.io/dc/dc/issues/159) ([b4c586a](https://git.datacontroller.io/dc/dc/commit/b4c586a859929e0122cd46449e43d4ca597b8b2b))
* viewer added catalog_cnt ([2aa19d1](https://git.datacontroller.io/dc/dc/commit/2aa19d1dca747f41274a032cde78d8ba73d66224))
### BREAKING CHANGES
* Introduction of 4 new tables for capturing information related to catalogs and their objects. Migration script prepared and available in the DB folder (usual place)
## [6.16.2](https://git.datacontroller.io/dc/dc/compare/v6.16.1...v6.16.2) (2025-06-06)
### Bug Fixes
* streaming viya deploy `isStreaming` function stability fix ([4830c6d](https://git.datacontroller.io/dc/dc/commit/4830c6d2191cb47abcc7919bc1d49e55595e6121))
## [6.16.1](https://git.datacontroller.io/dc/dc/compare/v6.16.0...v6.16.1) (2025-06-06)
### Bug Fixes
* viya deploy updating index html based on URL ([86134f4](https://git.datacontroller.io/dc/dc/commit/86134f478ae0b9426e01bfcc9ca4ee597ca733f7))
* viya streamed app deploy page flow fix ([89ab296](https://git.datacontroller.io/dc/dc/commit/89ab2961513b245eeea48d1867c6496d3261761e))
# [6.16.0](https://git.datacontroller.io/dc/dc/compare/v6.15.2...v6.16.0) (2025-06-05)
### Bug Fixes
* adapter bump ([ca7caa2](https://git.datacontroller.io/dc/dc/commit/ca7caa25b6eea1bd4579fb8b67ec9b211a893079))
* automatic viya deploy timing issue ([037a97b](https://git.datacontroller.io/dc/dc/commit/037a97b6ffa27b40891531ae6812ebe5b5e71e34))
* bump core to ensure ff works on viya streaming deploy ([cbd69df](https://git.datacontroller.io/dc/dc/commit/cbd69df708edf3a8446115ca7315fac3557dcf97)), closes [#156](https://git.datacontroller.io/dc/dc/issues/156)
* viya deploy load data timing ([abdbb67](https://git.datacontroller.io/dc/dc/commit/abdbb674713796e5308eb4272197a5c253868a85))
### Features
* viya deploy, update the index.html contextname ([7223955](https://git.datacontroller.io/dc/dc/commit/72239558af2ee50cdfc71b7e185e6661ab568ba1))
## [6.15.2](https://git.datacontroller.io/dc/dc/compare/v6.15.1...v6.15.2) (2025-06-04)
### Bug Fixes
* pipeline updates for DC.html ([624a7a8](https://git.datacontroller.io/dc/dc/commit/624a7a8f37f0265cf576da310ac330c75aa417cf))
## [6.15.1](https://git.datacontroller.io/dc/dc/compare/v6.15.0...v6.15.1) (2025-06-04)
### Bug Fixes
* updating pipeline to default to streaming on viya ([4b55894](https://git.datacontroller.io/dc/dc/commit/4b558948d997f456ff25a12a58827fe0d2075493))
# [6.15.0](https://git.datacontroller.io/dc/dc/compare/v6.14.10...v6.15.0) (2025-06-04)
### Bug Fixes
* makedata with context name ([da4d0b2](https://git.datacontroller.io/dc/dc/commit/da4d0b28c7109afd6f96455e1e0e80a40d25a942))
### Features
* viya deploy context ([6c96ef7](https://git.datacontroller.io/dc/dc/commit/6c96ef7fb0a55754a84ff0a8bbab838b78c1acaf))
## [6.14.10](https://git.datacontroller.io/dc/dc/compare/v6.14.9...v6.14.10) (2025-06-02)
### Bug Fixes
* bump core ([0e8503e](https://git.datacontroller.io/dc/dc/commit/0e8503ed2bb22a0fc3924ac929e7f19626772e0a))
* default to home directory for SAS Drive in Viya ([9682b54](https://git.datacontroller.io/dc/dc/commit/9682b548e6106d99d97dcc023a35d93addfd5170))
## [6.14.9](https://git.datacontroller.io/dc/dc/compare/v6.14.8...v6.14.9) (2025-06-02)
### Bug Fixes
* default DC path for viya ([f3125ff](https://git.datacontroller.io/dc/dc/commit/f3125ff4641e47e33cb203228f5b1014ea3343bc))
## [6.14.8](https://git.datacontroller.io/dc/dc/compare/v6.14.7...v6.14.8) (2025-05-28)
### Bug Fixes
* CSP issues, clarity local library build, fixed some style issues ([841201a](https://git.datacontroller.io/dc/dc/commit/841201adab582149b1cca3a42e75f7cac75167f9))
* deploy page, makedata error handling, added local build of clarity, to address clr-stack-view CSP issues (inline styles) ([7b5e7ae](https://git.datacontroller.io/dc/dc/commit/7b5e7ae18414152f9b9d8f2d94fc94de43152003))
* improved deploy flow for Viya ([9604661](https://git.datacontroller.io/dc/dc/commit/9604661f3b76111387bc9474cc26348d73ab112e))
* requests modal causing VIYA CSP errors ([1dc6934](https://git.datacontroller.io/dc/dc/commit/1dc69341cadb837e1f11624d5cf35788bbb98d96))
* sas viya service init timing issue ([9de04e9](https://git.datacontroller.io/dc/dc/commit/9de04e9a0ce016e1a9fb8b19c656077079ddcf2f))
* scss of components transferred to the global styles.scss so we do not cause CSP (inline styles) issues when streaming to Viya ([6c171a6](https://git.datacontroller.io/dc/dc/commit/6c171a6394aba8104fe0f50aa8a4e6b9fa8023a2))
* viya deploy page improved flow ([4bd2154](https://git.datacontroller.io/dc/dc/commit/4bd215491f8cdc68f78bade68e7cb98e07edc81e))
## [6.14.7](https://git.datacontroller.io/dc/dc/compare/v6.14.6...v6.14.7) (2025-05-08)
### Bug Fixes
* updated hot, clarity and improved accessibility score. ([2844c70](https://git.datacontroller.io/dc/dc/commit/2844c70f9507036216b8b621900c2bb9010c1d34))
## [6.14.6](https://git.datacontroller.io/dc/dc/compare/v6.14.5...v6.14.6) (2025-04-03)
### Bug Fixes
* history table modal links styling ([c63fcdd](https://git.datacontroller.io/dc/dc/commit/c63fcdd465950ada439d7d69622a3886e8f3a783))
## [6.14.5](https://git.datacontroller.io/dc/dc/compare/v6.14.4...v6.14.5) (2025-03-24)
### Bug Fixes
* improving accessibility lighthouse score ([7f3577c](https://git.datacontroller.io/dc/dc/commit/7f3577c3ef9f44e55a58bc64fbf89a3a64006dd4))
* prevent errors when using sqlrc in a DI job in a HOOK ([d1f0879](https://git.datacontroller.io/dc/dc/commit/d1f0879f0acf7e816c80f7635fd02f4f284214ed))
* user profile style fix, new select library and table icons ([69f8830](https://git.datacontroller.io/dc/dc/commit/69f883034fabbed31aa5d832e20561c4ae3042db))
## [6.14.4](https://git.datacontroller.io/dc/dc/compare/v6.14.3...v6.14.4) (2025-03-18)
### Bug Fixes
* removing cli dependency warnings2 ([43c0f73](https://git.datacontroller.io/dc/dc/commit/43c0f73c2189ff762986a964caae6b0b108164fc))
## [6.14.3](https://git.datacontroller.io/dc/dc/compare/v6.14.2...v6.14.3) (2025-03-15)
+34 -2
View File
@@ -23,10 +23,42 @@ _Problems with the above include:_
Data Controller for SAS® solves all these issues in a simple-to-install, user-friendly, secure, documented, battle-tested web application. Available on Viya, SAS 9 EBI, and [SASjs Server](https://server.sasjs.io).
For more information:
An individual Viya deploy can be done in just 2 lines of #SAS code!
```sas
filename dc url "https://git.datacontroller.io/dc/dc/releases/download/latest/viya.sas";
%inc dc;
```
For a multi-user deploy, using a shared system account, please see [deploy docs](https://docs.datacontroller.io/deploy-viya/).
For further information:
* Main site: https://datacontroller.io
* Docs: https://docs.datacontroller.io
* Code: https://code.datacontroller.io
For support, contact support@4gl.io or reach out on [Matrix](https://matrix.to/#/#dc:4gl.io)!
For support, contact support@4gl.io or reach out on [Matrix](https://matrix.to/#/#dc:4gl.io)!
## Development
### Lighthouse CI
This project includes automated Lighthouse performance and accessibility checks that run on pull requests. The checks ensure:
- **Accessibility Score**: Minimum 1.0 (100%) median score across all tested pages
The Lighthouse CI workflow:
1. Sets up the development environment with SASjs server and mocked services
2. Builds and serves the Angular frontend
3. Installs Chrome and runs `lhci autorun` (Lighthouse CI) against key pages
4. Uploads results as artifacts for review
To run Lighthouse checks locally:
```bash
cd client
npm install
npm run lighthouse
```
Configuration is in `client/lighthouserc.js` (URL list, `desktop` preset, Chrome flags, assertions).
+18 -32
View File
@@ -25,6 +25,7 @@
"options": {
"allowedCommonJsDependencies": [
"handsontable",
"exceljs",
"core-js",
"pikaday",
"querystring",
@@ -41,6 +42,8 @@
"zone.js",
"text-encoding",
"crypto-js/md5",
"crypto-js/sha1",
"crypto-js/sha512",
"buffer",
"numbro",
"@clr/icons",
@@ -51,26 +54,23 @@
"base64-arraybuffer",
"@handsontable/formulajs"
],
"polyfills": [
"src/polyfills.ts",
"zone.js"
],
"polyfills": ["src/polyfills.ts", "zone.js"],
"outputPath": "dist",
"resourcesOutputPath": "images",
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/images"
{
"glob": "**/*",
"input": "src/images",
"output": "images",
"ignore": ["spinner.svg", "caret.svg"]
}
],
"styles": [
"src/styles.scss"
],
"scripts": [
"node_modules/marked/marked.min.js"
],
"webWorkerTsConfig": "tsconfig.worker.json"
"styles": ["src/styles.scss"],
"scripts": ["node_modules/marked/marked.min.js"],
"webWorkerTsConfig": "tsconfig.worker.json",
"main": "src/main.ts"
},
"configurations": {
"production": {
@@ -103,9 +103,7 @@
}
},
"development": {
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
"sourceMap": true,
"optimization": false,
"namedChunks": true
@@ -134,20 +132,11 @@
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": [
"src/polyfills.ts",
"zone.js",
"zone.js/testing"
],
"polyfills": ["src/polyfills.ts", "zone.js", "zone.js/testing"],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.scss"],
"scripts": [],
"karmaConfig": "karma.conf.js",
"webWorkerTsConfig": "tsconfig.worker.json"
@@ -156,10 +145,7 @@
"lint": {
"builder": "@angular-eslint/builder:lint",
"options": {
"lintFilePatterns": [
"src/**/*.ts",
"src/**/*.html"
]
"lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
}
}
}
+26 -18
View File
@@ -1,13 +1,13 @@
import { defineConfig } from "cypress";
import { defineConfig } from 'cypress'
export default defineConfig({
reporter: "mochawesome",
reporter: 'mochawesome',
reporterOptions: {
reportDir: "cypress/results",
reportDir: 'cypress/results',
overwrite: false,
html: true,
json: false,
json: false
},
viewportHeight: 900,
viewportWidth: 1600,
@@ -16,24 +16,32 @@ export default defineConfig({
defaultCommandTimeout: 30000,
env: {
hosturl: "http://localhost:4200",
appLocation: "",
site_id_SAS9: "70221618",
site_id_SASVIYA: "70253615",
site_id_SASJS: "123",
serverType: "SASJS",
libraryToOpenIncludes_SASVIYA: "viya",
libraryToOpenIncludes_SAS9: "dc",
libraryToOpenIncludes_SASJS: "dc",
hosturl: 'http://localhost:4200',
appLocation: '',
site_id_SAS9: '70221618',
site_id_SASVIYA: '70253615',
site_id_SASJS: '123',
serverType: 'SASJS',
libraryToOpenIncludes_SASVIYA: 'viya',
libraryToOpenIncludes_SAS9: 'dc',
libraryToOpenIncludes_SASJS: 'dc',
debug: false,
screenshotOnRunFailure: false,
longerCommandTimeout: 50000,
testLicenceUserLimits: false,
testLicenceUserLimits: false
},
e2e: {
video: true,
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
});
// Pin the browser locale so locale-formatted cells (intl-date/time/datetime)
// render deterministically regardless of the runner's system locale.
on('before:browser:launch', (browser, launchOptions) => {
if (browser.family === 'chromium' && browser.name !== 'electron') {
launchOptions.args.push('--lang=en-GB')
}
return launchOptions
})
}
}
})
+95
View File
@@ -0,0 +1,95 @@
const username = Cypress.env('username')
const password = Cypress.env('password')
const hostUrl = Cypress.env('hosturl')
const appLocation = Cypress.env('appLocation')
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
const serverType = Cypress.env('serverType')
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
const fixturePath = 'csvs/'
context('csv file upload restriction (free tier): ', function () {
this.beforeEach(() => {
cy.visit(hostUrl + appLocation)
cy.get('body').then(($body) => {
const usernameInput = $body.find('input.username')[0]
if (usernameInput && !Cypress.dom.isHidden(usernameInput)) {
cy.get('input.username').type(username)
cy.get('input.password').type(password)
cy.get('.login-group button').click()
}
})
cy.get('.app-loading', { timeout: longerCommandTimeout }).should(
'not.exist'
)
// Skip licensing page if presented - continue with free tier
cy.url().then((url) => {
if (url.includes('licensing')) {
cy.get('button').contains('Continue with free tier').click()
}
})
visitPage('home')
})
it('1 | File upload is restricted on free tier', () => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
// Click upload button - should show feature locked modal
cy.get('.buttonBar button:last-child').should('exist').click()
cy.get('.modal-title').should('contain', 'Locked Feature (File Upload)')
})
})
const openTableFromTree = (libNameIncludes: string, tablename: string) => {
cy.get('.app-loading', { timeout: longerCommandTimeout })
.should('not.exist')
.then(() => {
cy.get('.nav-tree clr-tree > clr-tree-node', {
timeout: longerCommandTimeout
}).then((treeNodes: any) => {
let targetLib
for (let node of treeNodes) {
if (node.innerText.toLowerCase().includes(libNameIncludes)) {
targetLib = node
break
}
}
cy.get(targetLib).within(() => {
cy.get('.clr-tree-node-content-container > button').click()
cy.get('.clr-treenode-link').then((innerNodes: any) => {
for (let innerNode of innerNodes) {
if (innerNode.innerText.toLowerCase().includes(tablename)) {
innerNode.click()
break
}
}
})
})
})
})
}
const attachFile = (filename: string, callback?: any) => {
cy.get('.buttonBar button:last-child')
.should('exist')
.click()
.then(() => {
cy.get('input[type="file"]#file-upload')
.attachFile(`/${fixturePath}/${filename}`)
.then(() => {
if (callback) callback()
})
})
}
const visitPage = (url: string) => {
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
}
+20 -21
View File
@@ -32,27 +32,26 @@ context('excel tests: ', function () {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('regular.csv', () => {
cy.get('#approval-btn', { timeout: 60000 })
.should('be.visible')
// .then(() => {
// cy.get('#hotInstance', { timeout: 30000 })
// .find('div.ht_master.handsontable')
// .find('div.wtHolder')
// .find('div.wtHider')
// .find('div.wtSpreader')
// .find('table.htCore')
// .find('tbody')
// .then((data) => {
// let cell: any = data[0].children[0].children[1]
// expect(cell.innerText).to.equal('0')
// cell = data[0].children[0].children[2]
// expect(cell.innerText).to.equal('44')
// cell = data[0].children[0].children[3]
// expect(cell.innerText).to.equal('abc')
// cell = data[0].children[0].children[6]
// expect(cell.innerText).to.equal('Option abc')
// })
// })
cy.get('#approval-btn', { timeout: 60000 }).should('be.visible')
// .then(() => {
// cy.get('#hotInstance', { timeout: 30000 })
// .find('div.ht_master.handsontable')
// .find('div.wtHolder')
// .find('div.wtHider')
// .find('div.wtSpreader')
// .find('table.htCore')
// .find('tbody')
// .then((data) => {
// let cell: any = data[0].children[0].children[1]
// expect(cell.innerText).to.equal('0')
// cell = data[0].children[0].children[2]
// expect(cell.innerText).to.equal('44')
// cell = data[0].children[0].children[3]
// expect(cell.innerText).to.equal('abc')
// cell = data[0].children[0].children[6]
// expect(cell.innerText).to.equal('Option abc')
// })
// })
})
})
+1 -5
View File
@@ -217,11 +217,7 @@ const rejectExcel = (callback?: any) => {
.should('contain', 'Approve')
.then((allButtons: any) => {
for (let approvalButton of allButtons) {
if (
approvalButton.innerText
.toLowerCase()
.includes('approve')
) {
if (approvalButton.innerText.toLowerCase().includes('approve')) {
approvalButton.click()
break
}
+187 -98
View File
@@ -34,93 +34,162 @@ context('excel multi load tests: ', function () {
it('1 | Uploads Excel file with multiple sheets, 3 sheets including data, 2 sheets matched with dataset', (done) => {
attachExcelFile('multi_load_test_2.xlsx', () => {
checkHotUserDatasetTable('hotTableUserDataset', [
[library, mpeXTestTable],
[library, mpeTablesTable]
], () => {
cy.get('#continue-btn').trigger('click').then(() => {
checkIfTreeHasTables([`${library}.${mpeXTestTable}`, `${library}.${mpeTablesTable}`], undefined, (includes: boolean) => {
if (includes) {
// MPE_TABLES sheet does not have data so 1 error image must be shown
hasErrorTables(1, (valid: boolean) => {
if (valid) done()
})
}
})
})
})
checkHotUserDatasetTable(
'hotTableUserDataset',
[
[library, mpeXTestTable],
[library, mpeTablesTable]
],
() => {
cy.get('#continue-btn')
.trigger('click')
.then(() => {
checkIfTreeHasTables(
[`${library}.${mpeXTestTable}`, `${library}.${mpeTablesTable}`],
undefined,
(includes: boolean) => {
if (includes) {
// MPE_TABLES sheet does not have data so 1 error image must be shown
hasErrorTables(1, (valid: boolean) => {
if (valid) done()
})
}
}
)
})
}
)
})
})
it('2 | Uploads Excel file with multiple sheets, 2 sheets matched with dataset, 1 matched sheet does not have data', (done) => {
attachExcelFile('multi_load_test_1.xlsx', () => {
checkHotUserDatasetTable('hotTableUserDataset', [
[library, mpeXTestTable],
[library, mpeTablesTable]
], () => {
cy.get('#continue-btn').trigger('click').then(() => {
checkIfTreeHasTables([`${library}.${mpeXTestTable}`, `${library}.${mpeTablesTable}`], `${library}.${mpeXTestTable}`, (includes: boolean) => {
if (includes) {
cy.get('#hotTable').should('be.visible').then(() => {
checkHotUserDatasetTable('hotTable', [
['No', '1', 'more dummy data'],
['No', '1', 'It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told:'],
['No', '1', 'if you can fill the unforgiving minute']
], () => {
submitTables()
checkHotUserDatasetTable(
'hotTableUserDataset',
[
[library, mpeXTestTable],
[library, mpeTablesTable]
],
() => {
cy.get('#continue-btn')
.trigger('click')
.then(() => {
checkIfTreeHasTables(
[`${library}.${mpeXTestTable}`, `${library}.${mpeTablesTable}`],
`${library}.${mpeXTestTable}`,
(includes: boolean) => {
if (includes) {
cy.get('#hotTable')
.should('be.visible')
.then(() => {
checkHotUserDatasetTable(
'hotTable',
[
['No', '1', 'more dummy data'],
[
'No',
'1',
'It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told:'
],
[
'No',
'1',
'if you can fill the unforgiving minute'
]
],
() => {
submitTables()
hasSuccessSubmits(2, (valid: boolean) => {
if (valid) done()
})
})
})
}
})
})
})
hasSuccessSubmits(2, (valid: boolean) => {
if (valid) done()
})
}
)
})
}
}
)
})
}
)
})
})
it('3 | Uploads Excel file with multiple sheets, 1 sheets has 2 tables', (done) => {
attachExcelFile('multi_load_test_1.xlsx', () => {
checkHotUserDatasetTable('hotTableUserDataset', [
[library, mpeXTestTable],
[library, mpeTablesTable]
], () => {
cy.get('#continue-btn').trigger('click').then(() => {
checkIfTreeHasTables([`${library}.${mpeXTestTable}`, `${library}.${mpeTablesTable}`], `${library}.${mpeXTestTable}`, (includes: boolean) => {
if (includes) {
cy.get('#hotTable').should('be.visible').then(() => {
checkHotUserDatasetTable('hotTable', [
['No', '1', 'more dummy data'],
['No', '1', 'It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told:'],
['No', '1', 'if you can fill the unforgiving minute']
], () => {
clickOnTreeNode('DC996664.MPE_TABLES', () => {
cy.wait(1000).then(() => {
cy.get('#hotTable').should('be.visible').then(() => {
checkHotUserDatasetTable('hotTable', [
['No', 'DC914286', 'MPE_COLUMN_LEVEL_SECURITY'],
['No', 'DC914286', 'MPE_XLMAP_INFO'],
['No', 'DC914286', 'MPE_XLMAP_RULES']
], () => {
submitTables()
checkHotUserDatasetTable(
'hotTableUserDataset',
[
[library, mpeXTestTable],
[library, mpeTablesTable]
],
() => {
cy.get('#continue-btn')
.trigger('click')
.then(() => {
checkIfTreeHasTables(
[`${library}.${mpeXTestTable}`, `${library}.${mpeTablesTable}`],
`${library}.${mpeXTestTable}`,
(includes: boolean) => {
if (includes) {
cy.get('#hotTable')
.should('be.visible')
.then(() => {
checkHotUserDatasetTable(
'hotTable',
[
['No', '1', 'more dummy data'],
[
'No',
'1',
'It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told:'
],
[
'No',
'1',
'if you can fill the unforgiving minute'
]
],
() => {
clickOnTreeNode('DC996664.MPE_TABLES', () => {
cy.wait(1000).then(() => {
cy.get('#hotTable')
.should('be.visible')
.then(() => {
checkHotUserDatasetTable(
'hotTable',
[
[
'No',
'DC914286',
'MPE_COLUMN_LEVEL_SECURITY'
],
['No', 'DC914286', 'MPE_XLMAP_INFO'],
['No', 'DC914286', 'MPE_XLMAP_RULES']
],
() => {
submitTables()
hasSuccessSubmits(2, (valid: boolean) => {
if (valid) done()
})
})
hasSuccessSubmits(
2,
(valid: boolean) => {
if (valid) done()
}
)
}
)
})
})
})
}
)
})
})
})
})
})
}
})
})
})
}
}
)
})
}
)
})
})
@@ -142,25 +211,31 @@ const attachExcelFile = (excelFilename: string, callback?: any) => {
})
}
const checkHotUserDatasetTable = (hotId: string, dataToContain: any[][], callback?: () => void) => {
const checkHotUserDatasetTable = (
hotId: string,
dataToContain: any[][],
callback?: () => void
) => {
cy.get(`#${hotId}`, { timeout: longerCommandTimeout })
.find('div.ht_master.handsontable')
.find('div.wtHolder')
.find('div.wtHider')
.find('div.wtSpreader')
.find('table.htCore')
.find('tbody')
.then((data) => {
cy.wait(2000).then(() => {
for (let rowI = 0; rowI < dataToContain.length; rowI++) {
for (let colI = 0; colI < dataToContain[rowI].length; colI++) {
expect(data[0].children[rowI].children[colI]).to.contain(dataToContain[rowI][colI])
.find('div.ht_master.handsontable')
.find('div.wtHolder')
.find('div.wtHider')
.find('div.wtSpreader')
.find('table.htCore')
.find('tbody')
.then((data) => {
cy.wait(2000).then(() => {
for (let rowI = 0; rowI < dataToContain.length; rowI++) {
for (let colI = 0; colI < dataToContain[rowI].length; colI++) {
expect(data[0].children[rowI].children[colI]).to.contain(
dataToContain[rowI][colI]
)
}
}
}
if (callback) callback()
if (callback) callback()
})
})
})
}
const clickOnTreeNode = (clickOnNode: string, callback?: () => void) => {
@@ -174,7 +249,11 @@ const clickOnTreeNode = (clickOnNode: string, callback?: () => void) => {
})
}
const checkIfTreeHasTables = (tables: string[], clickOnNode?: string, callback?: (includes: boolean) => void) => {
const checkIfTreeHasTables = (
tables: string[],
clickOnNode?: string,
callback?: (includes: boolean) => void
) => {
cy.get('.nav-tree clr-tree > clr-tree-node').then((treeNodes: any) => {
let datasets = tables
let nodesCorrect = true
@@ -207,16 +286,26 @@ const submitTables = () => {
cy.wait(1000)
}
const hasSuccessSubmits = (expectedNoOfSubmits: number, callback: (valid: boolean) => void) => {
cy.get('.nav-tree clr-tree > clr-tree-node cds-icon[status="success"]').should('be.visible').then(($nodes) => {
callback(expectedNoOfSubmits === $nodes.length)
})
const hasSuccessSubmits = (
expectedNoOfSubmits: number,
callback: (valid: boolean) => void
) => {
cy.get('.nav-tree clr-tree > clr-tree-node cds-icon[status="success"]')
.should('be.visible')
.then(($nodes) => {
callback(expectedNoOfSubmits === $nodes.length)
})
}
const hasErrorTables = (expectedNoOfErrors: number, callback: (valid: boolean) => void) => {
cy.get('.nav-tree clr-tree > clr-tree-node cds-icon[status="danger"]').should('be.visible').then(($nodes) => {
callback(expectedNoOfErrors === $nodes.length)
})
const hasErrorTables = (
expectedNoOfErrors: number,
callback: (valid: boolean) => void
) => {
cy.get('.nav-tree clr-tree > clr-tree-node cds-icon[status="danger"]')
.should('be.visible')
.then(($nodes) => {
callback(expectedNoOfErrors === $nodes.length)
})
}
const visitPage = (url: string) => {
+95 -23
View File
@@ -234,7 +234,7 @@ context('excel tests: ', function () {
cy.get('.btn-upload-preview', { timeout: 60000 })
.should('be.visible')
.then(() => {
cy.get('#hotInstance', { timeout: 30000 })
cy.get('#hotTable', { timeout: 30000 })
.find('div.ht_master.handsontable')
.find('div.wtHolder')
.find('div.wtHider')
@@ -283,7 +283,7 @@ context('excel tests: ', function () {
cy.get('.btn-upload-preview', { timeout: 60000 })
.should('be.visible')
.then(() => {
cy.get('#hotInstance', { timeout: 30000 })
cy.get('#hotTable', { timeout: 30000 })
.find('div.ht_master.handsontable')
.find('div.wtHolder')
.find('div.wtHider')
@@ -309,6 +309,83 @@ context('excel tests: ', function () {
})
})
it('22 | Uploads password protected Excel and unlocks with correct password', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
cy.get('.buttonBar button:last-child')
.should('exist')
.click()
.then(() => {
cy.get('input[type="file"]#file-upload')
.attachFile(`/${fixturePath}/regular_excel_password.xlsx`)
.then(() => {
// Wait for password modal to appear
cy.get('#filePasswordInput', { timeout: 10000 })
.should('be.visible')
.type('123123')
// Click Unlock button
cy.get('.btn.btn-success-outline').should('not.be.disabled').click()
// Click away the overlay
cy.get('.modal-footer .btn.btn-primary', { timeout: 5000 }).click()
// Verify file loads successfully
cy.get('.btn-upload-preview', { timeout: 60000 })
.should('be.visible')
.then(() => {
submitExcel()
rejectExcel(done)
})
})
})
})
it('23 | Uploads password protected Excel and handles wrong password', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
cy.get('.buttonBar button:last-child')
.should('exist')
.click()
.then(() => {
cy.get('input[type="file"]#file-upload')
.attachFile(`/${fixturePath}/regular_excel_password.xlsx`)
.then(() => {
// First attempt: Enter wrong password
cy.get('#filePasswordInput', { timeout: 10000 })
.should('be.visible')
.type('wrongpassword')
cy.get('.btn.btn-success-outline').should('not.be.disabled').click()
// Verify error message appears
cy.get('.modal-footer .color-red', { timeout: 10000 })
.should('be.visible')
.should('contain', "Sorry that didn't work, try again.")
// Modal should still be open for retry
cy.get('#filePasswordInput')
.should('be.visible')
.clear()
.type('123123')
// Second attempt: Enter correct password
cy.get('.btn.btn-success-outline').should('not.be.disabled').click()
// Click away the overlay
cy.get('.modal-footer .btn.btn-primary', { timeout: 5000 }).click()
// Verify file loads successfully
cy.get('.btn-upload-preview', { timeout: 60000 })
.should('be.visible')
.then(() => {
submitExcel()
rejectExcel(done)
})
})
})
})
// Large files break Cypress
// it ('? | Uploads Excel with size of 5MB', (done) => {
@@ -399,11 +476,7 @@ const rejectExcel = (callback?: any) => {
.should('contain', 'Approve')
.then((allButtons: any) => {
for (let approvalButton of allButtons) {
if (
approvalButton.innerText
.toLowerCase()
.includes('approve')
) {
if (approvalButton.innerText.toLowerCase().includes('approve')) {
approvalButton.click()
break
}
@@ -432,11 +505,7 @@ const acceptExcel = (callback?: any) => {
.should('contain', 'Approve')
.then((allButtons: any) => {
for (let approvalButton of allButtons) {
if (
approvalButton.innerText
.toLowerCase()
.includes('approve')
) {
if (approvalButton.innerText.toLowerCase().includes('approve')) {
approvalButton.click()
break
}
@@ -455,7 +524,7 @@ const acceptExcel = (callback?: any) => {
}
const checkResultOfFormulaUpload = (callback?: any) => {
cy.get('#hotInstance', { timeout: longerCommandTimeout })
cy.get('#hotTable', { timeout: longerCommandTimeout })
.find('div.ht_master.handsontable')
.find('div.wtHolder')
.find('div.wtHider')
@@ -470,8 +539,10 @@ const checkResultOfFormulaUpload = (callback?: any) => {
}
const checkResultOfXLSUpload = (callback?: any) => {
cy.viewport(1280, 720)
cy.get('#hotInstance', { timeout: 30000 })
// Config-default width — wide enough that the date/datetime/time columns are
// not virtualized away (the old 1280 width hid the trailing time columns).
cy.viewport(1600, 900)
cy.get('#hotTable', { timeout: 30000 })
.find('div.ht_master.handsontable')
.find('div.wtHolder')
.find('div.wtHider')
@@ -488,19 +559,20 @@ const checkResultOfXLSUpload = (callback?: any) => {
expect(cell.innerText).to.equal('▼\nOption 1')
cell = data[0].children[0].children[5]
expect(cell.innerText).to.equal('42')
// Cells render locale-formatted (Cypress pins --lang=en-GB); stored value
// stays raw ISO. date -> dd/mm/yyyy, datetime -> dd/mm/yyyy, HH:mm:ss,
// time -> HH:mm:ss (en-GB 24h, identical to the raw ISO time).
cell = data[0].children[0].children[6]
expect(cell.innerText).to.equal('▼\n1960-02-12')
// When CI detached browser screen is smaller, below cells are not visible so test fails
// Commenting it out now until we figure out workaround
// cell = data[0].children[0].children[7]
// expect(cell.innerText).to.equal('▼\n1960-01-01 00:00:42')
// cell = data[0].children[0].children[8]
// expect(cell.innerText).to.equal('00:00:42')
expect(cell.innerText).to.equal('12/02/1960')
cell = data[0].children[0].children[7]
expect(cell.innerText).to.equal('01/01/1960, 00:00:42')
cell = data[0].children[0].children[8]
expect(cell.innerText).to.equal('00:00:42')
if (callback) callback()
})
cy.get('#hotInstance', { timeout: 30000 })
cy.get('#hotTable', { timeout: 30000 })
.find('div.ht_master.handsontable')
.find('div.wtHolder')
.scrollTo('right')
+8 -7
View File
@@ -16,7 +16,6 @@ context('filtering tests: ', function () {
this.beforeEach(() => {
cy.visit(hostUrl + appLocation, { timeout: longerCommandTimeout })
visitPage('home')
})
@@ -299,14 +298,16 @@ const setFilterWithValue = (
cy.get('.no-values')
.should('not.exist')
.then(() => {
cy.get('.in-values-modal clr-checkbox-wrapper input').then((inputs: any) => {
inputs[0].click()
cy.get('.in-values-modal .modal-footer button').click()
cy.get('.in-values-modal clr-checkbox-wrapper input').then(
(inputs: any) => {
inputs[0].click()
cy.get('.in-values-modal .modal-footer button').click()
cy.get('.modal-footer .btn-success-outline').click()
cy.get('.modal-footer .btn-success-outline').click()
if (callback) callback()
})
if (callback) callback()
}
)
})
})
+1 -8
View File
@@ -23,7 +23,6 @@ interface EditConfigTableCells {
context('licensing tests: ', function () {
this.beforeAll(() => {
cy.loginAndUpdateValidKey()
})
@@ -371,8 +370,6 @@ context('licensing tests: ', function () {
})
})
}
})
const logout = (callback?: any) => {
@@ -697,11 +694,7 @@ const approveTable = (callback?: any) => {
.should('contain', 'Approve')
.then((allButtons: any) => {
for (let approvalButton of allButtons) {
if (
approvalButton.innerText
.toLowerCase()
.includes('approve')
) {
if (approvalButton.innerText.toLowerCase().includes('approve')) {
approvalButton.click()
break
}
+1 -6
View File
@@ -18,7 +18,6 @@ context('liveness tests: ', function () {
this.beforeEach(() => {
cy.visit(hostUrl + appLocation)
visitPage('home')
})
@@ -125,11 +124,7 @@ const rejectExcel = (callback?: any) => {
.should('contain', 'Approve')
.then((allButtons: any) => {
for (let approvalButton of allButtons) {
if (
approvalButton.innerText
.toLowerCase()
.includes('approve')
) {
if (approvalButton.innerText.toLowerCase().includes('approve')) {
approvalButton.click()
break
}
+34 -29
View File
@@ -76,7 +76,8 @@ context('editor tests: ', function () {
cy.get('.viewbox-open').click()
openTableFromViewboxTree(
libraryToOpenIncludes,
viewboxes.map((viewbox) => viewbox.viewbox_table))
viewboxes.map((viewbox) => viewbox.viewbox_table)
)
cy.get('.open-viewbox').then((viewboxNodes: any) => {
let found = 0
@@ -91,32 +92,34 @@ context('editor tests: ', function () {
if (found < viewboxes.length) return
cy.get('.viewboxes-container .viewbox', { withinSubject: null }).then((viewboxNodes: any) => {
for (let viewboxNode of viewboxNodes) {
cy.get(viewboxNode).within(() => {
cy.get('.table-title').then((tableTitle) => {
const title = tableTitle[0].innerText
const viewbox = viewboxes.find((vb) =>
title.toLowerCase().includes(vb.viewbox_table)
)
if (viewbox) {
cy.get('.ht_master.handsontable .htCore thead tr').then(
(viewboxColNodes: any) => {
let allColsHtml = viewboxColNodes[0].innerHTML
for (let col of viewbox?.columns) {
if (!allColsHtml.includes(col)) return
}
done()
}
cy.get('.viewboxes-container .viewbox', { withinSubject: null }).then(
(viewboxNodes: any) => {
for (let viewboxNode of viewboxNodes) {
cy.get(viewboxNode).within(() => {
cy.get('.table-title').then((tableTitle) => {
const title = tableTitle[0].innerText
const viewbox = viewboxes.find((vb) =>
title.toLowerCase().includes(vb.viewbox_table)
)
}
if (viewbox) {
cy.get('.ht_master.handsontable .htCore thead tr').then(
(viewboxColNodes: any) => {
let allColsHtml = viewboxColNodes[0].innerHTML
for (let col of viewbox?.columns) {
if (!allColsHtml.includes(col)) return
}
done()
}
)
}
})
})
})
}
}
})
)
})
})
@@ -395,11 +398,13 @@ context('editor tests: ', function () {
})
const removeAllColumns = () => {
cy.get('.configuration-wrapper clr-icon[shape="trash"]').then(removeNodes => {
for (let removeNode of removeNodes) {
removeNode.click()
cy.get('.configuration-wrapper clr-icon[shape="trash"]').then(
(removeNodes) => {
for (let removeNode of removeNodes) {
removeNode.click()
}
}
})
)
}
const checkColumns = (columns: string[], callback: () => void) => {
@@ -412,7 +417,7 @@ const checkColumns = (columns: string[], callback: () => void) => {
console.log('viewboxColNode', viewboxColNodes)
console.log('columns', columns)
for (let i = 0; i < viewboxColNodes.length; i++) {
const col = columns[i]|| ''
const col = columns[i] || ''
const colNode = viewboxColNodes[i]
if (
+5 -1
View File
@@ -4,7 +4,11 @@ PRIMARY_KEY_FIELD,SOME_CHAR,SOME_DROPDOWN,SOME_NUM,SOME_DATE,SOME_DATETIME,SOME_
2,even more dummy data,Option 3,42,12FEB1960,01JAN1960:00:00:42,0:02:22,3,44
3,"It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told:",Option 2,1613.001,27FEB1961,01JAN1960:00:07:03,0:00:44,3,44
4,if you can fill the unforgiving minute,Option 1,1613.0011235,02AUG1971,29MAY1973:06:12:03,0:06:52,3,44
1010,10 bottles of beer on the wall,Option 1,0.9153696885,04MAR1962,01JAN1960:12:47:55,0:01:40,92,76
1010,"10 bottles of beer
on the wall",Option 1,0.9153696885,04MAR1962,01JAN1960:12:47:55,0:01:40,92,76
1011,11 bottles of beer on the wall,Option 1,0.3531217558,29MAR1960,01JAN1960:03:33:24,0:01:03,80,29
1012,12 bottles of beer on the wall,Option 1,0.6743748717,02AUG1962,01JAN1960:07:25:59,0:00:10,16,98
1013,13 bottles of beer on the wall,Option 1,0.1305445992,11SEP1960,01JAN1960:13:51:32,0:00:35,73,15
1 PRIMARY_KEY_FIELD SOME_CHAR SOME_DROPDOWN SOME_NUM SOME_DATE SOME_DATETIME SOME_TIME SOME_SHORTNUM SOME_BESTNUM
4 2 even more dummy data Option 3 42 12FEB1960 01JAN1960:00:00:42 0:02:22 3 44
5 3 It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: Option 2 1613.001 27FEB1961 01JAN1960:00:07:03 0:00:44 3 44
6 4 if you can fill the unforgiving minute Option 1 1613.0011235 02AUG1971 29MAY1973:06:12:03 0:06:52 3 44
7 1010 10 bottles of beer on the wall 10 bottles of beer on the wall Option 1 0.9153696885 04MAR1962 01JAN1960:12:47:55 0:01:40 92 76
8 1011 11 bottles of beer on the wall Option 1 0.3531217558 29MAR1960 01JAN1960:03:33:24 0:01:03 80 29
9 1012 12 bottles of beer on the wall Option 1 0.6743748717 02AUG1962 01JAN1960:07:25:59 0:00:10 16 98
10 1013 13 bottles of beer on the wall Option 1 0.1305445992 11SEP1960 01JAN1960:13:51:32 0:00:35 73 15
11 1014 14 bottles of beer on the wall Option 1 0.7409067949 26JUL1960 01JAN1960:05:18:10 0:00:41 30 89
12 1011 1015 11 bottles of beer on the wall 15 bottles of beer on the wall Option 1 0.3531217558 0.0869016028 29MAR1960 28FEB1961 01JAN1960:03:33:24 01JAN1960:13:23:45 0:01:03 0:00:44 80 29 3
13 1012 1016 12 bottles of beer on the wall 16 bottles of beer on the wall Option 1 0.6743748717 0.0462121419 02AUG1962 09AUG1962 01JAN1960:07:25:59 01JAN1960:07:42:38 0:00:10 0:01:17 16 62 98 2
14 1013 1017 13 bottles of beer on the wall 17 bottles of beer on the wall Option 1 0.1305445992 0.7501918947 11SEP1960 14MAY1962 01JAN1960:13:51:32 01JAN1960:04:40:20 0:00:35 0:00:15 73 53 15 65
@@ -1,255 +0,0 @@
const username = Cypress.env('username')
const password = Cypress.env('password')
const hostUrl = Cypress.env('hosturl')
const appLocation = Cypress.env('appLocation')
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
const serverType = Cypress.env('serverType')
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
const fixturePath = 'excels_general/'
const downloadsFolder = Cypress.config('downloadsFolder')
import { deleteDownloadsFolder } from '../util/deleteDownloadFolder'
context('download files test: ', function () {
this.beforeAll(() => {
cy.visit(`${hostUrl}/SASLogon/logout`)
cy.loginAndUpdateValidKey()
})
this.beforeEach(() => {
cy.visit(hostUrl + appLocation)
cy.get('input.username').type(username)
cy.get('input.password').type(password)
cy.get('.login-group button').click()
visitPage('home')
})
this.afterEach(() => {
deleteDownloadsFolder()
})
it('1 | downloads audit file', (done) => {
visitPage('approve/toapprove')
cy.get('.app-loading', { timeout: longerCommandTimeout })
.should('not.exist')
.then(() => {
cy.get('.btn.btn-success')
.should('be.visible')
.then((buttons) => {
buttons[0].click()
const id = buttons[0].id
checkForFileDownloaded(id, 'zip', () => done())
})
})
})
it('2 | downloads viewer csv', (done) => {
visitPage('view/data')
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openDownloadModal(() => {
cy.get('select')
.select('CSV')
.then(() => {
cy.get('.btn.btn-sm.btn-success-outline').then((button) => {
button.trigger('click')
const id = button[0].id
checkForFileDownloaded(id, 'csv', () => done())
})
})
})
})
it('3 | downloads viewer excel', (done) => {
visitPage('view/data')
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openDownloadModal(() => {
cy.get('select')
.select('Excel')
.then(() => {
cy.get('.btn.btn-sm.btn-success-outline').then((button) => {
button.trigger('click')
const id = button[0].id
checkForFileDownloaded(id, 'xlsx', () => done())
})
})
})
})
it('4 | downloads viewer SAS Datalines', (done) => {
visitPage('view/data')
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openDownloadModal(() => {
cy.get('select')
.select('SAS Datalines')
.then(() => {
cy.get('.btn.btn-sm.btn-success-outline').then((button) => {
button.trigger('click')
const id = button[0].id
checkForFileDownloaded(id, 'sas', () => done())
})
})
})
})
it('5 | downloads viewer SAS DDL', (done) => {
visitPage('view/data')
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openDownloadModal(() => {
cy.get('select')
.select('SAS DDL')
.then(() => {
cy.get('.btn.btn-sm.btn-success-outline').then((button) => {
button.trigger('click')
const id = button[0].id
checkForFileDownloaded(id, 'ddl', () => done(), '_')
})
})
})
})
it('6 | downloads viewer TSQL DDL', (done) => {
visitPage('view/data')
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openDownloadModal(() => {
cy.get('select')
.select('TSQL DDL')
.then(() => {
cy.get('.btn.btn-sm.btn-success-outline').then((button) => {
button.trigger('click')
const id = button[0].id
checkForFileDownloaded(id, 'ddl', () => done(), '_')
})
})
})
})
it('7 | downloads viewer PGSQL DDL', (done) => {
visitPage('view/data')
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openDownloadModal(() => {
cy.get('select')
.select('PGSQL DDL')
.then(() => {
cy.get('.btn.btn-sm.btn-success-outline').then((button) => {
button.trigger('click')
const id = button[0].id
checkForFileDownloaded(id, 'ddl', () => done(), '_')
})
})
})
})
this.afterEach(() => {
cy.visit(`${hostUrl}/SASLogon/logout`)
})
this.afterAll(() => {
cy.visit(`https://sas.4gl.io/mihmed/cypress_finish`)
})
})
const visitPage = (url: string) => {
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
}
const checkForFileDownloaded = (
id: string,
extension: string,
callback?: any,
libDivider: string = '.'
) => {
cy.on('url:changed', (newUrl) => {
console.log('newUrl', newUrl)
})
id = id.replace('.', libDivider)
const filename = downloadsFolder + '/' + id + '.' + extension
// browser might take a while to download the file,
// so use "cy.readFile" to retry until the file exists
// and has length - and we assume that it has finished downloading then
cy.readFile(filename, { timeout: longerCommandTimeout })
.should('have.length.gt', 10)
.then((file) => {
if (callback) callback()
})
}
const openDownloadModal = (callback?: any) => {
cy.get('.btn.btn-sm.btn-outline.filterSide.dropdown-toggle')
.click()
.then(() => {
cy.get('clr-dropdown-menu button').then((buttons) => {
for (let button of buttons) {
if (button.innerText.toLowerCase().includes('download')) {
button.click()
if (callback) callback()
}
}
})
})
}
const openTableFromTree = (libNameIncludes: string, tablename: string) => {
cy.get('.app-loading', { timeout: longerCommandTimeout })
.should('not.exist')
.then(() => {
cy.get('.nav-tree clr-tree > clr-tree-node', {
timeout: longerCommandTimeout
}).then((treeNodes: any) => {
let viyaLib
for (let node of treeNodes) {
if (node.innerText.toLowerCase().includes(libNameIncludes)) {
viyaLib = node
break
}
}
console.log('viyaLib', viyaLib)
cy.get(viyaLib).within(() => {
cy.get(
'.clr-tree-node-content-container .clr-treenode-content p'
).click()
cy.get('.clr-treenode-link').then((innerNodes: any) => {
for (let innerNode of innerNodes) {
if (innerNode.innerText.toLowerCase().includes(tablename)) {
innerNode.click()
break
}
}
})
})
})
})
}
-246
View File
@@ -1,246 +0,0 @@
const username = Cypress.env('username')
const password = Cypress.env('password')
const hostUrl = Cypress.env('hosturl')
const appLocation = Cypress.env('appLocation')
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
const serverType = Cypress.env('serverType')
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
const fixturePath = 'excels_general/'
context('editor tests: ', function () {
this.beforeAll(() => {
cy.visit(`${hostUrl}/SASLogon/logout`)
cy.loginAndUpdateValidKey()
})
this.beforeEach(() => {
cy.visit(hostUrl + appLocation)
visitPage('home')
})
it('1 | Submits duplicate primary keys', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_datadictionary')
attachExcelFile('MPE_DATADICTIONARY_duplicate_keys.xlsx', () => {
clickOnUploadPreview(() => {
confirmEditPreviewFile(() => {
submitTable(() => {
cy.get('.modal-body').then((modalBody: any) => {
if (modalBody[0].innerText.includes(`Duplicates found:`)) {
done()
}
})
})
})
})
})
})
it('2 | Submits null cells which must not be null', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
clickOnEdit(() => {
cy.get('.btn.btn-sm.btn-icon.btn-outline-danger', {
timeout: longerCommandTimeout
}).then(() => {
cy.get('.ht_master tbody tr').then((rows: any) => {
cy.get(rows[1].childNodes[2])
.dblclick({ force: true })
.then(() => {
cy.focused()
.clear()
.type('{enter}')
.then(() => {
submitTable(() => {
cy.get('.modal-body').then((modalBody: any) => {
if (
modalBody[0].innerHTML
.toLowerCase()
.includes(`invalid values are present`)
) {
done()
}
})
})
})
})
})
})
})
})
it('3 | Gets basic dynamic cell validation', () => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
clickOnEdit(() => {
cy.get('.btn.btn-sm.btn-icon.btn-outline-danger', {
timeout: longerCommandTimeout
}).then(() => {
cy.get('.ht_master tbody tr').then((rows: any) => {
cy.get(rows[1].childNodes[5])
.click({ force: true })
.then(($td) => {
cy.get('.htAutocompleteArrow', { withinSubject: $td }).should(
'exist'
)
})
})
})
})
})
it('4 | Gets advanced dynamic cell validation', () => {
openTableFromTree(libraryToOpenIncludes, 'mpe_tables')
clickOnEdit(() => {
cy.get('.btn.btn-sm.btn-icon.btn-outline-danger', {
timeout: longerCommandTimeout
}).then(() => {
cy.get('.ht_master tbody tr').then((rows: any) => {
cy.get(rows[1].childNodes[3])
.click({ force: true })
.then(($td) => {
cy.get('.htAutocompleteArrow', { withinSubject: $td }).should(
'exist'
)
cy.get('.htAutocompleteArrow', {
withinSubject: rows[1].childNodes[7]
}).should('exist')
cy.get('.htAutocompleteArrow', {
withinSubject: rows[1].childNodes[8]
}).should('exist')
})
})
})
})
})
})
const clickOnEdit = (callback?: any) => {
cy.get('.btnCtrl button.btn-primary', { timeout: longerCommandTimeout })
.click()
.then(() => {
if (callback) callback()
})
}
const openTableFromTree = (libNameIncludes: string, tablename: string) => {
cy.get('.app-loading', { timeout: longerCommandTimeout })
.should('not.exist')
.then(() => {
cy.get('.nav-tree clr-tree > clr-tree-node', {
timeout: longerCommandTimeout
}).then((treeNodes: any) => {
let viyaLib
for (let node of treeNodes) {
if (node.innerText.toLowerCase().includes(libNameIncludes)) {
viyaLib = node
break
}
}
cy.get(viyaLib).within(() => {
cy.get('.clr-tree-node-content-container > button').click()
cy.get('.clr-treenode-link').then((innerNodes: any) => {
for (let innerNode of innerNodes) {
if (innerNode.innerText.toLowerCase().includes(tablename)) {
innerNode.click()
break
}
}
})
})
})
})
}
const attachExcelFile = (excelFilename: string, callback?: any) => {
cy.get('.buttonBar button:last-child')
.click()
.then(() => {
cy.get('input[type="file"]#file-upload')
.attachFile(`/${fixturePath}/${excelFilename}`)
.then(() => {
cy.get('.modal-footer .btn.btn-primary').then((modalBtn) => {
modalBtn.click()
if (callback) callback()
})
})
})
}
const clickOnUploadPreview = (callback?: any) => {
cy.get('.buttonBar button.btn-primary.btn-upload-preview')
.click()
.then(() => {
if (callback) callback()
})
}
const confirmEditPreviewFile = (callback?: any) => {
cy.get('.modal-footer button.btn-success-outline')
.click()
.then(() => {
if (callback) callback()
})
}
const submitTable = (callback?: any) => {
cy.get('.btnCtrl button.btn-primary')
.click()
.then(() => {
if (callback) callback()
})
}
const submitTableMessage = (callback?: any) => {
cy.get('.modal-footer .btn.btn-sm.btn-success-outline')
.click()
.then(() => {
if (callback) callback()
})
}
const submitExcel = (callback?: any) => {
cy.get('.buttonBar button.preview-submit')
.click()
.then(() => {
if (callback) callback()
})
}
const rejectExcel = (callback?: any) => {
cy.get('button', { timeout: longerCommandTimeout })
.should('contain', 'Approve')
.then((allButtons: any) => {
for (let approvalButton of allButtons) {
if (approvalButton.innerText.toLowerCase().includes('approve')) {
approvalButton.click()
break
}
}
cy.get('button.btn-danger')
.should('exist')
.should('not.be.disabled')
.click()
.then(() => {
cy.get('.modal-footer button.btn-success-outline')
.click()
.then(() => {
cy.get('app-history')
.should('exist')
.then(() => {
if (callback) callback()
})
})
})
})
}
const visitPage = (url: string) => {
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
}
-527
View File
@@ -1,527 +0,0 @@
import { Callbacks } from 'cypress/types/jquery/index'
const username = Cypress.env('username')
const password = Cypress.env('password')
const hostUrl = Cypress.env('hosturl')
const appLocation = Cypress.env('appLocation')
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
const serverType = Cypress.env('serverType')
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
const fixturePath = 'excels/'
// TODO: 4 and 9 failing
context('excel tests: ', function () {
this.beforeAll(() => {
cy.visit(`${hostUrl}/SASLogon/logout`)
cy.loginAndUpdateValidKey()
})
this.beforeEach(() => {
cy.visit(hostUrl + appLocation)
visitPage('home')
colorLog(
`TEST START ---> ${
Cypress.mocha.getRunner().suite.ctx.currentTest.title
}`,
'#3498DB'
)
})
it('1 | Uploads regular Excel file', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('regular_excel.xlsx', () => {
submitExcel()
rejectExcel(done)
})
})
it('2 | Uploads Excel with data on the 7th tab', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('7th_tab_excel.xlsx', () => {
submitExcel()
rejectExcel(done)
})
})
it('3 | Uploads Excel with missing columns (should fail)', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('missing_columns_excel.xlsx', () => {
cy.get('.abortMsg', { timeout: longerCommandTimeout })
.should('exist')
.then((elements: any) => {
if (elements[0]) {
if (elements[0].innerText.toLowerCase().includes('missing')) done()
}
})
})
})
it('4 | Uploads Excel with formulas', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_datadictionary')
attachExcelFile('formulas_excel.xlsx', () => {
checkResultOfFormulaUpload(done)
})
})
it('5 | Uploads Excel with no data rows', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('nodata_rows_excel.xlsx', () => {
cy.get('.abortMsg', { timeout: longerCommandTimeout })
.should('exist')
.then((elements: any) => {
if (elements[0]) {
if (
elements[0].innerText
.toLowerCase()
.includes('no relevant data found')
)
done()
}
})
})
})
it('6 | Uploads Excel with a table that is surrounded by other data', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('surrounded_data_excel.xlsx', () => {
submitExcel()
rejectExcel(done)
})
})
it('7 | Uploads Excel with a extra columns in the middle', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('extra_column_excel.xlsx', () => {
submitExcel()
rejectExcel(done)
})
})
it('8 | Uploads Excel with a duplicate column', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('duplicate_column_excel.xlsx', () => {
cy.get('.abortMsg', { timeout: longerCommandTimeout })
.should('exist')
.then((elements: any) => {
if (elements[0]) {
if (elements[0].innerText.toLowerCase().includes('missing')) done()
}
})
})
})
// it('9 | Uploads Excel with a duplicate row', (done) => {
// openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
// attachExcelFile('duplicate_row_excel.xlsx', () => {
// submitExcel(() => {
// cy.get('.abortMsg', { timeout: longerCommandTimeout })
// .should('exist')
// .then((elements: any) => {
// if (elements[0]) {
// if (elements[0].innerText.toLowerCase().includes('duplicates'))
// done()
// }
// })
// })
// })
// })
it('10 | Uploads Excel with a mixed content', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('mixed_content_excel.xlsx', () => {
submitExcel(() => {
cy.get('.modal-body').then((modalBody: any) => {
if (
modalBody[0].innerHTML
.toLowerCase()
.includes(`invalid values are present`)
) {
done()
}
})
})
})
})
it('11 | Uploads Excel with a blank columns', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('blank_columns_excel.xlsx', () => {
cy.get('.abortMsg', { timeout: longerCommandTimeout })
.should('exist')
.then((elements: any) => {
if (elements[0]) {
if (elements[0].innerText.toLowerCase().includes('missing')) done()
}
})
})
})
it('12 | Uploads Excel xls extension', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('regular_excel_xls.xls', () => {
submitExcel()
rejectExcel(done)
})
})
// For some strange reason this file breaks cypress. When uploaded manually in DC it is working.
// it('13 | Uploads Excel xlsm extension', (done) => {
// openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
// attachExcelFile('regular_excel_macro.xlsm', () => {
// submitExcel()
// rejectExcel(done)
// })
// })
it('14 | Uploads Excel with composite primary key', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_datadictionary')
attachExcelFile('MPE_DATADICTIONARY_composite_keys.xlsx', () => {
submitExcel()
rejectExcel(done)
})
})
it('15 | Uploads Excel with missing row (empty table)', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_datadictionary')
attachExcelFile('MPE_DATADICTIONARY_missing_row.xlsx', () => {
cy.get('.abortMsg', { timeout: longerCommandTimeout })
.should('exist')
.then((elements: any) => {
if (elements[0]) {
if (
elements[0].innerText
.toLowerCase()
.includes('no relevant data found')
)
done()
}
})
})
})
it('16 | Uploads Excel with merged cells', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_datadictionary')
attachExcelFile('MPE_DATADICTIONARY_merged_cells.xlsx', () => {
submitExcel()
rejectExcel(done)
})
})
it('17 | Check uploaded values from excel with xls extension', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('regular_excel_xls.xls', () => {
checkResultOfXLSUpload(done)
})
})
it('18 | Uploads Excel with missing row (empty table)', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('blank_column_with_header.xlsx', () => {
cy.get('.btn-upload-preview', { timeout: 60000 })
.should('be.visible')
.then(() => {
cy.get('#hotInstance', { timeout: 30000 })
.find('div.ht_master.handsontable')
.find('div.wtHolder')
.find('div.wtHider')
.find('div.wtSpreader')
.find('table.htCore')
.find('tbody')
.should((data) => {
let allEmpty = true
for (let col = 0; col < data[0].children.length; col++) {
const cell: any = data[0].children[col].children[5]
if (cell.innerText !== '') {
allEmpty = false
break
}
}
if (allEmpty) done()
})
})
})
})
it('19 | Uploads Excel with data on random sheet surrounded with all empty cells', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('surrounded_data_all_cells_empty_excel.xlsx', () => {
checkResultOfXLSUpload(done)
})
})
it('20 | Uploads Excel with data surrounded with empty cells ', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('surrounded_data_empty_cells_excel.xlsx', () => {
checkResultOfXLSUpload(done)
})
})
it('21 | Uploads regular Excel file with first row marked for Delete (yes)', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('regular_excel_with_delete.xlsx', () => {
cy.get('.btn-upload-preview', { timeout: 60000 })
.should('be.visible')
.then(() => {
cy.get('#hotInstance', { timeout: 30000 })
.find('div.ht_master.handsontable')
.find('div.wtHolder')
.find('div.wtHider')
.find('div.wtSpreader')
.find('table.htCore')
.find('tbody')
.should((data: JQuery<HTMLTableSectionElement>) => {
const firstRowFirstCol: Partial<HTMLElement> =
data[0].children[0].children[1]
if (
firstRowFirstCol.innerText &&
!firstRowFirstCol.innerText.toLowerCase().includes('yes')
) {
done('Delete? column from file not applied')
}
})
.then(() => {
submitExcel()
rejectExcel(done)
})
})
})
})
// Large files break Cypress
// it ('? | Uploads Excel with size of 5MB', (done) => {
// attachExcelFile('5mb_excel.xlsx', () => {
// submitExcel();
// rejectExcel(done);
// });
// })
// it ('? | Uploads Excel with size of 15MB', (done) => {
// attachExcelFile('15mb_excel.xlsx', () => {
// submitExcel();
// rejectExcel(done);
// });
// })
//Large files tests end
this.afterEach(() => {
colorLog(`TEST END -------------`, '#3498DB')
})
})
const openTableFromTree = (libNameIncludes: string, tablename: string) => {
cy.get('.app-loading', { timeout: longerCommandTimeout })
.should('not.exist')
.then(() => {
cy.get('.nav-tree clr-tree > clr-tree-node', {
timeout: longerCommandTimeout
}).then((treeNodes: any) => {
let viyaLib
for (let node of treeNodes) {
if (node.innerText.toLowerCase().includes(libNameIncludes)) {
viyaLib = node
break
}
}
cy.get(viyaLib).within(() => {
cy.get('.clr-tree-node-content-container > button').click()
cy.get('.clr-treenode-link').then((innerNodes: any) => {
for (let innerNode of innerNodes) {
if (innerNode.innerText.toLowerCase().includes(tablename)) {
innerNode.click()
break
}
}
})
})
})
})
}
const attachExcelFile = (excelFilename: string, callback?: any) => {
cy.get('.buttonBar button:last-child')
.should('exist')
.click()
.then(() => {
cy.get('input[type="file"]#file-upload')
.attachFile(`/${fixturePath}/${excelFilename}`)
.then(() => {
cy.get('.clr-abort-modal .modal-title').then((modalTitle) => {
if (!modalTitle[0].innerHTML.includes('Abort Message')) {
cy.get('.modal-footer .btn.btn-primary').then((modalBtn) => {
modalBtn.click()
if (callback) callback()
})
} else {
if (callback) callback()
}
})
})
})
}
const submitExcel = (callback?: any) => {
cy.get('.buttonBar button.preview-submit', { timeout: longerCommandTimeout })
.click()
.then(() => {
if (callback) callback()
})
}
const rejectExcel = (callback?: any) => {
cy.get('button', { timeout: longerCommandTimeout })
.should('contain', 'Approve')
.then((allButtons: any) => {
for (let approvalButton of allButtons) {
if (approvalButton.innerText.toLowerCase().includes('approve')) {
approvalButton.click()
break
}
}
cy.get('button.btn-danger')
.should('exist')
.should('not.be.disabled')
.click()
.then(() => {
cy.get('.modal-footer button.btn-success-outline')
.click()
.then(() => {
cy.get('app-history')
.should('exist')
.then(() => {
if (callback) callback()
})
})
})
})
}
const acceptExcel = (callback?: any) => {
cy.get('button', { timeout: longerCommandTimeout })
.should('contain', 'Approve')
.then((allButtons: any) => {
for (let approvalButton of allButtons) {
if (approvalButton.innerText.toLowerCase().includes('approve')) {
approvalButton.click()
break
}
}
cy.get('#acceptBtn')
.should('exist')
.should('not.be.disabled')
.click()
.then(() => {
if (callback) {
callback()
}
})
})
}
const checkResultOfFormulaUpload = (callback?: any) => {
cy.get('#hotInstance', { timeout: longerCommandTimeout })
.find('div.ht_master.handsontable')
.find('div.wtHolder')
.find('div.wtHider')
.find('div.wtSpreader')
.find('table.htCore')
.find('tbody')
.should((data) => {
const cell: any = data[0].children[0].children[5]
expect(cell.innerText).to.equal('=1+1')
if (callback) callback()
})
}
const checkResultOfXLSUpload = (callback?: any) => {
cy.viewport(1280, 720)
cy.get('#hotInstance', { timeout: 30000 })
.find('div.ht_master.handsontable')
.find('div.wtHolder')
.find('div.wtHider')
.find('div.wtSpreader')
.find('table.htCore')
.find('tbody')
.should((data) => {
let cell: any = data[0].children[0].children[2]
expect(cell.innerText).to.equal('0')
cell = data[0].children[0].children[3]
expect(cell.innerText).to.equal('this is dummy data changed in excel')
cell = data[0].children[0].children[4]
expect(cell.innerText).to.equal('▼\nOption 1')
cell = data[0].children[0].children[5]
expect(cell.innerText).to.equal('42')
cell = data[0].children[0].children[6]
expect(cell.innerText).to.equal('▼\n1960-02-12')
cell = data[0].children[0].children[7]
expect(cell.innerText).to.equal('▼\n1960-01-01 00:00:42')
cell = data[0].children[0].children[8]
expect(cell.innerText).to.equal('00:00:42')
cell = data[0].children[0].children[9]
expect(cell.innerText).to.equal('3')
if (callback) callback()
})
cy.get('#hotInstance', { timeout: 30000 })
.find('div.ht_master.handsontable')
.find('div.wtHolder')
.scrollTo('right')
.find('div.wtHider')
.find('div.wtSpreader')
.find('table.htCore')
.find('tbody')
.should((data) => {
let cell: any = data[0].children[0].children[1]
cell = data[0].children[0].children[9]
expect(cell.innerText).to.equal('44')
if (callback) callback()
})
}
const visitPage = (url: string) => {
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
}
const colorLog = (msg: string, color: string) => {
console.log('%c' + msg, 'color:' + color + ';font-weight:bold;')
}
@@ -1,376 +0,0 @@
const username = Cypress.env('username')
const password = Cypress.env('password')
const hostUrl = Cypress.env('hosturl')
const appLocation = Cypress.env('appLocation')
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
const serverType = Cypress.env('serverType')
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
const fixturePath = 'excels_general/'
context('filtering tests: ', function () {
this.beforeAll(() => {
cy.visit(`${hostUrl}/SASLogon/logout`, { timeout: longerCommandTimeout })
cy.loginAndUpdateValidKey()
})
this.beforeEach(() => {
cy.visit(hostUrl + appLocation, { timeout: longerCommandTimeout })
visitPage('home')
})
it('1 | filter char field', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openFilterPopup(() => {
setFilterWithValue('SOME_CHAR', 'this is dummy data', 'value', () => {
checkInfoBarIncludes(
`AND,AND,0,SOME_CHAR,=,"'this is dummy data'"`,
(includes: boolean) => {
if (includes) done()
}
)
})
})
})
it('2 | filter number field', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openFilterPopup(() => {
setFilterWithValue('SOME_NUM', '42', 'value', () => {
checkInfoBarIncludes(`AND,AND,0,SOME_NUM,=,42`, (includes: boolean) => {
if (includes) done()
})
})
})
})
it.only('3 | filter time field', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openFilterPopup(() => {
setFilterWithValue('SOME_TIME', '00:00:42', 'time', () => {
checkInfoBarIncludes(
`AND,AND,0,SOME_TIME,=,42`,
(includes: boolean) => {
if (includes) done()
}
)
})
})
})
it('3.1 | Non picker - filter time field', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openFilterPopup(() => {
setFilterWithValue('SOME_TIME', '42', 'value', () => {
checkInfoBarIncludes(
`AND,AND,0,SOME_TIME,=,42`,
(includes: boolean) => {
if (includes) done()
}
)
})
}, false)
})
it('4 | filter date field', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openFilterPopup(() => {
setFilterWithValue('SOME_DATE', '12/02/1960', 'date', () => {
checkInfoBarIncludes(
`AND,AND,0,SOME_DATE,=,42`,
(includes: boolean) => {
if (includes) done()
}
)
})
})
})
it('4.1 | Non picker - filter date field', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openFilterPopup(() => {
setFilterWithValue('SOME_DATE', '42', 'value', () => {
checkInfoBarIncludes(
`AND,AND,0,SOME_DATE,=,42`,
(includes: boolean) => {
if (includes) done()
}
)
})
}, false)
})
it('5 | filter datetime field', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openFilterPopup(() => {
setFilterWithValue(
'SOME_DATETIME',
'01/01/1960 00:00:42',
'datetime',
() => {
checkInfoBarIncludes(
`AND,AND,0,SOME_DATETIME,=,42`,
(includes: boolean) => {
if (includes) done()
}
)
}
)
})
})
it('5.1 | Non picker - filter datetime field', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openFilterPopup(() => {
setFilterWithValue('SOME_DATETIME', '42', 'value', () => {
checkInfoBarIncludes(
`AND,AND,0,SOME_DATETIME,=,42`,
(includes: boolean) => {
if (includes) done()
}
)
})
}, false)
})
it('6 | filter date field IN', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openFilterPopup(() => {
setFilterWithValue('SOME_DATE', '', 'in', () => {
checkInfoBarIncludes(
`AND,AND,0,SOME_DATE,IN,(0)`,
(includes: boolean) => {
if (includes) done()
}
)
})
})
})
it('7 | filter bestnum field BETWEEN', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openFilterPopup(() => {
setFilterWithValue('SOME_BESTNUM', '0-10', 'between', () => {
checkInfoBarIncludes(
`AND,AND,0,SOME_BESTNUM,BETWEEN,0 AND 10`,
(includes: boolean) => {
if (includes) done()
}
)
})
})
})
})
const checkInfoBarIncludes = (text: string, callback: any) => {
cy.get('.infoBar b', { timeout: longerCommandTimeout }).then((el: any) => {
const includes = el[0].innerText.toLowerCase().includes(text.toLowerCase())
if (callback) callback(includes)
})
}
const openFilterPopup = (
callback?: any,
usePickers: boolean = true,
isViewerFiltering: boolean = false
) => {
const filterButton = isViewerFiltering
? '.btn-outline.filterSide'
: '.btnCtrl .btnView'
cy.get(filterButton, { timeout: longerCommandTimeout }).then(
(optionsButton: any) => {
optionsButton.click()
if (isViewerFiltering) {
cy.wait(300)
cy.get('.dropdown-menu button').then(async (dropdownButtons: any) => {
let filterButton = null
for (let btn of dropdownButtons) {
if (btn.innerText.toLowerCase().includes('filter')) {
filterButton = btn
break
}
}
if (filterButton) {
filterButton.click()
if (usePickers) turnOnPickers()
if (callback) callback()
return
}
})
}
if (usePickers) turnOnPickers()
if (callback) callback()
}
)
}
const turnOnPickers = () => {
cy.get('#usePickers')
.should('exist')
.then((picker: any) => {
picker[0].click()
})
}
const setFilterWithValue = (
variableValue: string,
valueString: string,
valueField: 'value' | 'time' | 'date' | 'datetime' | 'in' | 'between',
callback?: any
) => {
cy.wait(600)
cy.focused().type(variableValue)
cy.wait(100)
// cy.focused().trigger('input')
cy.get('.variable-col .autocomplete-wrapper', { withinSubject: null })
.first()
.trigger('keydown', { key: 'ArrowDown' })
cy.get('.variable-col .autocomplete-wrapper', {
withinSubject: null
}).trigger('keydown', { key: 'Enter' })
cy.focused().tab()
cy.wait(100)
if (valueField === 'in') {
cy.focused().select(valueField.toUpperCase()).trigger('change')
} else if (valueField === 'between') {
cy.focused().select(valueField.toUpperCase()).trigger('change')
} else {
cy.focused().tab()
cy.wait(100)
}
switch (valueField) {
case 'value': {
cy.focused().type(valueString)
break
}
case 'time': {
cy.focused().type(valueString)
break
}
case 'date': {
cy.focused().type(valueString)
cy.focused().tab()
break
}
case 'datetime': {
const date = valueString.split(' ')[0]
const time = valueString.split(' ')[1]
cy.focused().type(date)
cy.focused().tab()
cy.focused().tab()
cy.focused().type(time)
break
}
case 'in': {
cy.get('.checkbox-vals').then(() => {
cy.focused().tab()
cy.focused().click()
cy.get('.no-values')
.should('not.exist')
.then(() => {
cy.get('clr-checkbox-wrapper input').then((inputs: any) => {
inputs[0].click()
cy.get('.in-values-modal .modal-footer button').click()
cy.get('.modal-footer .btn-success-outline').click()
if (callback) callback()
})
})
})
break
}
case 'between': {
cy.focused().tab()
const start = valueString.split('-')[0]
const end = valueString.split('-')[1]
cy.focused().type(start)
cy.focused().tab()
cy.focused().type(end)
}
default: {
break
}
}
cy.wait(100)
cy.focused().tab()
cy.wait(100)
cy.focused().tab()
cy.wait(100)
cy.focused().tab()
cy.wait(100)
cy.focused().tab()
cy.wait(100)
cy.focused().click()
if (callback) callback()
}
const openTableFromTree = (libNameIncludes: string, tablename: string) => {
cy.get('.app-loading', { timeout: longerCommandTimeout })
.should('not.exist')
.then(() => {
cy.get('.nav-tree clr-tree > clr-tree-node', {
timeout: longerCommandTimeout
}).then((treeNodes: any) => {
let viyaLib
for (let node of treeNodes) {
if (new RegExp(libNameIncludes).test(node.innerText.toLowerCase())) {
viyaLib = node
break
}
}
cy.get(viyaLib).within(() => {
cy.get('.clr-tree-node-content-container p').click()
cy.get('.clr-treenode-link').then((innerNodes: any) => {
for (let innerNode of innerNodes) {
if (innerNode.innerText.toLowerCase().includes(tablename)) {
innerNode.click()
break
}
}
})
})
})
})
}
const visitPage = (url: string) => {
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
}
@@ -1,719 +0,0 @@
import { arrayBufferToBase64 } from './../util/helper-functions'
import * as moment from 'moment'
const username = Cypress.env('username')
const password = Cypress.env('password')
const hostUrl = Cypress.env('hosturl')
const appLocation = Cypress.env('appLocation')
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
const fixturePath = 'excels_general/'
const serverType = Cypress.env('serverType')
const site_id = Cypress.env(`site_id_${serverType}`)
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
const testLicenceUserLimits = Cypress.env('testLicenceUserLimits')
/** IMPORTANT NOTICE
* Before running tests, make sure that table `MPE_USERS` is present
*/
interface EditConfigTableCells {
varName: string
varValue: string
}
context('licensing tests: ', function () {
this.beforeAll(() => {
cy.loginAndUpdateValidKey()
})
this.beforeEach(() => {
cy.visit(hostUrl + appLocation)
visitPage('home')
})
it('1 | key valid, not expired', (done) => {
let keyData = {
valid_until: moment().add(1, 'year').format('YYYY-MM-DD'),
users_allowed: 4,
hot_license_key: '',
demo: false,
site_id: site_id
}
let keys: { licenseKey: any; activationKey: any }
generateKeys(keyData, (keysGen: any) => {
keys = keysGen
cy.wait(2000)
isLicensingPage((result: boolean) => {
if (result) {
inputLicenseKeyPage(keys.licenseKey, keys.activationKey)
cy.wait(2000)
}
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(10000)
}
visitPage('home')
cy.get('.nav-tree clr-tree > clr-tree-node', {
timeout: longerCommandTimeout
}).then((treeNodes: any) => {
done()
})
})
})
})
})
it('2 | Key will expire in less then 14 days, not free tier', (done) => {
// make 2 separate for this one
let keyData = {
valid_until: moment().add(10, 'day').format('YYYY-MM-DD'),
users_allowed: 4,
hot_license_key: '',
demo: false,
site_id: site_id
}
let keys: { licenseKey: any; activationKey: any }
generateKeys(keyData, (keysGen: any) => {
keys = keysGen
console.log('keys', keys)
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
updateLicenseKeyQuick(keysGen, () => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
verifyLicensingWarning('This license key will expire in ', () => {
done()
})
})
})
})
})
})
it('3 | key expired, free tier works', (done) => {
let keyData = {
valid_until: moment().subtract(1, 'day').format('YYYY-MM-DD'),
users_allowed: 4,
hot_license_key: '',
demo: false,
site_id: site_id
}
let keys: { licenseKey: any; activationKey: any }
generateKeys(keyData, (keysGen: any) => {
keys = keysGen
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
cy.wait(2000)
updateLicenseKeyQuick(keysGen, () => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
verifyLicensingPage(
'Licence key is expired - please contact',
(success: boolean) => {
if (success) {
verifyLicensingWarning(
'(FREE Tier) - Problem with licence',
() => {
done()
}
)
}
}
)
})
})
})
})
})
it('4 | key invalid, free tier works', (done) => {
let keyData = {
valid_until: moment().subtract(1, 'day').format('YYYY-MM-DD'),
users_allowed: 4,
hot_license_key: '',
demo: false,
site_id: site_id
}
let keys: { licenseKey: any; activationKey: any }
generateKeys(keyData, (keysGen: any) => {
keys = keysGen
keys.activationKey = 'invalid' + keys.activationKey
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
cy.wait(2000)
updateLicenseKeyQuick(keysGen, () => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
verifyLicensingPage(
'Licence key is invalid - please contact',
(success: boolean) => {
if (success) {
verifyLicensingWarning(
'(FREE Tier) - Problem with licence',
() => {
done()
}
)
}
}
)
})
})
})
})
})
it('5 | key for wrong organisation, free tier works', (done) => {
let keyData = {
valid_until: moment().add(1, 'year').format('YYYY-MM-DD'),
users_allowed: 4,
hot_license_key: '',
demo: false,
site_id: 100
}
let keys: { licenseKey: any; activationKey: any }
generateKeys(keyData, (keysGen: any) => {
keys = keysGen
keys.activationKey = keys.activationKey
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
cy.wait(2000)
updateLicenseKeyQuick(keysGen, () => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
verifyLicensingPage(
'SYSSITE (below) is not found',
(success: boolean) => {
if (success) {
verifyLicensingWarning(
'(FREE Tier) - Problem with licence',
() => {
done()
}
)
}
}
)
})
})
})
})
})
if (testLicenceUserLimits) {
it('4 | User try to register when limit is reached', (done) => {
let keyData = {
valid_until: moment().add(1, 'month').format('YYYY-MM-DD'),
users_allowed: 10,
hot_license_key: '',
demo: false,
site_id: site_id
}
let keyData2 = {
valid_until: moment().add(1, 'month').format('YYYY-MM-DD'),
users_allowed: 1,
hot_license_key: '',
demo: false,
site_id: site_id
}
generateKeys(keyData, (keysGen: any) => {
generateKeys(keyData2, (keysGen2: any) => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
updateLicenseKeyQuick(keysGen, () => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
updateLicenseKeyQuick(keysGen2, () => {
cy.wait(2000)
const random = Cypress._.random(0, 1000)
const newUser = {
username: `randomusername${random}notregistered`,
last_seen_at: moment().add(1, 'month').format('YYYY-MM-DD'),
registered_at: moment().add(1, 'month').format('YYYY-MM-DD')
}
updateUsersTable(
{ deleteAll: true, newUsers: [newUser] },
() => {
logout(() => {
cy.visit(hostUrl + appLocation)
// cy.get('input.username').type(username)
// cy.get('input.password').type(password)
// cy.get('.login-group button').click()
visitPage('home')
cy.wait(2000)
verifyLicensingPage(
'The registered number of users reached the limit specified for your licence.',
(success: boolean) => {
if (success) done()
}
)
})
}
)
})
})
})
})
})
})
})
it('5 | Show warning banner when limit is exceeded', (done) => {
let keyData = {
valid_until: moment().add(1, 'month').format('YYYY-MM-DD'),
users_allowed: 10,
hot_license_key: '',
demo: false,
site_id: site_id
}
let keyData2 = {
valid_until: moment().add(1, 'month').format('YYYY-MM-DD'),
users_allowed: 1,
hot_license_key: '',
demo: false,
site_id: site_id
}
generateKeys(keyData, (keysGen: any) => {
generateKeys(keyData2, (keysGen2: any) => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
updateLicenseKeyQuick(keysGen, () => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
const random = Cypress._.random(0, 1000)
const newUser = {
username: `randomusername${random}`,
last_seen_at: moment().add(1, 'month').format('YYYY-MM-DD'),
registered_at: moment().add(1, 'month').format('YYYY-MM-DD')
}
updateUsersTable(
{ deleteAll: true, keep: username, newUsers: [newUser] },
() => {
updateLicenseKeyQuick(keysGen2, () => {
cy.wait(2000)
verifyLicensingWarning(
'The registered number of users exceeds the limit specified for your license.',
() => {
done()
}
)
})
}
)
})
})
})
})
})
})
}
})
const logout = (callback?: any) => {
cy.get('.header-actions .dropdown-toggle')
.click()
.then(() => {
cy.get('.header-actions .dropdown-menu > .separator')
.next()
.click()
.then(() => {
if (callback) callback()
})
})
}
const acceptTermsIfPresented = (callback?: any) => {
cy.url().then((url: string) => {
if (url.includes('licensing/register')) {
cy.get('.card-block')
.scrollTo('bottom')
.then(() => {
cy.get('#checkbox1')
.click()
.then(() => {
if (callback) callback(true)
})
})
} else {
if (callback) callback(false)
}
})
}
const isLicensingPage = (callback: any) => {
return cy.url().then((url: string) => {
callback(
url.includes('#/licensing/') && !url.includes('licensing/register')
)
})
}
const verifyLicensingPage = (text: string, callback: any) => {
// visitPage('home')
cy.wait(1000)
isLicensingPage((result: boolean) => {
if (result) {
cy.get('p.key-error')
.should('contain', text)
.then((treeNodes: any) => {
callback(true)
})
}
})
}
const verifyLicensingWarning = (text: string, callback: any) => {
visitPage('home')
cy.wait(1000)
cy.get("div[role='alert'] .alert-text")
.invoke('text')
.should('contain', text)
.then(() => {
callback()
})
}
const inputLicenseKeyPage = (licenseKey: string, activationKey: string) => {
cy.get('button').contains('Paste licence').click()
cy.get('.license-key-form textarea', { timeout: longerCommandTimeout })
.invoke('val', licenseKey)
.trigger('input')
.should('not.be.undefined')
cy.get('.activation-key-form textarea', { timeout: longerCommandTimeout })
.invoke('val', activationKey)
.trigger('input')
.should('not.be.undefined')
cy.get('button.apply-keys').click()
}
const updateUsersTable = (options: any, callback?: any) => {
visitPage('home')
openTableFromTree(libraryToOpenIncludes, 'mpe_users')
clickOnEdit(() => {
cy.get('.ht_master tbody tr').then((rows: any) => {
if (options.deleteAll) {
for (let row of rows) {
const user_id = row.childNodes[2]
if (!options.keep || user_id.innerText !== options.keep) {
cy.get(row.childNodes[1])
.dblclick()
.then(() => {
cy.focused().type('{selectall}').type('Yes').type('{enter}')
})
}
}
}
if (options.newUsers && options.newUsers.length) {
for (let newUser of options.newUsers) {
clickOnAddRow(() => {
cy.get('#hotInstance tbody tr:last-child').then((rows: any) => {
cy.get(rows[0].childNodes[2])
.dblclick()
.then(() => {
cy.focused()
.type('{selectall}')
.type(newUser.username)
.type('{enter}')
})
// cy.get(rows[0].childNodes[3])
// .dblclick()
// .then(() => {
// cy.focused()
// .type('{selectall}')
// .type(newUser.last_seen_at)
// .type('{enter}')
// })
// cy.get(rows[0].childNodes[4])
// .dblclick()
// .then(() => {
// cy.focused()
// .type('{selectall}')
// .type(newUser.registered_at)
// .type('{enter}')
// })
submitTable(() => {
cy.wait(2000)
approveTable(callback)
})
})
})
}
}
})
})
}
const changeLicenseKeyTable = (keys: any, callback?: any) => {
visitPage('home')
openTableFromTree(libraryToOpenIncludes, 'mpe_config')
clickOnEdit(() => {
editTableField(
[
{ varName: 'DC_ACTIVATION_KEY', varValue: keys.activationKey },
{ varName: 'DC_LICENCE_KEY', varValue: keys.licenseKey }
],
() => {
submitTable(() => {
cy.wait(2000)
approveTable(() => {
cy.reload()
if (callback) callback()
})
})
}
)
})
}
const updateLicenseKeyQuick = (keys: any, callback: any) => {
isLicensingPage((result: boolean) => {
if (!result) {
visitPage('licensing/update')
cy.wait(2000)
}
inputLicenseKeyPage(keys.licenseKey, keys.activationKey)
callback()
})
}
const generateKeys = async (licenseData: any, resultCallback?: any) => {
let keyPair = await window.crypto.subtle.generateKey(
{
name: 'RSA-OAEP',
modulusLength: 2024,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256'
},
true,
['encrypt', 'decrypt']
)
const encoded = new TextEncoder().encode(JSON.stringify(licenseData))
const cipher = await window.crypto.subtle
.encrypt(
{
name: 'RSA-OAEP'
},
keyPair.publicKey,
encoded
)
.then(
(value) => {
return value
},
(err) => {
console.log('Encrpyt error', err)
}
)
if (!cipher) {
alert('Encryptin keys failed')
throw new Error('Encryptin keys failed')
}
const privateKeyBytes = await window.crypto.subtle.exportKey(
'pkcs8',
keyPair.privateKey
)
const activationKey = await arrayBufferToBase64(privateKeyBytes)
const licenseKey = await arrayBufferToBase64(cipher)
if (resultCallback)
resultCallback({
activationKey,
licenseKey
})
}
const editTableField = (edits: EditConfigTableCells[], callback?: any) => {
cy.get('td').then((tdNodes: any) => {
for (let edit of edits) {
let correctRow = false
for (let node of tdNodes) {
if (correctRow) {
cy.get(node)
.dblclick()
.then(() => {
// textarea update on long keys
cy.focused().invoke('val', edit.varValue).type('{enter}')
})
correctRow = false
break
}
if (node.innerText.includes(edit.varName)) {
correctRow = true
}
}
}
if (callback) callback()
})
}
const openTableFromTree = (
libNameIncludes: string,
tablename: string,
callback?: any
) => {
cy.get('.app-loading', { timeout: longerCommandTimeout })
.should('not.exist')
.then(() => {
cy.get('.nav-tree clr-tree > clr-tree-node', {
timeout: longerCommandTimeout
}).then((treeNodes: any) => {
let viyaLib
for (let node of treeNodes) {
if (node.innerText.toLowerCase().includes(libNameIncludes)) {
viyaLib = node
break
}
}
cy.get(viyaLib).within(() => {
cy.get('.clr-tree-node-content-container > button').click()
cy.get('.clr-treenode-link').then((innerNodes: any) => {
for (let innerNode of innerNodes) {
if (innerNode.innerText.toLowerCase().includes(tablename)) {
innerNode.click()
if (callback) callback()
break
}
}
})
})
})
})
}
const clickOnAddRow = (callback?: any) => {
cy.get('.btnCtrl button.btn-success')
.click()
.then(() => {
if (callback) callback()
})
}
const clickOnEdit = (callback?: any) => {
cy.get('.btnCtrl button.btn-primary', { timeout: longerCommandTimeout })
.click()
.then(() => {
if (callback) callback()
})
}
const submitTable = (callback?: any) => {
cy.get('.btnCtrl button.btn-primary', { timout: longerCommandTimeout })
.click()
.then(() => {
cy.get(".modal.ng-star-inserted button[type='submit']")
.click()
.then(() => {
if (callback) callback()
})
})
}
const approveTable = (callback?: any) => {
cy.get('button', { timeout: longerCommandTimeout })
.should('contain', 'Approve')
.then((allButtons: any) => {
for (let approvalButton of allButtons) {
if (approvalButton.innerText.toLowerCase().includes('approve')) {
approvalButton.click()
break
}
}
cy.get('button#acceptBtn', { timeout: longerCommandTimeout })
.should('exist')
.should('not.be.disabled')
.click()
.then(() => {
cy.get('app-history', { timeout: longerCommandTimeout })
.should('exist')
.then(() => {
if (callback) callback()
})
})
})
}
const visitPage = (url: string) => {
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
}
@@ -1,149 +0,0 @@
const username = Cypress.env('username')
const password = Cypress.env('password')
const hostUrl = Cypress.env('hosturl')
const appLocation = Cypress.env('appLocation')
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
const serverType = Cypress.env('serverType')
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
const fixturePath = 'excels/'
context('liveness tests: ', function () {
this.beforeAll(() => {
if (serverType !== 'SASJS') {
cy.visit(`${hostUrl}/SASLogon/logout`)
}
cy.loginAndUpdateValidKey(true)
})
this.beforeEach(() => {
cy.visit(hostUrl + appLocation)
visitPage('home')
})
it('1 | Login and submit test', (done) => {
cy.get('.nav-tree clr-tree > clr-tree-node', {
timeout: longerCommandTimeout
}).then((treeNodes: any) => {
libraryExistsInTree('viya', treeNodes)
? openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
: openTableFromTree('dc', 'mpe_x_test')
attachExcelFile('regular_excel.xlsx', () => {
submitExcel()
rejectExcel(done)
})
})
})
/**
* Thist part will be needed if we add more tests in future
*/
// this.afterEach(() => {
// cy.visit('https://sas.4gl.io/SASLogon/logout');
// })
})
const visitPage = (url: string) => {
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
}
const libraryExistsInTree = (libName: string, nodes: any) => {
for (let node of nodes) {
if (node.innerText.toLowerCase().includes(libName.toLowerCase()))
return true
}
return false
}
const openTableFromTree = (
libNameIncludes: string,
tablename: string,
finish: any
) => {
cy.get('.app-loading', { timeout: longerCommandTimeout })
.should('not.exist')
.then(() => {
cy.get('.nav-tree clr-tree > clr-tree-node', {
timeout: longerCommandTimeout
}).then((treeNodes: any) => {
let viyaLib
for (let node of treeNodes) {
if (node.innerText.toLowerCase().includes(libNameIncludes)) {
viyaLib = node
break
}
}
if (!viyaLib && finish) finish(false)
cy.get(viyaLib).within(() => {
cy.get('.clr-tree-node-content-container > button').click()
cy.get('.clr-treenode-link').then((innerNodes: any) => {
for (let innerNode of innerNodes) {
if (innerNode.innerText.toLowerCase().includes(tablename)) {
innerNode.click()
if (finish) finish(true)
break
}
}
})
})
})
})
}
const attachExcelFile = (excelFilename: string, callback?: any) => {
cy.get('.buttonBar button:last-child')
.should('exist')
.click()
.then(() => {
cy.get('input[type="file"]#file-upload').attachFile(
`/${fixturePath}/${excelFilename}`
)
cy.get('.modal-footer .btn.btn-primary').then((modalBtn) => {
modalBtn.click()
if (callback) callback()
})
})
}
const submitExcel = (callback?: any) => {
cy.get('.buttonBar button.preview-submit', { timeout: longerCommandTimeout })
.click()
.then(() => {
if (callback) callback()
})
}
const rejectExcel = (callback?: any) => {
cy.get('button', { timeout: longerCommandTimeout })
.should('contain', 'Approve')
.then((allButtons: any) => {
for (let approvalButton of allButtons) {
if (approvalButton.innerText.toLowerCase().includes('approve')) {
approvalButton.click()
break
}
}
cy.get('button.btn-danger')
.should('exist')
.should('not.be.disabled')
.click()
.then(() => {
cy.get('.modal-footer button.btn-success-outline')
.click()
.then(() => {
cy.get('app-history')
.should('exist')
.then(() => {
if (callback) callback()
})
})
})
})
}
@@ -1,61 +0,0 @@
const username = Cypress.env('username')
const password = Cypress.env('password')
const hostUrl = Cypress.env('hosturl')
const appLocation = Cypress.env('appLocation')
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
const serverType = Cypress.env('serverType')
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
const fixturePath = 'excels_general/'
context('metanav tests: ', function () {
this.beforeAll(() => {
cy.visit(`${hostUrl}/SASLogon/logout`)
cy.loginAndUpdateValidKey()
})
this.beforeEach(() => {
cy.visit(hostUrl + appLocation)
cy.get('input.username').type(username)
cy.get('input.password').type(password)
cy.get('.login-group button').click()
visitPage('view/metadata')
})
it('1 | Opens metadata object', (done) => {
openFirstMetadataFromTree(() => {
// BLOCKER
// For unkown reasons, .clr-accordion-header-button always null although it is present on the page.
cy.get('.clr-accordion-header-button').then((panelNodes: any) => {
panelNodes[0].querySelector('button').click()
})
})
})
this.afterEach(() => {
cy.visit(`${hostUrl}/SASLogon/logout`)
})
})
const openFirstMetadataFromTree = (callback?: any) => {
cy.get('.app-loading', { timeout: longerCommandTimeout })
.should('not.exist')
.then(() => {
cy.get('.nav-tree clr-tree > clr-tree-node', {
timeout: longerCommandTimeout
}).then((treeNodes: any) => {
let firstMetaNode
firstMetaNode = treeNodes[1]
cy.get(firstMetaNode).within(() => {
cy.get('.clr-treenode-content').click()
callback()
})
})
})
}
const visitPage = (url: string) => {
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
}
-624
View File
@@ -1,624 +0,0 @@
import { cloneDeep } from 'lodash-es'
const username = Cypress.env('username')
const password = Cypress.env('password')
const hostUrl = Cypress.env('hosturl')
const appLocation = Cypress.env('appLocation')
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
const serverType = Cypress.env('serverType')
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
const fixturePath = 'excels_general/'
context('editor tests: ', function () {
this.beforeAll(() => {
cy.visit(`${hostUrl}/SASLogon/logout`)
cy.loginAndUpdateValidKey()
})
this.beforeEach(() => {
cy.visit(hostUrl + appLocation)
cy.wait(2000)
cy.get('body').then(($body) => {
const usernameInput = $body.find('input.username')[0]
if (usernameInput && !Cypress.dom.isHidden(usernameInput)) {
cy.get('input.username').type(username)
cy.get('input.password').type(password)
cy.get('.login-group button').click()
}
})
visitPage('home')
})
it('1 | Add one viewbox', (done) => {
const viewbox_table = 'mpe_audit'
const columns = ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM']
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
cy.get('.viewbox-open').click()
openTableFromViewboxTree(libraryToOpenIncludes, [viewbox_table])
cy.get('.open-viewbox').then((viewboxNodes: any) => {
for (let viewboxNode of viewboxNodes) {
if (!viewboxNode.innerText.toLowerCase().includes(viewbox_table)) {
return
}
checkColumns(columns, () => {
done()
})
}
})
})
it('2 | Add two viewboxes', (done) => {
const viewboxes = [
{
viewbox_table: 'mpe_audit',
columns: ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM']
},
{
viewbox_table: 'mpe_alerts',
columns: [
'TX_FROM',
'ALERT_EVENT',
'ALERT_LIB',
'ALERT_DS',
'ALERT_USER'
]
}
]
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
cy.get('.viewbox-open').click()
openTableFromViewboxTree(
libraryToOpenIncludes,
viewboxes.map((viewbox) => viewbox.viewbox_table)
)
cy.get('.open-viewbox').then((viewboxNodes: any) => {
let found = 0
for (let viewboxNode of viewboxNodes) {
for (let viewbox of viewboxes) {
if (
viewboxNode.innerText.toLowerCase().includes(viewbox.viewbox_table)
)
found++
}
}
if (found < viewboxes.length) return
cy.get('.viewboxes-container .viewbox').then((viewboxNodes: any) => {
for (let viewboxNode of viewboxNodes) {
cy.get(viewboxNode).within(() => {
cy.get('.table-title').then((tableTitle) => {
const title = tableTitle[0].innerText
const viewbox = viewboxes.find((vb) =>
title.toLowerCase().includes(vb.viewbox_table)
)
if (viewbox) {
cy.get('.ht_master.handsontable .htCore thead tr').then(
(viewboxColNodes: any) => {
let allColsHtml = viewboxColNodes[0].innerHTML
for (let col of viewbox?.columns) {
if (!allColsHtml.includes(col)) return
}
done()
}
)
}
})
})
}
})
})
})
it('3 | Add viewbox, add columns', (done) => {
const viewbox_table = 'mpe_audit'
const columns = ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM']
const additionalColumns = ['IS_PK']
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
cy.get('.viewbox-open').click()
openTableFromViewboxTree(libraryToOpenIncludes, [viewbox_table])
cy.get('.open-viewbox').then((viewboxNodes: any) => {
for (let viewboxNode of viewboxNodes) {
if (!viewboxNode.innerText.toLowerCase().includes(viewbox_table)) {
return
}
openViewboxConfig(viewbox_table)
addColumns(additionalColumns)
checkColumns([...columns, ...additionalColumns], () => {
done()
})
}
})
})
it('4 | Add viewbox, add columns and reorder', (done) => {
const viewbox_table = 'mpe_audit'
const columns = ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM']
const additionalColumns = ['IS_PK', 'MOVE_TYPE']
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
cy.get('.viewbox-open').click()
openTableFromViewboxTree(libraryToOpenIncludes, [viewbox_table])
cy.get('.open-viewbox').then((viewboxNodes: any) => {
for (let viewboxNode of viewboxNodes) {
if (!viewboxNode.innerText.toLowerCase().includes(viewbox_table)) {
return
}
openViewboxConfig(viewbox_table)
addColumns(additionalColumns, () => {
cy.wait(1000)
//reorder
cy.get('.col-box.column-MOVE_TYPE')
.realMouseDown({ button: 'left', position: 'center' })
.realMouseMove(0, 10, { position: 'center' })
cy.wait(200) // In our case, we wait 200ms cause we have animations which we are sure that take this amount of time
cy.get('.col-box.column-IS_PK')
.realMouseMove(0, 0, { position: 'center' })
.realMouseUp()
//reorder end
cy.wait(500)
checkColumns([...columns, ...additionalColumns.reverse()], () => {
done()
})
})
}
})
})
it('5 | Add viewbox, add columns, reorder, remove column, add again', (done) => {
const viewbox_table = 'mpe_audit'
const columns = ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM']
const additionalColumns = ['IS_PK', 'MOVE_TYPE']
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
cy.get('.viewbox-open').click()
openTableFromViewboxTree(libraryToOpenIncludes, [viewbox_table])
cy.get('.open-viewbox').then((viewboxNodes: any) => {
for (let viewboxNode of viewboxNodes) {
if (!viewboxNode.innerText.toLowerCase().includes(viewbox_table)) {
return
}
viewboxNode.click()
addColumns(additionalColumns, () => {
cy.wait(1000)
//reorder
cy.get('.col-box.column-MOVE_TYPE')
.realMouseDown({ button: 'left', position: 'center' })
.realMouseMove(0, 10, { position: 'center' })
cy.wait(200) // In our case, we wait 200ms cause we have animations which we are sure that take this amount of time
cy.get('.col-box.column-IS_PK')
.realMouseMove(0, 0, { position: 'center' })
.realMouseUp()
//reorder end
cy.wait(500)
checkColumns([...columns, ...additionalColumns.reverse()], () => {
const colToRemove = 'MOVE_TYPE'
removeColumn(colToRemove)
checkColumns(
[
...columns,
...additionalColumns.filter((col) => col !== colToRemove)
],
() => {
addColumns([colToRemove], () => {
checkColumns(
[...columns, ...additionalColumns.reverse()],
() => {
done()
}
)
})
}
)
})
})
}
})
})
it('6 | Add viewboxes, reload and check url restored configuration', (done) => {
const viewboxes = [
{
viewbox_table: 'mpe_audit',
columns: ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM'],
additionalColumns: ['IS_PK', 'MOVE_TYPE']
},
{
viewbox_table: 'mpe_alerts',
columns: [
'TX_FROM',
'ALERT_EVENT',
'ALERT_LIB',
'ALERT_DS',
'ALERT_USER'
],
additionalColumns: ['TX_TO']
}
]
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
cy.get('.viewbox-open').click()
openTableFromViewboxTree(libraryToOpenIncludes, [
viewboxes[0].viewbox_table,
viewboxes[1].viewbox_table
])
openViewboxConfig(viewboxes[0].viewbox_table)
cy.wait(500)
addColumns(viewboxes[0].additionalColumns, () => {
cy.wait(1000)
if (viewboxes[0].viewbox_table === 'mpe_audit') {
cy.get('.col-box.column-MOVE_TYPE')
.realMouseDown({ button: 'left', position: 'center' })
.realMouseMove(0, 10, { position: 'center' })
cy.wait(200) // In our case, we wait 200ms cause we have animations which we are sure that take this amount of time
cy.get('.col-box.column-IS_PK')
.realMouseMove(0, 0, { position: 'center' })
.realMouseUp()
}
cy.wait(1000)
openViewboxConfig(viewboxes[1].viewbox_table)
addColumns(viewboxes[1].additionalColumns, () => {
cy.wait(1000).reload()
let result = 0
checkColumns(
[
...viewboxes[0].columns,
...cloneDeep(viewboxes[0].additionalColumns.reverse())
],
() => {
result++
if (result === 2) done()
}
)
checkColumns(
[...viewboxes[1].columns, ...viewboxes[1].additionalColumns],
() => {
result++
if (result === 2) done()
}
)
})
})
})
it('7 | Add viewboxes and filter', () => {
const viewboxes = ['mpe_x_test', 'mpe_validations']
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
cy.get('.viewbox-open').click()
openTableFromViewboxTree(libraryToOpenIncludes, viewboxes)
cy.wait(1000)
closeViewboxModal()
cy.get('.viewboxes-container .viewbox', { withinSubject: null }).then(
(viewboxNodes: any) => {
for (let viewboxNode of viewboxNodes) {
cy.get(viewboxNode).within(() => {
cy.get('.table-title').then((title: any) => {
cy.get('.hot-spinner')
.should('not.exist')
.then(() => {
cy.get('clr-icon[shape="filter"]').then((filterButton) => {
filterButton[0].click()
})
if (title[0].innerText.includes('MPE_X_TEST')) {
setFilterWithValue(
'SOME_CHAR',
'this is dummy data',
'value',
() => {
cy.get('app-query', { withinSubject: null })
.should('not.exist')
.get('.ht_master.handsontable tbody tr')
.then((rowNodes) => {
const tr = rowNodes[0]
expect(rowNodes).to.have.length(1)
expect(tr.innerText).to.equal('0')
})
}
)
} else if (title[0].innerText.includes('MPE_VALIDATIONS')) {
setFilterWithValue('BASE_COL', 'ALERT_LIB', 'value', () => {
cy.get('app-query', { withinSubject: null })
.should('not.exist')
.get('.ht_master.handsontable tbody tr')
.then((rowNodes) => {
const tr = rowNodes[0]
expect(rowNodes).to.have.length(1)
expect(tr.innerText).to.contain('ALERT_LIB')
})
})
}
})
})
})
}
}
)
})
})
const checkColumns = (columns: string[], callback: () => void) => {
cy.get('.viewboxes-container .viewbox', { withinSubject: null }).then(
(viewboxNodes: any) => {
for (let viewboxNode of viewboxNodes) {
cy.get(viewboxNode).within(() => {
cy.get('.ht_master.handsontable thead tr th').then(
(viewboxColNodes: any) => {
for (let i = 0; i < viewboxColNodes.length; i++) {
const col = columns[i]
const colNode = viewboxColNodes[i]
if (
!colNode.innerHTML.toLowerCase().includes(col.toLowerCase())
)
return
}
callback()
}
)
})
}
}
)
}
const closeViewboxModal = () => {
cy.get('app-viewboxes .close', { withinSubject: null }).click()
}
const removeColumn = (column: string) => {
cy.get(`.col-box.column-${column} clr-icon`, { withinSubject: null }).click()
}
const addColumns = (columns: string[], callback?: () => void) => {
for (let i = 0; i < columns.length; i++) {
const column = columns[i]
cy.get('.cols-search input', { withinSubject: null }).type(column)
cy.get('.cols-search .autocomplete-wrapper', { withinSubject: null })
.first()
.trigger('keydown', { key: 'ArrowDown' })
cy.get('.cols-search .autocomplete-wrapper', { withinSubject: null })
.first()
.trigger('keydown', { key: 'Enter' })
.then(() => {
if (i === columns.length - 1 && callback) callback()
})
}
}
const openViewboxConfig = (viewbox_tablename: string) => {
cy.get('.open-viewbox').then((viewboxes: any) => {
for (let openViewbox of viewboxes) {
if (openViewbox.innerText.toLowerCase().includes(viewbox_tablename))
openViewbox.click()
}
})
}
const openTableFromTree = (libNameIncludes: string, tablename: string) => {
cy.get('.app-loading', { timeout: longerCommandTimeout })
.should('not.exist')
.then(() => {
cy.get('.nav-tree clr-tree > clr-tree-node', {
timeout: longerCommandTimeout
}).then((treeNodes: any) => {
let viyaLib
for (let node of treeNodes) {
if (new RegExp(libNameIncludes).test(node.innerText.toLowerCase())) {
viyaLib = node
break
}
}
cy.get(viyaLib).within(() => {
cy.get('.clr-tree-node-content-container p').click()
cy.get('.clr-treenode-link').then((innerNodes: any) => {
for (let innerNode of innerNodes) {
if (innerNode.innerText.toLowerCase().includes(tablename)) {
innerNode.click()
break
}
}
})
})
})
})
}
const setFilterWithValue = (
variableValue: string,
valueString: string,
valueField: 'value' | 'time' | 'date' | 'datetime' | 'in' | 'between',
callback?: any
) => {
cy.wait(600)
cy.focused().type(variableValue)
cy.wait(100)
// cy.focused().trigger('input')
cy.get('.variable-col .autocomplete-wrapper', { withinSubject: null })
.first()
.trigger('keydown', { key: 'ArrowDown' })
cy.get('.variable-col .autocomplete-wrapper', {
withinSubject: null
}).trigger('keydown', { key: 'Enter' })
cy.focused().tab()
cy.wait(100)
if (valueField === 'in') {
cy.focused().select(valueField.toUpperCase()).trigger('change')
} else if (valueField === 'between') {
cy.focused().select(valueField.toUpperCase()).trigger('change')
} else {
cy.focused().tab()
cy.wait(100)
}
switch (valueField) {
case 'value': {
cy.focused().type(valueString)
break
}
case 'time': {
cy.focused().type(valueString)
break
}
case 'date': {
cy.focused().type(valueString)
cy.focused().tab()
break
}
case 'datetime': {
const date = valueString.split(' ')[0]
const time = valueString.split(' ')[1]
cy.focused().type(date)
cy.focused().tab()
cy.focused().tab()
cy.focused().type(time)
break
}
case 'in': {
cy.get('.checkbox-vals').then(() => {
cy.focused().tab()
cy.focused().click()
cy.get('.no-values')
.should('not.exist')
.then(() => {
cy.get('clr-checkbox-wrapper input').then((inputs: any) => {
inputs[0].click()
cy.get('.in-values-modal .modal-footer button').click()
cy.get('.modal-footer .btn-success-outline').click()
if (callback) callback()
})
})
})
break
}
case 'between': {
cy.focused().tab()
const start = valueString.split('-')[0]
const end = valueString.split('-')[1]
cy.focused().type(start)
cy.focused().tab()
cy.focused().type(end)
}
default: {
break
}
}
cy.wait(100)
cy.focused().tab()
cy.wait(100)
cy.focused().tab()
cy.wait(100)
cy.focused().tab()
cy.wait(100)
cy.focused().tab()
cy.wait(100)
cy.focused().click()
if (callback) callback()
}
const openTableFromViewboxTree = (
libNameIncludes: string,
tablenames: string[]
) => {
cy.get('.add-new clr-tree > clr-tree-node', {
timeout: longerCommandTimeout
}).then((treeNodes: any) => {
let viyaLib
for (let node of treeNodes) {
if (node.innerText.toLowerCase().includes(libNameIncludes)) {
viyaLib = node
break
}
}
cy.get(viyaLib).within(() => {
cy.get('p')
.click()
.then(() => {
cy.get('.clr-treenode-link').then((innerNodes: any) => {
for (let innerNode of innerNodes) {
for (let tablename of tablenames) {
if (innerNode.innerText.toLowerCase().includes(tablename)) {
innerNode.click()
}
}
}
})
})
})
})
}
const visitPage = (url: string) => {
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
}
+13 -1
View File
@@ -20,4 +20,16 @@ import './commands'
// require('./commands')
import 'cypress-plugin-tab'
import "cypress-real-events"
import 'cypress-real-events'
// Pin the locale
Cypress.on('window:before:load', (win) => {
Object.defineProperty(win.navigator, 'language', {
value: 'en-GB',
configurable: true
})
Object.defineProperty(win.navigator, 'languages', {
value: ['en-GB'],
configurable: true
})
})
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -10,7 +10,7 @@ const check = (cwd) => {
onlyAllow:
'AFLv2.1;Apache 2.0;Apache-2.0;Apache*;Artistic-2.0;0BSD;BSD*;BSD-2-Clause;BSD-3-Clause;CC0-1.0;CC-BY-3.0;CC-BY-4.0;ISC;MIT;MPL-2.0;ODC-By-1.0;Python-2.0;Unlicense;',
excludePackages:
'@cds/city@1.1.0;@handsontable/angular@14.6.2;handsontable@14.6.2;hyperformula@2.7.1;jackspeak@3.4.3;path-scurry@1.11.1;package-json-from-dist@1.0.1'
'@cds/city@1.1.0;@handsontable/angular-wrapper@16.0.1;@handsontable/angular-wrapper@17.1.0;handsontable@^16.0.1;handsontable@16.2.0;handsontable@17.1.0;hyperformula@2.7.1;hyperformula@3.0.0;hyperformula@3.1.0;hyperformula@3.2.0;hyperformula@3.3.0;jackspeak@3.4.3;path-scurry@1.11.1;package-json-from-dist@1.0.1;buffers@0.1.1'
},
(error, json) => {
if (error) {
+46
View File
@@ -0,0 +1,46 @@
module.exports = {
ci: {
collect: {
settings: {
preset: 'desktop',
chromeFlags: '--no-sandbox --disable-dev-shm-usage'
},
url: [
'http://localhost:5000/AppStream/clickme/#/home/tables',
'http://localhost:5000/AppStream/clickme/#/editor/DC996664.MPE_X_TEST',
'http://localhost:5000/AppStream/clickme/#/view/data',
'http://localhost:5000/AppStream/clickme/#/view/data/DC996664',
'http://localhost:5000/AppStream/clickme/#/view/data/DC996664.MPE_X_TEST',
'http://localhost:5000/AppStream/clickme/#/view/usernav/groups',
'http://localhost:5000/AppStream/clickme/#/view/usernav/groups',
'http://localhost:5000/AppStream/clickme/#/view/usernav/groups/1',
'http://localhost:5000/AppStream/clickme/#/view/usernav/users/1',
'http://localhost:5000/AppStream/clickme/#/home/excel-maps',
'http://localhost:5000/AppStream/clickme/#/home/excel-maps/BASEL-CR2',
'http://localhost:5000/AppStream/clickme/#/home/multi-load',
'http://localhost:5000/AppStream/clickme/#/review/submitted',
'http://localhost:5000/AppStream/clickme/#/review/approve',
'http://localhost:5000/AppStream/clickme/#/review/history',
'http://localhost:5000/AppStream/clickme/#/stage/DC20221006T142649516_059582_7169',
'http://localhost:5000/AppStream/clickme/#/review/submitted/DC20221006T142649516_059582_7169',
'http://localhost:5000/AppStream/clickme/#/system'
]
},
assert: {
assertions: {
'categories:accessibility': [
'error',
{ minScore: 1, aggregationMethod: 'median' }
],
'categories:performance': [
'error',
{ minScore: 0.4, aggregationMethod: 'median' }
]
}
},
upload: {
target: 'filesystem',
outputDir: './lighthouse-reports'
}
}
}
+11414 -6939
View File
File diff suppressed because it is too large Load Diff
+45 -36
View File
@@ -23,7 +23,7 @@
"watch": "ng test watch=true",
"pree2e": "webdriver-manager update",
"e2e": "protractor protractor.config.js",
"postinstall": "node ./src/version.ts && npm run add-githook",
"postinstall": "node ./src/version.ts && npm run add-githook && node ./scripts/strip-clr-base64-fonts.mjs && node ./scripts/gen-hot-icons.mjs",
"add-githook": "[ -d ../.git ] && git config core.hooksPath ./.git-hooks || true",
"cypress": "cypress open",
"cy:run": "cypress run",
@@ -31,26 +31,28 @@
"sasdocs": "sasjs doc && ./sasjs/utils/deploydocs.sh",
"compodoc:build": "compodoc -p tsconfig.doc.json --name 'Data Controller Client'",
"compodoc:build-and-serve": "compodoc -p tsconfig.doc.json -s --name 'Data Controller Client'",
"compodoc:serve": "compodoc -s --name 'Data Controller Client'"
"compodoc:serve": "compodoc -s --name 'Data Controller Client'",
"lighthouse": "lhci autorun",
"ng": "ng"
},
"private": true,
"dependencies": {
"@angular/animations": "^17.3.3",
"@angular/cdk": "^17.3.3",
"@angular/common": "^17.3.3",
"@angular/compiler": "^17.3.3",
"@angular/core": "^17.3.3",
"@angular/forms": "^17.3.3",
"@angular/platform-browser": "^17.3.3",
"@angular/platform-browser-dynamic": "^17.3.3",
"@angular/router": "^17.3.3",
"@cds/core": "^6.10.0",
"@clr/angular": "^17.0.1",
"@angular/animations": "^19.2.20",
"@angular/cdk": "^19.2.19",
"@angular/common": "^19.2.20",
"@angular/compiler": "^19.2.20",
"@angular/core": "^19.2.20",
"@angular/forms": "^19.2.20",
"@angular/platform-browser": "^19.2.20",
"@angular/platform-browser-dynamic": "^19.2.20",
"@angular/router": "^19.2.20",
"@cds/core": "^6.15.1",
"@clr/angular": "file:libraries/clr-angular-17.9.0.tgz",
"@clr/icons": "^13.0.2",
"@clr/ui": "^17.0.1",
"@handsontable/angular": "^14.3.0",
"@sasjs/adapter": "^4.11.0",
"@sasjs/utils": "^3.4.0",
"@clr/ui": "file:libraries/clr-ui-17.9.0.tgz",
"@handsontable/angular-wrapper": "^17.1.0",
"@sasjs/adapter": "^4.17.0",
"@sasjs/utils": "^3.5.3",
"@sheet/crypto": "file:libraries/sheet-crypto.tgz",
"@types/d3-graphviz": "^2.6.7",
"@types/text-encoding": "0.0.35",
@@ -59,15 +61,16 @@
"crypto-browserify": "^3.12.1",
"crypto-js": "^4.2.0",
"d3-graphviz": "^5.0.2",
"exceljs": "^4.4.0",
"fs-extra": "^7.0.1",
"handsontable": "^14.3.0",
"handsontable": "^17.1.0",
"https-browserify": "1.0.0",
"hyperformula": "^2.5.0",
"iconv-lite": "^0.5.0",
"jquery-datetimepicker": "^2.5.21",
"jsrsasign": "^10.2.0",
"jsrsasign": "11.1.1",
"marked": "^5.0.0",
"moment": "^2.26.0",
"moment": "^2.30.1",
"ngx-clipboard": "^16.0.0",
"ngx-json-viewer": "file:libraries/ngx-json-viewer-3.2.1.tgz",
"nodejs": "0.0.0",
@@ -80,21 +83,22 @@
"tslib": "^2.3.0",
"vm": "^0.1.0",
"webpack": "^5.91.0",
"xlsx": "^0.18.5",
"zone.js": "~0.14.4"
"xlsx": "file:libraries/xlsx-0.20.3.tgz",
"zone.js": "~0.15.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "^17.3.3",
"@angular-eslint/builder": "17.3.0",
"@angular-eslint/eslint-plugin": "17.3.0",
"@angular-eslint/eslint-plugin-template": "17.3.0",
"@angular-eslint/schematics": "17.3.0",
"@angular-eslint/template-parser": "17.3.0",
"@angular/cli": "^17.3.3",
"@angular/compiler-cli": "^17.3.3",
"@angular-devkit/build-angular": "^19.2.24",
"@angular-eslint/builder": "19.8.1",
"@angular-eslint/eslint-plugin": "19.8.1",
"@angular-eslint/eslint-plugin-template": "19.8.1",
"@angular-eslint/schematics": "19.8.1",
"@angular-eslint/template-parser": "19.8.1",
"@angular/cli": "^19.2.24",
"@angular/compiler-cli": "^19.2.20",
"@babel/plugin-proposal-private-methods": "^7.18.6",
"@compodoc/compodoc": "^1.1.21",
"@compodoc/compodoc": "^1.2.1",
"@cypress/webpack-preprocessor": "^5.17.1",
"@lhci/cli": "^0.15.1",
"@types/core-js": "^2.5.5",
"@types/crypto-js": "^4.2.1",
"@types/es6-shim": "^0.31.39",
@@ -102,15 +106,15 @@
"@types/lodash-es": "^4.17.3",
"@types/marked": "^4.3.0",
"@types/node": "12.20.50",
"@typescript-eslint/eslint-plugin": "^5.29.0",
"@typescript-eslint/parser": "^5.29.0",
"@typescript-eslint/eslint-plugin": "8.31.1",
"@typescript-eslint/parser": "8.31.1",
"core-js": "^2.5.4",
"cypress": "12.17.1",
"cypress": "^15.14.2",
"cypress-file-upload": "^5.0.8",
"cypress-plugin-tab": "^1.0.5",
"cypress-real-events": "^1.8.1",
"es6-shim": "^0.35.5",
"eslint": "^8.33.0",
"eslint": "8.57.1",
"git-describe": "^4.0.4",
"jasmine-core": "~5.1.2",
"karma": "~6.4.3",
@@ -126,8 +130,13 @@
"rimraf": "3.0.2",
"ts-loader": "^9.2.8",
"ts-node": "^3.3.0",
"typescript": "~5.4.4",
"typescript": "~5.8.3",
"wait-on": "^6.0.1",
"watch": "^1.0.2"
},
"overrides": {
"ajv": "8.18.0",
"uuid": "11.1.1",
"lighthouse": "13.4.0"
}
}
+69
View File
@@ -0,0 +1,69 @@
import { readFileSync, writeFileSync, mkdirSync, rmSync, existsSync } from 'fs'
import { resolve, join } from 'path'
import { createRequire } from 'module'
/**
* Generate static SVG assets + an SCSS partial that re-applies HOT v17 classic
* theme icons via real URLs (not data: URIs).
*
* Why: deployed app runs under CSP `img-src 'self'`. HOT v17's classic theme
* embeds icons as `data:image/svg+xml,...` in `-webkit-mask-image` rules, which
* the CSP blocks. We switch to `ht-theme-classic-no-icons.min.css` and re-add
* the icon rules pointing at same-origin SVG files emitted from this script.
*
* Inputs (HOT's own modules, so semantic names + selector list track upstream):
* handsontable/themes/theme/classic → { classicTheme: { icons } }
* handsontable/themes/static/variables/helpers/iconsMap → iconsMap(icons, themePrefix)
*
* Outputs:
* client/src/assets/hot-icons/<kebab-name>.svg
* client/src/_hot-icons.scss
*
* Idempotent: clears the output dir and rewrites both outputs each run.
* Skips silently if handsontable isn't installed yet (pre-install runs).
*/
const require = createRequire(import.meta.url)
const ASSETS_DIR = resolve('src/assets/hot-icons')
const SCSS_OUT = resolve('src/_hot-icons.scss')
const ASSET_URL_PREFIX = './assets/hot-icons/'
const themePath = resolve('node_modules/handsontable/themes/theme/classic.js')
const mapPath = resolve('node_modules/handsontable/themes/static/variables/helpers/iconsMap.js')
if (!existsSync(themePath) || !existsSync(mapPath)) {
console.log('skip: handsontable theme modules not found (likely pre-install run)')
process.exit(0)
}
const { classicTheme } = require(themePath)
const { iconsMap } = require(mapPath)
const icons = classicTheme.icons
const cssTemplate = iconsMap(icons, 'ht-theme-classic')
const kebab = (s) => s.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
rmSync(ASSETS_DIR, { recursive: true, force: true })
mkdirSync(ASSETS_DIR, { recursive: true })
const writeMap = {}
for (const [name, dataUri] of Object.entries(icons)) {
if (typeof dataUri !== 'string' || !dataUri.startsWith('data:image/svg+xml')) continue
const decoded = decodeURIComponent(dataUri.replace(/^data:image\/svg\+xml(;charset=utf-8)?,/, ''))
const fname = kebab(name) + '.svg'
writeFileSync(join(ASSETS_DIR, fname), decoded)
writeMap[dataUri] = ASSET_URL_PREFIX + fname
}
let scss = cssTemplate
for (const [uri, url] of Object.entries(writeMap)) {
scss = scss.split(`url("${uri}")`).join(`url("${url}")`)
}
const header = '/* Auto-generated by scripts/gen-hot-icons.mjs — do not edit by hand.\n' +
' Regenerated on postinstall; rerun manually via `node scripts/gen-hot-icons.mjs`. */\n\n'
writeFileSync(SCSS_OUT, header + scss + '\n')
console.log(`hot-icons: wrote ${Object.keys(writeMap).length} SVGs + ${SCSS_OUT}`)
+59
View File
@@ -0,0 +1,59 @@
import { readFileSync, writeFileSync, statSync, rmSync, existsSync } from 'fs'
import { resolve } from 'path'
/**
* Remove Clarity's Metropolis @font-face blocks from clr-ui.min.css.
*
* Why: Clarity ships Metropolis as base64 data: URLs. The deployed app
* runs under CSP `default-src 'self'` (no data: font-src), so every page
* logs a font-load failure for each weight. Firefox preemptively
* validates every parsed src against CSP even when a later @font-face
* supersedes the rule at render time, so the only way to silence the
* console is to remove the offending blocks from the parsed CSS.
*
* Our styles.scss declares the same family/weight/style with same-origin
* .woff files, so removing Clarity's blocks entirely is safe and leaves
* Metropolis fully functional.
*
* Idempotent: matches by font-family, so works on a fresh install or a
* file that's already been stripped on a previous run.
*/
const target = resolve('node_modules/@clr/ui/clr-ui.min.css')
let css
try {
css = readFileSync(target, 'utf8')
} catch (err) {
if (err.code === 'ENOENT') {
console.log(`skip: ${target} not found (likely pre-install run)`)
process.exit(0)
}
throw err
}
const sizeBefore = statSync(target).size
const blockRe = /@font-face\{[^}]*Metropolis[^}]*\}/g
const matches = css.match(blockRe) ?? []
if (matches.length === 0) {
console.log(`already stripped: ${target}`)
process.exit(0)
}
const stripped = css.replace(blockRe, '')
writeFileSync(target, stripped)
const sizeAfter = Buffer.byteLength(stripped)
console.log(
`removed ${matches.length} Metropolis @font-face block(s) from clr-ui.min.css ` +
`(${sizeBefore} -> ${sizeAfter} bytes, saved ${sizeBefore - sizeAfter})`
)
// Webpack 5's persistent cache treats node_modules as immutable
// (snapshot.module.managedPaths default), so in-place edits don't
// invalidate cached entries. Drop the Angular build cache so the next
// build re-reads our stripped clr-ui.min.css.
const cacheDir = resolve('.angular/cache')
if (existsSync(cacheDir)) {
rmSync(cacheDir, { recursive: true, force: true })
console.log(`cleared ${cacheDir} (webpack persistent cache)`)
}
+238
View File
@@ -0,0 +1,238 @@
/* Auto-generated by scripts/gen-hot-icons.mjs — do not edit by hand.
Regenerated on postinstall; rerun manually via `node scripts/gen-hot-icons.mjs`. */
[class*=ht-theme-classic] .htDropdownMenu table tbody tr td.htSubmenu .htItemWrapper::after,
[class*=ht-theme-classic] .htContextMenu table tbody tr td.htSubmenu .htItemWrapper::after,
[class*=ht-theme-classic] .htFiltersConditionsMenu table tbody tr td.htSubmenu .htItemWrapper::after,
[class*=ht-theme-classic] .pika-single .pika-next {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/arrow-right.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] .pika-single .pika-prev {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/arrow-left.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] .ht-page-size-section__select-wrapper::after {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/arrow-down.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] .changeType::before {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/select-arrow.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] .htUISelectCaption::after,
.htAutocompleteArrow::after {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/select-arrow.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] .columnSorting.sortAction.ascending::before {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/arrow-narrow-up.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] .columnSorting.sortAction.descending::before {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/arrow-narrow-down.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] .ht-page-navigation-section .ht-page-first::before {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/arrow-left-with-bar.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] [dir="rtl"] .ht-page-navigation-section .ht-page-first::before {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/arrow-right-with-bar.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] .ht-page-navigation-section .ht-page-prev::before {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/arrow-left.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] [dir="rtl"] .ht-page-navigation-section .ht-page-prev::before {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/arrow-right.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] .ht-page-navigation-section .ht-page-next::before {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/arrow-right.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] [dir="rtl"] .ht-page-navigation-section .ht-page-next::before {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/arrow-left.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] .ht-page-navigation-section .ht-page-last::before {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/arrow-right-with-bar.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] [dir="rtl"] .ht-page-navigation-section .ht-page-last::before {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/arrow-left-with-bar.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] .htDropdownMenu table tbody tr td .htItemWrapper span.selected::after,
[class*=ht-theme-classic] .htContextMenu table tbody tr td .htItemWrapper span.selected::after,
[class*=ht-theme-classic] .htFiltersConditionsMenu table tbody tr td .htItemWrapper span.selected::after {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/check.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] .htCheckboxRendererInput {
appearance: none;
}
[class*=ht-theme-classic] .htCheckboxRendererInput::after {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/checkbox.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] th.beforeHiddenColumn::after {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/caret-hidden-left.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] th.afterHiddenColumn::before {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/caret-hidden-right.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] th.beforeHiddenRow::after {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/caret-hidden-up.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] th.afterHiddenRow::before {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/caret-hidden-down.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] .collapsibleIndicator::before,
[class*=ht-theme-classic] .ht_nestingButton::before {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/collapse-off.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] .collapsibleIndicator.collapsed::before,
[class*=ht-theme-classic] .ht_nestingButton.ht_nestingExpand::before {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/collapse-on.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] .htUIRadio > input[type="radio"]::after {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/radio.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] .ht-multi-select-chip-remove::before {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/chip-close.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] .ht-notification__close::before {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/chip-close.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] .ht-multi-select-editor-search-icon {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/search.svg");
background-color: currentColor;
}
[class*=ht-theme-classic] .ht-multi-select-editor-item-selected input::after {
width: var(--ht-icon-size);
height: var(--ht-icon-size);
-webkit-mask-size: contain;
-webkit-mask-image: url("./assets/hot-icons/checkbox.svg");
background-color: currentColor;
}
+5
View File
@@ -55,6 +55,7 @@ export interface HandsontableStaticConfig {
* Cached viyaApi collections, search and selected endpoint
*/
export const globals: {
embed: boolean
rootParam: string
dcLib: string
xlmaps: XLMapListItem[]
@@ -69,6 +70,7 @@ export const globals: {
handsontable: HandsontableStaticConfig
[key: string]: any
} = {
embed: false,
rootParam: <string>'',
dcLib: '',
xlmaps: [],
@@ -148,5 +150,8 @@ export const globals: {
},
handsontable: {
darkTableHeaderClass: 'darkTH'
},
userDropdownConfig: {
closeOnDebugClick: false
}
}
+18 -7
View File
@@ -107,7 +107,7 @@
</div>
</ng-container>
<header class="app-header">
<header class="app-header" *ngIf="!embed">
<!-- <button
*ngIf="
isMainRoute('view') ||
@@ -127,9 +127,10 @@
"
(click)="toggleSidebar()"
type="button"
aria-label="Toggle sidebar"
class="cursor-pointer select-none ml-10 d-flex clr-justify-content-center clr-align-items-center"
>
<clr-icon size="24" shape="tree-view"></clr-icon>
<clr-icon size="24" shape="tree-view" aria-hidden="true"></clr-icon>
</div>
<div class="logo d-flex clr-align-items-center">
@@ -139,10 +140,15 @@
[routerLink]="['/']"
class="nav-link"
>
<img class="without-text d-block d-md-none" src="images/dc-logo.svg" />
<img
class="without-text d-block d-md-none"
src="images/dc-logo.svg"
alt="datacontroller logo without text"
/>
<img
class="with-text d-none d-md-block"
src="images/datacontroller.svg"
alt="datacontroller logo"
/>
</a>
@@ -208,9 +214,10 @@
</header>
<nav
*ngIf="
router.url.includes('submitted') ||
router.url.includes('approve') ||
router.url.includes('history')
!embed &&
(router.url.includes('submitted') ||
router.url.includes('approve') ||
router.url.includes('history'))
"
class="subnav"
>
@@ -283,7 +290,11 @@
<!-- App Loading Page -->
<div *ngIf="!startupDataLoaded" class="app-loading">
<img class="loading-logo" src="images/datacontroller.svg" />
<img
class="loading-logo"
src="images/datacontroller.svg"
alt="datacontroller logo"
/>
<div *ngIf="appActive === null" class="slider">
<div class="line"></div>
-447
View File
@@ -1,447 +0,0 @@
@import '../colors.scss';
// Copyright (c) 2016 VMware, Inc. All Rights Reserved.
// This software is released under MIT license.
// The full license information can be found in LICENSE in the root directory of this project.
app-requests-modal {
z-index: 10000;
}
header.app-header {
background: $headerBackground !important;
color: #fff;
}
.logo img.without-text {
width: 30px;
}
.logo img.with-text {
width: 210px;
}
.header-hamburger-trigger {
display: block;
background: transparent;
border: 0;
margin-left: 10px;
}
.demo-expired-notice {
display: flex;
justify-content: center;
align-items: center;
position: fixed;
left: 0;
top: 0;
height: 100vh !important;
width: 100vw !important;
z-index: 105;
background: rgba(33, 33, 33, .5);
.expired-details {
flex-direction: column;
align-items: center;
padding: 30px;
z-index: 110;
background: $headerBackground;
.expired-notice {
color: #e0e0e0;
font-size: 16px;
.mailto {
color: #8dc53e;
}
}
}
}
.main-container .update-key {
display: flex;
align-items: center;
color: white;
padding: 0px 10px;
background: #00000026;
}
.alert-icon-wrapper {
margin-top: 0 !important;
}
.nav-text {
margin-right: 20px;
}
.sidebar-toggle {
display: flex;
height: 100%;
align-items: center;
padding-left: 10px;
clr-icon {
cursor: pointer;
width: 30px;
height: 30px;
}
}
header {
.header-actions {
.dropdown {
position: unset; //without it, when opening user dropdown scrollbar was displaying without reason
}
}
.nav-link:hover {
color: #fafafa;
}
.nav-link.active {
background: #61717D;
}
}
.notf {
background: #16a57a;
color: #fffcfc;
font-size: 12px;
}
.toggle-switch input[type=checkbox]:checked+label:before {
border-color: #61717D;
background-color: #61717D;
transition: .15s ease-in;
transition-property: border-color,background-color;
}
.main-container {
min-height: 100vh !important;
}
.main-container .content-container .content-area {
padding: 0rem 1rem 1rem 1rem;
}
.content-container {
z-index: 0!important;
}
.navBarResp {
display: flex;
justify-content: center;
background: #495A67;
color: #fff;
}
::ng-deep {
.htInvalid {
background: black!important;
}
@media screen and (max-width:480px) {
h2 {
font-size: .7rem!important;
}
h3 {
font-size: .7rem;
}
}
.nav-link {
padding: 0rem 1rem 0rem 1rem;
}
body[cds-theme="light"] {
.btn-primary .btn, .btn.btn-primary {
border-color: $headerBackground;
background-color: $headerBackground;
color: #fff;
}
}
body[cds-theme="dark"] {
.btn-primary .btn, .btn.btn-primary {
border-color: #5e7382;
background-color: #5e7382;
color: #fff;
clr-icon, cds-icon {
color: #fff
}
}
}
.btn-primary .btn, .btn.btn-primary {
&:disabled {
opacity: 0.65;
}
}
.btn {
cursor: pointer;
display: inline-block;
-webkit-appearance: none!important;
border-radius: .125rem;
border: 1px solid;
min-width: 3rem;
max-width: 15rem;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
text-align: center;
text-transform: uppercase;
vertical-align: middle;
line-height: 1.5rem;
letter-spacing: .12em;
font-size: .5rem;
font-weight: 500;
height: 1.5rem;
padding: 0 .5rem;
}
.btn.btn-outline:hover {
border-color: $headerBackground;
background-color: #495A67;
color: #fff;
}
body[cds-theme="dark"] {
.btn.btn-icon.btn-dimmed {
color: #7295ae;
}
}
body[cds-theme="light"] {
.btn.btn-icon.btn-dimmed {
color: $headerBackground;
}
.btn.btn-outline {
border-color: $headerBackground;
background-color: transparent;
color: $headerBackground;
}
}
.htMobileEditorContainer .inputs textarea {
font-size: 13pt;
border: 2px solid #485967;
border-radius: 4px;
-webkit-appearance: none;
box-shadow: none;
position: absolute;
left: 14px;
right: 0px;
top: 0;
bottom: 0;
padding: 7pt;
width: 290px;
}
.htMobileEditorContainer .positionControls {
width: 333px;
position: absolute;
right: 5pt;
top: 50px;
bottom: 0;
display: flex;
justify-content: center;
}
.htMobileEditorContainer.active {
display: block;
height: 120px;
width: 350px;
}
/* Left and right */
/* Column headers */
body[cds-theme="light"] {
.wtBorder {
background-color: #495A67!important;
}
.ht_master tr:nth-of-type(odd) > td {
filter: brightness(0.95);
}
}
$darkBorderColor: #697c85;
body[cds-theme="dark"] {
.ht_master tr:nth-of-type(odd) > td {
filter: brightness(1.2);
}
.ht_master:not(.emptyColumns) ~ .handsontable tbody tr th, .ht_master:not(.emptyColumns) ~ .handsontable:not(.ht_clone_top) thead tr th:first-child {
background-color: #2d4048;
border-color: $darkBorderColor;
}
.handsontable td {
// border-right: 1px solid #697c85;
// border-bottom: 1px solid #697c85;
border-color: $darkBorderColor;
}
.handsontable tr:first-child th, .handsontable tr:first-child td {
border-color: $darkBorderColor;
}
.handsontable .handsontable.ht_clone_top .wtHider {
border-color: $darkBorderColor;
}
.handsontable .changeType {
background-color: #3c5662;
border-color: $darkBorderColor;
}
.handsontableInput {
background-color: #708b98;
}
}
.handsontable .handsontable.ht_clone_top .wtHider {
padding: 0 0 0px 0!important;
margin: 0px;
border-bottom: 3px solid #d6d3d3;
}
body[cds-theme="light"] {
.content-container {
// background: red;
background: #F5F6FF;
}
}
.datagrid-compact, .datagrid-history{
.datagrid {
border-collapse: separate;
border: 1px solid transparent;
border-radius: .125rem;
margin: 0;
margin-top: 1rem;
max-width: 100%;
width: 100%;
padding: 15px 15px 50px 15px;
}
.datagrid-foot {
-webkit-box-pack: end;
-ms-flex-pack: end;
justify-content: flex-end;
height: 1.5rem;
padding: 0 .5rem;
line-height: calc(1.5rem - 3px);
font-size: .45833rem;
background-color: #fff;
border-top: 1px solid #ccc;
border-radius: 0px;
// border-radius: 0 0 .125rem .125rem;
}
.datagrid-footer {
position: absolute;
right: 30px;
top: 1px;
}
.datagrid .datagrid-head {
background-color: #fff;
border-bottom: 1px solid #ccc;
}
}
.dropdown-menu {
position: absolute;
top: 100%;
left: 0;
margin-top: .083333rem;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
padding: .5rem 0;
border: 1px solid #ccc;
box-shadow: 0 1px 0.125rem hsla(0,0%,45%,.25);
min-width: 5rem;
max-width: 15rem;
border-radius: .125rem;
visibility: hidden;
z-index: 1000;
}
.table {
border-collapse: separate;
border: 1px solid transparent;
border-radius: 0px;
margin: 0;
margin-top: 1rem;
max-width: 100%;
width: 100%;
}
.table th {
font-size: .45833rem;
font-weight: 600;
letter-spacing: .03em;
vertical-align: bottom;
border-bottom: 1px solid #ccc;
text-transform: uppercase;
}
.modal-header {
border-bottom: 2px solid #e4e4e4;
padding: 0 0 .5rem 0;
margin-bottom: 1rem;
}
.main-container .content-container {
min-height: 0px;
position: relative;
}
}
.app-loading {
.loading-logo {
max-width: 400px;
width: 100%;
}
}
@media screen and (max-width: 768px) {
.navBarResp {
display: flex;
justify-content: flex-start;
background: #495A67;
color: #fff;
}
.main-container .sub-nav.clr-nav-level-1 .nav .nav-link, .main-container .sub-nav.clr-nav-level-2 .nav .nav-link, .main-container .subnav.clr-nav-level-1 .nav .nav-link, .main-container .subnav.clr-nav-level-2 .nav .nav-link {
padding: 0 .5rem 0 1rem;
width: 100%;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
border-radius: .125rem 0 0 .125rem;
color: #95c84b;
}
.card-block, .card-footer {
padding: 10px 0px 0px 0px;
}
.main-container[_ngcontent-c0] .content-container[_ngcontent-c0] .content-area[_ngcontent-c0] {
padding: 0rem 0rem 0rem 0rem;
}
}
+22 -3
View File
@@ -1,4 +1,9 @@
import { ChangeDetectorRef, Component, ElementRef } from '@angular/core'
import {
ChangeDetectorRef,
Component,
ElementRef,
ViewEncapsulation
} from '@angular/core'
import { Router } from '@angular/router'
import { VERSION } from '../environments/version'
import { ActivatedRoute } from '@angular/router'
@@ -6,7 +11,7 @@ import { Location } from '@angular/common'
import '@clr/icons'
import '@clr/icons/shapes/all-shapes'
import { globals } from './_globals'
import * as moment from 'moment'
import moment from 'moment'
import { EventService } from './services/event.service'
import { AppService } from './services/app.service'
import { InfoModal } from './models/InfoModal'
@@ -36,7 +41,9 @@ ClarityIcons.addIcons(
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
styleUrls: ['./app.component.scss'],
encapsulation: ViewEncapsulation.None,
standalone: false
})
export class AppComponent {
private dcAdapterSettings: DcAdapterSettings | undefined
@@ -63,6 +70,7 @@ export class AppComponent {
public syssite = this.appService.syssite
public licenceState = this.licenceService.licenceState
public embed = globals.embed
constructor(
private appService: AppService,
@@ -136,6 +144,16 @@ export class AppComponent {
}
})
const hashQuery = window.location.hash.split('?')[1]
if (hashQuery) {
const embedParam = new URLSearchParams(hashQuery).get('embed')
if (embedParam !== null) {
const isEmbed = embedParam !== 'false'
globals.embed = isEmbed
this.embed = isEmbed
}
}
this.subscribeToShowAbortModal()
this.subscribeToRequestsModal()
this.subscribeToStartupData()
@@ -191,6 +209,7 @@ export class AppComponent {
dcPath: getAppAttribute('dcPath') || '',
debug: getAppAttribute('debug') === 'true' || false,
useComputeApi: this.parseComputeApi(getAppAttribute('useComputeApi')),
runAsTask: getAppAttribute('runAsTask') === 'true' || false,
contextName: getAppAttribute('contextName') || '',
hotLicenceKey: getAppAttribute('hotLicenceKey') || ''
}
-1
View File
@@ -1,5 +1,4 @@
declare module 'save-svg-as-png'
declare module 'numbro/dist/languages.min'
declare interface Navigator {
msSaveBlob: (blob: any, defaultName?: string) => boolean
}
+9 -4
View File
@@ -1,4 +1,4 @@
import { HttpClientModule } from '@angular/common/http'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { NgModule } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { BrowserModule } from '@angular/platform-browser'
@@ -36,12 +36,12 @@ import { AppSettingsService } from './services/app-settings.service'
InfoModalComponent,
ViyaApiExplorerComponent
],
bootstrap: [AppComponent],
imports: [
BrowserAnimationsModule,
BrowserModule,
FormsModule,
ReactiveFormsModule,
HttpClientModule,
ROUTING,
SharedModule,
ClarityModule,
@@ -50,7 +50,12 @@ import { AppSettingsService } from './services/app-settings.service'
DirectivesModule,
NgxJsonViewerModule
],
providers: [AppService, SasStoreService, LicensingGuard, AppSettingsService],
bootstrap: [AppComponent]
providers: [
AppService,
SasStoreService,
LicensingGuard,
AppSettingsService,
provideHttpClient(withInterceptorsFromDi())
]
})
export class AppModule {}
+15 -16
View File
@@ -8,15 +8,7 @@ import { RouterModule, Routes } from '@angular/router'
import { NotFoundComponent } from './not-found/not-found.component'
import { DeployModule } from './deploy/deploy.module'
import { EditorModule } from './editor/editor.module'
import { HomeModule } from './home/home.module'
import { LicensingModule } from './licensing/licensing.module'
import { ReviewModule } from './review/review.module'
import { ReviewRouteComponent } from './routes/review-route/review-route.component'
import { StageModule } from './stage/stage.module'
import { SystemModule } from './system/system.module'
import { ViewerModule } from './viewer/viewer.module'
/**
* Defining routes
@@ -25,7 +17,8 @@ export const ROUTES: Routes = [
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{
path: 'view',
loadChildren: () => ViewerModule
loadChildren: () =>
import('./viewer/viewer.module').then((m) => m.ViewerModule)
},
{
/**
@@ -37,36 +30,42 @@ export const ROUTES: Routes = [
{ path: '', pathMatch: 'full', redirectTo: 'toapprove' },
{
path: '',
loadChildren: () => ReviewModule
loadChildren: () =>
import('./review/review.module').then((m) => m.ReviewModule)
}
]
},
{
path: 'licensing',
loadChildren: () => LicensingModule
loadChildren: () =>
import('./licensing/licensing.module').then((m) => m.LicensingModule)
},
{
path: 'home',
loadChildren: () => HomeModule
loadChildren: () => import('./home/home.module').then((m) => m.HomeModule)
},
{
/**
* Load editor module with subroutes
*/
path: 'editor',
loadChildren: () => EditorModule
loadChildren: () =>
import('./editor/editor.module').then((m) => m.EditorModule)
},
{
path: 'stage',
loadChildren: () => StageModule
loadChildren: () =>
import('./stage/stage.module').then((m) => m.StageModule)
},
{
path: 'system',
loadChildren: () => SystemModule
loadChildren: () =>
import('./system/system.module').then((m) => m.SystemModule)
},
{
path: 'deploy',
loadChildren: () => DeployModule
loadChildren: () =>
import('./deploy/deploy.module').then((m) => m.DeployModule)
},
{ path: '**', component: NotFoundComponent }
]
+3 -3
View File
@@ -1,11 +1,11 @@
<div class="content-area position-relative">
<main class="content-area position-relative">
<div class="clr-row">
<!-- T&C section -->
<div *ngIf="step === 0" id="TCS" class="card">
<div class="card-header">Terms and Conditions</div>
<div class="card-block">
<div class="card-text">
<p>
<p class="mt-0">
The Demo version of Data Controller is free for EVALUATION purposes
only. Before proceeding with configuration, please confirm that you
have read, understood, and agreed to the
@@ -97,4 +97,4 @@
</div>
</ng-container>
</ng-container>
</div>
</main>
@@ -1,50 +0,0 @@
.card {
margin-top: 0;
}
.btn {
margin-top: 10px;
}
.log-wrapper {
width: 100%;
background: #f0f0f0;
border: 1px solid #c9c9c9;
padding: 10px;
overflow: auto;
white-space: pre-wrap;
}
#contexts-btn {
padding: 0;
min-width: 30px;
margin-left: 10px;
height: 30px;
display: inline-flex;
justify-content: center;
align-items: center;
padding-top: 3px;
}
.validation-bar {
display: flex;
margin-top: 20px;
align-items: center;
clr-icon {
margin-right: 5px;
}
}
.autodeploy-section {
padding: 0px 15px;
.clr-checkbox-wrapper {
margin: 20px 0 20px 0;
}
.btn-autodeploy {
display: block;
margin: 15px 0 15px 0;
}
}
+4 -21
View File
@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'
import { Component, OnInit, ViewEncapsulation } from '@angular/core'
import { SasService } from '../services/sas.service'
import { SASjsConfig } from '@sasjs/adapter'
import { Router } from '@angular/router'
@@ -13,7 +13,9 @@ import { DcAdapterSettings } from '../models/DcAdapterSettings'
styleUrls: ['./deploy.component.scss'],
host: {
class: 'content-container'
}
},
encapsulation: ViewEncapsulation.None,
standalone: false
})
export class DeployComponent implements OnInit {
public step: number = 0
@@ -56,25 +58,6 @@ export class DeployComponent implements OnInit {
}
ngOnInit() {
if (this.sasJsConfig.serverType === ServerType.SasViya) {
fetch('sasbuild/viya.json')
.then((res) => res.text())
.then((res) => {
let initJsonFile: any = null
try {
initJsonFile = JSON.parse(res)
} catch (err) {
console.error(err)
}
if (initJsonFile) {
this.jsonFile = initJsonFile
this.loggerService.log(this.jsonFile)
}
})
}
this.setDeployDefaults()
}
@@ -9,14 +9,17 @@
<p class="m-0 align-self-start">Done</p>
<hr class="w-100" />
<div class="deploy-status-row">
<div
*ngIf="autoDeployStatus.deployServicePack !== null"
class="deploy-status-row"
>
<clr-icon
*ngIf="autoDeployStatus.deployServicePack"
*ngIf="autoDeployStatus.deployServicePack === true"
class="deploy-success"
shape="success-standard"
></clr-icon>
<clr-icon
*ngIf="!autoDeployStatus.deployServicePack"
*ngIf="!autoDeployStatus.deployServicePack === false"
class="deploy-error"
shape="times-circle"
></clr-icon>
@@ -52,7 +55,7 @@
class="deploy-error"
shape="times-circle"
></clr-icon>
LAUNCH / CONFIGURE
LAUNCH
</button>
<button
@@ -94,20 +97,72 @@
</div>
<label for="dcloc" class="mt-20 clr-control-label">DC Loc</label>
<div class="mb-10 clr-control-container">
<div class="clr-input-wrapper">
<p class="mt-0">{{ dcPath }}</p>
<div class="mb-10 clr-control-container dc-loc-input-wrapper">
<div class="clr-input-wrapper small-mt">
<input clrInput name="dcloc" [(ngModel)]="dcPath" />
</div>
</div>
<label for="dcloc" class="mt-20 clr-control-label">SAS Admin group</label>
<div class="mb-10 clr-control-container">
<div class="clr-input-wrapper">
<p class="mt-0">{{ selectedAdminGroup }}</p>
<div class="clr-input-wrapper small-mt">
<select
*ngIf="!adminGroupsLoading"
clrSelect
name="options"
[(ngModel)]="selectedAdminGroup"
>
<option *ngFor="let adminGroup of adminGroups" [value]="adminGroup.id">
{{ adminGroup.name }}
</option>
</select>
<clr-spinner
clrInline
class="spinner-sm"
*ngIf="adminGroupsLoading"
></clr-spinner>
</div>
</div>
<clr-checkbox-wrapper>
<label for="computeContext" class="mt-20 clr-control-label"
>Compute Context</label
>
<div class="mb-10 clr-control-container">
<div class="clr-input-wrapper small-mt">
<select
*ngIf="!computeContextsLoading"
clrSelect
name="options"
(ngModelChange)="onComputeContextChange($event)"
[(ngModel)]="selectedComputeContext"
>
<option
*ngFor="let computeContext of computeContexts"
[value]="computeContext.id"
>
{{ computeContext.name }}
</option>
</select>
<clr-spinner
clrInline
class="spinner-sm"
*ngIf="computeContextsLoading"
></clr-spinner>
</div>
</div>
<ng-container *ngIf="runningAsUser">
<label for="dcloc" class="mt-20 clr-control-label">Running as user:</label>
<div class="mb-10 clr-control-container">
<div class="clr-input-wrapper">
<p class="mt-0">{{ runningAsUser }}</p>
</div>
</div>
</ng-container>
<!-- Keeping this for a reference in case future VIYA changes and starts allowing separate backend and frontend) -->
<!-- <clr-checkbox-wrapper>
<input
clrCheckbox
[(ngModel)]="recreateDatabase"
@@ -116,19 +171,28 @@
checked
/>
<label>Recreate database</label>
</clr-checkbox-wrapper>
</clr-checkbox-wrapper> -->
<hr />
<button
(click)="runAutoDeploy()"
class="btn-autodeploy btn btn-primary d-inline-block mr-10"
>
Deploy
</button>
<!-- Keeping this for a reference in case future VIYA changes and starts allowing separate backend and frontend) -->
<!-- <button
(click)="executeJson()"
class="btn-autodeploy btn btn-primary d-inline-block mr-10"
[disabled]="!jsonFile"
>
Deploy {{ !jsonFile ? '(json file is not available)' : '' }}
</button>
</button> -->
<button
<!-- <button
(click)="uploadJsonAuto.click()"
class="btn-autodeploy btn btn-primary d-inline-block mr-10"
>
@@ -140,7 +204,7 @@
hidden
(click)="clearUploadInput($event)"
(change)="onJsonFileChange($event)"
/>
/> -->
<clr-modal [(clrModalOpen)]="recreateDatabaseModal" [clrModalClosable]="false">
<h3 class="modal-title">Warning</h3>
@@ -1,61 +0,0 @@
.auto-deploy {
display: flex;
justify-content: center;
align-items: center;
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 100;
}
.spinner-box {
width: 400px;
padding: 20px;
border-radius: 3px;
background: #fff;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
box-shadow: 1px 1px 8px 0px #00000082;
.buttons {
display: flex;
justify-content: space-between;
width: 100%;
}
}
.deploy-status-row {
display: flex;
align-items: center;
align-self: flex-start;
p {
margin: 0 0 0 10px;
}
}
.deploy-success {
color: #6ECF44;
}
.deploy-error {
color: #E74C3C;
// width: 20px;
// height: 20px;
}
.deploy-undeterminated {
color: #cacaca;
}
hr {
border: 0;
border-bottom: 1px solid #00000045;
}
@@ -1,15 +1,36 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
import {
Component,
EventEmitter,
Input,
OnInit,
Output,
ViewEncapsulation
} from '@angular/core'
import SASjs, { SASjsConfig } from '@sasjs/adapter'
import { DcAdapterSettings } from 'src/app/models/DcAdapterSettings'
import { HelperService } from 'src/app/services'
import { DeployService } from 'src/app/services/deploy.service'
import { EventService } from 'src/app/services/event.service'
import { LoggerService } from 'src/app/services/logger.service'
import { SasViyaService } from 'src/app/services/sas-viya.service'
import { SasService } from 'src/app/services/sas.service'
import { ViyaApiCurrentUser } from 'src/app/viya-api-explorer/models/viya-api-current-user.model'
import {
Item,
ViyaApiIdentities
} from 'src/app/viya-api-explorer/models/viya-api-identities.model'
import { ComputeContextDetails } from 'src/app/viya-api-explorer/models/viya-compute-context-details.model'
import {
ViyaComputeContexts,
Item as ComputeContextItem
} from 'src/app/viya-api-explorer/models/viya-compute-contexts.model'
@Component({
selector: 'app-automatic-deploy',
templateUrl: './automatic.component.html',
styleUrls: ['./automatic.component.scss']
styleUrls: ['./automatic.component.scss'],
encapsulation: ViewEncapsulation.None,
standalone: false
})
export class AutomaticComponent implements OnInit {
@Input() sasJs!: SASjs
@@ -21,6 +42,7 @@ export class AutomaticComponent implements OnInit {
@Output() onNavigateToHome: EventEmitter<any> = new EventEmitter<any>()
public selectedComputeContext: string = ''
public makeDataResponse: string = ''
public jsonFile: any = null
public autodeploying: boolean = false
@@ -28,8 +50,19 @@ export class AutomaticComponent implements OnInit {
public recreateDatabaseModal: boolean = false
public isSubmittingJson: boolean = false
public isJsonSubmitted: boolean = false
public recreateDatabase: boolean = false
/**
* Default was `false` when deploy was done with frontend and backend separately.
* Now we are using only streaming app, so we always want to recreate database (makedata)
*/
public recreateDatabase: boolean = true
public createDatabaseLoading: boolean = false
public adminGroupsLoading: boolean = false
public currentUserInfoLoading: boolean = false
public computeContextsLoading: boolean = false
public adminGroups: { id: string; name: string }[] = []
public runningAsUser: string | undefined
public currentUserInfo: ViyaApiCurrentUser | null = null
public computeContexts: ComputeContextItem[] = []
/** autoDeployStatus
* This object presents the status for two steps that we have for deploy.
@@ -46,14 +79,138 @@ export class AutomaticComponent implements OnInit {
runMakeData: null
}
public sasjsConfig = this.sasService.getSasjsConfig()
/**
* makedata service will be run in a new window
* This is needed to ensure that the user can see the logs
* and the progress of the service execution.
* If this is set to `false`, the service will be run in the same window
* using the adapter request method.
*/
public deployInNewWindow: boolean = true
constructor(
private eventService: EventService,
private deployService: DeployService,
private sasService: SasService,
private loggerService: LoggerService
private sasViyaService: SasViyaService,
private loggerService: LoggerService,
private helperService: HelperService
) {}
ngOnInit(): void {}
ngOnInit(): void {
this.loadData()
}
public async loadData() {
await this.getAdminGroups()
await this.getComputeContexts()
await this.getCurrentUser()
setTimeout(() => {
if (this.selectedComputeContext) {
this.onComputeContextChange(this.selectedComputeContext)
}
}, 500)
}
public async getComputeContexts() {
return new Promise<void>((resolve, reject) => {
this.computeContextsLoading = true
this.sasViyaService.getComputeContexts().subscribe(
(res: ViyaComputeContexts) => {
this.computeContextsLoading = false
const defaultContext = res.items.find(
(item: ComputeContextItem) =>
item.name === 'SAS Job Execution compute context'
)
if (defaultContext) {
this.selectedComputeContext = defaultContext.id
}
this.computeContexts = res.items
resolve()
},
(err) => {
reject(err)
}
)
})
}
public async getCurrentUser() {
return new Promise<void>((resolve, reject) => {
this.currentUserInfoLoading = true
this.sasViyaService.getCurrentUser().subscribe(
(res: ViyaApiCurrentUser) => {
this.currentUserInfoLoading = false
this.currentUserInfo = res
this.dcPath = `/export/viya/homes/${res.id}`
resolve()
},
(err) => {
console.error('Error while getting current user', err)
reject(err)
}
)
})
}
public async getAdminGroups() {
return new Promise<void>((resolve, reject) => {
this.adminGroupsLoading = true
;(this.sasViyaService
.getAdminGroups()
.subscribe((res: ViyaApiIdentities) => {
this.adminGroupsLoading = false
// Map admin groups with only needed fields
this.adminGroups = res.items.map((item: Item) => {
return {
id: item.id,
name: item.name
}
})
resolve()
}),
(err: any) => {
this.adminGroupsLoading = false
this.loggerService.error('Error while getting admin groups', err)
this.eventService.showAbortModal('admin groups', err)
reject(err)
})
})
}
public async onComputeContextChange(computeContextId: string) {
this.sasViyaService
.getComputeContextById(computeContextId)
.subscribe((res: ComputeContextDetails) => {
if (res.attributes && res.attributes.runServerAs) {
this.runningAsUser = res.attributes.runServerAs
} else {
this.runningAsUser = this.currentUserInfo?.id || 'unknown'
}
})
}
public getComputeContextName(id: string): string | undefined {
return (
this.computeContexts.find(
(context: ComputeContextItem) => context.id === id
)?.name || undefined
)
}
/**
* Executes sas.json file to deploy the backend
@@ -63,7 +220,6 @@ export class AutomaticComponent implements OnInit {
* to create database if checkbox is toggled on
*/
public async executeJson() {
this.autodeploying = true
this.isSubmittingJson = true
try {
@@ -98,11 +254,19 @@ export class AutomaticComponent implements OnInit {
}
this.isSubmittingJson = false
}
public async runAutoDeploy(executeJson: boolean = false) {
if (!this.deployInNewWindow) this.autodeploying = true
if (executeJson) {
this.executeJson()
}
if (this.recreateDatabase) {
this.createDatabase()
} else {
this.autodeployDone = true
if (!this.deployInNewWindow) this.autodeployDone = true
}
}
@@ -119,45 +283,160 @@ export class AutomaticComponent implements OnInit {
]
}
// Get and run service using the selected context name
let selectedComputeContextName = this.sasJsConfig.contextName
if (this.selectedComputeContext.length && this.computeContexts.length) {
const computeContextName = this.getComputeContextName(
this.selectedComputeContext
)
if (computeContextName) {
selectedComputeContextName = computeContextName
}
}
/**
* We are overriding default `sasjsConfig` object fields with this object fields.
* Here we want to run this request using original WEB method.
* contextName: null is the MUST field for it.
*/
let overrideConfig = {
useComputeApi: false,
contextName: this.sasJsConfig.contextName,
useComputeApi: null,
contextName: selectedComputeContextName,
debug: true
}
this.sasJs
.request(`services/admin/makedata`, data, overrideConfig, () => {
this.sasService.shouldLogin.next(true)
if (this.deployInNewWindow) {
this.runMakedataInNewWindow({
contextName: selectedComputeContextName,
admin: this.selectedAdminGroup,
dcPath: this.dcPath
})
.then((res: any) => {
this.autodeployDone = true
} else {
this.sasJs
.request(`services/admin/makedata`, data, overrideConfig, () => {
this.sasService.shouldLogin.next(true)
})
.then((res: any) => {
this.autodeployDone = true
try {
this.makeDataResponse = JSON.stringify(res)
} catch {
this.makeDataResponse = res
}
try {
this.makeDataResponse = JSON.stringify(res)
} catch {
this.makeDataResponse = res
}
if (res.result && res.result.length > 0) {
this.autoDeployStatus.runMakeData = true
} else {
if (res.result && res.result.length > 0) {
this.autoDeployStatus.runMakeData = true
} else {
this.autoDeployStatus.runMakeData = false
}
if (typeof res.sasjsAbort !== 'undefined') {
const abortRes = res
const abortMsg = abortRes.sasjsAbort[0].MSG
const macMsg = abortRes.sasjsAbort[0].MAC
this.eventService.showAbortModal('makedata', abortMsg, {
SYSWARNINGTEXT: abortRes.SYSWARNINGTEXT,
SYSERRORTEXT: abortRes.SYSERRORTEXT,
MAC: macMsg
})
}
if (this.helperService.isStreamingViya())
this.updateIndexHtmlComputeContext()
})
.catch((err: any) => {
this.eventService.showAbortModal('makedata', JSON.stringify(err))
this.autoDeployStatus.runMakeData = false
}
})
.catch((err: any) => {
this.autoDeployStatus.runMakeData = false
this.autodeployDone = true
this.autodeployDone = true
try {
this.makeDataResponse = JSON.stringify(err)
} catch {
this.makeDataResponse = err
}
try {
this.makeDataResponse = JSON.stringify(err)
} catch {
this.makeDataResponse = err
}
})
}
}
public runMakedataInNewWindow(params: {
contextName: string
admin: string
dcPath: string
}) {
let serverUrl = this.sasjsConfig.serverUrl
let appLoc = this.sasjsConfig.appLoc
const execPath = this.sasService.getExecutionPath()
let contextname = `&_contextname=${params.contextName}`
let admin = `&admin=${params.admin}`
let dcPath = `&dcpath=${params.dcPath}`
let debug = this.sasService.getDebugUrlParam()
let programUrl =
serverUrl +
execPath +
'/?_program=' +
appLoc +
'/services/admin/makedata' +
contextname +
admin +
dcPath +
debug
window.open(programUrl)
}
/**
* Only when on Viya streamed app, this method will update the `contextname` in the `index.html` on the SAS drive
* This is needed to ensure that the DC will use the same compute context `makedata` service used to run against.
*/
public async updateIndexHtmlComputeContext() {
const filenamePath = location.search.split('/').pop()
const filename = filenamePath?.includes('.') ? filenamePath : undefined
if (!filename) {
this.eventService.showAbortModal(
null,
'We could not figure out the file name of `index.html` based on the url.'
)
return
}
const indexHtmlContent = await this.sasService.getFileContent(
`${this.appLoc}/services`,
filename
)
if (!indexHtmlContent) {
this.loggerService.error(
`Failed to get ${filename} at ${this.appLoc}/services`
)
return
}
const computeContextName = this.getComputeContextName(
this.selectedComputeContext
)
if (!computeContextName) {
this.loggerService.error(
`Compute context name not found for ID: ${this.selectedComputeContext} | List: ${JSON.stringify(this.computeContexts)}`
)
return
}
const updatedContent = indexHtmlContent.replace(
/contextname="[^"]*"/g,
`contextname="${computeContextName}"`
)
await this.sasService
.updateFileContent(`${this.appLoc}/services`, filename, updatedContent)
.catch((err: any) => {
this.loggerService.error(`Failed to update DataController.html: ${err}`)
})
}
@@ -1,4 +0,0 @@
.clear-memory-button {
right: 10px;
top: 2px;
}
@@ -1,4 +1,11 @@
import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core'
import {
Component,
Input,
OnInit,
Output,
EventEmitter,
ViewEncapsulation
} from '@angular/core'
import SASjs, { SASjsConfig } from '@sasjs/adapter'
import { DcAdapterSettings } from 'src/app/models/DcAdapterSettings'
import { RequestWrapperResponse } from 'src/app/models/request-wrapper/RequestWrapperResponse'
@@ -10,7 +17,9 @@ import { SasService } from 'src/app/services/sas.service'
@Component({
selector: 'app-manual-deploy',
templateUrl: './manual.component.html',
styleUrls: ['./manual.component.scss']
styleUrls: ['./manual.component.scss'],
encapsulation: ViewEncapsulation.None,
standalone: false
})
export class ManualComponent implements OnInit {
@Input() sasJs!: SASjs
@@ -242,7 +251,7 @@ export class ManualComponent implements OnInit {
this.selectedAdminGroup +
'&DCPATH=' +
this.dcPath +
'&_debug=131'
this.sasService.getDebugUrlParam()
window.open(url, '_blank')
@@ -266,7 +275,7 @@ export class ManualComponent implements OnInit {
* contextName: null is the MUST field for it.
*/
let overrideConfig = {
useComputeApi: false,
useComputeApi: null,
contextName: this.sasJsConfig.contextName,
debug: true
}
@@ -1,23 +0,0 @@
.clr-control-container {
width: 50vw;
}
.clr-input-wrapper {
width: 100%;
input {
width: 100%;
}
}
.thinProgress {
left: 0px;
right: 0;
width: unset;
height: 1px;
margin-top: 0 !important;
&::after {
top: 0;
}
}
@@ -1,5 +1,12 @@
import { Location } from '@angular/common'
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
import {
Component,
EventEmitter,
Input,
OnInit,
Output,
ViewEncapsulation
} from '@angular/core'
import SASjs, { SASjsConfig } from '@sasjs/adapter'
import { ServerType } from '@sasjs/utils/types/serverType'
import { DcAdapterSettings } from 'src/app/models/DcAdapterSettings'
@@ -12,7 +19,9 @@ import { SasjsService } from 'src/app/services/sasjs.service'
@Component({
selector: 'app-sasjs-configurator',
templateUrl: './sasjs-configurator.component.html',
styleUrls: ['./sasjs-configurator.component.scss']
styleUrls: ['./sasjs-configurator.component.scss'],
encapsulation: ViewEncapsulation.None,
standalone: false
})
export class SasjsConfiguratorComponent implements OnInit {
@Input() sasJs!: SASjs
@@ -7,7 +7,8 @@ import {
} from '@angular/core'
@Directive({
selector: '[appDragNdrop]'
selector: '[appDragNdrop]',
standalone: false
})
export class DragNdropDirective {
@HostBinding('class.fileover') fileOver: boolean = false
@@ -9,7 +9,8 @@ import {
import { FileUploader } from '../models/FileUploader.class'
@Directive({
selector: '[appFileDrop]'
selector: '[appFileDrop]',
standalone: false
})
export class FileDropDirective {
@Input() uploader?: FileUploader
@@ -9,7 +9,8 @@ import {
import { FileUploader } from '../models/FileUploader.class'
@Directive({
selector: '[appFileSelect]'
selector: '[appFileSelect]',
standalone: false
})
export class FileSelectDirective {
@Input() uploader?: FileUploader
@@ -6,7 +6,8 @@ import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core'
* Calling functions in html is bad for performance
*/
@Directive({
selector: '[ngVar]'
selector: '[ngVar]',
standalone: false
})
export class NgVarDirective {
@Input()
@@ -1,7 +1,8 @@
import { Directive, HostListener } from '@angular/core'
@Directive({
selector: '[appStealFocus]'
selector: '[appStealFocus]',
standalone: false
})
export class StealFocusDirective {
constructor() {}
@@ -177,45 +177,45 @@
</div>
</ng-container>
<clr-textarea-container class="date-field" *ngSwitchCase="'time'">
<textarea
clrTextarea
(paste)="recordInputPaste($event)"
(input)="recordInputChange($event, col.key)"
<div class="date-field" *ngSwitchCase="'intl-time'">
<input
type="time"
step="1"
class="date-input"
[attr.aria-label]="col.key"
[class.invalid-data]="
currentRecordInvalidCols.includes(col.key)
"
[rows]="col.value.length > 80 ? 6 : 1"
[(ngModel)]="currentRecord[col.key]"
[class.not-char]="
currentRecordValidator?.getRule(col.key)?.type
"
></textarea>
<clr-control-helper>HH:mm:ss</clr-control-helper>
</clr-textarea-container>
[value]="currentRecord[col.key]"
(change)="recordTimeChange($event, col.key)"
/>
</div>
<div class="date-field" *ngSwitchCase="'date'">
<textarea
clrTextarea
(paste)="recordInputPaste($event)"
(input)="recordInputChange($event, col.key)"
<div class="date-field" *ngSwitchCase="'intl-datetime'">
<input
type="datetime-local"
step="1"
class="date-input"
[attr.aria-label]="col.key"
[class.invalid-data]="
currentRecordInvalidCols.includes(col.key)
"
rows="1"
cols="auto"
class="not-char"
[(ngModel)]="currentRecord[col.key]"
></textarea>
<clr-date-container class="date-picker">
<input
type="date"
name="date"
class="d-none"
(clrDateChange)="recordDateChange($event, col.key)"
clrDate
/>
</clr-date-container>
[value]="toNativeDatetime(currentRecord[col.key])"
(change)="recordDatetimeChange($event, col.key)"
/>
</div>
<div class="date-field" *ngSwitchCase="'intl-date'">
<input
type="date"
class="date-input"
[attr.aria-label]="col.key"
[class.invalid-data]="
currentRecordInvalidCols.includes(col.key)
"
[value]="currentRecord[col.key]"
(change)="recordDateChange($event, col.key)"
/>
</div>
<div *ngSwitchCase="'autocomplete'">
<ng-container
@@ -255,6 +255,7 @@
type="button"
class="btn btn-outline focusable"
tabindex="0"
aria-label="Previous record"
(click)="onPreviousRecordClick()"
[disabled]="currentRecordInvalidCols.length > 0"
>
@@ -267,6 +268,7 @@
type="button"
class="btn btn-outline focusable"
tabindex="0"
aria-label="Next record"
(click)="onNextRecordClick()"
[disabled]="currentRecordInvalidCols.length > 0"
>
@@ -1,238 +0,0 @@
.record-edit-modal {
.column-entry {
display: flex;
justify-content: space-between;
.name-input-row {
width: 100%;
max-width: 260px;
.cell-desc {
margin-right: 30px;
margin-top: 10px;
}
}
.inputs-wrapper {
flex: 1;
display: flex;
align-items: center;
::ng-deep >*:not(.date-field):not(clr-select-container) {
flex: 1;
}
}
p {
margin-top: 0px;
}
::ng-deep {
.clr-textarea-wrapper {
margin-top: 0 !important;
}
.clr-form-control {
margin-top: 0px !important;
}
app-soft-select {
display: block;
width: 224px;
border: 1px solid #999;
color: #000;
padding: calc(.25rem + 2px) .5rem;
border-radius: .125rem;
font-size: .541667rem;
margin-right: 6px;
input {
width: 100%;
border: 0;
&:focus {
background: none;
border: 0 !important;
}
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
}
}
}
&:first-child p:first-child {
margin-top: 0;
}
}
.date-field {
position: relative;
display: inline-block;
textarea {
width: 230px;
}
.date-picker {
position: absolute;
right: 0;
top: 4px;
::ng-deep {
// clr-datepicker-view-manager {
// transform: unset !important;
// left: unset !important;
// right: 70px !important;
// }
.clr-input-group {
border: 0 !important;
}
}
}
}
.modal-body {
padding-bottom: 10px;
}
::ng-deep {
clr-select-container {
border: 1px solid #999;
color: #000;
border-radius: .125rem;
margin-right: 5px;
.clr-select-wrapper {
max-height: unset;
&::after {
top: 15px;
}
}
select {
height: auto;
padding: 10px;
padding-right: 20px;
border: 0 !important;
&:focus {
background: 0 0 !important;
}
&:hover {
background: transparent;
}
}
}
clr-input-container {
width: 224px;
border: 1px solid #999;
color: #000;
padding: calc(.25rem + 2px) .5rem;
border-radius: .125rem;
font-size: .541667rem;
margin-right: 6px;
input {
width: 100%;
border: 0;
&:focus {
background: none;
border: 0 !important;
}
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
}
&.invalid-data {
border-color: red;
}
}
.modal-dialog {
width: 80vw;
}
.clr-control-container {
width: 100%;
textarea {
width: 100%;
resize: none;
border-color: #999;
&.invalid-data {
border-color: red;
outline: 0;
}
&.not-char {
font-family: "Lucida Console", Monaco, monospace;
}
}
}
.generate-record-url {
right: 40px;
top: 40px;
font-size: 12px;
}
.generate-record-url-button {
right: 25px;
top: 5px;
}
.modal-header {
padding: 0 0 1rem 0;
}
.modal-footer {
display: flex;
align-items: center;
justify-content: space-between;
// height: 65px;
.alert {
margin: 0;
}
}
}
}
.prev-next {
display: flex;
align-items: center;
p {
margin: 0;
}
button {
margin: 0px 10px;
}
}
.focusable {
&:focus {
box-shadow: 0 0 3px 0px #5aa220;
}
}
.entry-input-left-offset {
left: -30px;
}
.validation-info-alert {
width: 310px
}
@@ -1,12 +1,20 @@
import { KeyValue } from '@angular/common'
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
import moment from 'moment'
import {
Component,
EventEmitter,
HostListener,
Input,
OnInit,
Output,
ViewEncapsulation
} from '@angular/core'
import { ValidateFilterSASResponse } from 'src/app/models/sas/validate-filter.model'
import { QueryClause } from 'src/app/models/TableData'
import { HelperService } from 'src/app/services/helper.service'
import { SasStoreService } from 'src/app/services/sas-store.service'
import { DcValidator } from 'src/app/shared/dc-validator/dc-validator'
import { DcValidation } from 'src/app/shared/dc-validator/models/dc-validation.model'
import { isEmpty } from 'src/app/shared/dc-validator/utils/isEmpty'
import {
EditRecordDropdownChangeEvent,
EditRecordInputFocusedEvent
@@ -16,7 +24,9 @@ import { EditRecordModal } from '../../models/EditRecordModal'
@Component({
selector: 'app-edit-record',
templateUrl: './edit-record.component.html',
styleUrls: ['./edit-record.component.scss']
styleUrls: ['./edit-record.component.scss'],
encapsulation: ViewEncapsulation.None,
standalone: false
})
export class EditRecordComponent implements OnInit {
@Input() currentRecord!: EditRecordModal
@@ -81,17 +91,69 @@ export class EditRecordComponent implements OnInit {
}
/**
* Fired when date field in the record change
* Function will parse date and format to string
* @param date picker value
* Fired when the native date picker (intl-date) changes. A native
* `<input type="date">` value is always ISO `YYYY-MM-DD`, so it is stored
* as-is. Mirrors recordTimeChange / recordDatetimeChange.
* @param event native <input type="date"> change event
* @param colKey column name (key)
*/
recordDateChange(date: Date, colKey: string) {
let cellValidation = this.currentRecordValidator?.getRule(colKey)
let format = cellValidation ? cellValidation.dateFormat : ''
recordDateChange(event: Event, colKey: string) {
const value = (event.target as HTMLInputElement).value // YYYY-MM-DD
if (!value || !this.currentRecord) return
this.currentRecord[colKey] = value
this.revalidateRecordCol(colKey, value)
}
if (this.currentRecord)
this.currentRecord[colKey] = moment(date).format(format)
/**
* Stored `YYYY-MM-DD HH:mm:ss` → native `<input type="datetime-local">` value
* `YYYY-MM-DDTHH:mm:ss`, so the picker opens at the current value.
*/
toNativeDatetime(value: any): string {
if (typeof value !== 'string') return ''
return value.includes(' ') ? value.replace(' ', 'T') : value
}
/**
* Re-runs validation for a column after a native picker change (the picker
* handlers set the value programmatically, which does not fire the textarea's
* input handler). Mirrors recordInputChange's validate + state update.
*/
private revalidateRecordCol(colName: string, value: any) {
const colRules = this.currentRecordValidator?.getRule(colName)
this.validateRecordCol(colRules, value).then((valid: boolean) =>
this.updateValidationState(colName, valid)
)
}
/**
* Fired when the native time picker (intl-time) changes. Stores 24h
* `HH:mm:ss`, padding the seconds the browser may omit. Mirrors the
* IntlDatetimeEditor conversion used by the HOT grid.
* @param event native <input type="time"> change event
* @param colKey column name (key)
*/
recordTimeChange(event: Event, colKey: string) {
const value = (event.target as HTMLInputElement).value // HH:mm[:ss]
if (!value || !this.currentRecord) return
const stored = value.length === 5 ? `${value}:00` : value
this.currentRecord[colKey] = stored
this.revalidateRecordCol(colKey, stored)
}
/**
* Fired when the native datetime picker (intl-datetime) changes. Converts the
* native `YYYY-MM-DDTHH:mm[:ss]` into SAS' stored `YYYY-MM-DD HH:mm:ss`,
* padding seconds. Mirrors the IntlDatetimeEditor conversion used by the grid.
* @param event native <input type="datetime-local"> change event
* @param colKey column name (key)
*/
recordDatetimeChange(event: Event, colKey: string) {
const value = (event.target as HTMLInputElement).value // YYYY-MM-DDTHH:mm[:ss]
if (!value || !this.currentRecord) return
const [date, time] = value.split('T')
const stored = `${date} ${time.length === 5 ? `${time}:00` : time}`
this.currentRecord[colKey] = stored
this.revalidateRecordCol(colKey, stored)
}
/**
@@ -110,6 +172,16 @@ export class EditRecordComponent implements OnInit {
this.onRecordEditClose.emit()
}
/**
* Close the modal on Escape (cancel, like the Close button). A native picker
* swallows the first Escape to dismiss itself, so the modal only closes once
* focus is back on the form.
*/
@HostListener('document:keydown.escape')
onEscapeKey() {
this.closeRecordEdit()
}
/**
* Emitting output event when dropdown (autocomplete) input in any col change
* @param colName column name (key)
@@ -137,23 +209,63 @@ export class EditRecordComponent implements OnInit {
}, 0)
}
async recordInputChange(event: any, colName: string) {
async recordInputChange(event: any, colName: string): Promise<void> {
const colRules = this.currentRecordValidator?.getRule(colName)
const value = event.target.value
this.helperService.debounceCall(300, () => {
this.validateRecordCol(colRules, value).then((valid: boolean) => {
const index = this.currentRecordInvalidCols.indexOf(colName)
this.updateValidationState(colName, valid)
if (valid) {
if (index > -1) this.currentRecordInvalidCols.splice(index, 1)
} else {
if (index < 0) this.currentRecordInvalidCols.push(colName)
if (!valid) {
this.tryAutoPopulateNotNull(event, colName, colRules, value)
}
})
})
}
/**
* Updates the invalid columns list based on validation result
*/
private updateValidationState(colName: string, valid: boolean): void {
const index = this.currentRecordInvalidCols.indexOf(colName)
if (valid && index > -1) {
this.currentRecordInvalidCols.splice(index, 1)
} else if (!valid && index < 0) {
this.currentRecordInvalidCols.push(colName)
}
}
/**
* Auto-populates NOTNULL default value when the field is empty and has a default
*/
private tryAutoPopulateNotNull(
event: any,
colName: string,
colRules: DcValidation | undefined,
value: any
): void {
if (
!isEmpty(value) ||
!this.currentRecordValidator ||
!this.currentRecord
) {
return
}
const defaultValue =
this.currentRecordValidator.getNotNullDefaultValue(colName)
if (defaultValue === undefined) return
this.currentRecord[colName] = defaultValue
event.target.value = defaultValue
this.validateRecordCol(colRules, defaultValue).then((isValid: boolean) => {
this.updateValidationState(colName, isValid)
})
}
onNextRecordClick() {
this.onNextRecord.emit()
}
@@ -163,23 +275,8 @@ export class EditRecordComponent implements OnInit {
}
public copyToClip(text: string) {
const modalElement = document.querySelector('#recordModalRef .modal-title')
if (modalElement) {
const selBox = document.createElement('textarea')
selBox.style.position = 'fixed'
selBox.style.left = '0'
selBox.style.top = '0'
selBox.style.opacity = '0'
selBox.style.zIndex = '5000'
selBox.value = text
modalElement.appendChild(selBox)
selBox.focus()
selBox.select()
document.execCommand('copy')
modalElement.removeChild(selBox)
this.generatedRecordUrl = text
}
navigator.clipboard.writeText(text)
this.generatedRecordUrl = text
}
async generateEditRecordUrl() {
@@ -198,8 +295,9 @@ export class EditRecordComponent implements OnInit {
if (obj.data === key) {
if (
obj.type === 'numeric' ||
obj.type === 'date' ||
obj.type === 'time'
obj.type === 'intl-date' ||
obj.type === 'intl-time' ||
obj.type === 'intl-datetime'
) {
type = 'N'
}
@@ -1,8 +0,0 @@
:host {
display: block;
}
p {
margin: 0;
text-align: center;
}
@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'
import { Component, OnInit, ViewEncapsulation } from '@angular/core'
/**
* Goal of this component is to recieve array of strings where every element is one state
@@ -10,7 +10,9 @@ import { Component, OnInit } from '@angular/core'
@Component({
selector: 'app-upload-stater',
templateUrl: './upload-stater.component.html',
styleUrls: ['./upload-stater.component.scss']
styleUrls: ['./upload-stater.component.scss'],
encapsulation: ViewEncapsulation.None,
standalone: false
})
export class UploadStaterComponent implements OnInit {
public statesList: string[] = [] //States appended to be displayed
+51 -21
View File
@@ -1,4 +1,4 @@
<div class="content-area d-flex clr-flex-column">
<main class="content-area d-flex clr-flex-column">
<clr-modal
appFileDrop
(fileOver)="fileOverBase($event)"
@@ -36,7 +36,7 @@
<div class="clr-row card-block mt-15 d-flex justify-content-between">
<div class="clr-col-md-auto">
<div class="encoding-block">
<clr-radio-container class="mt-0-i" clrInline>
<clr-radio-container class="mt-0" clrInline>
<clr-radio-wrapper>
<input
type="radio"
@@ -165,21 +165,30 @@
class="card-header clr-row buttonBar headerBar clr-flex-md-row clr-justify-content-center clr-justify-content-lg-end"
>
<div
*ngIf="tableTrue"
*ngIf="tableTrue && !embed"
class="clr-col-12 clr-col-md-3 clr-col-lg-4 backBtn"
>
<span
class="btn icon-collapse btn-sm btn-icon btn-dimmed"
[routerLink]="['/home']"
>
<clr-icon shape="caret" dir="left" size="20"></clr-icon>
<clr-icon
aria-hidden="true"
shape="caret"
dir="left"
size="20"
></clr-icon>
<span class="text">Back to table selection</span>
</span>
<span
(click)="viewboxManager()"
class="btn icon-collapse btn-sm btn-icon btn-dimmed viewbox-open"
>
<clr-icon shape="view-cards" size="20"></clr-icon>
<clr-icon
aria-hidden="true"
shape="view-cards"
size="20"
></clr-icon>
<span class="text">Viewboxes</span>
</span>
</div>
@@ -193,19 +202,21 @@
libName: (libds?.split('.'))![0],
tableName: (libds?.split('.'))![1]
} as libdsParsed"
class="editor-title text-center mt-0-i"
class="editor-title text-center mt-0"
>
<clr-tooltip>
<clr-icon
clrTooltipTrigger
(click)="datasetInfo = true"
shape="info-circle"
aria-label="View dataset meta info"
class="is-highlight cursor-pointer"
size="24"
></clr-icon>
<clr-icon
*ngIf="libdsParsed.tableName.includes('-FC')"
aria-hidden="true"
shape="bolt"
class="color-yellow"
></clr-icon>
@@ -250,7 +261,7 @@
class="btnView btn icon-collapse btn-sm btn-icon btn-block btn-dimmed"
(click)="openQb()"
>
<clr-icon shape="filter"></clr-icon>
<clr-icon aria-hidden="true" shape="filter"></clr-icon>
<span class="text">Filter</span>
</button>
@@ -259,7 +270,7 @@
class="btn icon-collapse btn-sm btn-primary btn-block"
(click)="editTable()"
>
<clr-icon shape="note"></clr-icon>
<clr-icon aria-hidden="true" shape="note"></clr-icon>
<span class="text">Edit</span>
</button>
@@ -269,7 +280,7 @@
type="button"
class="btn icon-collapse btn-sm btn-success btn-block mr-0"
>
<clr-icon shape="upload"></clr-icon>
<clr-icon aria-hidden="true" shape="upload"></clr-icon>
<span class="text">Upload</span>
</button>
</ng-container>
@@ -280,7 +291,7 @@
class="btn btn-sm btn-icon btn-outline-danger"
(click)="cancelEdit()"
>
<clr-icon shape="times"></clr-icon>
<clr-icon aria-hidden="true" shape="times"></clr-icon>
<span>Cancel</span>
</button>
@@ -293,7 +304,8 @@
[class.dc-locked-control]="restrictions.restrictAddRow"
(click)="!restrictions.restrictAddRow ? addRow() : ''"
>
<clr-icon shape="plus" size="16"></clr-icon>Add Row
<clr-icon aria-hidden="true" shape="plus" size="16"></clr-icon
>Add Row
</button>
<clr-tooltip-content
@@ -318,7 +330,8 @@
class="btn btn-sm btn-primary"
(click)="checkSave()"
>
<clr-icon shape="check" size="20"></clr-icon>Submit
<clr-icon aria-hidden="true" shape="check" size="20"></clr-icon
>Submit
</button>
</ng-container>
@@ -328,7 +341,7 @@
class="btn btn-sm btn-icon btn-outline-danger btn-upload-preview"
(click)="discardSourceFile = true"
>
<clr-icon shape="times"></clr-icon>
<clr-icon aria-hidden="true" shape="times"></clr-icon>
<span>Discard file</span>
</button>
@@ -338,7 +351,7 @@
class="btn btn-sm btn-primary btn-upload-preview"
(click)="manualFileEditModal = true"
>
<clr-icon shape="note"></clr-icon>
<clr-icon aria-hidden="true" shape="note"></clr-icon>
<span>Edit</span>
</button>
@@ -348,7 +361,7 @@
(click)="submitExcel()"
[clrLoading]="uploadLoading"
>
<clr-icon shape="check" size="20"></clr-icon>
<clr-icon aria-hidden="true" shape="check" size="20"></clr-icon>
Submit
</button>
</ng-container>
@@ -381,7 +394,11 @@
<ng-container *ngIf="getdataError">
<span>
<clr-icon shape="error-standard" class="error-icon"></clr-icon>
<clr-icon
aria-hidden="true"
shape="error-standard"
class="error-icon"
></clr-icon>
</span>
<div class="mt-10">
@@ -407,12 +424,11 @@
<div class="hot-wrapper clr-flex-1">
<hot-table
#hotInstance
hotId="hotInstance"
id="hotTable"
class="edit-hot"
className="htDark"
[class.hidden]="hotTable.hidden"
[licenseKey]="hotTable.licenseKey"
[data]="hotTable.data"
[settings]="hotTableSettings"
>
</hot-table>
</div>
@@ -430,7 +446,7 @@
!restrictions.restrictAddRow ? addRecordButtonClick() : ''
"
>
<clr-icon shape="plus" size="16"></clr-icon>
<clr-icon aria-hidden="true" shape="plus" size="16"></clr-icon>
Add Record
</button>
@@ -686,7 +702,7 @@
</div> -->
</div>
</div>
</div>
</main>
<div class="modal z-index-highest" *ngIf="nullVariables">
<div class="modal-dialog" role="dialog" aria-hidden="true">
@@ -873,3 +889,17 @@
</app-dataset-info>
<app-viewboxes [(viewboxModal)]="viewboxes"></app-viewboxes>
<app-confirm-modal
[open]="confirmModal.open"
[title]="confirmModal.title"
[message]="confirmModal.message"
(result)="onConfirmModalResult($event)"
></app-confirm-modal>
<app-bulk-validation-modal
[open]="bulkValidation.active"
[done]="bulkValidation.done"
[total]="bulkValidation.total"
(cancel)="cancelBulkValidation({ revert: true })"
></app-bulk-validation-modal>
-246
View File
@@ -1,246 +0,0 @@
.card {
margin-top: 0;
border: 0;
}
.buttonBar {
padding: 2px 10px 2px 10px;
align-items: center;
}
.testRed {
color: white;
background: rgba(255,0,0, 0.8) !important;
}
hot-table {
::ng-deep {
.firstColumnHeaderStyle button.changeType {
display: none;
}
.handsontable tbody th.ht__highlight, .handsontable thead th.ht__highlight {
&.primaryKeyHeaderStyle {
background-color: #306b00b0 !important;
}
}
.primaryKeyHeaderStyle {
background-color: #306b006e !important;
}
th.readonlyCell {
div {
opacity: 0.4;
}
}
td.readonlyCell {
opacity: 0.5
}
}
}
.submit-reason {
min-height: 120px;
max-height: 120px;
height: 120px;
}
.infoBar {
margin-top:14px;
background: #495967;
color: white;
text-align:center;
padding: 3px;
font-size: 16px;
height: 30px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
span {
width: 80%;
}
&:hover {
height: unset;
white-space: normal;
span {
width: unset;
}
}
}
.pkHeader {
background: #687682;
color: #fff;
margin: -1px -1px -1px -1px;
}
.headerBar {
// padding: 13px 0px 14px 0px;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
background: var(--clr-vertical-nav-bg-color);
}
.error-icon {
width: 30px;
height: 30px;
color: red;
}
.btnCtrl {
display:flex;
justify-content:flex-end;
}
.card-header {
border-bottom: 1px solid transparent;
}
.hidden {
visibility: hidden;
}
.my-drop-zone {
border: solid 1px lightgray;
border-radius: 10px;
background: whitesmoke;
box-shadow: inset 0px 0px 4px 2px #a7a5a52b;
height: 50vh;
}
.nv-file-over {
border: solid 2px green;
} /* Default class applied to drop zones on over */
.file-drop-text{
text-align: center;
}
.nv-file-over {
border: solid 2px green;
} /* Default class applied to drop zones on over */
.file-drop-text{
text-align: center;
}
@media screen and (max-width: 768px) {
.progresStatic {
margin-top:9px!important;
}
.progress, .progress-static {
width: calc(100% - 14px);
}
}
.hotEditor {
position: relative;
}
.excel-parsing {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
.details {
margin: 0;
position: absolute;
top: -45px;
}
}
.edit-record-spinner {
display: flex;
justify-content: center;
align-items: center;
background: rgba(255, 255, 255, 0.6);
position: absolute;
top: 0px;
bottom: 0px;
width: 100%;
z-index: 500;
}
@media screen and (max-width: 480px) {
.progresStatic {
margin-top:32px!important;
}
.card-block, .card-footer {
padding: 10px 0px 0px 0px;
}
}
.content-area {
padding: 0 0.8rem 0.8rem 0.8rem !important;
padding-top: 0;
// .card {
// min-height: calc(100vh - 160px);
// }
}
.drop-area {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
display: flex;
justify-content: center;
align-items: flex-start;
margin: 1px;
border: 2px dashed #fff;
z-index: -1;
span {
font-size: 20px;
margin-top: 20px;
padding: 10px;
background: #dbdbdb;
border-radius: 5px;
color: black;
}
}
#submitBtn, #cancelSubmitBtn {
width: 150px;
}
.view-table {
font-size: inherit !important;
}
// When width is smaller remove the text from the buttons
// keep only the icons
@media (max-width: 992px) {
.icon-collapse {
.text {
display: none;
}
}
}
// FIXME
// Let's leave it here for a reference if there
// is an issue with viewboxes/filter modal overlaying
// we will remove it if no issues found
// .filter-modal {
// z-index: 1210;
// }
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { ClarityModule } from '@clr/angular'
import { HotTableModule } from '@handsontable/angular'
import { HotTableModule } from '@handsontable/angular-wrapper'
import { registerAllModules } from 'handsontable/registry'
import { AppSharedModule } from '../app-shared.module'
import { DirectivesModule } from '../directives/directives.module'
@@ -28,7 +28,7 @@ registerAllModules()
FormsModule,
EditorRoutingModule,
ClarityModule,
HotTableModule.forRoot(),
HotTableModule,
AppSharedModule,
DirectivesModule,
SharedModule,
@@ -5,7 +5,6 @@
export interface DynamicExtendedCellValidation {
DISPLAY_INDEX: number
DISPLAY_TYPE: string
DISPLAY_VALUE: string
EXTRA_COL_NAME: string
RAW_VALUE_CHAR: string
RAW_VALUE_NUM: number
@@ -0,0 +1,88 @@
import Handsontable from 'handsontable'
import { makeNumberFormatRenderer } from './renderers.utils'
describe('makeNumberFormatRenderer', () => {
it('renders a numeric cell as EUR currency without changing the value', () => {
const container = document.createElement('div')
document.body.appendChild(container)
const hot = new Handsontable(container, {
data: [{ amt: 1025 }],
columns: [
{
data: 'amt',
type: 'numeric',
renderer: makeNumberFormatRenderer(
'{"style":"currency","currency":"EUR"}'
)
}
],
licenseKey: 'non-commercial-and-evaluation'
})
hot.render()
const td = hot.getCell(0, 0)
// Display is formatted as currency...
expect(td?.textContent).toContain('€')
expect(td?.textContent).toContain('1,025')
// ...but the stored value is untouched
expect(hot.getDataAtCell(0, 0)).toEqual(1025)
hot.destroy()
container.remove()
})
it('is overridden by numbro numericFormat (why DcValidator clears it for NUMBER_FORMAT cols)', () => {
// Regression note: on a `type: 'numeric'` column, a `numericFormat` makes
// HOT re-render via numbro and drop our currency symbol. DcValidator clears
// numericFormat on NUMBER_FORMAT columns so the Intl renderer wins.
const container = document.createElement('div')
document.body.appendChild(container)
const hot = new Handsontable(container, {
data: [{ amt: 1025 }],
columns: [
{
data: 'amt',
type: 'numeric',
numericFormat: { pattern: '0,0', culture: 'en-US' },
renderer: makeNumberFormatRenderer(
'{"style":"currency","currency":"EUR"}'
)
}
],
licenseKey: 'non-commercial-and-evaluation'
})
hot.render()
const td = hot.getCell(0, 0)
expect(td?.textContent).not.toContain('€')
hot.destroy()
container.remove()
})
it('falls back to a plain number when options JSON is invalid', () => {
const container = document.createElement('div')
document.body.appendChild(container)
const hot = new Handsontable(container, {
data: [{ amt: 1025 }],
columns: [
{
data: 'amt',
type: 'numeric',
renderer: makeNumberFormatRenderer('not json')
}
],
licenseKey: 'non-commercial-and-evaluation'
})
hot.render()
const td = hot.getCell(0, 0)
expect(td?.textContent).not.toContain('€')
hot.destroy()
container.remove()
})
})
@@ -1,3 +1,57 @@
import Handsontable from 'handsontable'
/**
* Builds a display-only HOT renderer that formats numeric cell values using
* Intl.NumberFormat. The stored/submitted value is never changed — only the
* rendered text. `ruleValue` is a JSON string of Intl.NumberFormat options
* (e.g. '{"style":"currency","currency":"EUR","minimumFractionDigits":2}').
*
* Falls back to the plain text renderer when the JSON is invalid, the options
* are rejected by Intl.NumberFormat, or the value is not a finite number.
*/
export const makeNumberFormatRenderer = (ruleValue?: string) => {
let formatter: Intl.NumberFormat | null = null
try {
const options = ruleValue ? JSON.parse(ruleValue) : {}
formatter = new Intl.NumberFormat(window.navigator.language, options)
} catch (e) {
console.warn(
`NUMBER_FORMAT - invalid Intl.NumberFormat options: ${ruleValue}`
)
formatter = null
}
const baseRenderer = Handsontable.renderers.getRenderer('text')
return (
instance: any,
td: any,
row: number,
col: number,
prop: string | number,
value: any,
cellProperties: any
) => {
// Render via the base text renderer first to preserve cell styling/classes
// (readOnly, alignment, etc.), then override the displayed text.
baseRenderer(instance, td, row, col, prop, value, cellProperties)
const num = Number(value)
if (
formatter &&
value !== null &&
value !== undefined &&
value !== '' &&
!isNaN(num)
) {
td.textContent = formatter.format(num)
}
return td
}
}
/**
* Custom renderer for HOT cell
* Used to show error icon
+1 -1
View File
@@ -30,7 +30,7 @@ export const freeTierConfig: LicenceState = {
lineage_daily_limit: 3,
tables_in_library_limit: 35,
viewbox: true,
fileUpload: true,
fileUpload: false,
editRecord: true,
addRecord: true
}
-85
View File
@@ -1,85 +0,0 @@
@import '../../colors.scss';
::ng-deep body[cds-theme="dark"] {
.group-info {
background-color: $headerBackground;
border-color: $headerBackground;
}
.group-data {
background-color: $headerBackground;
border-color: $headerBackground;
}
.member-table tbody{
tr:hover{
background-color: #29404b;
}
}
}
::ng-deep body[cds-theme="light"] {
.group-info{
background-color: #f9f9f9;
border-color: #a7a7a7;
box-shadow: 0px 2px 5px #dad7d7;
}
.group-data {
background-color: #f9f9f9;
border-color: #a7a7a7;
box-shadow: 0px 2px 5px #dad7d7;
}
.member-table tbody{
tr:hover{
background-color: #e6e6e6;
}
}
}
.sidebar-height{
height: 100%;
}
.group-info-text{
display: inline;
font-size: 20px;
}
.group-info{
border: 1px solid;
border-radius: 3px;
}
.group-info td{
text-align: center;
}
.group-data{
border: 1px solid;
border-radius: 3px;
}
.group-data{
min-height: auto;
h3, h5{
text-align: center;
}
.member-table{
width: 100%;
}
.member-table tbody{
tr:hover{
cursor: pointer;
}
}
}
.table-container{
overflow: auto;
}
@media screen and (max-width: 768px){
.group-data{
min-height: unset !important;
}
}
+4 -2
View File
@@ -1,5 +1,5 @@
import { Location } from '@angular/common'
import { Component, OnInit } from '@angular/core'
import { Component, OnInit, ViewEncapsulation } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { SASjsConfig } from '@sasjs/adapter'
import { ServerType } from '@sasjs/utils/types/serverType'
@@ -14,7 +14,9 @@ import { RequestWrapperResponse } from '../models/request-wrapper/RequestWrapper
styleUrls: ['./group.component.scss'],
host: {
class: 'content-container'
}
},
encapsulation: ViewEncapsulation.None,
standalone: false
})
export class GroupComponent implements OnInit {
public groups: Array<any> | undefined
+22 -10
View File
@@ -16,11 +16,13 @@
<clr-icon
*ngIf="searchLibTreeInput.value.length < 1"
shape="search"
aria-hidden="true"
></clr-icon>
<clr-icon
*ngIf="searchLibTreeInput.value.length > 0"
(click)="librariesSearch = ''; libraryOnFilter()"
shape="times"
aria-label="Clear libraries search"
></clr-icon>
</div>
</clr-tree-node>
@@ -40,7 +42,7 @@
"
class="m-0 cursor-pointer"
>
<clr-icon shape="rack-server"></clr-icon>
<clr-icon shape="rack-server" aria-hidden="true"></clr-icon>
{{ library.LIBRARYREF }}
</p>
@@ -60,6 +62,7 @@
<clr-icon
*ngIf="searchTreeInput.value.length < 1"
shape="search"
aria-hidden="true"
></clr-icon>
<clr-icon
*ngIf="searchTreeInput.value.length > 0"
@@ -69,6 +72,7 @@
treeOnFilter(library, 'tables')
"
shape="times"
aria-label="Clear tables search"
></clr-icon>
</div>
</clr-tree-node>
@@ -90,8 +94,16 @@
[class.active]="libTabActive(library.LIBRARYREF, libTable)"
>
<ng-container [ngSwitch]="libTable.includes('-FC')">
<clr-icon *ngSwitchCase="true" shape="bolt"></clr-icon>
<clr-icon *ngSwitchCase="false" shape="table"></clr-icon>
<clr-icon
*ngSwitchCase="true"
shape="bolt"
aria-hidden="true"
></clr-icon>
<clr-icon
*ngSwitchCase="false"
shape="table"
aria-hidden="true"
></clr-icon>
</ng-container>
{{ libTable.replace('-FC', '') }}
</button>
@@ -114,7 +126,7 @@
</clr-tree>
</app-sidebar>
<div class="content-area">
<main class="content-area">
<div class="card-block">
<div *ngIf="loading" class="spinner-wrapper-fullpage">
<div class="loadingSpinner">
@@ -123,11 +135,11 @@
</div>
<div *ngIf="!loading" class="no-table-selected">
<clr-icon
shape="warning-standard"
size="60"
class="is-info icon-dc-fill"
></clr-icon>
<img
src="images/select-table.png"
class="select-table-icon"
alt="select table icon"
/>
<p
*ngIf="treeNodeLibraries?.length! > 0"
class="text-center color-gray mt-10"
@@ -144,4 +156,4 @@
</p>
</div>
</div>
</div>
</main>
-32
View File
@@ -1,32 +0,0 @@
clr-tree-node button {
white-space: nowrap;
}
.card-block {
height: 100%;
padding: 0;
}
.no-table-selected {
position: relative;
height: 100%;
}
::ng-deep {
// .badge.badge-info {
// background: #6a9235!important;
// color: #fff;
// }
clr-icon.is-blue, clr-icon.is-info {
fill: #6a9235;
}
}
.spinner-wrapper-fullpage {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}
+4 -2
View File
@@ -3,7 +3,7 @@
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
import { Component, AfterContentInit } from '@angular/core'
import { Component, AfterContentInit, ViewEncapsulation } from '@angular/core'
import { Router } from '@angular/router'
import { ActivatedRoute } from '@angular/router'
import { globals } from '../_globals'
@@ -18,7 +18,9 @@ import { LicenceService } from '../services/licence.service'
templateUrl: './home.component.html',
host: {
class: 'content-container'
}
},
encapsulation: ViewEncapsulation.None,
standalone: false
})
export class HomeComponent implements AfterContentInit {
public treeNodeLibraries: Array<any> | null = null
@@ -34,6 +34,8 @@
</p>
</ng-container>
<p><strong>Protocol:</strong> {{ protocol }}</p>
<p>
<strong>SYSSITE:</strong>
<span
@@ -1,51 +0,0 @@
:host {
height: calc(100% - 96px);
padding: 20px 20px;
}
.card {
margin-top: 0;
}
.key-error {
font-size: 16px;
}
.misskey {
color: #E74C3C;
}
.license-key-form, .activation-key-form {
padding: 0;
.clr-control-container {
width: 100%;
textarea {
width: 100%;
height: 170px;
max-height: 170px;
min-height: 170px;
resize: none;
}
}
}
.apply-keys {
height: 40px;
}
.drop-area {
display: flex;
justify-content: center;
align-items: center;
padding: 15px;
border: 2px dashed #b2b2b2;
border-radius: 4px;
cursor: pointer;
margin: 10px 0;
}
clr-tabs button {
box-shadow: none !important
}
+186 -177
View File
@@ -1,177 +1,186 @@
import { Component, OnInit } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { AppService, LicenceService, SasService } from '../services'
import { LicenseKeyData } from '../models/LicenseKeyData'
import { RequestWrapperResponse } from '../models/request-wrapper/RequestWrapperResponse'
enum LicenseActions {
key = 'key',
register = 'register',
limit = 'limit',
update = 'update'
}
@Component({
selector: 'app-licensing',
templateUrl: './licensing.component.html',
styleUrls: ['./licensing.component.scss']
})
export class LicensingComponent implements OnInit {
public action: LicenseActions | null = null
public licenseErrors: { [key: string]: string } = {
missing: `Licence key is missing - please contact <a class="color-green" href="mailto: support@datacontroller.io">support@datacontroller.io</a> and enter valid keys below.`,
expired: `Licence key is expired - please contact <a class="color-green" href="mailto: support@datacontroller.io">support@datacontroller.io</a> and enter valid keys below.`,
invalid: `Licence key is invalid - please contact <a class="color-green" href="mailto: support@datacontroller.io">support@datacontroller.io</a> and enter valid keys below.`,
missmatch: `Your SYSSITE (below) is not found in the licence key - please contact <a class="color-green" href="mailto: support@datacontroller.io">support@datacontroller.io</a> and enter valid keys below.`
}
public keyError: string | undefined
public errorDetails: string | undefined
public missmatchedKey: string | undefined
public licenceKeyValue: string = ''
public activationKeyValue: string = ''
public applyingKeys: boolean = false
public syssite = this.appService.syssite
public currentLicenceKey = this.licenceService.licenceKey
public currentActivationKey = this.licenceService.activationKey
public isAppFreeTier = this.licenceService.isAppFreeTier
public userCountLimitation = this.licenceService.userCountLimitation
public licenseKeyData: LicenseKeyData | null = null
public inputType: 'file' | 'paste' = 'file'
public licenceFileError: string | undefined
public licenceFileLoading: boolean = false
public licencefile: { filename: string } = {
filename: ''
}
constructor(
private route: ActivatedRoute,
private licenceService: LicenceService,
private sasService: SasService,
private appService: AppService
) {}
ngOnInit(): void {
this.licenceKeyValue = this.currentLicenceKey || ''
this.activationKeyValue = this.currentActivationKey || ''
this.route.queryParams.subscribe((queryParams: any) => {
this.keyError = queryParams.error
this.missmatchedKey = queryParams.missmatchId
if (queryParams.details) {
this.errorDetails = atob(queryParams.details)
}
})
this.route.params.subscribe((params: any) => {
let actionInUrl = params.action
if (actionInUrl) {
if (Object.values(LicenseActions).includes(actionInUrl)) {
this.action = actionInUrl
}
}
})
this.licenseKeyData = this.licenceService.getLicenseKeyData()
}
public trimKeys() {
this.licenceKeyValue = this.licenceKeyValue.trim()
this.activationKeyValue = this.activationKeyValue.trim()
}
public copySyssite(copyIconRef: any, copyTooltip: any, syssite: string[]) {
const syssiteString = syssite.join('\n')
navigator.clipboard.writeText(syssiteString).then(() => {
copyIconRef.setAttribute('shape', 'check')
copyIconRef.setAttribute('class', 'is-success')
copyTooltip.innerText = 'Copied!'
setTimeout(() => {
copyIconRef.setAttribute('shape', 'copy')
copyIconRef.removeAttribute('class')
copyTooltip.innerText = 'Copy to clipboard'
}, 1000)
})
}
public applyKeys() {
this.applyingKeys = true
let table = {
keyupload: [
{
ACTIVATION_KEY: this.activationKeyValue,
LICENCE_KEY: this.licenceKeyValue
}
]
}
this.sasService
.request('admin/registerkey', table)
.then((res: RequestWrapperResponse) => {
if (
res.adapterResponse.return &&
res.adapterResponse.return[0] &&
res.adapterResponse.return[0].MSG === 'SUCCESS'
) {
location.replace(location.href.split('#')[0])
}
})
.finally(() => {
this.applyingKeys = false
})
}
public onFileCapture(event: any, dropped = false) {
let file = dropped ? event[0] : event.target.files[0]
this.licencefile.filename = file.name
if (!file) return
this.licenceFileLoading = true
const reader = new FileReader()
reader.onload = (evt) => {
this.licenceFileError = 'Error reading file.'
if (!evt || !evt.target) return
if (evt.target.readyState != 2) return
if (evt.target.error) return
if (!evt.target.result) return
this.licenceFileLoading = false
this.licenceFileError = undefined
const fileArr = evt.target.result.toString().split('\n')
this.activationKeyValue = fileArr[1]
this.licenceKeyValue = fileArr[0]
}
reader.readAsText(file)
}
public switchType(type: 'paste' | 'file') {
this.inputType = type
}
get disableApplyButton(): boolean {
if (this.licenceKeyValue.length < 1 || this.activationKeyValue.length < 1)
return true
if (
this.licenceKeyValue === this.currentLicenceKey &&
this.activationKeyValue === this.currentActivationKey
)
return true
return false
}
}
import { Component, OnInit, ViewEncapsulation } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { AppService, LicenceService, SasService } from '../services'
import { LicenseKeyData } from '../models/LicenseKeyData'
import { RequestWrapperResponse } from '../models/request-wrapper/RequestWrapperResponse'
enum LicenseActions {
key = 'key',
register = 'register',
limit = 'limit',
update = 'update'
}
@Component({
selector: 'app-licensing',
templateUrl: './licensing.component.html',
styleUrls: ['./licensing.component.scss'],
encapsulation: ViewEncapsulation.None,
standalone: false
})
export class LicensingComponent implements OnInit {
public action: LicenseActions | null = null
public licenseErrors: { [key: string]: string } = {
missing: `Licence key is missing - please contact <a class="color-green" href="mailto: support@datacontroller.io">support@datacontroller.io</a> and enter valid keys below.`,
expired: `Licence key is expired - please contact <a class="color-green" href="mailto: support@datacontroller.io">support@datacontroller.io</a> and enter valid keys below.`,
invalid: `Licence key is invalid - please contact <a class="color-green" href="mailto: support@datacontroller.io">support@datacontroller.io</a> and enter valid keys below.`,
missmatch: `Your SYSSITE (below) is not found in the licence key - please contact <a class="color-green" href="mailto: support@datacontroller.io">support@datacontroller.io</a> and enter valid keys below.`
}
public keyError: string | undefined
public errorDetails: string | undefined
public missmatchedKey: string | undefined
public licenceKeyValue: string = ''
public activationKeyValue: string = ''
public applyingKeys: boolean = false
public protocol: string =
location.protocol === 'https:'
? 'HTTPS - secure connection'
: 'HTTP - insecure connection'
public syssite = this.appService.syssite
public currentLicenceKey = this.licenceService.licenceKey
public currentActivationKey = this.licenceService.activationKey
public isAppFreeTier = this.licenceService.isAppFreeTier
public userCountLimitation = this.licenceService.userCountLimitation
public licenseKeyData: LicenseKeyData | null = null
public inputType: 'file' | 'paste' = 'file'
public licenceFileError: string | undefined
public licenceFileLoading: boolean = false
public licencefile: { filename: string } = {
filename: ''
}
constructor(
private route: ActivatedRoute,
private router: Router,
private licenceService: LicenceService,
private sasService: SasService,
private appService: AppService
) {}
ngOnInit(): void {
this.licenceKeyValue = this.currentLicenceKey || ''
this.activationKeyValue = this.currentActivationKey || ''
this.route.queryParams.subscribe((queryParams: any) => {
this.keyError = queryParams.error
this.missmatchedKey = queryParams.missmatchId
if (queryParams.details) {
this.errorDetails = atob(queryParams.details)
}
})
this.route.params.subscribe((params: any) => {
let actionInUrl = params.action
if (actionInUrl) {
if (Object.values(LicenseActions).includes(actionInUrl)) {
this.action = actionInUrl
}
}
})
this.licenseKeyData = this.licenceService.getLicenseKeyData()
}
public trimKeys() {
this.licenceKeyValue = this.licenceKeyValue.trim()
this.activationKeyValue = this.activationKeyValue.trim()
}
public copySyssite(copyIconRef: any, copyTooltip: any, syssite: string[]) {
const syssiteString = syssite.join('\n')
navigator.clipboard.writeText(syssiteString).then(() => {
copyIconRef.setAttribute('shape', 'check')
copyIconRef.setAttribute('class', 'is-success')
copyTooltip.innerText = 'Copied!'
setTimeout(() => {
copyIconRef.setAttribute('shape', 'copy')
copyIconRef.removeAttribute('class')
copyTooltip.innerText = 'Copy to clipboard'
}, 1000)
})
}
public applyKeys() {
this.applyingKeys = true
let table = {
keyupload: [
{
ACTIVATION_KEY: this.activationKeyValue,
LICENCE_KEY: this.licenceKeyValue
}
]
}
this.sasService
.request('admin/registerkey', table)
.then((res: RequestWrapperResponse) => {
if (
res.adapterResponse.return &&
res.adapterResponse.return[0] &&
res.adapterResponse.return[0].MSG === 'SUCCESS'
) {
this.router.navigateByUrl('/').then(() => {
window.location.reload()
})
}
})
.finally(() => {
this.applyingKeys = false
})
}
public onFileCapture(event: any, dropped = false) {
let file = dropped ? event[0] : event.target.files[0]
this.licencefile.filename = file.name
if (!file) return
this.licenceFileLoading = true
const reader = new FileReader()
reader.onload = (evt) => {
this.licenceFileError = 'Error reading file.'
if (!evt || !evt.target) return
if (evt.target.readyState != 2) return
if (evt.target.error) return
if (!evt.target.result) return
this.licenceFileLoading = false
this.licenceFileError = undefined
const fileArr = evt.target.result.toString().split('\n')
this.activationKeyValue = fileArr[1]
this.licenceKeyValue = fileArr[0]
}
reader.readAsText(file)
}
public switchType(type: 'paste' | 'file') {
this.inputType = type
}
get disableApplyButton(): boolean {
if (this.licenceKeyValue.length < 1 || this.activationKeyValue.length < 1)
return true
if (
this.licenceKeyValue === this.currentLicenceKey &&
this.activationKeyValue === this.currentActivationKey
)
return true
return false
}
}
+2 -14
View File
@@ -239,13 +239,7 @@
<clr-dropdown-menu clrPosition="bottom-left" *clrIfOpen>
<div (click)="downloadSVG()" clrDropdownItem>SVG</div>
<div
*ngIf="!helperService.isMicrosoft"
(click)="downloadPNG()"
clrDropdownItem
>
PNG
</div>
<div (click)="downloadPNG()" clrDropdownItem>PNG</div>
<div (click)="downloadDot()" clrDropdownItem>Dot</div>
<div *ngIf="flatdata" (click)="downloadCSV()" clrDropdownItem>
CSV
@@ -366,13 +360,7 @@
<clr-dropdown-menu clrPosition="bottom-left" *clrIfOpen>
<div (click)="renderToDownload('SVG')" clrDropdownItem>SVG</div>
<div
*ngIf="!helperService.isMicrosoft"
(click)="renderToDownload('PNG')"
clrDropdownItem
>
PNG
</div>
<div (click)="renderToDownload('PNG')" clrDropdownItem>PNG</div>
<div (click)="downloadDot(); cancelRenderingGraph()" clrDropdownItem>
Dot
</div>
@@ -1,85 +0,0 @@
@import '../../colors.scss';
.toggle-switch input[type=checkbox]:checked+label:before {
border-color: $headerBackground;
background-color: $headerBackground !important;
transition: .15s ease-in;
transition-property: border-color,background-color;
}
#graph{
height: calc(100vh - 195px);
overflow: hidden;
text-align: center;
display: block;
width: 100%;
border: 1px solid #e4e4e4;
margin-top: 10px;
}
.selection-wrapper {
width: 100%;
max-width: 670px;
}
.column-active {
background: #d8e3e9;
color: black;
}
.content-area {
padding: 0.5rem !important;
.card {
min-height: calc(100vh - 120px);
.card-block {
padding: 0.5rem 0.35rem !important;
}
}
}
clr-tree-node button {
white-space: nowrap;
}
.btn-group.direction {
margin-left: var(--cds-global-space-6);
}
.graph-render-spinner {
position: absolute;
top: 0;
width: 100%;
display: flex;
justify-content: center;
margin-top: 10px;
}
.biglineage-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.modal-footer {
p {
margin: 0;
}
}
.lineage-title-wrapper {
left: 12px;
}
.max-depth-input {
width: 100%;
}
@media (max-width: 768px) {
.toggle-switch-container {
margin-bottom: 20px;
}
}
+17 -47
View File
@@ -1,4 +1,4 @@
import { Component } from '@angular/core'
import { Component, ViewEncapsulation } from '@angular/core'
import { Location } from '@angular/common'
import { globals } from '../_globals'
import * as d3Viz from 'd3-graphviz'
@@ -18,7 +18,9 @@ const moment = require('moment')
templateUrl: './lineage.component.html',
host: {
class: 'content-container'
}
},
encapsulation: ViewEncapsulation.None,
standalone: false
})
export class LineageComponent {
public switchFlag: boolean = false
@@ -745,28 +747,13 @@ export class LineageComponent {
return URL.createObjectURL(svg_blob)
}
private getSVGBlob() {
let svg: any = document.getElementById('graph')
let serializer = new XMLSerializer()
let svg_blob = new Blob([serializer.serializeToString(svg)], {
type: 'image/svg+xml'
})
return svg_blob
}
downloadSVG() {
d3Viz.graphviz('#graph').resetZoom()
if (navigator.appVersion.toString().indexOf('.NET') > 0) {
window.navigator.msSaveBlob(this.getSVGBlob(), this.constructName('svg'))
} else {
let downloadLink = document.createElement('a')
downloadLink.href = this.getSVGURL()
downloadLink.download = this.constructName('svg')
document.body.appendChild(downloadLink)
downloadLink.click()
document.body.removeChild(downloadLink)
}
let downloadLink = document.createElement('a')
downloadLink.href = this.getSVGURL()
downloadLink.download = this.constructName('svg')
downloadLink.click()
}
async downloadPNG() {
@@ -794,16 +781,11 @@ export class LineageComponent {
var a = document.createElement('a')
var blob = new Blob([csvArray], { type: 'text/csv' })
if (navigator.appVersion.toString().indexOf('.NET') > 0) {
window.navigator.msSaveBlob(blob, this.constructName('csv'))
} else {
var url = window.URL.createObjectURL(blob)
a.href = url
a.download = this.constructName('csv')
a.click()
window.URL.revokeObjectURL(url)
a.remove()
}
var url = window.URL.createObjectURL(blob)
a.href = url
a.download = this.constructName('csv')
a.click()
window.URL.revokeObjectURL(url)
}
private getDotUrl() {
@@ -812,23 +794,11 @@ export class LineageComponent {
return window.URL.createObjectURL(dot_blob)
}
private getDotBlob() {
let data = this.vizInput
let dot_blob = new Blob([data], { type: 'text/plain' })
return dot_blob
}
downloadDot() {
if (navigator.appVersion.toString().indexOf('.NET') > 0) {
window.navigator.msSaveBlob(this.getDotBlob(), this.constructName('txt'))
} else {
let downloadLink = document.createElement('a')
downloadLink.href = this.getDotUrl()
downloadLink.download = this.constructName('txt')
document.body.appendChild(downloadLink)
downloadLink.click()
document.body.removeChild(downloadLink)
}
let downloadLink = document.createElement('a')
downloadLink.href = this.getDotUrl()
downloadLink.download = this.constructName('txt')
downloadLink.click()
}
public showSvg() {
@@ -1,82 +0,0 @@
::ng-deep body[cds-theme="dark"] {
.object-header:hover {
background-color: #405560;
}
}
::ng-deep body[cds-theme="light"] {
.objects-col {
background: white;
}
.object-header:hover {
background-color: #d8e3e9;
}
}
.objects-col{
height: 75vh;
overflow: scroll;
border: 1px solid #cccccc;
border-radius: 4px;
}
.cols-head {
border: 1px solid #cccccc;
padding: 10px;
display: flex;
}
.object-text {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-left: 10px;
flex: 1;
}
.repo-dropdown{
margin-right: 15px;
margin-left: 15px;
margin-bottom: 10px;
}
.clr-accordion-title{
width: 100%;
}
.float-right{
margin: 0px;
float: right;
}
.full-width{
width: 100%;
}
.object-uri{
margin: 0px;
margin-top: 5px;
}
.object-header{
display: flex;
align-items: center;
justify-content: space-between;
padding-left: 3px;
padding-right: 3px;
}
.object-header:hover{
border-radius: 3px;
}
.datagrid-host{
display: unset !important;
}
.card {
margin-top: 0;
flex: 1;
display: flex;
flex-direction: column;
}
.content-area {
padding: 0.5rem !important;
display: flex;
flex-direction: column;
}
@@ -1,5 +1,5 @@
import { Location } from '@angular/common'
import { Component, OnInit } from '@angular/core'
import { Component, OnInit, ViewEncapsulation } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { ClrDatagridStringFilterInterface } from '@clr/angular'
import { Observable, of } from 'rxjs'
@@ -50,7 +50,9 @@ class ValueFilter implements ClrDatagridStringFilterInterface<MetaData> {
styleUrls: ['./metadata.component.scss'],
host: {
class: 'content-container'
}
},
encapsulation: ViewEncapsulation.None,
standalone: false
})
export class MetadataComponent implements OnInit {
metaDataList: Array<any> | undefined
@@ -98,6 +100,11 @@ export class MetadataComponent implements OnInit {
}
this.pageSize = 5
// Initialize filters for accessibility
this.typeFilter = new TypeFilter()
this.nameFilter = new NameFilter()
this.valueFilter = new ValueFilter()
if (
globals.metadata.metaDataList &&
globals.metadata.metaRepositories &&
@@ -6,4 +6,5 @@ export interface CellValidationSource {
extended_values?: string[]
hash: string
count: number
pending?: Promise<void>
}
@@ -8,4 +8,5 @@ export interface Libinfo {
LIBID: string
LIBSIZE: number
TABLE_CNT: number
CATALOG_CNT: number
}
@@ -1,30 +1,14 @@
import { BaseSASResponse } from './common/BaseSASResponse'
export interface EditorsStageDataSASResponse extends BaseSASResponse {
SYSDATE: string
SYSTIME: string
sasparams: Sasparam[]
_DEBUG: string
_PROGRAM: string
AUTOEXEC: string
MF_GETUSER: string
SYSCC: string
SYSENCODING: string
SYSERRORTEXT: string
SYSHOSTINFOLONG: string
SYSHOSTNAME: string
SYSPROCESSID: string
SYSPROCESSMODE: string
SYSPROCESSNAME: string
SYSJOBID: string
SYSSCPL: string
SYSSITE: string
SYSTCPIPHOSTNAME: string
SYSUSERID: string
SYSVLONG: string
SYSWARNINGTEXT: string
END_DTTM: string
MEMSIZE: string
}
export interface Sasparam {
@@ -0,0 +1,28 @@
import { BaseSASResponse } from './common/BaseSASResponse'
export interface PublicGetChangeinfo extends BaseSASResponse {
jsparams: Jsparam[]
}
export interface Jsparam {
TABLE_ID: string
SUBMIT_STATUS_CD: string
BASE_LIB: string
BASE_DS: string
SUBMITTED_BY_NM: string
SUBMITTED_ON: number
SUBMITTED_REASON_TXT: string
INPUT_OBS: number
INPUT_VARS: number
NUM_OF_APPROVALS_REQUIRED: number
NUM_OF_APPROVALS_REMAINING: number
REVIEWED_BY_NM: string
REVIEWED_ON?: any
TABLE_NM: string
BASE_TABLE: string
REVIEWED_ON_DTTM: string
SUBMITTED_ON_DTTM: string
LIB_ENGINE: string
ALLOW_RESTORE: string
REASON: string
}
@@ -127,7 +127,8 @@
class="no-table-selected pointer-events-none"
>
<clr-icon
shape="warning-standard"
aria-hidden="true"
shape="upload-cloud"
size="40"
class="is-info icon-dc-fill"
></clr-icon>
@@ -166,13 +167,10 @@
>
<hot-table
hotId="hotInstanceUserDataset"
#hotInstanceUserDataset
id="hotTableUserDataset"
class="mt-15"
[afterGetColHeader]="afterGetColHeader"
[settings]="hotUserDatasets"
[licenseKey]="hotTableLicenseKey"
stretchH="all"
[settings]="hotUserDatasetsSettings"
>
</hot-table>
@@ -200,6 +198,7 @@
>
<ng-container *ngIf="fileLoadingState !== FileLoadingState.parsed">
<clr-icon
aria-hidden="true"
shape="process-on-vm"
size="40"
class="is-info icon-dc-fill"
@@ -212,6 +211,7 @@
<ng-container *ngIf="fileLoadingState === FileLoadingState.parsed">
<clr-icon
aria-hidden="true"
shape="warning-standard"
size="40"
class="is-info icon-dc-fill"
@@ -360,17 +360,10 @@
</div>
<hot-table
hotId="hotInstance"
#hotInstanceMain
id="hotTable"
class="mt-15"
[afterGetColHeader]="afterGetColHeader"
[className]="['htDark', 'htCustomHidden']"
[licenseKey]="hotTableLicenseKey"
[multiColumnSorting]="true"
[viewportRowRenderingOffset]="50"
[manualColumnResize]="true"
[filters]="true"
stretchH="all"
[settings]="hotMainTableSettings"
>
</hot-table>
</ng-container>
@@ -382,6 +375,7 @@
class="no-table-selected pointer-events-none"
>
<clr-icon
aria-hidden="true"
shape="warning-standard"
size="40"
class="is-info icon-dc-fill"
@@ -1,54 +0,0 @@
.no-table-selected {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
position: absolute;
background: var(--clr-vertical-nav-bg-color);
z-index: 10;
width: 100%;
height: 100%;
top: 0;
}
.header-row {
padding: 15px 0;
border-bottom: 1px solid #d3d3d3;
}
.dataset-input-wrapper {
max-width: 500px;
width: 100%;
textarea {
min-height: 200px;
height: 200px;
}
}
.submit-reason {
min-height: 70px;
max-height: 70px;
height: 70px;
}
.log-wrapper {
margin: 0 10px;
height: auto;
}
::ng-deep td.not-matched {
background-color: #ff000054;
}
.dataset-selection-actions {
border-top: 1px solid #d3d3d3;
}
.licence-limit-notice {
color: var(--cds-alias-status-warning-dark);
}
.submission-results {
border-bottom: 1px solid #d3d3d3;
}
@@ -4,7 +4,9 @@ import {
ElementRef,
HostBinding,
OnInit,
ViewChild
AfterViewInit,
ViewChild,
ViewEncapsulation
} from '@angular/core'
import {
EventService,
@@ -21,7 +23,7 @@ import { HotTableInterface } from '../models/HotTable.interface'
import { Col } from '../shared/dc-validator/models/col.model'
import { SpreadsheetService } from '../services/spreadsheet.service'
import Handsontable from 'handsontable'
import { HotTableRegisterer } from '@handsontable/angular'
import { HotTableComponent } from '@handsontable/angular-wrapper'
import { EditorsStageDataSASResponse } from '../models/sas/editors-stagedata.model'
import { CellChange, ChangeSource } from 'handsontable/common'
import { baseAfterGetColHeader } from '../shared/utils/hot.utils'
@@ -45,9 +47,11 @@ enum FileLoadingState {
@Component({
selector: 'app-multi-dataset',
templateUrl: './multi-dataset.component.html',
styleUrls: ['./multi-dataset.component.scss']
styleUrls: ['./multi-dataset.component.scss'],
encapsulation: ViewEncapsulation.None,
standalone: false
})
export class MultiDatasetComponent implements OnInit {
export class MultiDatasetComponent implements OnInit, AfterViewInit {
@HostBinding('class.content-container') contentContainerClass = true
@ViewChild('contentArea', { static: true }) contentAreaRef!: ElementRef
@@ -87,7 +91,13 @@ export class MultiDatasetComponent implements OnInit {
public hotInstance!: Handsontable
public hotInstanceUserDataset!: Handsontable
private hotRegisterer: HotTableRegisterer
@ViewChild('hotInstanceMain', { static: false })
hotTableMainComponent!: HotTableComponent
@ViewChild('hotInstanceUserDataset', { static: false })
hotTableUserDatasetComponent!: HotTableComponent
public hotMainTableSettings: Handsontable.GridSettings = {}
public hotUserDatasetsSettings: Handsontable.GridSettings = {}
public showSubmitReasonModal: boolean = false
public submitReasonMessage: string = ''
@@ -134,7 +144,38 @@ export class MultiDatasetComponent implements OnInit {
}
},
manualRowMove: true,
columnSorting: true
columnSorting: true,
afterGetColHeader: baseAfterGetColHeader,
stretchH: 'all'
}
private initializeHotSettings() {
this.hotMainTableSettings = {
className: ['htDark'],
licenseKey: this.hotTableLicenseKey,
multiColumnSorting: true,
viewportRowRenderingOffset: 50,
manualColumnResize: true,
autoColumnSize: true,
filters: true,
stretchH: 'all',
afterGetColHeader: baseAfterGetColHeader,
modifyColWidth: this.maxWidthCheker,
theme: 'ht-theme-classic'
}
// Exclude data from settings for HOT v16 - it will be loaded manually
const { data, ...settingsWithoutData } = this.hotUserDatasets
this.hotUserDatasetsSettings = {
...settingsWithoutData,
licenseKey: this.hotTableLicenseKey,
theme: 'ht-theme-classic'
}
}
public maxWidthCheker(width: any, col: any) {
if (width > 200) return 200
else return width
}
public afterGetColHeader = baseAfterGetColHeader
@@ -147,16 +188,28 @@ export class MultiDatasetComponent implements OnInit {
private spreadsheetService: SpreadsheetService,
private sasService: SasService,
private cdr: ChangeDetectorRef
) {
this.hotRegisterer = new HotTableRegisterer()
}
) {}
ngOnInit() {
this.licenceService.hot_license_key.subscribe(
(hot_license_key: string | undefined) => {
this.hotTableLicenseKey = hot_license_key
this.initializeHotSettings()
}
)
this.initializeHotSettings()
}
ngAfterViewInit() {
// Ensure HOT instances are properly initialized after view is ready
setTimeout(() => {
if (this.hotTableUserDatasetComponent && !this.hotInstanceUserDataset) {
this.initUserInputHot()
}
if (this.hotTableMainComponent && !this.hotInstance) {
this.initHot()
}
}, 50)
}
ngAfterContentInit(): void {
@@ -231,7 +284,10 @@ export class MultiDatasetComponent implements OnInit {
}
this.initUserInputHot()
this.onAutoDetectColumns()
// Call onAutoDetectColumns after HOT is initialized
setTimeout(() => {
this.onAutoDetectColumns()
}, 100)
} else if (matchedExtension === 'csv') {
this.onMultiCsvFiles(event.target.files)
} else {
@@ -390,84 +446,112 @@ export class MultiDatasetComponent implements OnInit {
initHot() {
setTimeout(() => {
this.hotInstance = this.hotRegisterer.getInstance('hotInstance')
if (this.hotTableMainComponent?.hotInstance) {
this.hotInstance = this.hotTableMainComponent.hotInstance
// Set height of parsed data to full height of the page content area
const contentAreaHeight = this.contentAreaRef.nativeElement.clientHeight
const hotHeight = `${contentAreaHeight - 160}px`
// Set height of parsed data to full height of the page content area
const contentAreaHeight = this.contentAreaRef.nativeElement.clientHeight
const hotHeight = `${contentAreaHeight - 160}px`
if (this.activeParsedDataset) {
this.hotInstance.updateSettings({
data: this.activeParsedDataset.datasource || [],
colHeaders: this.activeParsedDataset.datasetInfo.headerColumns,
columns: this.activeParsedDataset.datasetInfo.dcValidator?.getRules(),
readOnly: true,
height: hotHeight || '300px',
className: 'htDark'
})
if (this.activeParsedDataset) {
// Update settings without data - data will be loaded manually
this.hotInstance.updateSettings({
colHeaders: this.activeParsedDataset.datasetInfo.headerColumns,
columns:
this.activeParsedDataset.datasetInfo.dcValidator?.getRules(),
readOnly: true,
height: hotHeight || '300px',
className: ['htDark']
})
// Trigger change detection to avoid ExpressionChangedAfterItHasBeenCheckedError
this.cdr.detectChanges()
// Load data manually - this is required for HOT v16 Angular wrapper
setTimeout(() => {
if (
this.activeParsedDataset &&
this.activeParsedDataset.datasource
) {
this.hotInstance.loadData(this.activeParsedDataset.datasource)
this.hotInstance.render()
}
}, 100)
}
}
})
}, 100)
}
initUserInputHot() {
setTimeout(() => {
this.hotInstanceUserDataset = this.hotRegisterer.getInstance(
'hotInstanceUserDataset'
)
if (this.hotTableUserDatasetComponent?.hotInstance) {
this.hotInstanceUserDataset =
this.hotTableUserDatasetComponent.hotInstance
this.hotInstanceUserDataset.addHook(
'beforeChange',
(changes: (CellChange | null)[], source: ChangeSource) => {
if (changes) {
for (let change of changes) {
if (change && change[3]) {
change[3] = change[3].toUpperCase()
// Load initial data manually after instance is ready
setTimeout(() => {
if (this.hotUserDatasets.data) {
this.hotInstanceUserDataset.loadData(this.hotUserDatasets.data)
this.hotInstanceUserDataset.render()
}
}, 50)
this.hotInstanceUserDataset.addHook(
'beforeChange',
(changes: (CellChange | null)[], source: ChangeSource) => {
if (changes) {
for (let change of changes) {
if (change && change[3]) {
change[3] = change[3].toUpperCase()
}
}
}
}
}
)
)
this.hotInstanceUserDataset.addHook(
'afterChange',
async (changes: CellChange[] | null, source: ChangeSource) => {
if (changes) {
if (source === 'edit') {
await this.onUserInputDatasetsChange()
this.hotInstanceUserDataset.addHook(
'afterChange',
async (changes: CellChange[] | null, source: ChangeSource) => {
if (changes) {
if (source === 'edit') {
await this.onUserInputDatasetsChange()
}
for (let change of changes) {
const row = change[0] as number
this.markUnmatchedRows(row)
}
this.dynamicCellValidations()
this.hotInstanceUserDataset.render()
}
}
)
for (let change of changes) {
const row = change[0] as number
this.hotInstanceUserDataset.addHook(
'afterRemoveRow',
async (
index: number,
amount: number,
physicalRows: number[],
source?: Handsontable.ChangeSource | undefined
) => {
await this.onUserInputDatasetsChange()
for (let row of physicalRows) {
this.markUnmatchedRows(row)
}
this.dynamicCellValidations()
this.hotInstanceUserDataset.render()
}
}
)
this.hotInstanceUserDataset.addHook(
'afterRemoveRow',
async (
index: number,
amount: number,
physicalRows: number[],
source?: Handsontable.ChangeSource | undefined
) => {
await this.onUserInputDatasetsChange()
for (let row of physicalRows) {
this.markUnmatchedRows(row)
}
}
)
})
)
}
}, 100)
}
dynamicCellValidations() {
if (!this.hotInstanceUserDataset) return
const hotData = this.hotInstanceUserDataset.getData()
hotData.forEach((row, rowIndex) => {
@@ -481,6 +565,8 @@ export class MultiDatasetComponent implements OnInit {
}
markUnmatchedRows(row: number) {
if (!this.hotInstanceUserDataset) return
const dataAtRow = this.hotInstanceUserDataset.getDataAtRow(row) as number[]
const dataset = `${dataAtRow[0]}.${dataAtRow[1]}`
const cellMetaAtRow = this.hotInstanceUserDataset.getCellMetaAtRow(row)
@@ -554,6 +640,20 @@ export class MultiDatasetComponent implements OnInit {
* convention. {@link isValidDatasetFormat}
*/
async onAutoDetectColumns() {
// Wait for hotInstanceUserDataset to be ready
if (!this.hotInstanceUserDataset) {
let attempts = 0
const maxAttempts = 20
while (!this.hotInstanceUserDataset && attempts < maxAttempts) {
await new Promise((resolve) => setTimeout(resolve, 100))
attempts++
}
if (!this.hotInstanceUserDataset) {
console.warn('hotInstanceUserDataset not ready after waiting')
return
}
}
let passwordError = false
await this.parseExcelSheetNames()
@@ -614,7 +714,13 @@ export class MultiDatasetComponent implements OnInit {
}
}
this.hotInstanceUserDataset.updateData(hotReadyData)
if (this.hotInstanceUserDataset) {
// Load data manually - this is required for HOT v16 Angular wrapper
setTimeout(() => {
this.hotInstanceUserDataset.loadData(hotReadyData)
this.hotInstanceUserDataset.render()
}, 100)
}
this.dynamicCellValidations()
}
@@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { ClarityModule } from '@clr/angular'
import { HotTableModule } from '@handsontable/angular'
import { HotTableModule } from '@handsontable/angular-wrapper'
import { registerAllModules } from 'handsontable/registry'
import { AppSharedModule } from '../app-shared.module'
import { DirectivesModule } from '../directives/directives.module'
@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'
import { Component, OnInit, ViewEncapsulation } from '@angular/core'
@Component({
selector: 'app-not-found',
@@ -6,7 +6,9 @@ import { Component, OnInit } from '@angular/core'
styleUrls: ['./not-found.component.scss'],
host: {
class: 'content-container'
}
},
encapsulation: ViewEncapsulation.None,
standalone: false
})
export class NotFoundComponent implements OnInit {
constructor() {}
+2 -1
View File
@@ -2,7 +2,8 @@ import { Pipe, PipeTransform } from '@angular/core'
import { bytesToSize } from '@sasjs/utils/utils/bytesToSize'
@Pipe({
name: 'convertSize'
name: 'convertSize',
standalone: false
})
export class ConvertSizePipe implements PipeTransform {
transform(bytes: string | number, ...args: string[]): string {
@@ -1,7 +1,8 @@
import { Pipe, PipeTransform } from '@angular/core'
import * as moment from 'moment'
import moment from 'moment'
@Pipe({
name: 'dateTimeFormatter'
name: 'dateTimeFormatter',
standalone: false
})
export class DateTimeFormatterPipe implements PipeTransform {
transform(value: Date | string, type: string): string {

Some files were not shown because too many files have changed in this diff Show More