200 Commits

Author SHA1 Message Date
485783a782 chore(release): 6.8.1 [skip ci]
## [6.8.1](https://git.datacontroller.io/dc/dc/compare/v6.8.0...v6.8.1) (2024-05-02)

### Bug Fixes

* hide approve button when table revertable ([ec0f539](ec0f539a33))
2024-05-02 16:10:44 +00:00
ee07bef2b8 Merge pull request 'fix: hide approve button when table revertable' (#95) from ci-fix into main
Some checks failed
Release / Build-production-and-ng-test (push) Successful in 3m59s
Release / Build-and-test-development (push) Successful in 7m45s
Release / release (push) Failing after 1m55s
Reviewed-on: #95
2024-05-02 15:57:22 +00:00
0de8481314 ci: ng build
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 4m4s
2024-05-02 17:48:30 +02:00
ec0f539a33 fix: hide approve button when table revertable
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 2m1s
2024-05-02 17:44:53 +02:00
173ee2daff chore(release): 6.8.0 [skip ci]
# [6.8.0](https://git.datacontroller.io/dc/dc/compare/v6.7.0...v6.8.0) (2024-05-02)

### Bug Fixes

* ci sheet lib, submit message auto focus ([c5e4650](c5e4650327))
* **clarity:** new version style issues ([8c7de5a](8c7de5aad7))
* cypress tests ([3dd85cc](3dd85cc60b))
* ensuring that only restorable versions are restorable ([a402856](a4028562ce))
* ensuring version history only includes loaded versions ([51ebd25](51ebd25aa3))
* final testing on restore feature ([297a84d](297a84d3a4))
* issue with multiple adds/deletes, [#84](#84) ([904ca30](904ca30f91))
* load_ref var ([aaad9f7](aaad9f7207))
* removing alerts dummy data, closes [#93](#93) ([eba21e9](eba21e96b4))
* restore table version improvement ([549f357](549f35766b))
* **sas:** viewer versions fix ([c6595c1](c6595c1f61))
* stage and approve buttons renaming ([ef81e33](ef81e33f70))
* supporting SCD2 data reversions ([fa8396f](fa8396f039))
* table info modal, versions - column names ([801c8c6](801c8c6a9f))
* **updates:** angular, clarity, resolved legacy-peer-deps ([c60dd65](c60dd65a16))

### Features

* backend to show in getchangeinfo whether a user is allowed to restore ([8769841](8769841f08))
* list versions of target tables (backend) ([f8a14d4](f8a14d4bde))
* restore ([604c2e7](604c2e70bd))
* SAS services & tests for RESTORE, [#84](#84) ([9ad7ae4](9ad7ae47b5))
* staging page, restore buttons ([02a8a1c](02a8a1c565))
* table metadata modal, versions tab (and link) ([b27fea5](b27fea5b91))
* **versions:** getting list of versions (plus test) ([8003da9](8003da94e6))
2024-05-02 12:08:05 +00:00
5c0091b5e8 chore: test workaround
Some checks failed
Release / Build-production-and-ng-test (push) Successful in 3m58s
Release / Build-and-test-development (push) Successful in 7m44s
Release / release (push) Failing after 1m55s
2024-05-02 13:54:44 +02:00
84dce7f6e8 Merge pull request 'Restore Previous State / Data Rollback' (#94) from restore into main
Some checks failed
Release / Build-production-and-ng-test (push) Successful in 4m1s
Release / Build-and-test-development (push) Failing after 8m26s
Release / release (push) Has been skipped
Reviewed-on: #94
2024-05-02 11:18:28 +00:00
e8a943a35a chore: ng test
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 2m1s
2024-05-02 13:08:06 +02:00
b3171a8125 ci: fix
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 1m57s
2024-05-02 12:56:52 +02:00
d77f2eb674 chore(git): Merge branch 'deps-update' into restore
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 1m30s
2024-05-02 12:53:32 +02:00
5474fad9cc chore: package-lock
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 1m2s
2024-05-02 12:46:50 +02:00
3dd85cc60b fix: cypress tests 2024-05-02 12:39:07 +02:00
510e412ff2 chore: cypress tests fix
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 15s
2024-05-02 11:25:46 +02:00
3dfdccbc6b ci: sheet lib
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 1m2s
2024-05-02 10:04:31 +02:00
a55661548a chore: licence checker
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 1m2s
2024-05-02 09:59:21 +02:00
69363b37e9 ci: sheet lib
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 1m2s
2024-05-02 09:54:10 +02:00
2d6a753921 ci: sheet lib
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 15s
2024-05-02 09:52:22 +02:00
dc989e5668 ci: fix
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 58s
2024-05-02 09:38:43 +02:00
227ac480d5 style: lint
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 16s
2024-05-02 09:30:23 +02:00
c5e4650327 fix: ci sheet lib, submit message auto focus
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 16s
2024-05-02 09:28:39 +02:00
56cf271e77 Merge branch 'main' into deps-update
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 57s
2024-04-30 17:41:54 +00:00
^
fa8396f039 fix: supporting SCD2 data reversions
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 15s
2024-04-30 18:39:00 +01:00
^
904ca30f91 fix: issue with multiple adds/deletes, #84
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 16s
2024-04-30 17:46:00 +01:00
549f35766b fix: restore table version improvement
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 15s
2024-04-30 15:43:17 +02:00
1589c799ec chore(git): Merge branch 'restore' of ssh://git.datacontroller.io:29419/dc/dc into restore
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 15s
2024-04-30 15:26:07 +02:00
604c2e70bd feat: restore 2024-04-30 15:25:58 +02:00
^
297a84d3a4 fix: final testing on restore feature
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 59s
2024-04-30 14:21:02 +01:00
^
aaad9f7207 fix: load_ref var
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 58s
2024-04-30 12:34:22 +01:00
^
a4028562ce fix: ensuring that only restorable versions are restorable
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 58s
2024-04-30 11:14:47 +01:00
5ab3f98855 chore(git): Merge branch 'main' into restore
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 59s
2024-04-30 09:04:46 +02:00
^
eba21e96b4 fix: removing alerts dummy data, closes #93
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 18s
2024-04-30 00:22:44 +01:00
^
9ad7ae47b5 feat: SAS services & tests for RESTORE, #84 2024-04-30 00:20:40 +01:00
cf54e4c8f3 Update README.md
Some checks failed
Release / Build-production-and-ng-test (push) Failing after 1m31s
Release / Build-and-test-development (push) Has been skipped
Release / release (push) Has been skipped
2024-04-23 13:06:51 +00:00
57aa6fa0fc ci: fix
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 58s
2024-04-15 09:37:20 +02:00
4a01f3d490 ci: fix
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 57s
2024-04-12 21:37:49 +02:00
80a0db951d chore: angular testing
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 1m5s
2024-04-12 15:48:00 +02:00
3202cb8e08 ci: ng test fix
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 1m4s
2024-04-12 14:23:04 +02:00
ddf36230bf ci: ng test fix
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 1m4s
2024-04-12 14:06:29 +02:00
b67c2be968 chore: package json test script fix
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 1m3s
2024-04-12 13:53:35 +02:00
dc2c8da92b ci: added angular tests on PR
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 1m10s
2024-04-12 13:30:44 +02:00
5d6c3701d0 chore: licence checker update
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 1m3s
2024-04-12 13:28:18 +02:00
b024e263b4 chore: updated package-lock
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 1m2s
2024-04-12 13:20:08 +02:00
b706864e40 Merge pull request 'Angular and Clarity Update' (#91) from deps-update into main
Some checks failed
Release / Build-production-and-ng-test (push) Failing after 1m37s
Release / Build-and-test-development (push) Has been skipped
Release / release (push) Has been skipped
Reviewed-on: #91
2024-04-12 10:45:57 +00:00
60cc666b67 chore: licence checker
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 1m15s
2024-04-12 12:34:01 +02:00
efff4dd553 chore(cypress): bigger viewport size
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 1m1s
2024-04-12 11:48:23 +02:00
2b1dad8e48 style: lint
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 1m2s
2024-04-12 10:51:32 +02:00
8c7de5aad7 fix(clarity): new version style issues 2024-04-12 10:51:11 +02:00
1db8bc2573 style: lint
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 1m12s
2024-04-11 13:23:40 +02:00
c60dd65a16 fix(updates): angular, clarity, resolved legacy-peer-deps 2024-04-11 13:23:24 +02:00
f7f59a4b0a Merge pull request 'feat: Display Previous Versions' (#88) from restore into main
Some checks failed
Release / Build-production-and-ng-test (push) Successful in 4m18s
Release / Build-and-test-development (push) Failing after 15m54s
Release / release (push) Has been skipped
Reviewed-on: #88
2024-04-04 13:24:25 +00:00
^
ec7615e7e3 chore: adding sheet-crypto to gitignoreg
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 1m5s
2024-04-03 10:56:49 +01:00
f411c33754 chore(git): Merge branch 'main' into restore
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 1m4s
2024-04-01 13:51:33 +02:00
79121168e4 style: lint
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 1m4s
2024-04-01 13:50:50 +02:00
ef81e33f70 fix: stage and approve buttons renaming
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 15s
2024-04-01 13:37:41 +02:00
96066c66cb chore(release): 6.7.0 [skip ci]
# [6.7.0](https://git.datacontroller.io/dc/dc/compare/v6.6.4...v6.7.0) (2024-04-01)

### Features

* numeric values in hot dropdown aligned right ([9635626](963562621d))
2024-04-01 11:23:31 +00:00
b1819b776d chore(git): Merge branch 'restore' of ssh://git.datacontroller.io:29419/dc/dc into restore
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 15s
2024-04-01 13:09:34 +02:00
bd7a392ffc chore(git): Merge branch 'main' into restore 2024-04-01 13:09:27 +02:00
7997b77158 Merge pull request 'Numeric values in hot dropdown aligned right' (#86) from numeric-values-diff into main
All checks were successful
Release / Build-production-and-ng-test (push) Successful in 4m26s
Release / Build-and-test-development (push) Successful in 8m10s
Release / release (push) Successful in 6m32s
Reviewed-on: #86
2024-04-01 11:08:53 +00:00
d1966bcdc5 chore(git): Merge branch 'main' into numeric-values-diff
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 1m3s
2024-04-01 12:33:43 +02:00
7b5bbe024d chore(release): 6.6.4 [skip ci]
## [6.6.4](https://git.datacontroller.io/dc/dc/compare/v6.6.3...v6.6.4) (2024-04-01)

### Bug Fixes

* ordering SOFTSELECT numerically in dropdown ([f522038](f522038b8d)), closes [#85](#85)
* reverting col ([fbbcf90](fbbcf90956))
* typo ([31d4e5c](31d4e5c727))
2024-04-01 09:51:54 +00:00
4d84f15aca Merge pull request 'ci: install sheet temporarily' (#89) from ci into main
All checks were successful
Release / Build-production-and-ng-test (push) Successful in 4m16s
Release / Build-and-test-development (push) Successful in 8m1s
Release / release (push) Successful in 6m22s
Reviewed-on: #89
2024-04-01 09:37:56 +00:00
928937daab ci: sheet
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 1m4s
2024-04-01 11:14:00 +02:00
3bd8d247e5 ci: sheet
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 1m3s
2024-04-01 10:59:03 +02:00
cf6c9dd5f2 ci: sheet
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 1m0s
2024-04-01 10:55:59 +02:00
ff55cbbaad ci: sheet
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 17s
2024-04-01 10:45:16 +02:00
3eda4e2c58 ci: install sheet temporarily
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 17s
2024-04-01 10:39:30 +02:00
02a8a1c565 feat: staging page, restore buttons 2024-03-29 13:08:48 +01:00
^
8769841f08 feat: backend to show in getchangeinfo whether a user is allowed to restore
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 15s
2024-03-28 16:03:27 +00:00
7208fe1c3b chore(git): Merge branch 'restore' of ssh://git.datacontroller.io:29419/dc/dc into restore
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 15s
2024-03-26 19:14:53 +01:00
801c8c6a9f fix: table info modal, versions - column names 2024-03-26 19:14:41 +01:00
^
51ebd25aa3 fix: ensuring version history only includes loaded versions
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 48s
2024-03-26 14:25:58 +00:00
c6595c1f61 fix(sas): viewer versions fix
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 46s
2024-03-26 14:20:01 +01:00
a267666e99 style: lint
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 46s
2024-03-25 22:41:25 +01:00
b27fea5b91 feat: table metadata modal, versions tab (and link) 2024-03-25 22:41:00 +01:00
^
f8a14d4bde feat: list versions of target tables (backend)
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 47s
2024-03-25 12:37:11 +00:00
^
633e35338d chore: remove unnecessary concatenation 2024-03-20 21:38:44 +00:00
^
8003da94e6 feat(versions): getting list of versions (plus test) 2024-03-20 21:37:13 +00:00
c3af97ef57 Merge pull request 'issue85' (#87) from issue85 into main
Some checks failed
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: #87
2024-03-19 22:41:04 +00:00
31d4e5c727 fix: typo
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 47s
2024-03-19 22:36:00 +00:00
fbbcf90956 fix: reverting col 2024-03-19 22:35:16 +00:00
f522038b8d fix: ordering SOFTSELECT numerically in dropdown
Closes #85
2024-03-19 22:33:39 +00:00
ace599b39f style: lint
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 46s
2024-03-19 10:01:30 +01:00
963562621d feat: numeric values in hot dropdown aligned right
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 29s
2024-03-18 17:32:29 +01:00
5171d07441 chore(release): 6.6.3 [skip ci]
## [6.6.3](https://git.datacontroller.io/dc/dc/compare/v6.6.2...v6.6.3) (2024-02-26)

### Bug Fixes

* allow empty clause value when NE or CONTAINS ([432450a](432450a15b))
2024-02-26 14:17:24 +00:00
9a0b9573d5 Merge pull request 'Allow empty clause value when operator is NE or CONTAINS' (#83) from issue-82 into main
All checks were successful
Release / Build-production-and-ng-test (push) Successful in 4m18s
Release / Build-and-test-development (push) Successful in 7m46s
Release / release (push) Successful in 6m18s
Reviewed-on: #83
2024-02-26 14:03:36 +00:00
4733311ef3 style: lint
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 1m0s
2024-02-26 14:15:34 +01:00
432450a15b fix: allow empty clause value when NE or CONTAINS 2024-02-26 14:14:51 +01:00
47638becc0 chore(release): 6.6.2 [skip ci]
## [6.6.2](https://git.datacontroller.io/dc/dc/compare/v6.6.1...v6.6.2) (2024-02-22)

### Bug Fixes

* excel with commas getting wrapped in quotes ([3860134](38601346a5))
2024-02-22 12:33:10 +00:00
bdd3a95685 Merge pull request 'excel with commas getting wrapped in quotes' (#80) from issue-77 into main
All checks were successful
Release / Build-production-and-ng-test (push) Successful in 4m13s
Release / Build-and-test-development (push) Successful in 7m44s
Release / release (push) Successful in 6m15s
Reviewed-on: #80
Reviewed-by: sabir <sabir@4gl.io>
2024-02-22 12:19:34 +00:00
38601346a5 fix: excel with commas getting wrapped in quotes
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 15s
2024-02-21 14:48:42 +01:00
dc3a6ae6a1 chore(release): 6.6.1 [skip ci]
## [6.6.1](https://git.datacontroller.io/dc/dc/compare/v6.6.0...v6.6.1) (2024-02-19)

### Bug Fixes

* **client:** bumped @sasjs/adapter with fixed redirected login ([eb1c09d](eb1c09d790))
2024-02-19 13:38:12 +00:00
f668b1e7f7 Merge pull request 'fix(client): bumped @sasjs/adapter with fixed redirected login' (#79) from @sasjs/adapter-bump into main
All checks were successful
Release / Build-production-and-ng-test (push) Successful in 4m53s
Release / Build-and-test-development (push) Successful in 8m34s
Release / release (push) Successful in 7m2s
Reviewed-on: #79
Reviewed-by: mihajlo <mihajlo@4gl.io>
2024-02-19 13:22:56 +00:00
eb1c09d790 fix(client): bumped @sasjs/adapter with fixed redirected login
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 1m6s
2024-02-19 16:16:30 +03:00
9bf324c74b chore(release): 6.6.0 [skip ci]
# [6.6.0](https://git.datacontroller.io/dc/dc/compare/v6.5.2...v6.6.0) (2024-02-12)

### Bug Fixes

* adjust the col numbers in extracted data ([cff5989](cff5989559))

### Features

* extra table metadata for [#75](#75) ([837821f](837821fd01))
* show dsnote on hover title ([6565834](6565834ad4))
2024-02-12 11:30:37 +00:00
f13e909478 Merge pull request 'fix: adjust the col numbers in extracted data' (#76) from fix-complex-xl-upload into main
All checks were successful
Release / Build-production-and-ng-test (push) Successful in 4m53s
Release / Build-and-test-development (push) Successful in 8m34s
Release / release (push) Successful in 7m0s
Reviewed-on: #76
2024-02-12 09:58:42 +00:00
6a0fe287dd chore: add comment
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 1m4s
2024-02-12 14:53:53 +05:00
5a48f2e6e3 chore: lint fix
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 1m5s
2024-02-12 12:51:08 +05:00
6565834ad4 feat: show dsnote on hover title
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 18s
2024-02-12 11:30:59 +05:00
837821fd01 feat: extra table metadata for #75
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 17s
2024-02-09 19:02:24 +00:00
cff5989559 fix: adjust the col numbers in extracted data
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 18s
2024-02-09 18:37:25 +05:00
60510a4d68 chore(release): 6.5.2 [skip ci]
## [6.5.2](https://git.datacontroller.io/dc/dc/compare/v6.5.1...v6.5.2) (2024-02-06)

### Bug Fixes

* ordering mpe_selectbox data by the data values after selectbox_order ([2b54034](2b54034973))
2024-02-06 18:55:05 +00:00
2b54034973 fix: ordering mpe_selectbox data by the data values after selectbox_order
All checks were successful
Release / Build-production-and-ng-test (push) Successful in 4m51s
Release / Build-and-test-development (push) Successful in 8m35s
Release / release (push) Successful in 7m2s
2024-02-06 18:39:47 +00:00
347b0f9065 chore(release): 6.5.1 [skip ci]
## [6.5.1](https://git.datacontroller.io/dc/dc/compare/v6.5.0...v6.5.1) (2024-02-02)

### Bug Fixes

* ensuring submitter email can be pulled from mpe_emails ([eac0104](eac0104d7a))
2024-02-02 11:35:49 +00:00
eac0104d7a fix: ensuring submitter email can be pulled from mpe_emails
All checks were successful
Release / Build-production-and-ng-test (push) Successful in 4m46s
Release / Build-and-test-development (push) Successful in 8m34s
Release / release (push) Successful in 6m51s
2024-02-02 11:20:43 +00:00
1c8e4604de chore(release): 6.5.0 [skip ci]
# [6.5.0](https://git.datacontroller.io/dc/dc/compare/v6.4.0...v6.5.0) (2024-01-26)

### Features

* filtering by reference to Variables as well as Values ([6eb1aa8](6eb1aa85d2))
2024-01-26 10:55:18 +00:00
e9624635ed Merge pull request 'feat: Filtering with variable as well as values' (#70) from issue-68 into main
All checks were successful
Release / Build-production-and-ng-test (push) Successful in 4m42s
Release / Build-and-test-development (push) Successful in 8m27s
Release / release (push) Successful in 6m47s
Reviewed-on: #70
2024-01-26 10:40:29 +00:00
f9beda1ddb chore: lint fix
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 1m4s
2024-01-26 09:22:50 +05:00
53400de110 chore: quick fix
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 17s
2024-01-25 18:24:01 +05:00
cf37ddab22 Merge branch 'main' into issue-68
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 17s
2024-01-25 11:34:20 +00:00
625af199f4 chore(release): 6.4.0 [skip ci]
# [6.4.0](https://git.datacontroller.io/dc/dc/compare/v6.3.1...v6.4.0) (2024-01-24)

### Bug Fixes

* add dcLib to globals ([5d93346](5d93346b52))
* add service to get xlmap rules and fixed interface name ([9ffa30a](9ffa30ab74))
* increasing length of mpe_excel_map cols to ([2d4d068](2d4d068413))
* providing info on mapids to FE ([fd94945](fd94945466))
* removing tables from EDIT menu that are in xlmaps ([9550ae4](9550ae4d11))
* removing XLMAP_TARGETLIBDS from mpe_xlmaps_rules table ([93702c6](93702c63dc))
* renaming TABLE macvar to LOAD_REF in postdata.sas ([01915a2](01915a2db9))
* reverting xlmap in getdata change ([2d6e747](2d6e747db9))
* update edit tab to load ([516e5a2](516e5a2062))

### Features

* adding ability to define the target table for excel maps ([c86fba9](c86fba9dc7))
* adding ismap attribute to getdata response (and fixing test) ([2702bb3](2702bb3c84))
* Complex Excel Uploads ([cf19381](cf19381060)), closes [#69](#69)
* Create Tables / Files dropdown under load tab ([b473b19](b473b198a6))
* display list of maps in sidebar ([5aec024](5aec024242))
* implemented the logic for xlmap component ([50696bb](50696bb926))
* model changes for [#69](#69) ([271543a](271543a446))
* new getxlmaps service to return rules for a particular xlmap_id ([56264ec](56264ecc69))
* validating the excel map after stage (adding load-ref) ([a485c3b](a485c3b787))
2024-01-24 17:50:00 +00:00
56e9217f4b Merge pull request 'ci: semantic release requires node 20 or above' (#74) from ci-deploy into main
All checks were successful
Release / Build-production-and-ng-test (push) Successful in 4m48s
Release / Build-and-test-development (push) Successful in 8m32s
Release / release (push) Successful in 7m1s
Reviewed-on: #74
2024-01-24 17:34:51 +00:00
86f1af7926 Merge branch 'main' into ci-deploy
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 17s
2024-01-24 17:34:21 +00:00
7737f8455d ci: semantic release requires node 20 or above
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 17s
2024-01-24 18:33:50 +01:00
b0f1677fcc Merge pull request 'Fixed mocked startupservice used for cypress testing' (#73) from ci-deploy into main
Some checks failed
Release / Build-production-and-ng-test (push) Successful in 4m47s
Release / Build-and-test-development (push) Successful in 8m35s
Release / release (push) Failing after 1m46s
Reviewed-on: #73
2024-01-24 16:40:02 +00:00
4406e0d4b4 ci: fixed mocked startupservice used for cypress testing
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 16s
2024-01-24 17:34:07 +01:00
cf19381060 feat: Complex Excel Uploads
Some checks failed
Release / Build-production-and-ng-test (push) Successful in 4m49s
Release / release (push) Has been skipped
Release / Build-and-test-development (push) Failing after 9m24s
Reviewed-on: #71

Closes #69
2024-01-24 13:48:07 +00:00
802d8a3b08 Merge branch 'main' into issue-68
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 17s
2024-01-24 13:47:24 +00:00
2a852496e9 chore: add specs
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 17s
2024-01-24 17:05:18 +05:00
4653097225 chore: move utils to separate file
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 23s
2024-01-24 15:30:22 +05:00
8afee29e02 chore: limit submitting rows based on liscence
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 17s
2024-01-24 14:28:56 +05:00
233eca39ef chore: move utility functions to separate file
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 16s
2024-01-24 14:10:11 +05:00
1a96bb1233 chore: show variables in dropdown instead of values when variable is selected
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 16s
2024-01-24 10:40:33 +05:00
93702c63dc fix: removing XLMAP_TARGETLIBDS from mpe_xlmaps_rules table
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 17s
2024-01-23 16:54:44 +00:00
df065562d1 chore: bumping core
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 1m9s
2024-01-23 12:10:18 +00:00
802c99adf9 chore: fix .npmrc
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
2024-01-22 10:30:19 +00:00
482c7455f5 chore: fix the logic for goback button in stage component
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
2024-01-19 21:03:34 +05:00
731b96dccc chore: modifired xlmaps array in global variables
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 14s
2024-01-19 18:48:38 +05:00
9550ae4d11 fix: removing tables from EDIT menu that are in xlmaps
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
2024-01-19 11:12:31 +00:00
2d6e747db9 fix: reverting xlmap in getdata change
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 12s
2024-01-19 10:52:39 +00:00
fd94945466 fix: providing info on mapids to FE
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
2024-01-18 17:39:10 +00:00
d3b0c09332 chore: in editors/loadfile service pass attached excel file too as payload
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
2024-01-18 22:19:04 +05:00
01915a2db9 fix: renaming TABLE macvar to LOAD_REF in postdata.sas
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
also adding a sample post approve hook for xlmap dataloads
2024-01-18 16:31:11 +00:00
51b043b6d2 chore: postedit hook example updates
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
2024-01-18 15:43:48 +00:00
c144fd8087 chore: fixed hanging state after getting error in upload and submit
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
2024-01-18 18:39:23 +05:00
12b15df78c chore: move to data tab after extracting data
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
2024-01-17 22:38:26 +05:00
d6ecd12cea chore: added tab view in xlmap component
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 12s
2024-01-17 22:33:42 +05:00
1c3d498da6 chore: wording of rules page
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 12s
2024-01-17 11:23:07 +00:00
d75e10aef5 chore: quick fix
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
2024-01-16 15:25:52 +05:00
f0f9d85558 chore: quick fix
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
2024-01-16 14:27:44 +05:00
86f3411896 Merge pull request 'feat: complex excel upload (UI)' (#72) from issue-69-ui into issue69
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
Reviewed-on: #72
2024-01-16 09:16:43 +00:00
6daef39268 chore: add modal for displaying submit limit notice
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
2024-01-16 13:56:47 +05:00
7d1720a360 Merge branch 'issue69' into issue-69-ui
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
2024-01-16 08:47:47 +00:00
b11a4884b4 chore: quick fix
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 14s
2024-01-16 13:00:15 +05:00
50696bb926 feat: implemented the logic for xlmap component
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
2024-01-16 12:21:45 +05:00
d67d4e2f86 chore: added xlmap.module.ts file 2024-01-16 12:19:52 +05:00
2f01c4d251 chore: added xlmap routing component 2024-01-16 12:17:42 +05:00
9ffa30ab74 fix: add service to get xlmap rules and fixed interface name 2024-01-16 12:14:28 +05:00
5d93346b52 fix: add dcLib to globals 2024-01-16 12:08:42 +05:00
39762b36c6 chore: updated workspace settings 2024-01-16 12:04:39 +05:00
e40ebdff05 Merge branch 'main' into issue69
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
2024-01-12 13:09:39 +00:00
8d12d9e51e chore: fixing validations
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
2024-01-11 18:48:13 +00:00
23708c9aae chore: fix xlmap validation logic
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 12s
2024-01-11 18:28:51 +00:00
c86fba9dc7 feat: adding ability to define the target table for excel maps
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
2024-01-11 18:11:22 +00:00
e747e6e4e7 chore: additional xlmaps to cover LASTDOWN and BLANKROW scenarios
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 14s
2024-01-11 14:44:13 +00:00
5aec024242 feat: display list of maps in sidebar
implemented routing module/component for home-routing
2024-01-04 11:33:38 +05:00
b473b198a6 feat: Create Tables / Files dropdown under load tab 2024-01-04 11:28:16 +05:00
516e5a2062 fix: update edit tab to load 2024-01-04 11:26:13 +05:00
fb3abbe491 chore: update workspace settings 2024-01-04 11:24:39 +05:00
3e009f3037 chore: adding migration for new tables
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
2024-01-03 12:12:38 +00:00
e63d304953 chore(release): 6.3.1 [skip ci]
## [6.3.1](https://git.datacontroller.io/dc/dc/compare/v6.3.0...v6.3.1) (2024-01-01)

### Bug Fixes

* enabling excel uploads to tables with retained keys, also adding more validation to MPE_TABLES updates ([3efccc4](3efccc4cf3))
2024-01-01 17:53:14 +00:00
3cd90c2d47 Merge pull request 'fix: enabling excel uploads to tables with retained keys, also adding more validation to MPE_TABLES updates' (#67) from dcfixes into main
All checks were successful
Release / Build-production-and-ng-test (push) Successful in 3m11s
Release / Build-and-test-development (push) Successful in 6m31s
Release / release (push) Successful in 5m16s
Reviewed-on: #67
2024-01-01 17:42:07 +00:00
a485c3b787 feat: validating the excel map after stage (adding load-ref)
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
2024-01-01 16:53:50 +00:00
2702bb3c84 feat: adding ismap attribute to getdata response (and fixing test)
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
2024-01-01 16:07:47 +00:00
56264ecc69 feat: new getxlmaps service to return rules for a particular xlmap_id
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
2024-01-01 14:50:02 +00:00
cc4535245c chore: adding xlmaps in startupservice response, #69
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
2024-01-01 14:10:49 +00:00
6ae31de1dd chore: adding sample data for basel KM1 template
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
2024-01-01 13:54:08 +00:00
2d4d068413 fix: increasing length of mpe_excel_map cols to
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 14s
2024-01-01 12:23:07 +00:00
271543a446 feat: model changes for #69
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 47s
2023-12-27 16:57:48 +01:00
8f796aec36 chore(git): Merge branch 'main' into issue-68
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 14s
2023-12-26 21:09:16 +01:00
6eb1aa85d2 feat: filtering by reference to Variables as well as Values 2023-12-26 21:08:58 +01:00
ac59b77ad5 Merge branch 'main' into dcfixes
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 46s
2023-12-12 08:30:25 +00:00
3efccc4cf3 fix: enabling excel uploads to tables with retained keys, also adding more validation to MPE_TABLES updates
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 13s
2023-12-12 08:27:45 +00:00
8cbcd18f4b chore(release): 6.3.0 [skip ci]
# [6.3.0](https://git.datacontroller.io/dc/dc/compare/v6.2.8...v6.3.0) (2023-12-04)

### Features

* viewer row handle ([dadac4f](dadac4f13f))
2023-12-04 18:49:31 +00:00
6bb2378790 Merge pull request 'ci: doxygen fix' (#66) from ci-fix into main
All checks were successful
Release / Build-production-and-ng-test (push) Successful in 3m8s
Release / Build-and-test-development (push) Successful in 6m27s
Release / release (push) Successful in 5m23s
Reviewed-on: #66
2023-12-04 18:38:27 +00:00
e7d0ffe8c0 Merge branch 'main' into ci-fix
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 48s
2023-12-04 17:38:00 +00:00
ab89600c73 style: lint
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 47s
2023-12-04 18:37:05 +01:00
830e3816a0 ci: build, syntax fix
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 12s
2023-12-04 18:32:17 +01:00
dadac4f13f feat: viewer row handle
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 0s
2023-12-04 18:17:49 +01:00
1de48a49af ci: doxygen fix
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 0s
2023-12-04 17:06:18 +01:00
687a1e1cb5 ci: doxygen fix
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 33s
2023-12-04 17:02:08 +01:00
665a04f5c5 ci: doxygen fix
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 14s
2023-12-04 17:01:26 +01:00
fdb18d242b ci: doxygen fix
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 19s
2023-12-04 14:47:30 +01:00
ec173da4ce ci: doxygen fix
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 9s
2023-12-04 14:47:02 +01:00
bb35cc15d2 ci: doxygen fix
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 8s
2023-12-04 14:46:32 +01:00
181f52eaea ci: doxygen fix
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 8s
2023-12-04 14:45:57 +01:00
fc7c8101ed ci: doxygen fix
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 8s
2023-12-04 14:44:49 +01:00
a347603fe0 chore(release): 6.2.8 [skip ci]
## [6.2.8](https://git.datacontroller.io/dc/dc/compare/v6.2.7...v6.2.8) (2023-12-04)

### Bug Fixes

* bumping sasjs/core to fix mp_loadformat issue ([a1d308e](a1d308ea07))
* new logic for -fc suffix.  Closes [#63](#63) ([5579db0](5579db0eaf))
2023-12-04 11:51:52 +00:00
09022c995f chore: prettier fix
Some checks failed
Release / Build-production-and-ng-test (push) Successful in 3m5s
Release / Build-and-test-development (push) Successful in 6m15s
Release / release (push) Failing after 4m56s
2023-12-04 12:41:04 +01:00
3609943f30 Merge pull request 'fix: new logic for -fc suffix, plus fixes for format record additions / deletions' (#64) from dcfixes into main
Some checks failed
Release / Build-production-and-ng-test (push) Successful in 3m5s
Release / Build-and-test-development (push) Failing after 7m12s
Release / release (push) Has been skipped
Reviewed-on: #64
2023-12-03 13:56:26 +00:00
a1d308ea07 fix: bumping sasjs/core to fix mp_loadformat issue
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 12s
2023-12-03 13:53:53 +00:00
5579db0eaf fix: new logic for -fc suffix. Closes #63
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 52s
2023-12-03 11:19:40 +00:00
3a3e488b23 chore: updating yaml to use self-hosted doc site
Some checks failed
Release / Build-production-and-ng-test (push) Successful in 7m20s
Release / Build-and-test-development (push) Successful in 13m43s
Release / release (push) Failing after 2m36s
2023-11-14 22:23:05 +00:00
0a82ec0a70 chore: updating link in README to releases page
Some checks failed
Release / Build-production-and-ng-test (push) Successful in 7m50s
Release / Build-and-test-development (push) Successful in 14m11s
Release / release (push) Failing after 2m42s
2023-11-14 21:43:55 +00:00
bc1d89218e chore(release): 6.2.7 [skip ci]
## [6.2.7](https://git.datacontroller.io/dc/dc/compare/v6.2.6...v6.2.7) (2023-11-09)

### Bug Fixes

* **audit:** updated crypto-js (hashing rows in dynamic cell validation) ([a7aa42a](a7aa42a59b))
* missing dependency and avoiding label length limit issue ([91f128c](91f128c2fe))
2023-11-09 09:01:38 +00:00
817b9adeac Merge pull request 'fix(audit): updated crypto-js (hashing rows in dynamic cell validation)' (#62) from audit-fix into main
All checks were successful
Release / Build-production-and-ng-test (push) Successful in 8m0s
Release / Build-and-test-development (push) Successful in 13m41s
Release / release (push) Successful in 12m7s
Reviewed-on: #62
2023-11-09 08:37:19 +00:00
a7aa42a59b fix(audit): updated crypto-js (hashing rows in dynamic cell validation)
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 1m45s
2023-11-09 09:20:21 +01:00
34f239036d Merge pull request 'fix: missing dependency and avoiding label length limit issue' (#61) from corebump into main
Some checks failed
Release / Build-production-and-ng-test (push) Failing after 2m51s
Release / Build-and-test-development (push) Has been skipped
Release / release (push) Has been skipped
Reviewed-on: #61
2023-11-08 21:45:25 +00:00
91f128c2fe fix: missing dependency and avoiding label length limit issue
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 1m43s
Relates to the following core issues:
* https://github.com/sasjs/core/issues/364
* https://github.com/sasjs/core/issues/363
2023-11-08 21:36:17 +00:00
a00ebea692 chore(release): 6.2.6 [skip ci]
## [6.2.6](https://git.datacontroller.io/dc/dc/compare/v6.2.5...v6.2.6) (2023-10-18)

### Bug Fixes

* bumping core to address mm_assigndirectlib issue ([c27cdab](c27cdab3fc))
2023-10-18 10:48:48 +00:00
c27cdab3fc fix: bumping core to address mm_assigndirectlib issue
All checks were successful
Release / Build-production-and-ng-test (push) Successful in 7m19s
Release / Build-and-test-development (push) Successful in 11m45s
Release / release (push) Successful in 11m9s
2023-10-18 11:26:29 +01:00
142 changed files with 12602 additions and 27530 deletions

View File

@ -10,7 +10,14 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 18 node-version: 20
- 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
- name: Write .npmrc file - name: Write .npmrc file
run: echo "$NPMRC" > client/.npmrc run: echo "$NPMRC" > client/.npmrc
@ -18,8 +25,29 @@ jobs:
env: env:
NPMRC: ${{ secrets.NPMRC}} NPMRC: ${{ secrets.NPMRC}}
- run: npm run lint:check - name: Lint check
- run: | run: npm run lint:check
- name: Install dependencies
run: |
cd client cd client
npm ci npm ci
npm run license-checker # Decrypt and Install sheet
echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg
npm i ./libraries/sheet-crypto.tgz
# End
- name: Licence checker
run: |
cd client
npm run license-checker
- name: Angular Tests
run: |
cd client
npm run test:headless
- name: Production Build
run: |
cd client
npm run build

View File

@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 18 node-version: 20
- name: Write .npmrc file - name: Write .npmrc file
run: | run: |
@ -34,7 +34,13 @@ jobs:
CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }} CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }}
- name: Install dependencies - name: Install dependencies
run: npm ci run: |
cd client
npm ci
# Decrypt and Install sheet
echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg
npm i ./libraries/sheet-crypto.tgz
# End
- name: Check audit - 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
@ -48,7 +54,7 @@ jobs:
- name: Angular Tests - name: Angular Tests
run: | run: |
cd client cd client
npm test -- --no-watch --no-progress --browsers=ChromeHeadlessCI npm run test:headless
- name: Angular Production Build - name: Angular Production Build
run: | run: |
@ -64,7 +70,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 18 node-version: 20
- name: Write .npmrc file - name: Write .npmrc file
run: | run: |
@ -86,7 +92,13 @@ jobs:
CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }} CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }}
- name: Install dependencies - name: Install dependencies
run: npm ci run: |
cd client
npm ci
# Decrypt and Install sheet
echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg
npm i ./libraries/sheet-crypto.tgz
# End
# Install pm2 and prepare SASJS server # Install pm2 and prepare SASJS server
- run: npm i -g pm2 - run: npm i -g pm2
@ -150,7 +162,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 18 node-version: 20
- name: Write .npmrc file - name: Write .npmrc file
run: | run: |
@ -168,6 +180,9 @@ jobs:
npm i -g @sasjs/cli npm i -g @sasjs/cli
# jq is used to parse the release JSON # jq is used to parse the release JSON
apt-get install jq -y apt-get install jq -y
# doxygen is used for the SASJS docs
apt-get update
apt-get install doxygen -y
- name: Create Empty Release (assets are posted later) - name: Create Empty Release (assets are posted later)
run: | run: |
@ -182,6 +197,10 @@ jobs:
run: | run: |
cd client cd client
npm ci npm ci
# Decrypt and Install sheet
echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg
npm i ./libraries/sheet-crypto.tgz
# End
npm run build npm run build
- name: Build SAS9 EBI Release - name: Build SAS9 EBI Release
@ -240,6 +259,12 @@ jobs:
npm run compodoc:build npm run compodoc:build
surfer put --token ${{ secrets.TSDOC_TOKEN }} --server webdoc.datacontroller.io documentation/* / surfer put --token ${{ secrets.TSDOC_TOKEN }} --server webdoc.datacontroller.io documentation/* /
- name: Release code.datacontroller.io
run: |
cd sas
sasjs doc
surfer put --token ${{ secrets.CODE_DATACONTROLLER_IO }} --server code.datacontroller.io sasjsbuild/sasdocs/* /
- name: Upload assets to release - name: Upload assets to release
run: | run: |
RELEASE_ID=`curl -k 'https://git.datacontroller.io/api/v1/repos/dc/dc/releases/latest?access_token=${{ secrets.RELEASE_TOKEN }}' | jq -r '.id'` RELEASE_ID=`curl -k 'https://git.datacontroller.io/api/v1/repos/dc/dc/releases/latest?access_token=${{ secrets.RELEASE_TOKEN }}' | jq -r '.id'`

2
.gitignore vendored
View File

@ -11,6 +11,8 @@ client/cypress/screenshots
client/cypress/results client/cypress/results
client/cypress/videos client/cypress/videos
client/documentation client/documentation
client/sheet-crypto*
client/.nx
cypress.env.json cypress.env.json
sasjsbuild sasjsbuild
sasjsresults sasjsresults

35
.vscode/settings.json vendored
View File

@ -1,18 +1,19 @@
{ {
"cSpell.words": [ "cSpell.words": [
"SYSERRORTEXT", "Licence",
"SYSWARNINGTEXT" "SYSERRORTEXT",
], "SYSWARNINGTEXT",
"editor.rulers": [ "xlmaprules",
80 "xlmaps"
], ],
"files.trimTrailingWhitespace": true, "editor.rulers": [80],
"[markdown]": { "files.trimTrailingWhitespace": true,
"files.trimTrailingWhitespace": false "[markdown]": {
}, "files.trimTrailingWhitespace": false
"workbench.colorCustomizations": { },
"titleBar.activeForeground": "#ebe8e8", "workbench.colorCustomizations": {
"titleBar.activeBackground": "#95ff0053", "titleBar.activeForeground": "#ebe8e8",
}, "titleBar.activeBackground": "#95ff0053"
"terminal.integrated.wordSeparators": " ()[]{}',\"`─‘’" },
} "terminal.integrated.wordSeparators": " ()[]{}',\"`─‘’"
}

View File

@ -1,3 +1,178 @@
## [6.8.1](https://git.datacontroller.io/dc/dc/compare/v6.8.0...v6.8.1) (2024-05-02)
### Bug Fixes
* hide approve button when table revertable ([ec0f539](https://git.datacontroller.io/dc/dc/commit/ec0f539a337b176c83a661ff520a6892d47efa02))
# [6.8.0](https://git.datacontroller.io/dc/dc/compare/v6.7.0...v6.8.0) (2024-05-02)
### Bug Fixes
* ci sheet lib, submit message auto focus ([c5e4650](https://git.datacontroller.io/dc/dc/commit/c5e46503272f3f3d9cd83ac04225babf79d4de44))
* **clarity:** new version style issues ([8c7de5a](https://git.datacontroller.io/dc/dc/commit/8c7de5aad7e7e32a64769696af9b93eb9a6225d3))
* cypress tests ([3dd85cc](https://git.datacontroller.io/dc/dc/commit/3dd85cc60bd5ac99bc930b6b9c89a8e707e4d51d))
* ensuring that only restorable versions are restorable ([a402856](https://git.datacontroller.io/dc/dc/commit/a4028562ce91b32ff971ab9821328b97cd23f381))
* ensuring version history only includes loaded versions ([51ebd25](https://git.datacontroller.io/dc/dc/commit/51ebd25aa362aa8e66c83b29b2c64aa0f206f5bd))
* final testing on restore feature ([297a84d](https://git.datacontroller.io/dc/dc/commit/297a84d3a4ebb47bef7f3ca9758978d727afed8d))
* issue with multiple adds/deletes, [#84](https://git.datacontroller.io/dc/dc/issues/84) ([904ca30](https://git.datacontroller.io/dc/dc/commit/904ca30f918da085fa05dae066367b512933d1a9))
* load_ref var ([aaad9f7](https://git.datacontroller.io/dc/dc/commit/aaad9f7207115599a006980fff099d59738dd2cd))
* removing alerts dummy data, closes [#93](https://git.datacontroller.io/dc/dc/issues/93) ([eba21e9](https://git.datacontroller.io/dc/dc/commit/eba21e96b4fa34e63b4477281f47d9a01d621f2e))
* restore table version improvement ([549f357](https://git.datacontroller.io/dc/dc/commit/549f35766ba7b5bbe55694845e85bfefc4193375))
* **sas:** viewer versions fix ([c6595c1](https://git.datacontroller.io/dc/dc/commit/c6595c1f618803d9202cba1a1fe76986449cf2e2))
* stage and approve buttons renaming ([ef81e33](https://git.datacontroller.io/dc/dc/commit/ef81e33f704d0b4f99405d96498e1d29ac817982))
* supporting SCD2 data reversions ([fa8396f](https://git.datacontroller.io/dc/dc/commit/fa8396f0394cbddb6dbacb4b355de078fad49980))
* table info modal, versions - column names ([801c8c6](https://git.datacontroller.io/dc/dc/commit/801c8c6a9fb95388a06a6c6284fec4dc25bb77c5))
* **updates:** angular, clarity, resolved legacy-peer-deps ([c60dd65](https://git.datacontroller.io/dc/dc/commit/c60dd65a1637333f11a0c39ef697c7292a6ede07))
### Features
* backend to show in getchangeinfo whether a user is allowed to restore ([8769841](https://git.datacontroller.io/dc/dc/commit/8769841f08694f672ef7ae1a17beacd0dbedda52))
* list versions of target tables (backend) ([f8a14d4](https://git.datacontroller.io/dc/dc/commit/f8a14d4bdef055b99930491d1f6fabe55a50a497))
* restore ([604c2e7](https://git.datacontroller.io/dc/dc/commit/604c2e70bdeeeb1aa5bb18b94f525ebd049397fa))
* SAS services & tests for RESTORE, [#84](https://git.datacontroller.io/dc/dc/issues/84) ([9ad7ae4](https://git.datacontroller.io/dc/dc/commit/9ad7ae47b5e793ce68ab21c9eeb8dee6cb85e496))
* staging page, restore buttons ([02a8a1c](https://git.datacontroller.io/dc/dc/commit/02a8a1c5654350cafc53b749cceb686fc6848c33))
* table metadata modal, versions tab (and link) ([b27fea5](https://git.datacontroller.io/dc/dc/commit/b27fea5b91e33b4673a3a991aedae558e366ca29))
* **versions:** getting list of versions (plus test) ([8003da9](https://git.datacontroller.io/dc/dc/commit/8003da94e615463ed3ddfd60b0cbf2e58615eab1))
# [6.7.0](https://git.datacontroller.io/dc/dc/compare/v6.6.4...v6.7.0) (2024-04-01)
### Features
* numeric values in hot dropdown aligned right ([9635626](https://git.datacontroller.io/dc/dc/commit/963562621ddf0e8d24a29a8481c5e6da1b040708))
## [6.6.4](https://git.datacontroller.io/dc/dc/compare/v6.6.3...v6.6.4) (2024-04-01)
### Bug Fixes
* ordering SOFTSELECT numerically in dropdown ([f522038](https://git.datacontroller.io/dc/dc/commit/f522038b8ddb1da14b8adbf8346d0a4539a94cc8)), closes [#85](https://git.datacontroller.io/dc/dc/issues/85)
* reverting col ([fbbcf90](https://git.datacontroller.io/dc/dc/commit/fbbcf90956bf538b032b0107c07b8576d20353b9))
* typo ([31d4e5c](https://git.datacontroller.io/dc/dc/commit/31d4e5c727f790d428fb2ea8da60dca929561805))
## [6.6.3](https://git.datacontroller.io/dc/dc/compare/v6.6.2...v6.6.3) (2024-02-26)
### Bug Fixes
* allow empty clause value when NE or CONTAINS ([432450a](https://git.datacontroller.io/dc/dc/commit/432450a15b51a269821ba1d430854f5d1dd04703))
## [6.6.2](https://git.datacontroller.io/dc/dc/compare/v6.6.1...v6.6.2) (2024-02-22)
### Bug Fixes
* excel with commas getting wrapped in quotes ([3860134](https://git.datacontroller.io/dc/dc/commit/38601346a529cfe3787bb286a639e0293c365020))
## [6.6.1](https://git.datacontroller.io/dc/dc/compare/v6.6.0...v6.6.1) (2024-02-19)
### Bug Fixes
* **client:** bumped @sasjs/adapter with fixed redirected login ([eb1c09d](https://git.datacontroller.io/dc/dc/commit/eb1c09d7909ba07faf763da261545dc1efaec1b3))
# [6.6.0](https://git.datacontroller.io/dc/dc/compare/v6.5.2...v6.6.0) (2024-02-12)
### Bug Fixes
* adjust the col numbers in extracted data ([cff5989](https://git.datacontroller.io/dc/dc/commit/cff598955930d2581349e5c6e8b2dd3f9ac96b4c))
### Features
* extra table metadata for [#75](https://git.datacontroller.io/dc/dc/issues/75) ([837821f](https://git.datacontroller.io/dc/dc/commit/837821fd01477d340524dfdaf8dd3d3758cf3095))
* show dsnote on hover title ([6565834](https://git.datacontroller.io/dc/dc/commit/6565834ad4089ecf2de39967e6ed6f217ee4a0a5))
## [6.5.2](https://git.datacontroller.io/dc/dc/compare/v6.5.1...v6.5.2) (2024-02-06)
### Bug Fixes
* ordering mpe_selectbox data by the data values after selectbox_order ([2b54034](https://git.datacontroller.io/dc/dc/commit/2b5403497317632a4be8a00f21455c036f1e6461))
## [6.5.1](https://git.datacontroller.io/dc/dc/compare/v6.5.0...v6.5.1) (2024-02-02)
### Bug Fixes
* ensuring submitter email can be pulled from mpe_emails ([eac0104](https://git.datacontroller.io/dc/dc/commit/eac0104d7aebaf98ff1d1c504c1ce3b25d4a0ce8))
# [6.5.0](https://git.datacontroller.io/dc/dc/compare/v6.4.0...v6.5.0) (2024-01-26)
### Features
* filtering by reference to Variables as well as Values ([6eb1aa8](https://git.datacontroller.io/dc/dc/commit/6eb1aa85d29294d63e6af377e622fbed7fd1fab8))
# [6.4.0](https://git.datacontroller.io/dc/dc/compare/v6.3.1...v6.4.0) (2024-01-24)
### Bug Fixes
* add dcLib to globals ([5d93346](https://git.datacontroller.io/dc/dc/commit/5d93346b52eda27c2829770e96686a713296d373))
* add service to get xlmap rules and fixed interface name ([9ffa30a](https://git.datacontroller.io/dc/dc/commit/9ffa30ab747f5b62acbd452431a5e6e440afcb80))
* increasing length of mpe_excel_map cols to ([2d4d068](https://git.datacontroller.io/dc/dc/commit/2d4d068413dcdac98581f08939e74bde65b73428))
* providing info on mapids to FE ([fd94945](https://git.datacontroller.io/dc/dc/commit/fd94945466c1a797ddc89815258a65624a9cb0cf))
* removing tables from EDIT menu that are in xlmaps ([9550ae4](https://git.datacontroller.io/dc/dc/commit/9550ae4d1154a0272f8a2427ac9d2afdfd699c96))
* removing XLMAP_TARGETLIBDS from mpe_xlmaps_rules table ([93702c6](https://git.datacontroller.io/dc/dc/commit/93702c63dc280cdba1e46f0fd8fe0deaec879611))
* renaming TABLE macvar to LOAD_REF in postdata.sas ([01915a2](https://git.datacontroller.io/dc/dc/commit/01915a2db9a4dfb94e4e8213e2c32181da36d349))
* reverting xlmap in getdata change ([2d6e747](https://git.datacontroller.io/dc/dc/commit/2d6e747db9b84e9fb0dfcf9102a2f7dd2cb51891))
* update edit tab to load ([516e5a2](https://git.datacontroller.io/dc/dc/commit/516e5a206216f79ab1dce9f4eab0d31115743160))
### Features
* adding ability to define the target table for excel maps ([c86fba9](https://git.datacontroller.io/dc/dc/commit/c86fba9dc75ddc6033132f469ad1c31b9131b12e))
* adding ismap attribute to getdata response (and fixing test) ([2702bb3](https://git.datacontroller.io/dc/dc/commit/2702bb3c84c45903def1aa2b8cc20a6dd080281b))
* Complex Excel Uploads ([cf19381](https://git.datacontroller.io/dc/dc/commit/cf193810606f287b8d6f864c4eb64d43c5ab5f3c)), closes [#69](https://git.datacontroller.io/dc/dc/issues/69)
* Create Tables / Files dropdown under load tab ([b473b19](https://git.datacontroller.io/dc/dc/commit/b473b198a61f468dff74cd8e64692e7847084a80))
* display list of maps in sidebar ([5aec024](https://git.datacontroller.io/dc/dc/commit/5aec0242429942f8a989b5fb79f8d3865e9de01a))
* implemented the logic for xlmap component ([50696bb](https://git.datacontroller.io/dc/dc/commit/50696bb926dd00472db65a008771a4b6352871be))
* model changes for [#69](https://git.datacontroller.io/dc/dc/issues/69) ([271543a](https://git.datacontroller.io/dc/dc/commit/271543a446a2116718f99f0540e3cd911f9f5fe7))
* new getxlmaps service to return rules for a particular xlmap_id ([56264ec](https://git.datacontroller.io/dc/dc/commit/56264ecc6908bf6c8e3e666dfeba7068d6195df8))
* validating the excel map after stage (adding load-ref) ([a485c3b](https://git.datacontroller.io/dc/dc/commit/a485c3b78724a36f7bacb264fb02140cc62d6512))
## [6.3.1](https://git.datacontroller.io/dc/dc/compare/v6.3.0...v6.3.1) (2024-01-01)
### Bug Fixes
* enabling excel uploads to tables with retained keys, also adding more validation to MPE_TABLES updates ([3efccc4](https://git.datacontroller.io/dc/dc/commit/3efccc4cf3752763d049836724f2491c287f65db))
# [6.3.0](https://git.datacontroller.io/dc/dc/compare/v6.2.8...v6.3.0) (2023-12-04)
### Features
* viewer row handle ([dadac4f](https://git.datacontroller.io/dc/dc/commit/dadac4f13f85b5446198b6340cad28844defc94d))
## [6.2.8](https://git.datacontroller.io/dc/dc/compare/v6.2.7...v6.2.8) (2023-12-04)
### Bug Fixes
* bumping sasjs/core to fix mp_loadformat issue ([a1d308e](https://git.datacontroller.io/dc/dc/commit/a1d308ea078786b27bf7ec940d018fc657d4c398))
* new logic for -fc suffix. Closes [#63](https://git.datacontroller.io/dc/dc/issues/63) ([5579db0](https://git.datacontroller.io/dc/dc/commit/5579db0eafc668b1bc310099b7cc3062e0598fc4))
## [6.2.7](https://git.datacontroller.io/dc/dc/compare/v6.2.6...v6.2.7) (2023-11-09)
### Bug Fixes
* **audit:** updated crypto-js (hashing rows in dynamic cell validation) ([a7aa42a](https://git.datacontroller.io/dc/dc/commit/a7aa42a59b71597399924b8d2d06010c806321f3))
* missing dependency and avoiding label length limit issue ([91f128c](https://git.datacontroller.io/dc/dc/commit/91f128c2fead1e4f72267d689e67f49ec9a2ab35))
## [6.2.6](https://git.datacontroller.io/dc/dc/compare/v6.2.5...v6.2.6) (2023-10-18)
### Bug Fixes
* bumping core to address mm_assigndirectlib issue ([c27cdab](https://git.datacontroller.io/dc/dc/commit/c27cdab3fccbde814a29424d0344173a73ea816c))
## [6.2.5](https://git.datacontroller.io/dc/dc/compare/v6.2.4...v6.2.5) (2023-10-17) ## [6.2.5](https://git.datacontroller.io/dc/dc/compare/v6.2.4...v6.2.5) (2023-10-17)

View File

@ -27,4 +27,6 @@ For more information:
* Main site: https://datacontroller.io * Main site: https://datacontroller.io
* Docs: https://docs.datacontroller.io * Docs: https://docs.datacontroller.io
* Code: https://code.datacontroller.io * Code: https://code.datacontroller.io
For support, contact support@4gl.io or reach out on [Matrix](https://matrix.to/#/#dc:4gl.io)!

View File

@ -45,6 +45,7 @@
"numbro", "numbro",
"@clr/icons", "@clr/icons",
"@sasjs/adapter", "@sasjs/adapter",
"@sasjs/utils/types/serverType",
"@sasjs/utils/input/validators", "@sasjs/utils/input/validators",
"@sasjs/utils/utils/bytesToSize", "@sasjs/utils/utils/bytesToSize",
"base64-arraybuffer", "base64-arraybuffer",
@ -67,7 +68,6 @@
"src/styles.scss" "src/styles.scss"
], ],
"scripts": [ "scripts": [
"node_modules/@clr/icons/clr-icons.min.js",
"node_modules/marked/marked.min.js" "node_modules/marked/marked.min.js"
] ]
}, },
@ -116,10 +116,10 @@
"builder": "@angular-devkit/build-angular:dev-server", "builder": "@angular-devkit/build-angular:dev-server",
"configurations": { "configurations": {
"production": { "production": {
"browserTarget": "datacontroller:build:production" "buildTarget": "datacontroller:build:production"
}, },
"development": { "development": {
"browserTarget": "datacontroller:build:development" "buildTarget": "datacontroller:build:development"
} }
}, },
"defaultConfiguration": "development" "defaultConfiguration": "development"
@ -127,30 +127,27 @@
"extract-i18n": { "extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n", "builder": "@angular-devkit/build-angular:extract-i18n",
"options": { "options": {
"browserTarget": "datacontroller:build" "buildTarget": "datacontroller:build"
} }
}, },
"test": { "test": {
"builder": "@angular-devkit/build-angular:karma", "builder": "@angular-devkit/build-angular:karma",
"options": { "options": {
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"codeCoverage": true,
"polyfills": [ "polyfills": [
"src/polyfills.ts", "src/polyfills.ts",
"zone.js", "zone.js",
"zone.js/testing" "zone.js/testing"
], ],
"styles": [ "tsConfig": "tsconfig.spec.json",
"src/styles.scss" "inlineStyleLanguage": "scss",
],
"scripts": [
],
"assets": [ "assets": [
"src/favicon.ico", "src/favicon.ico",
"src/assets" "src/assets"
], ],
"styles": [
"src/styles.scss"
],
"scripts": [],
"karmaConfig": "karma.conf.js" "karmaConfig": "karma.conf.js"
} }
}, },

View File

@ -9,6 +9,8 @@ export default defineConfig({
html: true, html: true,
json: false, json: false,
}, },
viewportHeight: 900,
viewportWidth: 1600,
chromeWebSecurity: false, chromeWebSecurity: false,
defaultCommandTimeout: 30000, defaultCommandTimeout: 30000,

View File

@ -221,13 +221,13 @@ const submitExcel = (callback?: any) => {
const rejectExcel = (callback?: any) => { const rejectExcel = (callback?: any) => {
cy.get('button', { timeout: longerCommandTimeout }) cy.get('button', { timeout: longerCommandTimeout })
.should('contain', 'Go to approvals screen') .should('contain', 'Approve')
.then((allButtons: any) => { .then((allButtons: any) => {
for (let approvalButton of allButtons) { for (let approvalButton of allButtons) {
if ( if (
approvalButton.innerText approvalButton.innerText
.toLowerCase() .toLowerCase()
.includes('go to approvals screen') .includes('approve')
) { ) {
approvalButton.click() approvalButton.click()
break break

View File

@ -405,13 +405,13 @@ const submitExcel = (callback?: any) => {
const rejectExcel = (callback?: any) => { const rejectExcel = (callback?: any) => {
cy.get('button', { timeout: longerCommandTimeout }) cy.get('button', { timeout: longerCommandTimeout })
.should('contain', 'Go to approvals screen') .should('contain', 'Approve')
.then((allButtons: any) => { .then((allButtons: any) => {
for (let approvalButton of allButtons) { for (let approvalButton of allButtons) {
if ( if (
approvalButton.innerText approvalButton.innerText
.toLowerCase() .toLowerCase()
.includes('go to approvals screen') .includes('approve')
) { ) {
approvalButton.click() approvalButton.click()
break break
@ -438,13 +438,13 @@ const rejectExcel = (callback?: any) => {
const acceptExcel = (callback?: any) => { const acceptExcel = (callback?: any) => {
cy.get('button', { timeout: longerCommandTimeout }) cy.get('button', { timeout: longerCommandTimeout })
.should('contain', 'Go to approvals screen') .should('contain', 'Approve')
.then((allButtons: any) => { .then((allButtons: any) => {
for (let approvalButton of allButtons) { for (let approvalButton of allButtons) {
if ( if (
approvalButton.innerText approvalButton.innerText
.toLowerCase() .toLowerCase()
.includes('go to approvals screen') .includes('approve')
) { ) {
approvalButton.click() approvalButton.click()
break break

View File

@ -159,20 +159,21 @@ context('filtering tests: ', function () {
}) })
}) })
it('7 | filter bestnum field BETWEEN', (done) => { // TODO: fix
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test') // it('7 | filter bestnum field BETWEEN', (done) => {
// openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openFilterPopup(() => { // openFilterPopup(() => {
setFilterWithValue('SOME_BESTNUM', '0-10', 'between', () => { // setFilterWithValue('SOME_BESTNUM', '0-10', 'between', () => {
checkInfoBarIncludes( // checkInfoBarIncludes(
`AND,AND,0,SOME_BESTNUM,BETWEEN,0 AND 10`, // `AND,AND,0,SOME_BESTNUM,BETWEEN,0 AND 10`,
(includes: boolean) => { // (includes: boolean) => {
if (includes) done() // if (includes) done()
} // }
) // )
}) // })
}) // })
}) // })
this.afterEach(() => { this.afterEach(() => {
// cy.visit(`${hostUrl}/SASLogon/logout`) // cy.visit(`${hostUrl}/SASLogon/logout`)

View File

@ -699,13 +699,13 @@ const submitTable = (callback?: any) => {
const approveTable = (callback?: any) => { const approveTable = (callback?: any) => {
cy.get('button', { timeout: longerCommandTimeout }) cy.get('button', { timeout: longerCommandTimeout })
.should('contain', 'Go to approvals screen') .should('contain', 'Approve')
.then((allButtons: any) => { .then((allButtons: any) => {
for (let approvalButton of allButtons) { for (let approvalButton of allButtons) {
if ( if (
approvalButton.innerText approvalButton.innerText
.toLowerCase() .toLowerCase()
.includes('go to approvals screen') .includes('approve')
) { ) {
approvalButton.click() approvalButton.click()
break break

View File

@ -125,13 +125,13 @@ const submitExcel = (callback?: any) => {
const rejectExcel = (callback?: any) => { const rejectExcel = (callback?: any) => {
cy.get('button', { timeout: longerCommandTimeout }) cy.get('button', { timeout: longerCommandTimeout })
.should('contain', 'Go to approvals screen') .should('contain', 'Approve')
.then((allButtons: any) => { .then((allButtons: any) => {
for (let approvalButton of allButtons) { for (let approvalButton of allButtons) {
if ( if (
approvalButton.innerText approvalButton.innerText
.toLowerCase() .toLowerCase()
.includes('go to approvals screen') .includes('approve')
) { ) {
approvalButton.click() approvalButton.click()
break break

View File

@ -221,14 +221,10 @@ const submitExcel = (callback?: any) => {
const rejectExcel = (callback?: any) => { const rejectExcel = (callback?: any) => {
cy.get('button', { timeout: longerCommandTimeout }) cy.get('button', { timeout: longerCommandTimeout })
.should('contain', 'Go to approvals screen') .should('contain', 'Approve')
.then((allButtons: any) => { .then((allButtons: any) => {
for (let approvalButton of allButtons) { for (let approvalButton of allButtons) {
if ( if (approvalButton.innerText.toLowerCase().includes('approve')) {
approvalButton.innerText
.toLowerCase()
.includes('go to approvals screen')
) {
approvalButton.click() approvalButton.click()
break break
} }

View File

@ -407,14 +407,10 @@ const submitExcel = (callback?: any) => {
const rejectExcel = (callback?: any) => { const rejectExcel = (callback?: any) => {
cy.get('button', { timeout: longerCommandTimeout }) cy.get('button', { timeout: longerCommandTimeout })
.should('contain', 'Go to approvals screen') .should('contain', 'Approve')
.then((allButtons: any) => { .then((allButtons: any) => {
for (let approvalButton of allButtons) { for (let approvalButton of allButtons) {
if ( if (approvalButton.innerText.toLowerCase().includes('approve')) {
approvalButton.innerText
.toLowerCase()
.includes('go to approvals screen')
) {
approvalButton.click() approvalButton.click()
break break
} }
@ -440,14 +436,10 @@ const rejectExcel = (callback?: any) => {
const acceptExcel = (callback?: any) => { const acceptExcel = (callback?: any) => {
cy.get('button', { timeout: longerCommandTimeout }) cy.get('button', { timeout: longerCommandTimeout })
.should('contain', 'Go to approvals screen') .should('contain', 'Approve')
.then((allButtons: any) => { .then((allButtons: any) => {
for (let approvalButton of allButtons) { for (let approvalButton of allButtons) {
if ( if (approvalButton.innerText.toLowerCase().includes('approve')) {
approvalButton.innerText
.toLowerCase()
.includes('go to approvals screen')
) {
approvalButton.click() approvalButton.click()
break break
} }

View File

@ -699,14 +699,10 @@ const submitTable = (callback?: any) => {
const approveTable = (callback?: any) => { const approveTable = (callback?: any) => {
cy.get('button', { timeout: longerCommandTimeout }) cy.get('button', { timeout: longerCommandTimeout })
.should('contain', 'Go to approvals screen') .should('contain', 'Approve')
.then((allButtons: any) => { .then((allButtons: any) => {
for (let approvalButton of allButtons) { for (let approvalButton of allButtons) {
if ( if (approvalButton.innerText.toLowerCase().includes('approve')) {
approvalButton.innerText
.toLowerCase()
.includes('go to approvals screen')
) {
approvalButton.click() approvalButton.click()
break break
} }

View File

@ -125,14 +125,10 @@ const submitExcel = (callback?: any) => {
const rejectExcel = (callback?: any) => { const rejectExcel = (callback?: any) => {
cy.get('button', { timeout: longerCommandTimeout }) cy.get('button', { timeout: longerCommandTimeout })
.should('contain', 'Go to approvals screen') .should('contain', 'Approve')
.then((allButtons: any) => { .then((allButtons: any) => {
for (let approvalButton of allButtons) { for (let approvalButton of allButtons) {
if ( if (approvalButton.innerText.toLowerCase().includes('approve')) {
approvalButton.innerText
.toLowerCase()
.includes('go to approvals screen')
) {
approvalButton.click() approvalButton.click()
break break
} }

View File

@ -42,4 +42,4 @@ module.exports = function (config) {
} }
}, },
}); });
}; };

Binary file not shown.

View File

@ -10,7 +10,7 @@ const check = (cwd) => {
onlyAllow: 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;', '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: excludePackages:
'@cds/city@1.1.0;@handsontable/angular@13.1.0;handsontable@13.1.0;hyperformula@2.5.0;jackspeak@2.2.0;path-scurry@1.7.0' '@cds/city@1.1.0;@handsontable/angular@13.1.0;handsontable@13.1.0;hyperformula@2.7.0;jackspeak@2.2.0;path-scurry@1.7.0'
}, },
(error, json) => { (error, json) => {
if (error) { if (error) {

25100
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -18,8 +18,8 @@
"deploy_sasjs": "rsync -avhe ssh ./dist/* --delete root@${npm_config_account}.4gl.io:/var/www/html/dc/dev", "deploy_sasjs": "rsync -avhe ssh ./dist/* --delete root@${npm_config_account}.4gl.io:/var/www/html/dc/dev",
"viyabuild": "cd build; ./viyabuild.sh", "viyabuild": "cd build; ./viyabuild.sh",
"lint": "cd .. && npm run lint", "lint": "cd .. && npm run lint",
"test": "ng test", "test": "npx ng test",
"test:headless": "ng test --browsers ChromeHeadless", "test:headless": "npx ng test --no-watch --no-progress --browsers ChromeHeadlessCI",
"watch": "ng test watch=true", "watch": "ng test watch=true",
"pree2e": "webdriver-manager update", "pree2e": "webdriver-manager update",
"e2e": "protractor protractor.config.js", "e2e": "protractor protractor.config.js",
@ -35,29 +35,28 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^16.1.2", "@angular/animations": "^17.3.3",
"@angular/cdk": "^15.2.0", "@angular/cdk": "^17.3.3",
"@angular/common": "^16.1.2", "@angular/common": "^17.3.3",
"@angular/compiler": "^16.1.2", "@angular/compiler": "^17.3.3",
"@angular/core": "^16.1.2", "@angular/core": "^17.3.3",
"@angular/forms": "^16.1.2", "@angular/forms": "^17.3.3",
"@angular/platform-browser": "^16.1.2", "@angular/platform-browser": "^17.3.3",
"@angular/platform-browser-dynamic": "^16.1.2", "@angular/platform-browser-dynamic": "^17.3.3",
"@angular/router": "^16.1.2", "@angular/router": "^17.3.3",
"@cds/core": "^6.4.2", "@cds/core": "^6.10.0",
"@clr/angular": "^13.17.0", "@clr/angular": "^17.0.1",
"@clr/icons": "^13.0.2", "@clr/icons": "^13.0.2",
"@clr/ui": "^13.17.0", "@clr/ui": "^17.0.1",
"@handsontable/angular": "^13.1.0", "@handsontable/angular": "^13.1.0",
"@sasjs/adapter": "4.10.1", "@sasjs/adapter": "4.10.2",
"@sasjs/utils": "^3.4.0", "@sasjs/utils": "^3.4.0",
"@sheet/crypto": "1.20211122.1",
"@types/d3-graphviz": "^2.6.7", "@types/d3-graphviz": "^2.6.7",
"@types/text-encoding": "0.0.35", "@types/text-encoding": "0.0.35",
"base64-arraybuffer": "^0.2.0", "base64-arraybuffer": "^0.2.0",
"buffer": "^5.4.3", "buffer": "^5.4.3",
"crypto-browserify": "3.12.0", "crypto-browserify": "3.12.0",
"crypto-js": "^3.3.0", "crypto-js": "^4.2.0",
"d3-graphviz": "^5.0.2", "d3-graphviz": "^5.0.2",
"fs-extra": "^7.0.1", "fs-extra": "^7.0.1",
"handsontable": "^13.1.0", "handsontable": "^13.1.0",
@ -78,24 +77,25 @@
"stream-http": "3.2.0", "stream-http": "3.2.0",
"text-encoding": "^0.7.0", "text-encoding": "^0.7.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"zone.js": "~0.13.0" "vm": "^0.1.0",
"zone.js": "~0.14.4"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^16.1.0", "@angular-devkit/build-angular": "^17.3.3",
"@angular-eslint/builder": "16.0.3", "@angular-eslint/builder": "17.3.0",
"@angular-eslint/eslint-plugin": "16.0.3", "@angular-eslint/eslint-plugin": "17.3.0",
"@angular-eslint/eslint-plugin-template": "16.0.3", "@angular-eslint/eslint-plugin-template": "17.3.0",
"@angular-eslint/schematics": "16.0.3", "@angular-eslint/schematics": "17.3.0",
"@angular-eslint/template-parser": "16.0.3", "@angular-eslint/template-parser": "17.3.0",
"@angular/cli": "^16.1.0", "@angular/cli": "^17.3.3",
"@angular/compiler-cli": "^16.1.2", "@angular/compiler-cli": "^17.3.3",
"@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-proposal-private-methods": "^7.18.6",
"@compodoc/compodoc": "^1.1.21", "@compodoc/compodoc": "^1.1.21",
"@cypress/webpack-preprocessor": "^5.17.1", "@cypress/webpack-preprocessor": "^5.17.1",
"@types/core-js": "^2.5.5", "@types/core-js": "^2.5.5",
"@types/crypto-js": "^4.0.1", "@types/crypto-js": "^4.2.1",
"@types/es6-shim": "^0.31.39", "@types/es6-shim": "^0.31.39",
"@types/jasmine": "~3.6.0", "@types/jasmine": "~5.1.4",
"@types/lodash-es": "^4.17.3", "@types/lodash-es": "^4.17.3",
"@types/marked": "^4.3.0", "@types/marked": "^4.3.0",
"@types/node": "12.20.50", "@types/node": "12.20.50",
@ -109,12 +109,12 @@
"es6-shim": "^0.35.5", "es6-shim": "^0.35.5",
"eslint": "^8.33.0", "eslint": "^8.33.0",
"git-describe": "^4.0.4", "git-describe": "^4.0.4",
"jasmine-core": "~3.6.0", "jasmine-core": "~5.1.2",
"karma": "~6.3.0", "karma": "~6.4.3",
"karma-chrome-launcher": "~3.1.0", "karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.1.0", "karma-coverage": "~2.2.1",
"karma-jasmine": "~4.0.0", "karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~1.7.0", "karma-jasmine-html-reporter": "~2.1.0",
"license-checker": "25.0.1", "license-checker": "25.0.1",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mochawesome": "^7.1.3", "mochawesome": "^7.1.3",
@ -123,9 +123,7 @@
"rimraf": "3.0.2", "rimraf": "3.0.2",
"ts-loader": "^9.2.8", "ts-loader": "^9.2.8",
"ts-node": "^3.3.0", "ts-node": "^3.3.0",
"typedoc": "^0.24.8", "typescript": "~5.4.4",
"typedoc-plugin-external-module-name": "^4.0.6",
"typescript": "~4.9.4",
"wait-on": "^6.0.1", "wait-on": "^6.0.1",
"watch": "^1.0.2" "watch": "^1.0.2"
} }

View File

@ -37,6 +37,12 @@ export const initFilter: { filter: FilterCache } = {
} }
} }
export interface XLMapListItem {
id: string
description: string
targetDS: string
}
/** /**
* Cached filtering values across whole app (editor, viewer, viewboxes) * Cached filtering values across whole app (editor, viewer, viewboxes)
* Cached lineage libraries, tables * Cached lineage libraries, tables
@ -46,6 +52,8 @@ export const initFilter: { filter: FilterCache } = {
*/ */
export const globals: { export const globals: {
rootParam: string rootParam: string
dcLib: string
xlmaps: XLMapListItem[]
editor: any editor: any
viewer: any viewer: any
viewboxes: ViewboxCache viewboxes: ViewboxCache
@ -57,11 +65,13 @@ export const globals: {
[key: string]: any [key: string]: any
} = { } = {
rootParam: <string>'', rootParam: <string>'',
dcLib: '',
xlmaps: [],
editor: { editor: {
startupSet: <boolean>false, startupSet: <boolean>false,
treeNodeLibraries: <any[] | null>[], treeNodeLibraries: <any[] | null>[],
libsAndTables: <any[]>[], libsAndTables: <any[]>[],
libraries: <String[] | undefined>[], libraries: <string[] | undefined>[],
library: <string>'', library: <string>'',
table: <string>'', table: <string>'',
filter: <FilterCache>{ filter: <FilterCache>{

View File

@ -12,7 +12,7 @@
<div class="alert-items"> <div class="alert-items">
<div class="alert-item static"> <div class="alert-item static">
<div class="alert-icon-wrapper"> <div class="alert-icon-wrapper">
<clr-icon class="mt-2" shape="warning-standard"></clr-icon> <cds-icon class="alert-icon" shape="warning-standard"></cds-icon>
</div> </div>
<div class="alert-text"> <div class="alert-text">
Data Controller (FREE Tier) - to upgrade contact Data Controller (FREE Tier) - to upgrade contact
@ -30,7 +30,7 @@
<div class="alert-items"> <div class="alert-items">
<div class="alert-item static"> <div class="alert-item static">
<div class="alert-icon-wrapper"> <div class="alert-icon-wrapper">
<clr-icon class="mt-2" shape="warning-standard"></clr-icon> <cds-icon class="alert-icon" shape="warning-standard"></cds-icon>
</div> </div>
<div class="alert-text"> <div class="alert-text">
Data Controller (FREE Tier) - Problem with licence Data Controller (FREE Tier) - Problem with licence
@ -55,7 +55,7 @@
<div class="alert-items"> <div class="alert-items">
<div class="alert-item static"> <div class="alert-item static">
<div class="alert-icon-wrapper"> <div class="alert-icon-wrapper">
<clr-icon class="mt-2" shape="warning-standard"></clr-icon> <cds-icon class="alert-icon" shape="warning-standard"></cds-icon>
</div> </div>
<div class="alert-text"> <div class="alert-text">
@ -85,7 +85,7 @@
<div class="alert-items"> <div class="alert-items">
<div class="alert-item static"> <div class="alert-item static">
<div class="alert-icon-wrapper"> <div class="alert-icon-wrapper">
<clr-icon class="mt-2" shape="warning-standard"></clr-icon> <cds-icon class="alert-icon" shape="warning-standard"></cds-icon>
</div> </div>
<div class="alert-text"> <div class="alert-text">
@ -168,7 +168,7 @@
</button> </button>
<clr-dropdown-menu *clrIfOpen clrPosition="bottom-left"> <clr-dropdown-menu *clrIfOpen clrPosition="bottom-left">
<a [routerLink]="['/view']" clrDropdownItem>VIEW</a> <a [routerLink]="['/view']" clrDropdownItem>VIEW</a>
<a [routerLink]="['/home']" clrDropdownItem>EDIT</a> <a [routerLink]="['/home']" clrDropdownItem>LOAD</a>
<a [routerLink]="['/review/submitted']" clrDropdownItem>REVIEW</a> <a [routerLink]="['/review/submitted']" clrDropdownItem>REVIEW</a>
</clr-dropdown-menu> </clr-dropdown-menu>
</clr-dropdown> </clr-dropdown>
@ -189,7 +189,7 @@
router.url.includes('edit-record') || router.url.includes('edit-record') ||
router.url.includes('home') router.url.includes('home')
" "
>EDIT</a >LOAD</a
> >
<a <a
[routerLink]="['/review/submitted']" [routerLink]="['/review/submitted']"
@ -204,14 +204,7 @@
</div> </div>
</ng-container> </ng-container>
<div class="header-actions"> <app-header-actions></app-header-actions>
<div class="nav-text">
<app-loading-indicator></app-loading-indicator>
</div>
<div class="dropdown">
<app-user-nav-dropdown></app-user-nav-dropdown>
</div>
</div>
</header> </header>
<nav <nav
*ngIf=" *ngIf="

View File

@ -91,33 +91,12 @@ header {
} }
} }
.nav .nav-link:hover {
.nav-link {
color: #fafafa;
opacity: .9;
line-height: 1.45rem;
}
.nav .nav-link:hover {
box-shadow: inset 0 -3px 0 transparent;
transition: box-shadow .2s ease-in;
}
.nav
.nav-link:hover {
color: #fafafa; color: #fafafa;
opacity: 1;
} }
.nav .nav-link.active { .nav-link.active {
background: #61717D; background: #61717D;
opacity: 1;
box-shadow: inset 0 -3px transparent;
// padding: 0 1rem 0 1rem;
}
.nav .nav-item {
margin-right: 1rem;
} }
} }

View File

@ -46,7 +46,6 @@ import { NgxJsonViewerModule } from 'ngx-json-viewer'
SharedModule, SharedModule,
ClarityModule, ClarityModule,
AppSharedModule, AppSharedModule,
HomeModule,
PipesModule, PipesModule,
DirectivesModule, DirectivesModule,
NgxJsonViewerModule NgxJsonViewerModule

View File

@ -4,19 +4,19 @@
* The full license information can be found in LICENSE in the root directory of this project. * The full license information can be found in LICENSE in the root directory of this project.
*/ */
import { ModuleWithProviders } from '@angular/core' import { ModuleWithProviders } from '@angular/core'
import { Routes, RouterModule } from '@angular/router' import { RouterModule, Routes } from '@angular/router'
import { HomeComponent } from './home/home.component'
import { NotFoundComponent } from './not-found/not-found.component' 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 { ReviewRouteComponent } from './routes/review-route/review-route.component'
import { StageModule } from './stage/stage.module' import { StageModule } from './stage/stage.module'
import { EditorModule } from './editor/editor.module'
import { ViewerModule } from './viewer/viewer.module'
import { ReviewModule } from './review/review.module'
import { DeployModule } from './deploy/deploy.module'
import { LicensingModule } from './licensing/licensing.module'
import { SystemModule } from './system/system.module' import { SystemModule } from './system/system.module'
import { ViewerModule } from './viewer/viewer.module'
/** /**
* Defining routes * Defining routes
@ -45,7 +45,10 @@ export const ROUTES: Routes = [
path: 'licensing', path: 'licensing',
loadChildren: () => LicensingModule loadChildren: () => LicensingModule
}, },
{ path: 'home', component: HomeComponent }, {
path: 'home',
loadChildren: () => HomeModule
},
{ {
/** /**
* Load editor module with subroutes * Load editor module with subroutes

View File

@ -24,8 +24,8 @@
generatedRecordUrl generatedRecordUrl
? 'copy to clipboard' ? 'copy to clipboard'
: generateEditRecordUrlLoading : generateEditRecordUrlLoading
? 'Generating url...' ? 'Generating url...'
: 'Link to this record' : 'Link to this record'
}} }}
</button> </button>
</ng-container> </ng-container>
@ -112,7 +112,7 @@
<div <div
*ngIf=" *ngIf="
['autocomplete'].includes( ['autocomplete', 'autocomplete.custom'].includes(
$any(currentRecordValidator?.getRule(col.key)?.editor) $any(currentRecordValidator?.getRule(col.key)?.editor)
) )
" "
@ -163,7 +163,7 @@
<div <div
*ngIf=" *ngIf="
['autocomplete'].includes( ['autocomplete', 'autocomplete.custom'].includes(
$any(currentRecordValidator?.getRule(col.key)?.editor) $any(currentRecordValidator?.getRule(col.key)?.editor)
) )
" "

View File

@ -186,24 +186,40 @@
} as libdsParsed" } as libdsParsed"
class="editor-title text-center mt-0-i" class="editor-title text-center mt-0-i"
> >
<clr-icon <clr-tooltip>
(click)="datasetInfo = true" <clr-icon
shape="info-circle" clrTooltipTrigger
class="is-highlight cursor-pointer" (click)="datasetInfo = true"
size="24" shape="info-circle"
></clr-icon> class="is-highlight cursor-pointer"
size="24"
></clr-icon>
<clr-icon <clr-icon
*ngIf="libdsParsed.tableName.includes('-FC')" *ngIf="libdsParsed.tableName.includes('-FC')"
shape="bolt" shape="bolt"
class="color-yellow" class="color-yellow"
></clr-icon> ></clr-icon>
<span clrTooltipTrigger>
{{ libdsParsed.libName }}.<a
class="mr-10 view-table"
[routerLink]="'/view/data/' + libds!"
>{{ libdsParsed.tableName.replace('-FC', '') }}</a
>
</span>
<ng-container *ngIf="this.dsNote && this.dsNote.length > 0">
<clr-tooltip-content
clrPosition="bottom-left"
clrSize="lg"
*clrIfOpen
>
{{ this.dsNote }}
</clr-tooltip-content>
</ng-container>
</clr-tooltip>
{{ libdsParsed.libName }}.<a
class="mr-10"
[routerLink]="'/view/data/' + libds!"
>{{ libdsParsed.tableName.replace('-FC', '') }}</a
>
<ng-container *ngIf="dataSource"> <ng-container *ngIf="dataSource">
<ng-container *ngIf="!zeroFilterRows"> <ng-container *ngIf="!zeroFilterRows">
({{ dataSource.length | thousandSeparator: ',' }} ({{ dataSource.length | thousandSeparator: ',' }}
@ -280,7 +296,7 @@
licenceState.value.editor_rows_allowed === 1 licenceState.value.editor_rows_allowed === 1
? 'row' ? 'row'
: 'rows' : 'rows'
}}, contact support@datacontroller.io</span }}, contact support&#64;datacontroller.io</span
> >
</clr-tooltip-content> </clr-tooltip-content>
</clr-tooltip> </clr-tooltip>
@ -417,7 +433,7 @@
licenceState.value.editor_rows_allowed === 1 licenceState.value.editor_rows_allowed === 1
? 'row' ? 'row'
: 'rows' : 'rows'
}}, contact support@datacontroller.io</span }}, contact support&#64;datacontroller.io</span
> >
</clr-tooltip-content> </clr-tooltip-content>
</clr-tooltip> </clr-tooltip>
@ -467,7 +483,7 @@
: 'rows' : 'rows'
}} }}
will be submitted. To remove the restriction, contact will be submitted. To remove the restriction, contact
support@datacontroller.io</span support&#64;datacontroller.io</span
> >
<div *ngIf="tableTrue" class="clr-offset-md-2 clr-col-md-8"> <div *ngIf="tableTrue" class="clr-offset-md-2 clr-col-md-8">
<div class="form-group"> <div class="form-group">
@ -528,7 +544,7 @@
Due to current licence, only Due to current licence, only
{{ licenceState.value.submit_rows_limit }} rows in a file will {{ licenceState.value.submit_rows_limit }} rows in a file will
be submitted. To remove the restriction, contact be submitted. To remove the restriction, contact
support@datacontroller.io support&#64;datacontroller.io
</p> </p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
@ -830,6 +846,12 @@
</div> </div>
</clr-modal> </clr-modal>
<app-dataset-info [(open)]="datasetInfo" [dsmeta]="dsmeta"></app-dataset-info> <app-dataset-info
[(open)]="datasetInfo"
[dsmeta]="dsmeta"
[versions]="versions"
(rowClicked)="datasetInfoModalRowClicked($event)"
>
</app-dataset-info>
<app-viewboxes [(viewboxModal)]="viewboxes"></app-viewboxes> <app-viewboxes [(viewboxModal)]="viewboxes"></app-viewboxes>

View File

@ -214,6 +214,10 @@ hot-table {
width: 150px; width: 150px;
} }
.view-table {
font-size: inherit !important;
}
// FIXME // FIXME
// Let's leave it here for a reference if there // Let's leave it here for a reference if there
// is an issue with viewboxes/filter modal overlaying // is an issue with viewboxes/filter modal overlaying

View File

@ -38,7 +38,8 @@ import { HotTableInterface } from '../models/HotTable.interface'
import { import {
$DataFormats, $DataFormats,
DSMeta, DSMeta,
EditorsGetdataServiceResponse EditorsGetDataServiceResponse,
Version
} from '../models/sas/editors-getdata.model' } from '../models/sas/editors-getdata.model'
import { DataFormat } from '../models/sas/common/DateFormat' import { DataFormat } from '../models/sas/common/DateFormat'
import SheetInfo from '../models/SheetInfo' import SheetInfo from '../models/SheetInfo'
@ -121,6 +122,8 @@ export class EditorComponent implements OnInit, AfterViewInit {
datasetInfo: boolean = false datasetInfo: boolean = false
dsmeta: DSMeta[] = [] dsmeta: DSMeta[] = []
versions: Version[] = []
dsNote = ''
viewboxes: boolean = false viewboxes: boolean = false
@ -939,13 +942,30 @@ export class EditorComponent implements OnInit, AfterViewInit {
return row.map((col: any, index: number) => { return row.map((col: any, index: number) => {
if (!col && col !== 0) col = '' if (!col && col !== 0) col = ''
if (isNaN(col)) { /**
col = col.replace(/"/g, '""') * Keeping this for the reference
* Code below used to convert JSON to CSV
* now the XLSX is converting to CSV
*/
// if (isNaN(col)) {
// // Match and replace the double quotes, ignore the first and last char
// // in case they are double quotes already
// col = col.replace(/(?<!^)"(?!$)/g, '""')
if (col.search(/,/g) > -1) { // if (col.search(/,/g) > -1 ||
col = '"' + col + '"' // col.search(/\r|\n/g) > -1
} // ) {
} // // Missing quotes at the end
// if (col.search(/"$/g) < 0) {
// col = col + '"' // So we add them
// }
// // Missing quotes at the start
// if (col.search(/^"/g) < 0) {
// col = '"' + col // So we add them
// }
// }
// }
const colName = this.headerShow[index] const colName = this.headerShow[index]
const colRule = this.dcValidator?.getRule(colName) const colRule = this.dcValidator?.getRule(colName)
@ -960,20 +980,30 @@ export class EditorComponent implements OnInit, AfterViewInit {
this.data = csvArrayData this.data = csvArrayData
let csvContent = csvArrayHeaders.join(',') + '\n' // Apply licence rows limitation if exists, it is only affecting data
// Apply licence rows limitation if exists // which will be send to SAS
csvContent += csvArrayData const strippedCsvArrayData = csvArrayData.slice(
.slice(0, this.licenceState.value.submit_rows_limit) 0,
.map((e) => e.join(',')) this.licenceState.value.submit_rows_limit
.join('\n') )
// To submit to sas service, we need clean version of CSV of file
// attached. XLSX will do the parsing and heavy lifting
// First we create worksheet of json (data we extracted)
let ws = XLSX.utils.json_to_sheet(strippedCsvArrayData, {
skipHeader: true
})
// create CSV to be uploaded from worksheet
let csvContentClean = XLSX.utils.sheet_to_csv(ws)
// Prepend headers
csvContentClean = csvArrayHeaders.join(',') + '\n' + csvContentClean
if (this.encoding === 'WLATIN1') { if (this.encoding === 'WLATIN1') {
let encoded = iconv.decode(Buffer.from(csvContent), 'CP-1252') let encoded = iconv.decode(Buffer.from(csvContentClean), 'CP-1252')
let blob = new Blob([encoded], { type: 'application/csv' }) let blob = new Blob([encoded], { type: 'application/csv' })
let newCSVFile: File = this.blobToFile(blob, this.filename + '.csv') let newCSVFile: File = this.blobToFile(blob, this.filename + '.csv')
this.uploader.addToQueue([newCSVFile]) this.uploader.addToQueue([newCSVFile])
} else { } else {
let blob = new Blob([csvContent], { type: 'application/csv' }) let blob = new Blob([csvContentClean], { type: 'application/csv' })
let newCSVFile: File = this.blobToFile(blob, this.filename + '.csv') let newCSVFile: File = this.blobToFile(blob, this.filename + '.csv')
this.uploader.addToQueue([newCSVFile]) this.uploader.addToQueue([newCSVFile])
} }
@ -1928,13 +1958,13 @@ export class EditorComponent implements OnInit, AfterViewInit {
if (entry.values.length > 0) { if (entry.values.length > 0) {
hot.setCellMeta(entry.row, entry.col, 'renderer', 'autocomplete') hot.setCellMeta(entry.row, entry.col, 'renderer', 'autocomplete')
hot.setCellMeta(entry.row, entry.col, 'editor', 'autocomplete') hot.setCellMeta(entry.row, entry.col, 'editor', 'autocomplete.custom')
hot.setCellMeta(entry.row, entry.col, 'strict', entry.strict) hot.setCellMeta(entry.row, entry.col, 'strict', entry.strict)
hot.setCellMeta(entry.row, entry.col, 'filter', false) hot.setCellMeta(entry.row, entry.col, 'filter', false)
this.currentEditRecordValidator?.updateRule(entry.col, { this.currentEditRecordValidator?.updateRule(entry.col, {
renderer: 'autocomplete', renderer: 'autocomplete',
editor: 'autocomplete', editor: 'autocomplete.custom',
strict: entry.strict, strict: entry.strict,
filter: false filter: false
}) })
@ -2029,13 +2059,13 @@ export class EditorComponent implements OnInit, AfterViewInit {
} }
hot.setCellMeta(row, cellCol, 'renderer', 'autocomplete') hot.setCellMeta(row, cellCol, 'renderer', 'autocomplete')
hot.setCellMeta(row, cellCol, 'editor', 'autocomplete') hot.setCellMeta(row, cellCol, 'editor', 'autocomplete.custom')
hot.setCellMeta(row, cellCol, 'strict', cellValidationEntry.strict) hot.setCellMeta(row, cellCol, 'strict', cellValidationEntry.strict)
hot.setCellMeta(row, cellCol, 'filter', false) hot.setCellMeta(row, cellCol, 'filter', false)
this.currentEditRecordValidator?.updateRule(cellCol, { this.currentEditRecordValidator?.updateRule(cellCol, {
renderer: 'autocomplete', renderer: 'autocomplete',
editor: 'autocomplete', editor: 'autocomplete.custom',
strict: cellValidationEntry.strict, strict: cellValidationEntry.strict,
filter: false filter: false
}) })
@ -2233,8 +2263,8 @@ export class EditorComponent implements OnInit, AfterViewInit {
setTimeout(() => { setTimeout(() => {
let txt: any = document.getElementById('formFields_8') let txt: any = document.getElementById('formFields_8')
txt.focus() if (txt) txt.focus()
}) }, 200)
}) })
// let cnt = 0; // let cnt = 0;
@ -2678,13 +2708,13 @@ export class EditorComponent implements OnInit, AfterViewInit {
const strict = this.cellValidationSource[validationSourceIndex].strict const strict = this.cellValidationSource[validationSourceIndex].strict
hot.setCellMeta(row, column, 'renderer', 'autocomplete') hot.setCellMeta(row, column, 'renderer', 'autocomplete')
hot.setCellMeta(row, column, 'editor', 'autocomplete') hot.setCellMeta(row, column, 'editor', 'autocomplete.custom')
hot.setCellMeta(row, column, 'strict', strict) hot.setCellMeta(row, column, 'strict', strict)
hot.setCellMeta(row, column, 'filter', false) hot.setCellMeta(row, column, 'filter', false)
this.currentEditRecordValidator?.updateRule(column, { this.currentEditRecordValidator?.updateRule(column, {
renderer: 'autocomplete', renderer: 'autocomplete',
editor: 'autocomplete', editor: 'autocomplete.custom',
strict: strict, strict: strict,
filter: false filter: false
}) })
@ -2906,6 +2936,16 @@ export class EditorComponent implements OnInit, AfterViewInit {
} }
} }
datasetInfoModalRowClicked(value: Version | DSMeta) {
if ((<Version>value).LOAD_REF !== undefined) {
// Type is Version
const row = value as Version
const url = `/stage/${row.LOAD_REF}`
this.router.navigate([url])
}
}
viewboxManager() { viewboxManager() {
this.viewboxes = true this.viewboxes = true
} }
@ -2918,6 +2958,37 @@ export class EditorComponent implements OnInit, AfterViewInit {
) )
} }
/**
* Function checks if selected hot cell is solo cell selected
* and if it is, set the `filter` property based on filter param.
*
* @param filter
*/
private setCellFilter(filter: boolean) {
const hotSelected = this.hotInstance.getSelected()
const selection = hotSelected ? hotSelected[0] : hotSelected
// When we open a dropdown we want filter disabled so value in cell
// don't filter out items, since we want to see them all.
// But when we start typing we want to be able to start filtering values
// again
if (selection) {
const startRow = selection[0]
const endRow = selection[2]
const startCell = selection[1]
const endCell = selection[3]
if (startRow === endRow && startCell === endCell) {
const cellMeta = this.hotInstance.getCellMeta(startRow, startCell)
// If filter is not already set at the value in the param, set it
if (cellMeta && cellMeta.filter === !filter) {
this.hotInstance.setCellMeta(startRow, startCell, 'filter', filter)
}
}
}
}
async ngOnInit() { async ngOnInit() {
this.licenceService.hot_license_key.subscribe( this.licenceService.hot_license_key.subscribe(
(hot_license_key: string | undefined) => { (hot_license_key: string | undefined) => {
@ -2964,7 +3035,7 @@ export class EditorComponent implements OnInit, AfterViewInit {
await this.sasStoreService await this.sasStoreService
.callService(myParams, 'SASControlTable', 'editors/getdata', this.libds) .callService(myParams, 'SASControlTable', 'editors/getdata', this.libds)
.then((res: EditorsGetdataServiceResponse) => { .then((res: EditorsGetDataServiceResponse) => {
this.initSetup(res) this.initSetup(res)
}) })
.catch((err: any) => { .catch((err: any) => {
@ -2976,7 +3047,7 @@ export class EditorComponent implements OnInit, AfterViewInit {
ngAfterViewInit() {} ngAfterViewInit() {}
initSetup(response: EditorsGetdataServiceResponse) { initSetup(response: EditorsGetDataServiceResponse) {
this.hotInstance = this.hotRegisterer.getInstance('hotInstance') this.hotInstance = this.hotRegisterer.getInstance('hotInstance')
if (this.getdataError) return if (this.getdataError) return
@ -2985,6 +3056,21 @@ export class EditorComponent implements OnInit, AfterViewInit {
this.cols = response.data.cols this.cols = response.data.cols
this.dsmeta = response.data.dsmeta this.dsmeta = response.data.dsmeta
this.versions = response.data.versions || []
const notes = this.dsmeta.find((item) => item.NAME === 'NOTES')
const longDesc = this.dsmeta.find((item) => item.NAME === 'DD_LONGDESC')
const shortDesc = this.dsmeta.find((item) => item.NAME === 'DD_SHORTDESC')
if (notes && notes.VALUE) {
this.dsNote = notes.VALUE
} else if (longDesc && longDesc.VALUE) {
this.dsNote = longDesc.VALUE
} else if (shortDesc && shortDesc.VALUE) {
this.dsNote = shortDesc.VALUE
} else {
this.dsNote = ''
}
const hot: Handsontable = this.hotInstance const hot: Handsontable = this.hotInstance
@ -3259,28 +3345,16 @@ export class EditorComponent implements OnInit, AfterViewInit {
} }
) )
hot.addHook('beforeKeyDown', (e: any) => { hot.addHook('afterBeginEditing', () => {
const hotSelected = this.hotInstance.getSelected()
const selection = hotSelected ? hotSelected[0] : hotSelected
// When we open a dropdown we want filter disabled so value in cell // When we open a dropdown we want filter disabled so value in cell
// don't filter out items, since we want to see them all. // don't filter out items, since we want to see them all.
this.setCellFilter(false)
})
hot.addHook('beforeKeyDown', () => {
// When we start typing, we are enabling the filter since we want to find // When we start typing, we are enabling the filter since we want to find
// values faster. // values faster.
if (selection) { this.setCellFilter(true)
const startRow = selection[0]
const endRow = selection[2]
const startCell = selection[1]
const endCell = selection[3]
if (startRow === endRow && startCell === endCell) {
const cellMeta = this.hotInstance.getCellMeta(startRow, startCell)
if (cellMeta && cellMeta.filter === false) {
this.hotInstance.setCellMeta(startRow, startCell, 'filter', true)
}
}
}
}) })
hot.addHook('afterChange', (source: any, change: any) => { hot.addHook('afterChange', (source: any, change: any) => {

View File

@ -12,7 +12,6 @@ import { EditRecordComponent } from './components/edit-record/edit-record.compon
import { UploadStaterComponent } from './components/upload-stater/upload-stater.component' import { UploadStaterComponent } from './components/upload-stater/upload-stater.component'
import { EditorRoutingModule } from './editor-routing.module' import { EditorRoutingModule } from './editor-routing.module'
import { EditorComponent } from './editor.component' import { EditorComponent } from './editor.component'
import { HomeModule } from '../home/home.module'
import { DcTreeModule } from '../shared/dc-tree/dc-tree.module' import { DcTreeModule } from '../shared/dc-tree/dc-tree.module'
import { DragDropModule } from '@angular/cdk/drag-drop' import { DragDropModule } from '@angular/cdk/drag-drop'
import { ViewboxesModule } from '../shared/viewboxes/viewboxes.module' import { ViewboxesModule } from '../shared/viewboxes/viewboxes.module'
@ -33,7 +32,6 @@ registerAllModules()
AppSharedModule, AppSharedModule,
DirectivesModule, DirectivesModule,
SharedModule, SharedModule,
HomeModule,
PipesModule, PipesModule,
DcTreeModule, DcTreeModule,
DragDropModule, DragDropModule,

View File

@ -0,0 +1,23 @@
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { HomeRouteComponent } from '../routes/home-route/home-route.component'
import { HomeComponent } from './home.component'
import { XLMapModule } from '../xlmap/xlmap.module'
const routes: Routes = [
{
path: '',
component: HomeRouteComponent,
children: [
{ path: '', pathMatch: 'full', redirectTo: 'tables' },
{ path: 'tables', component: HomeComponent },
{ path: 'files', loadChildren: () => XLMapModule }
]
}
]
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class HomeRoutingModule {}

View File

@ -94,15 +94,17 @@
{{ libTable.replace('-FC', '') }} {{ libTable.replace('-FC', '') }}
</button> </button>
<clr-tooltip-content <ng-container *ngIf="tableLocked">
clrPosition="bottom-right" <clr-tooltip-content
clrSize="lg" clrPosition="bottom-right"
*clrIfOpen clrSize="lg"
> *clrIfOpen
<span *ngIf="tableLocked"> >
To unlock all tables, contact support@datacontroller.io <span>
</span> To unlock all tables, contact support&#64;datacontroller.io
</clr-tooltip-content> </span>
</clr-tooltip-content>
</ng-container>
</clr-tooltip> </clr-tooltip>
</clr-tree-node> </clr-tree-node>
</clr-tree-node> </clr-tree-node>

View File

@ -1,15 +1,18 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { HomeComponent } from './home.component' import { NgModule } from '@angular/core'
import { ClarityModule } from '@clr/angular'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { ClarityModule } from '@clr/angular'
import { AppSharedModule } from '../app-shared.module' import { AppSharedModule } from '../app-shared.module'
import { DcTreeModule } from '../shared/dc-tree/dc-tree.module'
import { DirectivesModule } from '../directives/directives.module' import { DirectivesModule } from '../directives/directives.module'
import { HomeRouteComponent } from '../routes/home-route/home-route.component'
import { DcTreeModule } from '../shared/dc-tree/dc-tree.module'
import { HomeRoutingModule } from './home-routing.module'
import { HomeComponent } from './home.component'
@NgModule({ @NgModule({
declarations: [HomeComponent], declarations: [HomeComponent, HomeRouteComponent],
imports: [ imports: [
HomeRoutingModule,
FormsModule, FormsModule,
ClarityModule, ClarityModule,
AppSharedModule, AppSharedModule,

View File

@ -2,147 +2,152 @@
<div class="card-header">Licencing</div> <div class="card-header">Licencing</div>
<div [ngSwitch]="action" class="card-block"> <div [ngSwitch]="action" class="card-block">
<ng-container *ngSwitchCase="'key'"> <div class="card-text">
<p class="key-error" *ngIf="!keyError"> <ng-container *ngSwitchCase="'key'">
Licence key is invalid. We can't provide you more details at the moment <p class="key-error" *ngIf="!keyError">
Licence key is invalid. We can't provide you more details at the
moment
</p>
<p
class="key-error"
*ngIf="keyError"
[innerHTML]="licenseErrors[keyError]"
></p>
<p *ngIf="errorDetails"><strong>Details:</strong> {{ errorDetails }}</p>
</ng-container>
<ng-container *ngSwitchCase="'limit'">
<p class="key-error">
The registered number of users reached the limit specified for your
licence. Please contact
<contact-link classes="color-green" />
or your reseller to arrange additional licences for this product.
</p>
</ng-container>
<ng-container *ngSwitchCase="'update'">
<p class="key-error">
Update the license key by uploading the licence file or by pasting a
license key and activation key in the inputs below.
</p>
</ng-container>
<p>
<strong>SYSSITE:</strong>
<span
*ngFor="let id of syssite.value; let i = index"
[class.misskey]="missmatchedKey && missmatchedKey === id"
>
{{ id }}{{ i === syssite.value?.length! - 1 ? '' : ',' }}
</span>
<a
class="tooltip tooltip-md tooltip-top-right"
(click)="copySyssite(copyIcon, copyTooltip, syssite.value || [])"
>
<clr-icon
#copyIcon
class="cursor-pointer"
shape="copy"
size="15"
></clr-icon>
<span #copyTooltip class="tooltip-content">Copy to clipboard</span>
</a>
</p> </p>
<p <p *ngIf="licenseKeyData && userCountLimitation" class="m-0">
class="key-error" <strong>Allowed users:</strong>
*ngIf="keyError" {{ licenseKeyData.users_allowed }}
[innerHTML]="licenseErrors[keyError]"
></p>
<p *ngIf="errorDetails"><strong>Details:</strong> {{ errorDetails }}</p>
</ng-container>
<ng-container *ngSwitchCase="'limit'">
<p class="key-error">
The registered number of users reached the limit specified for your
licence. Please contact
<contact-link classes="color-green" />
or your reseller to arrange additional licences for this product.
</p> </p>
</ng-container>
<ng-container *ngSwitchCase="'update'"> <clr-tabs>
<p class="key-error"> <clr-tab>
Update the license key by uploading the licence file or by pasting a <button clrTabLink>Upload licence</button>
license key and activation key in the inputs below. <clr-tab-content>
</p> <input
</ng-container> #licenceFile
(change)="onFileCapture($event)"
<p> type="file"
<strong>SYSSITE:</strong> hidden
<span />
*ngFor="let id of syssite.value; let i = index" <div
[class.misskey]="missmatchedKey && missmatchedKey === id" (click)="licenceFile.click()"
> appFileDrop
{{ id }}{{ i === syssite.value?.length! - 1 ? '' : ',' }} (fileDrop)="onFileCapture($event, true)"
</span> class="drop-area"
>
<a <clr-spinner
class="tooltip tooltip-md tooltip-top-right" class="spinner-sm"
(click)="copySyssite(copyIcon, copyTooltip, syssite.value || [])" *ngIf="licenceFileLoading"
> ></clr-spinner>
<clr-icon <ng-container *ngIf="!licenceFileLoading">
#copyIcon <div *ngIf="licencefile.filename === ''">
class="cursor-pointer" Drop / Browse licence file
shape="copy" </div>
size="15" <div *ngIf="licencefile.filename !== ''">
></clr-icon> Selected file: <strong>{{ licencefile.filename }}</strong>
<span #copyTooltip class="tooltip-content">Copy to clipboard</span> </div>
</a> <div *ngIf="licenceFileError">
</p> <strong>{{ licenceFileError }}</strong>
</div>
<p *ngIf="licenseKeyData && userCountLimitation" class="m-0"> </ng-container>
<strong>Allowed users:</strong>
{{ licenseKeyData.users_allowed }}
</p>
<clr-tabs>
<clr-tab>
<button clrTabLink>Upload licence</button>
<clr-tab-content>
<input
#licenceFile
(change)="onFileCapture($event)"
type="file"
hidden
/>
<div
(click)="licenceFile.click()"
appFileDrop
(fileDrop)="onFileCapture($event, true)"
class="drop-area"
>
<clr-spinner
class="spinner-sm"
*ngIf="licenceFileLoading"
></clr-spinner>
<ng-container *ngIf="!licenceFileLoading">
<div *ngIf="licencefile.filename === ''">
Drop / Browse licence file
</div>
<div *ngIf="licencefile.filename !== ''">
Selected file: <strong>{{ licencefile.filename }}</strong>
</div>
<div *ngIf="licenceFileError">
<strong>{{ licenceFileError }}</strong>
</div>
</ng-container>
</div>
</clr-tab-content>
</clr-tab>
<clr-tab>
<button clrTabLink>Paste licence</button>
<clr-tab-content>
<form class="clr-form license-key-form">
<p>Licence key:</p>
<div class="clr-control-container">
<textarea
[(ngModel)]="licenceKeyValue"
(mouseleave)="trimKeys()"
name="license-key-area"
placeholder="Paste licence key here"
class="clr-textarea"
></textarea>
</div> </div>
</form> </clr-tab-content>
</clr-tab>
<form class="clr-form activation-key-form"> <clr-tab>
<p>Activation key:</p> <button clrTabLink>Paste licence</button>
<div class="clr-control-container"> <clr-tab-content>
<textarea <form class="clr-form license-key-form">
[(ngModel)]="activationKeyValue" <p>Licence key:</p>
(mouseleave)="trimKeys()" <div class="clr-control-container">
name="activation-key-area" <textarea
placeholder="Paste activation key here" [(ngModel)]="licenceKeyValue"
class="clr-textarea" (mouseleave)="trimKeys()"
></textarea> name="license-key-area"
</div> placeholder="Paste licence key here"
</form> class="clr-textarea"
</clr-tab-content> ></textarea>
</clr-tab> </div>
</clr-tabs> </form>
<button <form class="clr-form activation-key-form">
(click)="applyKeys()" <p>Activation key:</p>
class="btn btn-primary apply-keys" <div class="clr-control-container">
[clrLoading]="applyingKeys" <textarea
[disabled]="disableApplyButton" [(ngModel)]="activationKeyValue"
> (mouseleave)="trimKeys()"
Apply licence keys name="activation-key-area"
</button> placeholder="Paste activation key here"
class="clr-textarea"
></textarea>
</div>
</form>
</clr-tab-content>
</clr-tab>
</clr-tabs>
</div>
<button <div class="card-footer d-flex clr-align-items-center">
*ngIf="isAppFreeTier.value" <button
routerLink="/" (click)="applyKeys()"
class="btn btn-sm btn-link" class="btn btn-primary apply-keys"
> [clrLoading]="applyingKeys"
Continue with free tier [disabled]="disableApplyButton"
</button> >
Apply licence keys
</button>
<button
*ngIf="isAppFreeTier.value"
routerLink="/"
class="btn btn-sm btn-link"
>
Continue with free tier
</button>
</div>
</div> </div>
</div> </div>

View File

@ -33,7 +33,6 @@
.apply-keys { .apply-keys {
height: 40px; height: 40px;
width: 200px;
} }
.drop-area { .drop-area {

View File

@ -656,11 +656,10 @@ export class LineageComponent {
this.flatdata = res.flatdata this.flatdata = res.flatdata
if (this.libraryList) { if (this.libraryList) {
let libraryToSelect = this.libraryList.find( let libraryToSelect = this.libraryList.find((library: any) =>
(library: any) => res.info[0]?.LIBURI?.toUpperCase()?.includes(
res.info[0]?.LIBURI?.toUpperCase()?.includes( library?.LIBRARYID?.toUpperCase()
library?.LIBRARYID?.toUpperCase() )
)
) )
let tableToSelect: any let tableToSelect: any

View File

@ -6,6 +6,7 @@ export interface FilterClause {
operators: string[] operators: string[]
type: string type: string
value: any value: any
valueVariable: boolean
values: { formatted: string; unformatted: any }[] values: { formatted: string; unformatted: any }[]
variable: string variable: string
} }

View File

@ -4,12 +4,12 @@ import { DQData, SASParam } from '../TableData'
import { BaseSASResponse } from './common/BaseSASResponse' import { BaseSASResponse } from './common/BaseSASResponse'
import { DataFormat } from './common/DateFormat' import { DataFormat } from './common/DateFormat'
export interface EditorsGetdataServiceResponse { export interface EditorsGetDataServiceResponse {
data: EditorsGetdataSASResponse data: EditorsGetDataSASResponse
libds: string libds: string
} }
export interface EditorsGetdataSASResponse extends BaseSASResponse { export interface EditorsGetDataSASResponse extends BaseSASResponse {
$sasdata: $DataFormats $sasdata: $DataFormats
sasdata: Sasdata[] sasdata: Sasdata[]
sasparams: SASParam[] sasparams: SASParam[]
@ -17,6 +17,7 @@ export interface EditorsGetdataSASResponse extends BaseSASResponse {
dqrules: DQRule[] dqrules: DQRule[]
dsmeta: DSMeta[] dsmeta: DSMeta[]
dqdata: DQData[] dqdata: DQData[]
versions: Version[]
cols: Col[] cols: Col[]
maxvarlengths: Maxvarlength[] maxvarlengths: Maxvarlength[]
xl_rules: any[] xl_rules: any[]
@ -27,6 +28,18 @@ export interface DSMeta {
ODS_TABLE: string ODS_TABLE: string
NAME: string NAME: string
VALUE: string VALUE: string
[key: string]: string
}
export interface Version {
LOAD_REF: string
USER_NM: string
VERSION_DTTM: string
VERSION_DESC: string
CHANGED_RECORDS: number
NEW_RECORDS: number
DELETED_RECORDS: number
[key: string]: string | number
} }
export interface Sasdata { export interface Sasdata {

View File

@ -0,0 +1,9 @@
import { BaseSASResponse } from './common/BaseSASResponse'
export interface EditorsRestoreServiceResponse extends BaseSASResponse {
restore_out: RestoreOut[]
}
export interface RestoreOut {
LOADREF: string
}

View File

@ -143,7 +143,6 @@
(ngModelChange)=" (ngModelChange)="
setVariableOperator(queryIndex, query.operator, clauseIndex) setVariableOperator(queryIndex, query.operator, clauseIndex)
" "
class="mt-2"
clrSelect clrSelect
> >
<option *ngFor="let opr of query.operators">{{ opr }}</option> <option *ngFor="let opr of query.operators">{{ opr }}</option>
@ -413,7 +412,10 @@
> >
<app-soft-select <app-soft-select
label="Value" label="Value"
[secondLabel]="'Variable'"
[emitOnlySelected]="query.valueVariable"
[inputId]="'vals_' + queryIndex + '_' + clauseIndex" [inputId]="'vals_' + queryIndex + '_' + clauseIndex"
(selectedLabelChange)="selectedLabelChange($event, query)"
[(value)]="query.value" [(value)]="query.value"
[enableLoadMore]="query.nobs > query.values.length" [enableLoadMore]="query.nobs > query.values.length"
(onInputEvent)=" (onInputEvent)="
@ -423,9 +425,19 @@
onAutocompleteLoadingMore($event, query.variable, queryIndex, clauseIndex) onAutocompleteLoadingMore($event, query.variable, queryIndex, clauseIndex)
" "
> >
<option [value]="column.unformatted" *ngFor="let column of query.values"> <div *ngIf="!query.valueVariable">
{{ column.formatted.trim() }} <option [value]="column.unformatted" *ngFor="let column of query.values">
</option> {{ column.formatted.trim() }}
</option>
</div>
<div *ngIf="query.valueVariable">
<ng-container *ngFor="let column of cols">
<option [value]="column.NAME" *ngIf="column.TYPE === query.type">
{{ column.NAME }}
</option>
</ng-container>
</div>
</app-soft-select> </app-soft-select>
</ng-template> </ng-template>

View File

@ -95,6 +95,7 @@ export class QueryComponent
variable: null, variable: null,
operator: null, operator: null,
value: null, value: null,
valueVariable: false,
startrow: 0, startrow: 0,
rows: 0, rows: 0,
nobs: 0, nobs: 0,
@ -193,6 +194,20 @@ export class QueryComponent
*/ */
usePickersChange() { usePickersChange() {
this.queryDateTime = [] this.queryDateTime = []
if (this.usePickers) {
this.clauses.queryObj.forEach((queryObj: any) => {
queryObj.elements.forEach((element: any) => {
const isDateOrTime = ['DATETIME', 'TIME', 'DATE'].includes(
element.ddtype
)
if (isDateOrTime && element.valueVariable) {
element.value = ''
element.valueVariable = false
}
})
})
}
} }
/** /**
@ -253,8 +268,6 @@ export class QueryComponent
get(globals, objPath).filter.libds = this.libds get(globals, objPath).filter.libds = this.libds
} }
get(globals, objPath).filter.clauses = this.clauses get(globals, objPath).filter.clauses = this.clauses
console.log('globals', globals)
} }
/** /**
@ -750,6 +763,12 @@ export class QueryComponent
) )
} }
public selectedLabelChange(label: string, query: any) {
query.valueVariable = label === 'Variable'
query.value = ''
this.whereClauseFn()
}
public variableInputChange( public variableInputChange(
queryVariable: any, queryVariable: any,
index: number, index: number,
@ -859,17 +878,25 @@ export class QueryComponent
*/ */
public hasInvalidCluase(clauses: any): boolean { public hasInvalidCluase(clauses: any): boolean {
for (let clause of clauses) { for (let clause of clauses) {
clause['invalidClause'] = false
if ( if (
clause.variable === null || clause.value === '' &&
clause.operator === null || !(clause.operator === 'NE' || clause.operator === 'CONTAINS')
clause.value === null || ) {
clause.value === '' clause['invalidClause'] = true
return true
}
if (
clause.variable === null ||
clause.operator === null ||
clause.value === null
) { ) {
clause['invalidClause'] = true clause['invalidClause'] = true
return true return true
} else {
clause['invalidClause'] = false
} }
} }

View File

@ -139,10 +139,7 @@
<div class="card-header p-0"> <div class="card-header p-0">
<div class="clr-row"> <div class="clr-row">
<div class="clr-col-md-4 approvalBack"> <div class="clr-col-md-4 approvalBack">
<span <span class="btn btn-outline m-0" (click)="goToApprovalsList()">
class="btn btn-sm btn-outline m-0"
(click)="goToApprovalsList()"
>
<clr-icon shape="caret" dir="left" size="20"></clr-icon>Back to <clr-icon shape="caret" dir="left" size="20"></clr-icon>Back to
approvals list approvals list
</span> </span>
@ -209,22 +206,22 @@
<div class="d-flex justify-content-center mt-0"> <div class="d-flex justify-content-center mt-0">
<div class="clr-row clr-gap-5 clr-gap-sm-0"> <div class="clr-row clr-gap-5 clr-gap-sm-0">
<button <button
class="btn btn-sm btn-outline text-center mt-5" class="btn btn-sm btn-outline text-center mt-5 mr-5i"
(click)="goToBase(jsParams?.TABLE_NM)" (click)="goToBase(jsParams?.TABLE_NM)"
> >
Go to base table screen View base table
</button> </button>
<button <button
class="btn btn-sm btn-success-outline text-center mt-5" class="btn btn-sm btn-success-outline text-center mt-5 mr-5i"
(click)="getTable(tableId)" (click)="getTable(tableId)"
> >
Go to edited screen View staged data
</button> </button>
<button <button
class="btn btn-sm btn-info-outline text-center mt-5" class="btn btn-sm btn-info-outline text-center mt-5"
(click)="goBack(jsParams?.TABLE_NM)" (click)="goBack(jsParams?.TABLE_NM)"
> >
Go back to editor Edit base table
</button> </button>
</div> </div>
</div> </div>
@ -236,7 +233,7 @@
id="acceptBtn" id="acceptBtn"
[clrLoading]="acceptLoading" [clrLoading]="acceptLoading"
type="submit" type="submit"
class="btn btn-sm btn-success" class="btn btn-sm btn-success mr-5i"
(click)="approveTable()" (click)="approveTable()"
[disabled]=" [disabled]="
!loadingTable || params?.ISAPPROVER === 'NO' || noChanges !loadingTable || params?.ISAPPROVER === 'NO' || noChanges
@ -246,7 +243,7 @@
</button> </button>
<button <button
id="rejectBtn" id="rejectBtn"
class="btn btn-sm btn btn-danger mr-0" class="btn btn-sm btn btn-danger mr-5i"
(click)="rejectOpen = true" (click)="rejectOpen = true"
[disabled]=" [disabled]="
!loadingTable || params?.ISAPPROVER === 'NO' || noChanges !loadingTable || params?.ISAPPROVER === 'NO' || noChanges
@ -394,9 +391,9 @@
<div class="card-header"> <div class="card-header">
<div class="clr-row"> <div class="clr-row">
<div class="clr-col-md-4 approvalBack"> <div class="clr-col-md-4 approvalBack">
<span class="btn btn-sm btn-outline" (click)="goToSubmitList()"> <span class="btn btn-outline" (click)="goToSubmitList()">
<clr-icon shape="caret" dir="left" size="20"></clr-icon>Back to <cds-icon shape="angle" direction="left" size="20"></cds-icon
submitted list >Back to submitted list
</span> </span>
</div> </div>
<div class="clr-col-md-4"> <div class="clr-col-md-4">
@ -443,22 +440,22 @@
<div class="d-flex justify-content-center mt-0"> <div class="d-flex justify-content-center mt-0">
<div class="clr-row clr-gap-5 clr-gap-sm-0"> <div class="clr-row clr-gap-5 clr-gap-sm-0">
<button <button
class="btn btn-sm btn-outline text-center mt-5" class="btn btn-sm btn-outline text-center mt-5 mr-5i"
(click)="goToBase(subObj.base)" (click)="goToBase(subObj.base)"
> >
Go to base table screen View base table
</button> </button>
<button <button
class="btn btn-sm btn-success-outline text-center mt-5" class="btn btn-sm btn-success-outline text-center mt-5 mr-5i"
(click)="getTable(subObj.tableId)" (click)="getTable(subObj.tableId)"
> >
Go to edited screen View staged data
</button> </button>
<button <button
class="btn btn-sm btn-info-outline text-center mt-5" class="btn btn-sm btn-info-outline text-center mt-5"
(click)="goBack(subObj.base)" (click)="goBack(subObj.base)"
> >
Go back to editor Edit base table
</button> </button>
</div> </div>
</div> </div>

View File

@ -72,7 +72,7 @@
> >
To unlock more than To unlock more than
{{ licenceState.value.history_rows_allowed }} records, contact {{ licenceState.value.history_rows_allowed }} records, contact
support@datacontroller.io support&#64;datacontroller.io
</p> </p>
</div> </div>

View File

@ -0,0 +1 @@
<router-outlet></router-outlet>

View File

@ -0,0 +1,17 @@
import { Component, OnInit, OnDestroy } from '@angular/core'
@Component({
selector: 'app-home-route',
templateUrl: './home-route.component.html',
styleUrls: ['./home-route.component.scss'],
host: {
class: 'content-container'
}
})
export class HomeRouteComponent implements OnInit, OnDestroy {
constructor() {}
ngOnInit() {}
ngOnDestroy() {}
}

View File

@ -0,0 +1 @@
<router-outlet></router-outlet>

View File

@ -0,0 +1,17 @@
import { Component, OnInit, OnDestroy } from '@angular/core'
@Component({
selector: 'app-xlmap-route',
templateUrl: './xlmap-route.component.html',
styleUrls: ['./xlmap-route.component.scss'],
host: {
class: 'content-container'
}
})
export class XLMapRouteComponent implements OnInit, OnDestroy {
constructor() {}
ngOnInit() {}
ngOnDestroy() {}
}

View File

@ -74,6 +74,7 @@ export class AppService {
missingProps.push('Globvars') missingProps.push('Globvars')
if (!res.sasdatasets) missingProps.push('Sasdatasets') if (!res.sasdatasets) missingProps.push('Sasdatasets')
if (!res.saslibs) missingProps.push('Saslibs') if (!res.saslibs) missingProps.push('Saslibs')
if (!res.xlmaps) missingProps.push('XLMaps')
if (missingProps.length > 0) { if (missingProps.length > 0) {
startupServiceError = true startupServiceError = true
@ -135,10 +136,17 @@ export class AppService {
globals.editor.libsAndTables = libsAndTables globals.editor.libsAndTables = libsAndTables
} }
globals.xlmaps = res.xlmaps.map((xlmap: any) => ({
id: xlmap[0],
description: xlmap[1],
targetDS: xlmap[2]
}))
globals.editor.treeNodeLibraries = treeNodeLibraries globals.editor.treeNodeLibraries = treeNodeLibraries
globals.editor.libraries = libraries globals.editor.libraries = libraries
globals.editor.startupSet = true globals.editor.startupSet = true
globals.dcLib = res.globvars[0].DCLIB
await this.licenceService.activation(res) await this.licenceService.activation(res)
}) })
.catch((err: any) => { .catch((err: any) => {

View File

@ -10,8 +10,8 @@ import { globals } from '../_globals'
import { FilterClause, FilterGroup, FilterQuery } from '../models/FilterQuery' import { FilterClause, FilterGroup, FilterQuery } from '../models/FilterQuery'
import { import {
$DataFormats, $DataFormats,
EditorsGetdataSASResponse, EditorsGetDataSASResponse,
EditorsGetdataServiceResponse EditorsGetDataServiceResponse
} from '../models/sas/editors-getdata.model' } from '../models/sas/editors-getdata.model'
import { LoggerService } from './logger.service' import { LoggerService } from './logger.service'
import { isSpecialMissing } from '@sasjs/utils/input/validators' import { isSpecialMissing } from '@sasjs/utils/input/validators'
@ -57,13 +57,13 @@ export class SasStoreService {
libds: string libds: string
) { ) {
this.libds = libds this.libds = libds
let tables: any = {} const tables: any = {}
tables[tableName] = [tableData] tables[tableName] = [tableData]
let res: EditorsGetdataSASResponse = await this.sasService.request( const res: EditorsGetDataSASResponse = await this.sasService.request(
program, program,
tables tables
) )
let response: EditorsGetdataServiceResponse = { const response: EditorsGetDataServiceResponse = {
data: res, data: res,
libds: this.libds libds: this.libds
} }
@ -209,6 +209,14 @@ export class SasStoreService {
return res return res
} }
public async getXLMapRules(id: string) {
const tables = {
getxlmaps_in: [{ XLMAP_ID: id }]
}
const res: any = await this.sasService.request('editors/getxlmaps', tables)
return res
}
public async getDetails(tableData: any, tableName: string, program: string) { public async getDetails(tableData: any, tableName: string, program: string) {
let tables: any = {} let tables: any = {}
tables[tableName] = [tableData] tables[tableName] = [tableData]
@ -408,14 +416,18 @@ export class SasStoreService {
for (let index = 0; index < clauses.queryObj.length; index++) { for (let index = 0; index < clauses.queryObj.length; index++) {
let string = '' let string = ''
let clause = clauses.queryObj[index] let clause = clauses.queryObj[index]
for (let ind = 0; ind < clause.elements.length; ind++) { for (let ind = 0; ind < clause.elements.length; ind++) {
let query = clause.elements[ind] let query = clause.elements[ind]
if (ind < clause.elements.length - 1) { if (ind < clause.elements.length - 1) {
opr = clause.clauseLogic opr = clause.clauseLogic
} else { } else {
opr = '' opr = ''
} }
let val: any let val: any
for (let k = 0; k < query.values.length; k++) { for (let k = 0; k < query.values.length; k++) {
if ( if (
typeof query.value === 'string' && typeof query.value === 'string' &&
@ -486,6 +498,8 @@ export class SasStoreService {
} }
let type = query.type let type = query.type
//if the value is variable, omit quotes in the 'where' string
const isValueVariable = query.valueVariable
let variable = query.variable === null ? '' : query.variable let variable = query.variable === null ? '' : query.variable
let oper = query.operator === null ? '' : query.operator let oper = query.operator === null ? '' : query.operator
// let value = val === null ? "''" : val; // let value = val === null ? "''" : val;
@ -499,10 +513,14 @@ export class SasStoreService {
if (type === 'char' && oper !== 'IN' && oper !== 'NOT IN') { if (type === 'char' && oper !== 'IN' && oper !== 'NOT IN') {
if (typeof value === 'undefined') { if (typeof value === 'undefined') {
value = '' value = ''
value = " '" + value + "' "
} else {
value = " '" + value + "' "
} }
if (isValueVariable) {
value = ' ' + value + ' ' //without quotes, with spaces
} else {
value = " '" + value + "' " //with quotes and spaces
}
string = string + ' ' + variable + ' ' + oper + value + opr string = string + ' ' + variable + ' ' + oper + value + opr
} else { } else {
if (type === 'num' && typeof value === 'undefined') { if (type === 'num' && typeof value === 'undefined') {
@ -596,7 +614,7 @@ export class SasStoreService {
rawValue = '.' rawValue = '.'
} }
} else { } else {
if (filterClause.type === 'char') { if (filterClause.type === 'char' && !filterClause.valueVariable) {
rawValue = `'${filterClause.value.replace(/'/g, "''")}'` rawValue = `'${filterClause.value.replace(/'/g, "''")}'`
} }
} }

View File

@ -2,5 +2,5 @@
[ngClass]="classes" [ngClass]="classes"
[class.unset]="classes !== ''" [class.unset]="classes !== ''"
href="mailto:support@datacontroller.io?subject=Licence" href="mailto:support@datacontroller.io?subject=Licence"
>support@datacontroller.io</a >support&#64;datacontroller.io</a
> >

View File

@ -6,25 +6,31 @@
> >
<h3 class="modal-title center text-center color-darker-gray">Dataset Meta</h3> <h3 class="modal-title center text-center color-darker-gray">Dataset Meta</h3>
<div class="modal-body"> <div class="modal-body">
<p *ngIf="dsmetaGroupped.length < 1" class="text-center"> <p *ngIf="dsmetaTabs.length < 1" class="text-center">
No dataset meta to show. No dataset meta to show.
</p> </p>
<clr-tabs clrLayout="vertical"> <clr-tabs clrLayout="vertical">
<clr-tab *ngFor="let dsmeta of dsmetaGroupped; let index = index"> <clr-tab *ngFor="let tab of tabs; let index = index">
<button clrTabLink id="link1">{{ dsmeta.group }}</button> <button clrTabLink id="link1">{{ tab.name }}</button>
<clr-tab-content <clr-tab-content
id="content1" id="content1"
*clrIfActive="index === 0" *clrIfActive="index === 0"
class="d-flex clr-justify-content-center w-100" class="d-flex clr-justify-content-center w-100"
> >
<clr-datagrid> <clr-datagrid>
<clr-dg-column>Name</clr-dg-column> <ng-container *ngFor="let col of tab.colsToDisplay">
<clr-dg-column>Value</clr-dg-column> <clr-dg-column>{{ col.colName || col.colKey }}</clr-dg-column>
</ng-container>
<clr-dg-row *ngFor="let info of dsmeta.dsmeta"> <clr-dg-row
<clr-dg-cell>{{ info.NAME }}</clr-dg-cell> (click)="tab.onRowClick ? tab.onRowClick(info) : ''"
<clr-dg-cell>{{ info.VALUE }}</clr-dg-cell> class="clickable-row"
*ngFor="let info of tab.meta"
>
<ng-container *ngFor="let col of tab.colsToDisplay">
<clr-dg-cell>{{ info[col.colKey] }}</clr-dg-cell>
</ng-container>
</clr-dg-row> </clr-dg-row>
</clr-datagrid> </clr-datagrid>
</clr-tab-content> </clr-tab-content>

View File

@ -13,4 +13,23 @@
} }
} }
} }
}
clr-modal {
::ng-deep {
.modal-dialog {
height: 100%;
}
}
}
.clickable-row {
cursor: pointer;
}
::ng-deep {
.datagrid-table .datagrid-cell:focus {
outline: none;
outline-offset: 0;
}
} }

View File

@ -7,8 +7,8 @@ import {
Output, Output,
SimpleChanges SimpleChanges
} from '@angular/core' } from '@angular/core'
import { DSMeta } from 'src/app/models/sas/editors-getdata.model' import { DSMeta, Version } from 'src/app/models/sas/editors-getdata.model'
import { DSMetaGroupped } from './models/dsmeta-groupped.model' import { Tab } from './models/dsmeta-groupped.model'
@Component({ @Component({
selector: 'app-dataset-info', selector: 'app-dataset-info',
@ -18,10 +18,15 @@ import { DSMetaGroupped } from './models/dsmeta-groupped.model'
export class DatasetInfoComponent implements OnInit, OnChanges { export class DatasetInfoComponent implements OnInit, OnChanges {
@Input() open: boolean = false @Input() open: boolean = false
@Input() dsmeta: DSMeta[] = [] @Input() dsmeta: DSMeta[] = []
@Input() versions: Version[] = []
@Output() openChange = new EventEmitter<boolean>() @Output() openChange = new EventEmitter<boolean>()
@Output() rowClicked = new EventEmitter<Version | DSMeta>()
dsmetaGroupped: DSMetaGroupped[] = [] dsmetaTabs: Tab<DSMeta>[] = []
versionsTabs: Tab<Version>[] = []
tabs: Tab<DSMeta | Version>[] = []
constructor() {} constructor() {}
@ -30,28 +35,58 @@ export class DatasetInfoComponent implements OnInit, OnChanges {
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
if (changes.dsmeta?.currentValue?.length > 0) { if (changes.dsmeta?.currentValue?.length > 0) {
this.parseDSMeta() this.parseDSMeta()
this.parseVersions()
this.tabs = [...[...this.dsmetaTabs], ...[...this.versionsTabs]]
} }
} }
parseDSMeta() { parseDSMeta() {
this.dsmetaGroupped = [] this.dsmetaTabs = []
for (let info of this.dsmeta) { for (let info of this.dsmeta) {
let groupIndex = this.dsmetaGroupped.findIndex( let groupIndex = this.dsmetaTabs.findIndex(
(x) => x.group === info.ODS_TABLE (x) => x.name === info.ODS_TABLE
) )
if (groupIndex < 0) if (groupIndex < 0)
groupIndex = groupIndex =
this.dsmetaGroupped.push({ this.dsmetaTabs.push({
group: info.ODS_TABLE, name: info.ODS_TABLE,
dsmeta: [] title: 'Dataset Meta',
colsToDisplay: [{ colKey: 'NAME' }, { colKey: 'VALUE' }],
meta: [],
onRowClick: (value: DSMeta) => {
this.rowClicked.emit(value)
}
}) - 1 }) - 1
this.dsmetaGroupped[groupIndex].dsmeta.push(info) this.dsmetaTabs[groupIndex].meta.push(info)
} }
} }
parseVersions() {
this.versionsTabs = [
{
name: 'VERSIONS',
title: 'Dataset Meta',
colsToDisplay: [
{ colKey: 'LOAD_REF' },
{ colKey: 'USER_NM' },
{ colKey: 'VERSION_DTTM' },
{ colKey: 'NEW_RECORDS', colName: 'ADD' },
{ colKey: 'CHANGED_RECORDS', colName: 'MOD' },
{ colKey: 'DELETED_RECORDS', colName: 'DEL' },
{ colKey: 'VERSION_DESC' }
],
meta: this.versions,
onRowClick: (value: Version) => {
this.rowClicked.emit(value)
}
}
]
}
onOpenChange(open: boolean) { onOpenChange(open: boolean) {
this.open = open this.open = open
this.openChange.emit(open) this.openChange.emit(open)

View File

@ -1,6 +1,14 @@
import { DSMeta } from 'src/app/models/sas/editors-getdata.model' export interface Tab<T> {
name: string
export interface DSMetaGroupped { title: string
group: string /**
dsmeta: DSMeta[] * Columns to be displayed in the the grid
* If empty, all columns will be displayed
*/
colsToDisplay: {
colKey: string
colName?: string
}[]
meta: T[]
onRowClick?: (value: any) => void
} }

View File

@ -106,7 +106,7 @@
*clrIfOpen *clrIfOpen
> >
<span *ngIf="tableLocked"> <span *ngIf="tableLocked">
To unlock all tables, contact support@datacontroller.io To unlock all tables, contact support&#64;datacontroller.io
</span> </span>
</clr-tooltip-content> </clr-tooltip-content>

View File

@ -20,6 +20,7 @@ import { parseColType } from './utils/parseColType'
import { dqValidate } from './validations/dq-validation' import { dqValidate } from './validations/dq-validation'
import { specialMissingNumericValidator } from './validations/hot-custom-validators' import { specialMissingNumericValidator } from './validations/hot-custom-validators'
import { applyNumericFormats } from './utils/applyNumericFormats' import { applyNumericFormats } from './utils/applyNumericFormats'
import { CustomAutocompleteEditor } from './editors/numericAutocomplete'
export class DcValidator { export class DcValidator {
private rules: DcValidation[] = [] private rules: DcValidation[] = []
@ -38,6 +39,8 @@ export class DcValidator {
dqData: DQData[], dqData: DQData[],
hotInstance?: Handsontable hotInstance?: Handsontable
) { ) {
this.registerCustomEditors()
this.sasparams = sasparams this.sasparams = sasparams
this.hotInstance = hotInstance this.hotInstance = hotInstance
this.rules = parseColType(sasparams.COLTYPE) this.rules = parseColType(sasparams.COLTYPE)
@ -51,6 +54,13 @@ export class DcValidator {
this.setupValidations() this.setupValidations()
} }
registerCustomEditors() {
Handsontable.editors.registerEditor(
'autocomplete.custom',
CustomAutocompleteEditor
)
}
getRules(): DcValidation[] { getRules(): DcValidation[] {
return this.rules return this.rules
} }
@ -262,6 +272,7 @@ export class DcValidator {
if (source.length > 0) { if (source.length > 0) {
this.rules[i].source = source this.rules[i].source = source
this.rules[i].type = 'autocomplete' this.rules[i].type = 'autocomplete'
this.rules[i].editor = 'autocomplete.custom'
this.rules[i].filter = false this.rules[i].filter = false
} }
@ -315,7 +326,10 @@ export class DcValidator {
// Because of dynamic cell validation, that will change the type of cell to dropdown // Because of dynamic cell validation, that will change the type of cell to dropdown
// `rules[i].colType` could be different type (eg. numeric). So we check if current cell is dropdown, to call HOT native dropdown validator // `rules[i].colType` could be different type (eg. numeric). So we check if current cell is dropdown, to call HOT native dropdown validator
if (this.editor === 'autocomplete') { if (
this.editor === 'autocomplete' ||
this.editor === 'autocomplete.custom'
) {
self self
.getHandsontableValidator('autocomplete') .getHandsontableValidator('autocomplete')
.call(this, value, (valid: boolean) => { .call(this, value, (valid: boolean) => {

View File

@ -0,0 +1,28 @@
import Handsontable from 'handsontable'
import Core from 'handsontable/core'
export class CustomAutocompleteEditor extends Handsontable.editors
.AutocompleteEditor {
constructor(instance: Core) {
super(instance)
}
createElements() {
super.createElements()
}
// Listbox open
open(event?: Event | undefined): void {
super.open(event)
if (this.isCellNumeric()) {
this.htContainer.classList.add('numericListbox')
} else {
this.htContainer.classList.remove('numericListbox')
}
}
isCellNumeric() {
return this.cellProperties?.className?.includes('htNumeric')
}
}

View File

@ -11,6 +11,8 @@ $clr-green: #60b515;
height: $clr-header-height; height: $clr-header-height;
display: flex; display: flex;
align-items: center; align-items: center;
height: 100%;
margin-right: 10px;
.spinner { .spinner {
vertical-align: middle; vertical-align: middle;

View File

@ -16,8 +16,6 @@
*ngFor="let programLog of sasjsRequests; let i = index" *ngFor="let programLog of sasjsRequests; let i = index"
[id]="'request_' + i" [id]="'request_' + i"
[clrStackViewLevel]="1" [clrStackViewLevel]="1"
[clrStackViewSetsize]="3"
[clrStackViewPosinset]="3"
> >
<clr-stack-label> <clr-stack-label>
{{ programLog.serviceLink }} {{ programLog.serviceLink }}

View File

@ -8,7 +8,7 @@ import { LoadingIndicatorComponent } from './loading-indicator/loading-indicator
import { LoginComponent } from './login/login.component' import { LoginComponent } from './login/login.component'
import { UserService } from './user.service' import { UserService } from './user.service'
import { AlertsService } from './alerts/alerts.service' import { AlertsService } from './alerts/alerts.service'
import { UserNavDropdownComponent } from './user-nav-dropdown/user-nav-dropdown.component' import { HeaderActions } from './user-nav-dropdown/header-actions.component'
import { AlertsComponent } from './alerts/alerts.component' import { AlertsComponent } from './alerts/alerts.component'
import { TermsComponent } from './terms/terms.component' import { TermsComponent } from './terms/terms.component'
import { DirectivesModule } from '../directives/directives.module' import { DirectivesModule } from '../directives/directives.module'
@ -26,7 +26,7 @@ import { ContactLinkComponent } from './contact-link/contact-link.component'
declarations: [ declarations: [
LoadingIndicatorComponent, LoadingIndicatorComponent,
LoginComponent, LoginComponent,
UserNavDropdownComponent, HeaderActions,
AlertsComponent, AlertsComponent,
TermsComponent, TermsComponent,
DatasetInfoComponent, DatasetInfoComponent,
@ -35,7 +35,7 @@ import { ContactLinkComponent } from './contact-link/contact-link.component'
exports: [ exports: [
LoadingIndicatorComponent, LoadingIndicatorComponent,
LoginComponent, LoginComponent,
UserNavDropdownComponent, HeaderActions,
AlertsComponent, AlertsComponent,
TermsComponent, TermsComponent,
DatasetInfoComponent, DatasetInfoComponent,

View File

@ -107,7 +107,29 @@
</clr-tab-content> </clr-tab-content>
</clr-tab> </clr-tab>
</clr-tabs> </clr-tabs>
<p *ngIf="isMainRoute('home')" class="page-title">Edit</p>
<div
*ngIf="isMainRoute('home')"
class="d-flex justify-content-center sub-dropdown"
>
<clr-dropdown>
<button class="dropdown-toggle btn btn-link" clrDropdownTrigger>
{{ getSubPage() }}
<clr-icon shape="caret down"></clr-icon>
</button>
<clr-dropdown-menu *clrIfOpen>
<a
clrVerticalNavLink
routerLink="/home/tables"
routerLinkActive="active"
>Tables</a
>
<a clrVerticalNavLink routerLink="/home/files" routerLinkActive="active"
>Files</a
>
</clr-dropdown-menu>
</clr-dropdown>
</div>
<div class="nav-divider"></div> <div class="nav-divider"></div>

View File

@ -1,4 +1,22 @@
<label *ngIf="label" class="clr-control-label">{{ label }}</label> <label
*ngIf="label"
[class.secondLabelActive]="secondLabel && secondLabel.length > 0"
class="clr-control-label"
>
<span
(click)="onChangeLabel('first')"
[class.value-type-selected]="labelSelected === 'first'"
>{{ label }}</span
>
<ng-container *ngIf="secondLabel">
/
<span
(click)="onChangeLabel('second')"
[class.value-type-selected]="labelSelected === 'second'"
>{{ secondLabel }}</span
>
</ng-container>
</label>
<ng-container [ngSwitch]="type"> <ng-container [ngSwitch]="type">
<ng-container *ngSwitchCase="'date'"> <ng-container *ngSwitchCase="'date'">
<clr-date-container> <clr-date-container>

View File

@ -28,4 +28,12 @@ clr-date-container {
margin-top: -5px; margin-top: -5px;
} }
} }
}
label.secondLabelActive span {
&:not(.value-type-selected) {
text-decoration: line-through;
cursor: pointer;
opacity: 0.6;
}
} }

View File

@ -18,6 +18,7 @@ import { OnLoadingMoreEvent } from '../autocomplete/autocomplete.component'
export class SoftSelectComponent implements OnInit, OnChanges { export class SoftSelectComponent implements OnInit, OnChanges {
@Input() inputId: string = '' @Input() inputId: string = ''
@Input() label: string | undefined @Input() label: string | undefined
@Input() secondLabel: string | undefined
@Input() value: Date | string | null = '' @Input() value: Date | string | null = ''
@Input() disabled: boolean = false @Input() disabled: boolean = false
@Input() type: string = 'text' @Input() type: string = 'text'
@ -30,20 +31,24 @@ export class SoftSelectComponent implements OnInit, OnChanges {
@Output() focusinInput: EventEmitter<any> = new EventEmitter() @Output() focusinInput: EventEmitter<any> = new EventEmitter()
@Output() onAutocompleteLoadingMore: EventEmitter<OnLoadingMoreEvent> = @Output() onAutocompleteLoadingMore: EventEmitter<OnLoadingMoreEvent> =
new EventEmitter() new EventEmitter()
@Output() selectedLabelChange: EventEmitter<string> = new EventEmitter()
@ViewChild('input') inputElement: any @ViewChild('input') inputElement: any
temp: Date | string | null = '' temp: Date | string | null = ''
inputFocused: boolean = false inputFocused: boolean = false
labelSelected: LabelTypes = 'first'
constructor() {} constructor() {}
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
if ( if (
changes.value && changes.value &&
changes.value.currentValue !== changes.value.previousValue changes.value.currentValue !== changes.value.previousValue
) ) {
this.valueChange.emit(changes.value.currentValue) this.valueChange.emit(changes.value.currentValue)
}
} }
ngOnInit(): void {} ngOnInit(): void {}
@ -85,4 +90,14 @@ export class SoftSelectComponent implements OnInit, OnChanges {
onFocusinInput(event: any) { onFocusinInput(event: any) {
this.focusinInput.emit(event) this.focusinInput.emit(event)
} }
onChangeLabel(label: LabelTypes) {
this.labelSelected = label
const selectedLabelText = label === 'first' ? this.label : this.secondLabel
this.selectedLabelChange.emit(selectedLabelText)
}
} }
export type LabelTypes = 'first' | 'second'

View File

@ -0,0 +1,69 @@
<div class="header-actions">
<app-loading-indicator></app-loading-indicator>
<clr-dropdown class="app-nav-dropdown">
<button class="nav-text color-white" clrDropdownToggle>
<span>{{ userName }}</span>
<span *ngIf="userName !== 'Not logged in' && isViya"
><img class="avatar-img" src="{{ getPictureUrl() }}" alt=""
/></span>
<span
class="badge badge-danger"
*ngIf="!sasjsConfig.debug"
[class.hidden]="failedReqs.length === 0"
>{{ failedReqs.length }}</span
>
<span
class="badge badge-info"
*ngIf="sasjsConfig.debug"
[class.hidden]="debugLogs.length === 0"
>{{ debugLogs.length }}</span
>
<clr-icon *ngIf="!isViya" shape="caret down"></clr-icon>
</button>
<clr-dropdown-menu clrPosition="bottom-right" *clrIfOpen>
<div #dropdownItemDebug class="debug-switch-item" clrDropdownItem>
<clr-toggle-container
class="toggle-switch"
(click)="onDebugRowClick($event, dropdownItemDebug)"
>
<clr-toggle-wrapper>
<input
id="debug-toggle1"
type="checkbox"
[(ngModel)]="sasjsConfig.debug"
(ngModelChange)="onDebugModeChange()"
clrToggle
/>
<label>Debug Mode</label>
</clr-toggle-wrapper>
</clr-toggle-container>
</div>
<a (click)="openRequestsModal()" clrDropdownItem>
<span>SAS Requests</span>
</a>
<ng-container *ngIf="!isDeployPage">
<a
target="_blank"
href="https://docs.datacontroller.io"
clrDropdownItem
>
<span class="dropdown-text">Documentation</span>
</a>
</ng-container>
<div class="separator"></div>
<a href="..." routerLink="/system" clrDropdownItem>
<span>System</span>
</a>
<a href="..." (click)="logout($event)" clrDropdownItem>
<span>Log Out</span>
<clr-icon class="clr-logout" shape="logout"></clr-icon>
</a>
<div class="copyRight">
<span>v{{ commitVer }}</span>
</div>
</clr-dropdown-menu>
</clr-dropdown>
</div>

View File

@ -1,17 +1,12 @@
// it must be a better way to read clarity variables...
//@import '../../../../node_modules/@clr/ui/src/utils/helpers.clarity';
//@import '../../../../node_modules/@clr/ui/src/color/utils/colors.clarity';
//@import '../../../../node_modules/@clr/ui/src/color/utils/contrast-cache.clarity';
//@import '../../../../node_modules/@clr/ui/src/color/utils/helpers.clarity';
//@import '../../../../node_modules/@clr/ui/src/utils/variables.clarity';
$clr-header-height: 3rem; $clr-header-height: 3rem;
$clr-near-white: #fafafa; $clr-near-white: #fafafa;
$clr-dark-gray: #565656; $clr-dark-gray: #565656;
$clr-light-gray: #eee; $clr-light-gray: #eee;
:host {
display: contents;
}
.copyRight { .copyRight {
margin-top: 10px; margin-top: 10px;

View File

@ -8,11 +8,11 @@ import { EventService } from '../../services/event.service'
import { Router } from '@angular/router' import { Router } from '@angular/router'
@Component({ @Component({
selector: 'app-user-nav-dropdown', selector: 'app-header-actions',
templateUrl: './user-nav-dropdown.component.html', templateUrl: './header-actions.component.html',
styleUrls: ['./user-nav-dropdown.component.scss'] styleUrls: ['./header-actions.component.scss']
}) })
export class UserNavDropdownComponent implements OnInit, OnDestroy { export class HeaderActions implements OnInit, OnDestroy {
public userName: string = 'Not logged in' public userName: string = 'Not logged in'
private reqSub: Subscription = new Subscription() private reqSub: Subscription = new Subscription()
private userSub: Subscription = new Subscription() private userSub: Subscription = new Subscription()

View File

@ -1,121 +0,0 @@
<clr-dropdown class="app-nav-dropdown d-md-block">
<button class="nav-text color-white" clrDropdownToggle>
<span>{{ userName }}</span>
<span *ngIf="userName !== 'Not logged in' && isViya"
><img class="avatar-img" src="{{ getPictureUrl() }}" alt=""
/></span>
<span
class="badge badge-danger"
*ngIf="!sasjsConfig.debug"
[class.hidden]="failedReqs.length === 0"
>{{ failedReqs.length }}</span
>
<span
class="badge badge-info"
*ngIf="sasjsConfig.debug"
[class.hidden]="debugLogs.length === 0"
>{{ debugLogs.length }}</span
>
<clr-icon *ngIf="!isViya" shape="caret down"></clr-icon>
</button>
<clr-dropdown-menu clrPosition="bottom-right" *clrIfOpen>
<div #dropdownItemDebug class="debug-switch-item" clrDropdownItem>
<clr-toggle-container
class="toggle-switch"
(click)="onDebugRowClick($event, dropdownItemDebug)"
>
<clr-toggle-wrapper>
<input
id="debug-toggle1"
type="checkbox"
[(ngModel)]="sasjsConfig.debug"
(ngModelChange)="onDebugModeChange()"
clrToggle
/>
<label>Debug Mode</label>
</clr-toggle-wrapper>
</clr-toggle-container>
</div>
<a (click)="openRequestsModal()" clrDropdownItem>
<span>SAS Requests</span>
</a>
<ng-container *ngIf="!isDeployPage">
<a target="_blank" href="https://docs.datacontroller.io" clrDropdownItem>
<span class="dropdown-text">Documentation</span>
</a>
</ng-container>
<div class="separator"></div>
<a href="..." routerLink="/system" clrDropdownItem>
<span>System</span>
</a>
<a href="..." (click)="logout($event)" clrDropdownItem>
<span>Log Out</span>
<clr-icon class="clr-logout" shape="logout"></clr-icon>
</a>
<div class="copyRight">
<span>v{{ commitVer }}</span>
</div>
</clr-dropdown-menu>
</clr-dropdown>
<div class="content-container h-auto">
<nav class="sidenav d-block d-md-none" [clr-nav-level]="2">
<section class="sidenav-content">
<a href="..." class="nav-link active">
{{ userName }}
</a>
<div>
<form>
<div class="toggle-switch">
<input
id="debug-toggle2"
type="checkbox"
[(ngModel)]="sasjsConfig.debug"
(ngModelChange)="onDebugModeChange()"
[ngModelOptions]="{ standalone: true }"
/>
<label
for="debug-toggle2"
class="debug-toggle-label color-dark-gray"
>Debug Mode</label
>
</div>
</form>
</div>
<!-- <a href="..." class="nav-link d-block" [routerLink]="['/application-logs']">
<span>Application Logs</span>
<span class="badge" *ngIf="appLogs.length > 0">{{appLogs.length}}</span>
</a>
<a *ngIf="debugMode" class="nav-link d-block" href="..." [routerLink]="['/debug-logs']">
<span>Debug Logs</span>
<span class="badge badge-info" *ngIf="debugLogs.length > 0">{{debugLogs.length}}</span>
</a>
<a *ngIf="!debugMode" class="nav-link d-block" href="..." [routerLink]="['/failed-requests']">
<span>Failed Requests</span>
<span class="badge badge-danger" *ngIf="failedReqs.length > 0">{{failedReqs.length}}</span>
</a>
<a href="..." class="nav-link d-block" [routerLink]="['/errors']">
<span>Errors</span>
<span class="badge badge-warning" *ngIf="sasErrors.length > 0">{{sasErrors.length}}</span>
</a> -->
<a
class="nav-link d-block"
target="_blank"
href="https://docs.datacontroller.io"
>
<span>Documentation</span>
</a>
<div class="separator"></div>
<a routerLink="/system" class="nav-link d-block">
<span>System</span>
<clr-icon shape="logout"></clr-icon>
</a>
<a href="..." class="nav-link d-block" (click)="logout($event)">
<span>Log Out</span>
<clr-icon shape="logout"></clr-icon>
</a>
</section>
</nav>
</div>

View File

@ -13,7 +13,7 @@
class="licence-notice" class="licence-notice"
>To unlock more then {{ licenceState.value.viewbox_limit }} >To unlock more then {{ licenceState.value.viewbox_limit }}
{{ licenceState.value.viewbox_limit === 1 ? 'viewbox' : 'viewboxes' }}, {{ licenceState.value.viewbox_limit === 1 ? 'viewbox' : 'viewboxes' }},
contact support@datacontroller.io</span contact support&#64;datacontroller.io</span
> >
</h3> </h3>

View File

@ -58,34 +58,58 @@
<div class="mt-20"> <div class="mt-20">
<div class="row"> <div class="row">
<button <button
class="btn btn-sm btn-outline text-center mt-20" class="btn btn-sm btn-outline text-center mr-5i"
(click)="viewerTableScreen()" (click)="viewerTableScreen()"
[disabled]="revertingChanges"
> >
Go to base table screen View base table
</button> </button>
<button <button
*ngIf="!(tableDetails?.['ALLOW_RESTORE'] === 'YES')"
id="approval-btn" id="approval-btn"
class="btn btn-sm btn-success-outline text-center mt-20" class="btn btn-sm btn-success-outline text-center mr-5i"
[disabled]=" [disabled]="
tableDetails?.REVIEW_STATUS_ID === 'APPROVED' || tableDetails?.REVIEW_STATUS_ID === 'APPROVED' ||
tableDetails?.REVIEW_STATUS_ID === 'REJECTED' tableDetails?.REVIEW_STATUS_ID === 'REJECTED'
" "
(click)="approveTableScreen()" (click)="approveTableScreen()"
[disabled]="revertingChanges"
> >
Go to approvals screen Approve
</button> </button>
<button <button
class="btn btn-sm btn-info-outline text-center mt-20" class="btn btn-sm btn-info-outline text-center mr-5i"
(click)="goBack()" (click)="goBack()"
[disabled]="revertingChanges"
> >
Go back to editor Edit base table
</button> </button>
<button <button
class="btn btn-sm btn-success text-center mt-20 min-w-0" class="btn btn-sm btn-success text-center mr-5i min-w-0"
(click)="download(tableDetails?.TABLE_ID)" (click)="download(tableDetails?.TABLE_ID)"
> >
<clr-icon shape="download"></clr-icon> <clr-icon shape="download"></clr-icon>
</button> </button>
<clr-tooltip>
<button
*ngIf="tableDetails?.['ALLOW_RESTORE'] === 'YES'"
(click)="revertChanges()"
clrTooltipTrigger
[clrLoading]="revertingChanges"
class="btn btn-sm btn-danger text-center mt-20"
>
REVERT
<clr-tooltip-content
clrPosition="bottom-left"
clrSize="lg"
*clrIfOpen
>
<span> Revert this and all subsequent changes </span>
</clr-tooltip-content>
</button>
</clr-tooltip>
</div> </div>
</div> </div>
</div> </div>

View File

@ -4,9 +4,10 @@ import { Router } from '@angular/router'
import { ActivatedRoute } from '@angular/router' import { ActivatedRoute } from '@angular/router'
import { SasService } from '../services/sas.service' import { SasService } from '../services/sas.service'
import { EventService } from '../services/event.service' import { EventService } from '../services/event.service'
import { AppService } from '../services/app.service'
import { HotTableInterface } from '../models/HotTable.interface' import { HotTableInterface } from '../models/HotTable.interface'
import { LicenceService } from '../services/licence.service' import { LicenceService } from '../services/licence.service'
import { globals } from '../_globals'
import { EditorsRestoreServiceResponse } from '../models/sas/editors-restore.model'
@Component({ @Component({
selector: 'app-stage', selector: 'app-stage',
@ -22,6 +23,7 @@ export class StageComponent implements OnInit {
public keysArray: any public keysArray: any
public tableDetails: any public tableDetails: any
public loaded: boolean = false public loaded: boolean = false
public revertingChanges: boolean = false
public licenceState = this.licenceService.licenceState public licenceState = this.licenceService.licenceState
public hotTable: HotTableInterface = { public hotTable: HotTableInterface = {
data: [], data: [],
@ -55,7 +57,15 @@ export class StageComponent implements OnInit {
} }
public goBack() { public goBack() {
this.route.navigateByUrl('/editor/' + this.tableDetails.BASE_TABLE) const xlmap = globals.xlmaps.find(
(xlmap) => xlmap.targetDS === this.tableDetails.BASE_TABLE
)
if (xlmap) {
const id = this.hotTable.data[0].XLMAP_ID
this.route.navigateByUrl('/home/files/' + id)
} else {
this.route.navigateByUrl('/editor/' + this.tableDetails.BASE_TABLE)
}
} }
public download(id: any) { public download(id: any) {
@ -153,6 +163,31 @@ export class StageComponent implements OnInit {
} }
} }
revertChanges() {
this.revertingChanges = true
const data = {
restore_in: [
{
load_ref: this.table_id
}
]
}
this.sasService
.request('editors/restore', data)
.then((res: EditorsRestoreServiceResponse) => {
if (res.restore_out) {
this.route.navigate([`/stage`]).then(() => {
this.route.navigate([`/stage/${res.restore_out[0].LOADREF}`])
})
}
})
.finally(() => {
this.revertingChanges = false
})
}
private setFocus() { private setFocus() {
setTimeout(() => { setTimeout(() => {
let approvalBtn: any = window.document.getElementById('approval-btn') let approvalBtn: any = window.document.getElementById('approval-btn')

View File

@ -9,43 +9,45 @@
class="sys-info d-flex clr-justify-content-center clr-flex-column clr-flex-lg-row" class="sys-info d-flex clr-justify-content-center clr-flex-column clr-flex-lg-row"
> >
<div> <div>
<h6 class="m-0">Environment Details <span class="dark"></span></h6> <h6 cds-text="subsection" class="mb-10">
<p class="m-0"> Environment Details <span class="dark"></span>
</h6>
<p cds-text="label" class="m-0">
SYSSITE: <span class="dark">{{ environmentInfo?.SYSSITE }}</span> SYSSITE: <span class="dark">{{ environmentInfo?.SYSSITE }}</span>
</p> </p>
<p class="m-0"> <p cds-text="label" class="m-0">
SYSSCPL: <span class="dark">{{ environmentInfo?.SYSSCPL }}</span> SYSSCPL: <span class="dark">{{ environmentInfo?.SYSSCPL }}</span>
</p> </p>
<p class="m-0"> <p cds-text="label" class="m-0">
SYSTCPIPHOSTNAME: SYSTCPIPHOSTNAME:
<span class="dark">{{ environmentInfo?.SYSTCPIPHOSTNAME }}</span> <span class="dark">{{ environmentInfo?.SYSTCPIPHOSTNAME }}</span>
</p> </p>
<p class="m-0"> <p cds-text="label" class="m-0">
SYSVLONG: <span class="dark">{{ environmentInfo?.SYSVLONG }}</span> SYSVLONG: <span class="dark">{{ environmentInfo?.SYSVLONG }}</span>
</p> </p>
<p class="m-0"> <p cds-text="label" class="m-0">
MEMSIZE: <span class="dark">{{ environmentInfo?.MEMSIZE }}</span> MEMSIZE: <span class="dark">{{ environmentInfo?.MEMSIZE }}</span>
</p> </p>
<p class="m-0"> <p cds-text="label" class="m-0">
SYSPROCESSMODE: SYSPROCESSMODE:
<span class="dark">{{ environmentInfo?.SYSPROCESSMODE }}</span> <span class="dark">{{ environmentInfo?.SYSPROCESSMODE }}</span>
</p> </p>
<p class="m-0"> <p cds-text="label" class="m-0">
SYSHOSTNAME: SYSHOSTNAME:
<span class="dark">{{ environmentInfo?.SYSHOSTNAME }}</span> <span class="dark">{{ environmentInfo?.SYSHOSTNAME }}</span>
</p> </p>
<p class="m-0"> <p cds-text="label" class="m-0">
SYSHOSTINFOLONG: SYSHOSTINFOLONG:
<span class="dark">{{ environmentInfo?.SYSHOSTINFOLONG }}</span> <span class="dark">{{ environmentInfo?.SYSHOSTINFOLONG }}</span>
</p> </p>
<p class="m-0"> <p cds-text="label" class="m-0">
SYSENCODING: SYSENCODING:
<span class="dark">{{ environmentInfo?.SYSENCODING }}</span> <span class="dark">{{ environmentInfo?.SYSENCODING }}</span>
</p> </p>
<p class="m-0"> <p cds-text="label" class="m-0">
AUTOEXEC: <span class="dark">{{ environmentInfo?.AUTOEXEC }}</span> AUTOEXEC: <span class="dark">{{ environmentInfo?.AUTOEXEC }}</span>
</p> </p>
<p class="m-0"> <p cds-text="label" class="m-0">
DC ADMIN GROUP: DC ADMIN GROUP:
<span class="dark">{{ environmentInfo?.DC_ADMIN_GROUP }}</span> <span class="dark">{{ environmentInfo?.DC_ADMIN_GROUP }}</span>
</p> </p>
@ -53,42 +55,44 @@
<div class="d-flex clr-justify-content-lg-center"> <div class="d-flex clr-justify-content-lg-center">
<div> <div>
<h6 class="m-0"> <h6 cds-text="subsection" class="mb-10">
Data Controller Details <span class="dark"></span> Data Controller Details <span class="dark"></span>
</h6> </h6>
<p class="m-0"> <p cds-text="label" class="m-0">
Application version: Application version:
<span class="dark">{{ appInfo.appVersion }}</span> <span class="dark">{{ appInfo.appVersion }}</span>
</p> </p>
<p class="m-0"> <p cds-text="label" class="m-0">
Build timestamp: Build timestamp:
<span class="dark">{{ appInfo.buildTimestamp }}</span> <span class="dark">{{ appInfo.buildTimestamp }}</span>
</p> </p>
<p class="m-0"> <p cds-text="label" class="m-0">
Adapter version: Adapter version:
<span class="dark">{{ appInfo.adapterVersion }}</span> <span class="dark">{{ appInfo.adapterVersion }}</span>
</p> </p>
<p class="m-0"> <p cds-text="label" class="m-0">
HTTP: <span class="dark">{{ http ? 'YES' : 'NO' }}</span> HTTP: <span class="dark">{{ http ? 'YES' : 'NO' }}</span>
</p> </p>
</div> </div>
</div> </div>
<div> <div>
<h6 class="m-0">Licence details <span class="dark"></span></h6> <h6 cds-text="subsection" class="mb-10">
<p class="m-0"> Licence details <span class="dark"></span>
</h6>
<p cds-text="label" class="m-0">
Valid until: Valid until:
<span class="dark">{{ licenceInfo?.valid_until }}</span> <span class="dark">{{ licenceInfo?.valid_until }}</span>
</p> </p>
<p class="m-0"> <p cds-text="label" class="m-0">
Users allowed: Users allowed:
<span class="dark">{{ licenceInfo?.users_allowed }}</span> <span class="dark">{{ licenceInfo?.users_allowed }}</span>
</p> </p>
<p class="m-0"> <p cds-text="label" class="m-0">
Site IDs: Site IDs:
<span class="dark">{{ licenceInfo?.site_id_multiple }}</span> <span class="dark">{{ licenceInfo?.site_id_multiple }}</span>
</p> </p>
<p class="m-0"> <p cds-text="label" class="m-0">
Free Tier: Free Tier:
<span class="dark">{{ licenceInfo?.demo ? 'YES' : 'NO' }}</span> <span class="dark">{{ licenceInfo?.demo ? 'YES' : 'NO' }}</span>
</p> </p>
@ -157,25 +161,25 @@
licenceState.value.lineage_daily_limit licenceState.value.lineage_daily_limit
}}</span> }}</span>
</p> </p>
<p class="m-0"> <p cds-text="label" class="m-0">
Viewboxes: Viewboxes:
<span class="dark">{{ <span class="dark">{{
licenceState.value.viewbox ? 'YES' : 'NO' licenceState.value.viewbox ? 'YES' : 'NO'
}}</span> }}</span>
</p> </p>
<p class="m-0"> <p cds-text="label" class="m-0">
File Upload: File Upload:
<span class="dark">{{ <span class="dark">{{
licenceState.value.fileUpload ? 'YES' : 'NO' licenceState.value.fileUpload ? 'YES' : 'NO'
}}</span> }}</span>
</p> </p>
<p class="m-0"> <p cds-text="label" class="m-0">
Edit record: Edit record:
<span class="dark">{{ <span class="dark">{{
licenceState.value.editRecord ? 'YES' : 'NO' licenceState.value.editRecord ? 'YES' : 'NO'
}}</span> }}</span>
</p> </p>
<p class="m-0"> <p cds-text="label" class="m-0">
Add record: Add record:
<span class="dark">{{ <span class="dark">{{
licenceState.value.addRecord ? 'YES' : 'NO' licenceState.value.addRecord ? 'YES' : 'NO'

View File

@ -99,15 +99,18 @@
</ng-container> </ng-container>
{{ libTable.replace('-FC', '') }} {{ libTable.replace('-FC', '') }}
</button> </button>
<clr-tooltip-content
clrPosition="bottom-right" <ng-container *ngIf="tableLocked">
clrSize="lg" <clr-tooltip-content
*clrIfOpen clrPosition="bottom-right"
> clrSize="lg"
<span *ngIf="tableLocked"> *clrIfOpen
To unlock all tables, contact support@datacontroller.io >
</span> <span>
</clr-tooltip-content> To unlock all tables, contact support&#64;datacontroller.io
</span>
</clr-tooltip-content>
</ng-container>
</clr-tooltip> </clr-tooltip>
</clr-tree-node> </clr-tree-node>
</clr-tree-node> </clr-tree-node>
@ -358,36 +361,52 @@
</section> </section>
<div class="title-col clr-col-auto clr-flex-column clr-flex-sm-row"> <div class="title-col clr-col-auto clr-flex-column clr-flex-sm-row">
<clr-icon
(click)="datasetInfo = true"
shape="info-circle"
class="is-highlight cursor-pointer"
size="24"
></clr-icon>
<clr-icon
*ngIf="tableTitle?.includes('-FC')"
shape="bolt"
class="color-yellow mt-5 mr-5"
></clr-icon>
<h3 <h3
*ngIf="tableTitle && tableTitle.length > 0" class="viewerTitle clr-flex-column d-flex clr-flex-sm-row clr-align-items-center clr-justify-content-center"
class="viewerTitle clr-flex-column d-flex clr-flex-sm-row clr-align-items-center"
> >
{{ tableTitle?.replace('-FC', '') }} <clr-tooltip class="d-flex clr-align-items-center">
<clr-icon
clrTooltipTrigger
(click)="datasetInfo = true"
shape="info-circle"
class="is-highlight cursor-pointer"
size="24"
></clr-icon>
<span *ngIf="numberOfRows !== null"> <clr-icon
({{ numberOfRows | thousandSeparator: ',' }} *ngIf="tableTitle?.includes('-FC')"
{{ numberOfRows! === 1 ? 'row' : 'rows' }}, {{ filterCols.length shape="bolt"
}}{{ filterCols.length === 1 ? ' col' : ' cols' }}) class="color-yellow mr-5"
</span> ></clr-icon>
<clr-icon <span clrTooltipTrigger *ngIf="tableTitle && tableTitle.length > 0">
(click)="reloadTableData()" {{ tableTitle?.replace('-FC', '') }}
class="refresh-table" </span>
shape="refresh"
></clr-icon> <ng-container *ngIf="this.dsNote && this.dsNote.length > 0">
<clr-tooltip-content
clrPosition="bottom-left"
clrSize="lg"
*clrIfOpen
>
{{ this.dsNote }}
</clr-tooltip-content>
</ng-container>
</clr-tooltip>
<ng-container *ngIf="tableTitle && tableTitle.length > 0">
<span *ngIf="numberOfRows !== null">
({{ numberOfRows | thousandSeparator: ',' }}
{{ numberOfRows! === 1 ? 'row' : 'rows' }}, {{ filterCols.length
}}{{ filterCols.length === 1 ? ' col' : ' cols' }})
</span>
<clr-icon
(click)="reloadTableData()"
class="refresh-table"
shape="refresh"
></clr-icon>
</ng-container>
</h3> </h3>
</div> </div>
@ -406,62 +425,34 @@
options options
</button> </button>
<clr-dropdown-menu clrPosition="bottom-right" *clrIfOpen> <clr-dropdown-menu clrPosition="bottom-right" *clrIfOpen>
<button <div (click)="newViewbox()" clrDropdownItem>
type="button"
class="btn btn-sm btn-success-outline"
(click)="newViewbox()"
clrDropdownItem
>
<clr-icon shape="view-cards"></clr-icon> <clr-icon shape="view-cards"></clr-icon>
<span>Viewboxes</span> <span>Viewboxes</span>
</button> </div>
<button <div
type="button"
class="btn btn-sm btn-success-outline"
*ngIf="tableEditExists()" *ngIf="tableEditExists()"
(click)="editTable()" (click)="editTable()"
clrDropdownItem clrDropdownItem
> >
<clr-icon shape="pencil"></clr-icon> <clr-icon shape="pencil"></clr-icon>
<span>Edit</span> <span>Edit</span>
</button> </div>
<button <div *ngIf="tableuri" (click)="goToLineage()" clrDropdownItem>
type="button"
class="btn btn-sm btn-success-outline"
*ngIf="tableuri"
(click)="goToLineage()"
clrDropdownItem
>
<clr-icon shape="switch"></clr-icon> <clr-icon shape="switch"></clr-icon>
<span>Lineage</span> <span>Lineage</span>
</button> </div>
<button <div (click)="openQb()" clrDropdownItem>
type="button"
class="btn btn-sm btn-outline btn-block"
(click)="openQb()"
clrDropdownItem
>
<clr-icon shape="filter"></clr-icon> <clr-icon shape="filter"></clr-icon>
<span>Filter</span> <span>Filter</span>
</button> </div>
<button <div (click)="openDownload = true" clrDropdownItem>
type="button"
class="btn btn-sm btn-success-outline"
(click)="openDownload = true"
clrDropdownItem
>
<clr-icon shape="download"></clr-icon> <clr-icon shape="download"></clr-icon>
<span>Download</span> <span>Download</span>
</button> </div>
<button <div (click)="showWebQuery()" clrDropdownItem>
type="button"
class="btn btn-sm btn-success-outline"
(click)="showWebQuery()"
clrDropdownItem
>
<clr-icon shape="download-cloud"></clr-icon> <clr-icon shape="download-cloud"></clr-icon>
<span>Web Query URL</span> <span>Web Query URL</span>
</button> </div>
</clr-dropdown-menu> </clr-dropdown-menu>
</clr-dropdown> </clr-dropdown>
</div> </div>
@ -630,6 +621,9 @@
[cells]="hotTable.cells" [cells]="hotTable.cells"
[maxRows]="hotTable.maxRows" [maxRows]="hotTable.maxRows"
[manualColumnResize]="true" [manualColumnResize]="true"
[rowHeaders]="hotTable.rowHeaders"
[rowHeaderWidth]="hotTable.rowHeaderWidth"
[rowHeights]="hotTable.rowHeights"
[licenseKey]="hotTable.licenseKey" [licenseKey]="hotTable.licenseKey"
> >
</hot-table> </hot-table>
@ -651,6 +645,12 @@
</div> </div>
</div> </div>
<app-dataset-info [(open)]="datasetInfo" [dsmeta]="dsmeta"></app-dataset-info> <app-dataset-info
[(open)]="datasetInfo"
[dsmeta]="dsmeta"
[versions]="versions"
(rowClicked)="datasetInfoModalRowClicked($event)"
>
</app-dataset-info>
<app-viewboxes [(viewboxModal)]="viewboxOpen"></app-viewboxes> <app-viewboxes [(viewboxModal)]="viewboxOpen"></app-viewboxes>

View File

@ -57,6 +57,7 @@ clr-tree-node button {
.viewerTitle { .viewerTitle {
text-align:center; text-align:center;
margin-bottom: 15px; margin-bottom: 15px;
margin-top: 16px;
} }
.dropdown-menu { .dropdown-menu {

View File

@ -25,7 +25,11 @@ import { FilterGroup, FilterQuery } from '../models/FilterQuery'
import { HotTableInterface } from '../models/HotTable.interface' import { HotTableInterface } from '../models/HotTable.interface'
import { LoggerService } from '../services/logger.service' import { LoggerService } from '../services/logger.service'
import Handsontable from 'handsontable' import Handsontable from 'handsontable'
import { $DataFormats, DSMeta } from '../models/sas/editors-getdata.model' import {
$DataFormats,
DSMeta,
Version
} from '../models/sas/editors-getdata.model'
import { mergeColsRules } from '../shared/dc-validator/utils/mergeColsRules' import { mergeColsRules } from '../shared/dc-validator/utils/mergeColsRules'
import { PublicViewtablesServiceResponse } from '../models/sas/public-viewtables.model' import { PublicViewtablesServiceResponse } from '../models/sas/public-viewtables.model'
import { PublicViewlibsServiceResponse } from '../models/sas/public-viewlibs.model' import { PublicViewlibsServiceResponse } from '../models/sas/public-viewlibs.model'
@ -95,6 +99,8 @@ export class ViewerComponent implements AfterContentInit, AfterViewInit {
public $dataFormats: $DataFormats | null = null public $dataFormats: $DataFormats | null = null
public datasetInfo: boolean = false public datasetInfo: boolean = false
public dsmeta: DSMeta[] = [] public dsmeta: DSMeta[] = []
public versions: Version[] = []
public dsNote = ''
public licenceState = this.licenceService.licenceState public licenceState = this.licenceService.licenceState
public Infinity = Infinity public Infinity = Infinity
@ -108,6 +114,11 @@ export class ViewerComponent implements AfterContentInit, AfterViewInit {
settings: {}, settings: {},
afterGetColHeader: undefined, afterGetColHeader: undefined,
licenseKey: undefined, licenseKey: undefined,
rowHeaders: (index: number) => {
return ' '
},
rowHeaderWidth: 15,
rowHeights: 20,
contextMenu: ['copy_with_column_headers', 'copy_column_headers_only'], contextMenu: ['copy_with_column_headers', 'copy_column_headers_only'],
copyPaste: { copyPaste: {
copyColumnHeaders: true, copyColumnHeaders: true,
@ -241,6 +252,8 @@ export class ViewerComponent implements AfterContentInit, AfterViewInit {
this.hotTable.data = res.viewdata this.hotTable.data = res.viewdata
this.$dataFormats = res.$viewdata this.$dataFormats = res.$viewdata
this.dsmeta = res.dsmeta this.dsmeta = res.dsmeta
this.versions = res.versions || []
this.setDSNote()
this.numberOfRows = res.sasparams[0].NOBS this.numberOfRows = res.sasparams[0].NOBS
this.queryText = res.sasparams[0].FILTER_TEXT this.queryText = res.sasparams[0].FILTER_TEXT
this.headerPks = res.sasparams[0].PK_FIELDS.split(' ') this.headerPks = res.sasparams[0].PK_FIELDS.split(' ')
@ -798,6 +811,8 @@ export class ViewerComponent implements AfterContentInit, AfterViewInit {
this.hotTable.data = res.viewdata this.hotTable.data = res.viewdata
this.$dataFormats = res.$viewdata this.$dataFormats = res.$viewdata
this.dsmeta = res.dsmeta this.dsmeta = res.dsmeta
this.versions = res.versions || []
this.setDSNote()
this.queryText = res.sasparams[0].FILTER_TEXT this.queryText = res.sasparams[0].FILTER_TEXT
let columns: any[] = [] let columns: any[] = []
let colArr = [] let colArr = []
@ -1011,6 +1026,32 @@ export class ViewerComponent implements AfterContentInit, AfterViewInit {
this.sasStoreService.removeClause() this.sasStoreService.removeClause()
} }
public datasetInfoModalRowClicked(value: Version | DSMeta) {
if ((<Version>value).LOAD_REF !== undefined) {
// Type is Version
const row = value as Version
const url = `/stage/${row.LOAD_REF}`
this.router.navigate([url])
}
}
private setDSNote() {
const notes = this.dsmeta.find((item) => item.NAME === 'NOTES')
const longDesc = this.dsmeta.find((item) => item.NAME === 'DD_LONGDESC')
const shortDesc = this.dsmeta.find((item) => item.NAME === 'DD_SHORTDESC')
if (notes && notes.VALUE) {
this.dsNote = notes.VALUE
} else if (longDesc && longDesc.VALUE) {
this.dsNote = longDesc.VALUE
} else if (shortDesc && shortDesc.VALUE) {
this.dsNote = shortDesc.VALUE
} else {
this.dsNote = ''
}
}
private setupHot() { private setupHot() {
setTimeout(() => { setTimeout(() => {
if (!this.loadingTableView && this.libDataset) { if (!this.loadingTableView && this.libDataset) {

View File

@ -0,0 +1,159 @@
import {
extractRowAndCol,
getCellAddress,
getFinishingCell,
isBlankRow
} from '../utils/xl.utils'
describe('isBlankRow', () => {
it('should return true for a blank row', () => {
const blankRow = { __rowNum__: 1 }
expect(isBlankRow(blankRow)).toBeTrue()
})
it('should return false for a non-blank row', () => {
const nonBlankRow = {
B: 3,
C: 'some value',
D: -203
}
expect(isBlankRow(nonBlankRow)).toBeFalse()
})
})
describe('extractRowAndCol', () => {
it('should extract row and column from "MATCH F R[2]C[0]: CASH BALANCE"', () => {
const input = 'MATCH F R[2]C[0]: CASH BALANCE'
const result = extractRowAndCol(input)
expect(result).toEqual({ row: 2, column: 0 })
})
it('should extract row and column from "RELATIVE R[10]C[6]"', () => {
const input = 'RELATIVE R[10]C[6]'
const result = extractRowAndCol(input)
expect(result).toEqual({ row: 10, column: 6 })
})
it('should return null for invalid input', () => {
const invalidInput = 'INVALID INPUT'
const result = extractRowAndCol(invalidInput)
expect(result).toBeNull()
})
})
describe('getCellAddress', () => {
const arrayOfObjects = [
{ A: 'valueA1', B: 'valueB1' },
{ A: 'valueA2', B: 'valueB2' }
]
it('should convert "ABSOLUTE D8" to A1-style address', () => {
const input = 'ABSOLUTE D8'
const result = getCellAddress(input, arrayOfObjects)
expect(result).toBe('D8')
})
it('should convert "RELATIVE R[10]C[6]" to A1-style address', () => {
const input = 'RELATIVE R[10]C[6]'
const result = getCellAddress(input, arrayOfObjects)
expect(result).toBe('F10')
})
it('should convert "MATCH 1 R[0]C[0]:valueA1" to A1-style address', () => {
const input = 'MATCH 1 R[0]C[0]:valueA1'
const result = getCellAddress(input, arrayOfObjects)
expect(result).toBe('A1')
})
it('should convert "MATCH A R[0]C[0]:valueA1" to A1-style address', () => {
const input = 'MATCH A R[0]C[0]:valueA1'
const result = getCellAddress(input, arrayOfObjects)
expect(result).toBe('A1')
})
it('should convert "MATCH 1 R[1]C[0]:valueA1" to A1-style address', () => {
const input = 'MATCH 1 R[1]C[0]:valueA1'
const result = getCellAddress(input, arrayOfObjects)
expect(result).toBe('A2')
})
it('should convert "MATCH A R[0]C[1]:valueA1" to A1-style address', () => {
const input = 'MATCH A R[0]C[1]:valueA1'
const result = getCellAddress(input, arrayOfObjects)
expect(result).toBe('B1')
})
it('should convert "MATCH 1 R[1]C[1]:valueA1" to A1-style address', () => {
const input = 'MATCH 1 R[1]C[1]:valueA1'
const result = getCellAddress(input, arrayOfObjects)
expect(result).toBe('B2')
})
it('should convert "MATCH A R[1]C[1]:valueA1" to A1-style address', () => {
const input = 'MATCH A R[1]C[1]:valueA1'
const result = getCellAddress(input, arrayOfObjects)
expect(result).toBe('B2')
})
})
describe('getFinishingCell', () => {
const arrayOfObjects = [
{ A: 'valueA1', B: 'valueB1' },
{ A: 'valueA2', B: 'valueB2' },
{ A: 'valueA3', B: 'valueB3' },
{ B: 'valueB4' },
{ A: 'valueA5' },
{ A: 'valueA6', B: 'valueB6' },
{},
{ A: 'valueA8' }
]
it('should return the start cell if finish is an empty string', () => {
const start = 'A1'
const finish = ''
const result = getFinishingCell(start, finish, arrayOfObjects)
expect(result).toBe(start)
})
it('should convert "ABSOLUTE D8" to A1-style address', () => {
const start = 'A1'
const finish = 'ABSOLUTE D8'
const result = getFinishingCell(start, finish, arrayOfObjects)
expect(result).toBe('D8')
})
it('should convert "RELATIVE R[2]C[1]" to A1-style address', () => {
const start = 'A1'
const finish = 'RELATIVE R[2]C[1]'
const result = getFinishingCell(start, finish, arrayOfObjects)
expect(result).toBe('B3')
})
it('should convert "MATCH A R[0]C[1]:valueA1" to A1-style address', () => {
const start = 'A1'
const finish = 'MATCH A R[0]C[1]:valueA1'
const result = getFinishingCell(start, finish, arrayOfObjects)
expect(result).toBe('B1')
})
it('should convert "MATCH 1 R[4]C[0]:valueB1" to A1-style address', () => {
const start = 'A1'
const finish = 'MATCH 1 R[4]C[0]:valueB1'
const result = getFinishingCell(start, finish, arrayOfObjects)
expect(result).toBe('B5')
})
it('should convert "LASTDOWN" to A1-style address of the last non-blank cell in column A', () => {
const start = 'A1'
const finish = 'LASTDOWN'
const result = getFinishingCell(start, finish, arrayOfObjects)
expect(result).toBe('A3')
})
it('should convert "BLANKROW" to A1-style address of the last row with blank cells', () => {
const start = 'A1'
const finish = 'BLANKROW'
const result = getFinishingCell(start, finish, arrayOfObjects)
expect(result).toBe('B6')
})
})

View File

@ -0,0 +1,31 @@
export const blobToFile = (blob: Blob, fileName: string): File => {
const file = new File([blob], fileName, {
lastModified: new Date().getTime()
})
return file
}
/**
* Convert an array of bytes (Uint8Array) to a binary string.
* @param {Uint8Array} res - The array of bytes to convert.
* @returns {string} The binary string representation of the array of bytes.
*/
export const byteArrayToBinaryString = (res: Uint8Array): string => {
// Create a Uint8Array from the input array (if it's not already)
const bytes = new Uint8Array(res)
// Initialize an empty string to store the binary representation
let binary = ''
// Get the length of the byte array
const length = bytes.byteLength
// Iterate through each byte in the array
for (let i = 0; i < length; i++) {
// Convert each byte to its binary representation and append to the string
binary += String.fromCharCode(bytes[i])
}
// Return the binary string
return binary
}

View File

@ -0,0 +1,225 @@
import * as XLSX from '@sheet/crypto'
/**
* Checks if an excel row is blank or not
*
* @param row object is of shape {[key: string]: any}
*/
export const isBlankRow = (row: any) => {
for (const key in row) {
if (key !== '__rowNum__') {
return false
}
}
return true
}
/**
* Extracts row and column number from xlmap rule.
*
* Input string should be in form of
* either "MATCH F R[2]C[0]: CASH BALANCE" or "RELATIVE R[10]C[6]"
*/
export const extractRowAndCol = (str: string) => {
// Regular expression to match and capture the values inside square brackets
const regex = /R\[(\d+)\]C\[(\d+)\]/
// Match the regular expression against the input string
const match = str.match(regex)
if (!match) {
return null
}
// Extract values from the match groups
const row = parseInt(match[1], 10)
const column = parseInt(match[2], 10)
return {
row,
column
}
}
/**
* Generate an A1-Style excel cell address from xlmap rule.
*
* Expect "ABSOLUTE D8" or "RELATIVE R[10]C[6]" or
* "MATCH C R[0]C[4]:Common Equity Tier 1 (CET1)" kinds of string as rule input
*/
export const getCellAddress = (rule: string, arrayOfObjects: any[]) => {
if (rule.startsWith('ABSOLUTE ')) {
rule = rule.replace('ABSOLUTE ', '')
}
if (rule.startsWith('RELATIVE ')) {
const rowAndCol = extractRowAndCol(rule)
if (rowAndCol) {
const { row, column } = rowAndCol
// Generate an A1-Style address string from a SheetJS cell address
// Spreadsheet applications typically display ordinal row numbers,
// where 1 is the first row, 2 is the second row, etc. The numbering starts at 1.
// SheetJS follows JavaScript counting conventions,
// where 0 is the first row, 1 is the second row, etc. The numbering starts at 0.
// Therefore, we have to subtract 1 from row and column to match SheetJS indexing convention
rule = XLSX.utils.encode_cell({ r: row - 1, c: column - 1 })
}
}
if (rule.startsWith('MATCH ')) {
let targetValue = ''
// using a regular expression to match "C[x]:" and extract the value after it
const match = rule.match(/C\[\d+\]:(.+)/)
// Check if there is a match
if (match) {
// Extract the value after "C[x]:"
targetValue = match[1]
}
// Split the string by spaces to get target row/column
const splittedArray = rule.split(' ')
// Extract the second word
const secondWord = splittedArray[1]
let targetColumn = ''
let targetRow = -1
let cellAddress = ''
// Check if the secondWord is a number
if (!isNaN(Number(secondWord))) {
targetRow = parseInt(secondWord)
} else {
targetColumn = secondWord
}
if (targetRow !== -1) {
// sheetJS index starts from 0,
// therefore, decremented 1 to make it correct row address for js array
const row = arrayOfObjects[targetRow - 1]
for (const col in row) {
if (col !== '__rowNum__' && row[col] === targetValue) {
cellAddress = col + targetRow
break
}
}
} else {
for (let i = 0; i < arrayOfObjects.length; i++) {
const row = arrayOfObjects[i]
if (row[targetColumn] === targetValue) {
// sheetJS index starts from 0,
// therefore, incremented 1 to make it correct row address
const rowIndex = i + 1
cellAddress = targetColumn + rowIndex
break
}
}
}
// Converts A1 cell address to 0-indexed form
const matchedCellAddress = XLSX.utils.decode_cell(cellAddress)
// extract number of rows and columns that we have to move
// from matched cell to reach target cell
const rowAndCol = extractRowAndCol(rule)
if (rowAndCol) {
const { row, column } = rowAndCol
// Converts 0-indexed cell address to A1 form
rule = XLSX.utils.encode_cell({
r: matchedCellAddress.r + row,
c: matchedCellAddress.c + column
})
}
}
return rule
}
/**
* Generate an A1-Style excel cell address for last cell
*
* @param start A1 style excel cell address
* @param finish XLMAP_FINISH attribute of xlmap rule
* @param arrayOfObjects an array of row objects
* @returns
*/
export const getFinishingCell = (
start: string,
finish: string,
arrayOfObjects: any[]
) => {
// in this case an individual cell would be extracted
if (finish === '') {
return start
}
if (finish.startsWith('ABSOLUTE ')) {
finish = finish.replace('ABSOLUTE ', '')
}
if (finish.startsWith('RELATIVE ')) {
const rowAndCol = extractRowAndCol(finish)
if (rowAndCol) {
const { row, column } = rowAndCol
const { r, c } = XLSX.utils.decode_cell(start)
// finish is relative to starting point
// therefore, we need to add extracted row and columns
// in starting cell address to get actual finishing cell
finish = XLSX.utils.encode_cell({ r: r + row, c: c + column })
}
}
if (finish.startsWith('MATCH ')) {
finish = getCellAddress(finish, arrayOfObjects)
}
if (finish === 'LASTDOWN') {
const { r, c } = XLSX.utils.decode_cell(start)
const colName = XLSX.utils.encode_col(c)
let lastNonBlank = r
for (let i = r + 1; i < arrayOfObjects.length; i++) {
const row = arrayOfObjects[i]
if (!row[colName]) {
break
}
lastNonBlank = i
}
finish = colName + (lastNonBlank + 1) // excel numbering starts from 1. So incremented 1 to 0 based index
}
if (finish === 'BLANKROW') {
const { r } = XLSX.utils.decode_cell(start)
let lastNonBlankRow = r
for (let i = r + 1; i < arrayOfObjects.length; i++) {
const row = arrayOfObjects[i]
if (isBlankRow(row)) {
break
}
lastNonBlankRow = i
}
const row = arrayOfObjects[lastNonBlankRow]
// Get the keys of the object (excluding '__rowNum__')
const keys = Object.keys(row).filter((key) => key !== '__rowNum__')
// Finding last column in a row
// Find the key with the highest alphanumeric value (assumes keys are letters)
const lastColumn = keys.reduce(
(maxKey, currentKey) => (currentKey > maxKey ? currentKey : maxKey),
''
)
// make finishing cell address in A1 style
finish = lastColumn + (lastNonBlankRow + 1) // excel numbering starts from 1. So incremented 1 to 0 based index
}
return finish
}

View File

@ -0,0 +1,22 @@
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { XLMapComponent } from '../xlmap/xlmap.component'
import { XLMapRouteComponent } from '../routes/xlmap-route/xlmap-route.component'
const routes: Routes = [
{
path: '',
component: XLMapRouteComponent,
children: [
{ path: '', component: XLMapComponent },
{ path: ':id', component: XLMapComponent }
]
}
]
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class XLMapRoutingModule {}

View File

@ -0,0 +1,260 @@
<app-sidebar>
<div *ngIf="xlmapsLoading" class="my-10-mx-auto text-center">
<clr-spinner clrMedium></clr-spinner>
</div>
<clr-tree>
<clr-tree-node class="search-node">
<div class="tree-search-wrapper">
<input
clrInput
#searchXLMapTreeInput
placeholder="Filter by Id"
name="input"
[(ngModel)]="searchString"
(keyup)="xlmapListOnFilter()"
autocomplete="off"
/>
<clr-icon
*ngIf="searchXLMapTreeInput.value.length < 1"
shape="search"
></clr-icon>
<clr-icon
*ngIf="searchXLMapTreeInput.value.length > 0"
(click)="searchString = ''; xlmapListOnFilter()"
shape="times"
></clr-icon>
</div>
</clr-tree-node>
<ng-container *ngFor="let xlmap of xlmaps">
<clr-tree-node>
<button
(click)="xlmapOnClick(xlmap)"
class="clr-treenode-link"
[class.table-active]="isActiveXLMap(xlmap.id)"
>
<clr-icon shape="file"></clr-icon>
{{ xlmap.id }}
</button>
</clr-tree-node>
</ng-container>
</clr-tree>
</app-sidebar>
<div class="content-area">
<div *ngIf="!selectedXLMap" class="no-table-selected">
<clr-icon
shape="warning-standard"
size="60"
class="is-info icon-dc-fill"
></clr-icon>
<h3 *ngIf="xlmaps.length > 0" class="text-center color-gray">
Please select a map
</h3>
<h3 *ngIf="xlmaps.length < 1" class="text-center color-gray">
No excel map is found
</h3>
</div>
<div class="loadingSpinner" *ngIf="isLoading">
<span class="spinner"> Loading... </span>
<div>
<h4>{{ isLoadingDesc }}</h4>
</div>
</div>
<div
appDragNdrop
(fileDraggedOver)="onShowUploadModal()"
class="card h-100 d-flex clr-flex-column"
*ngIf="!isLoading && selectedXLMap"
>
<clr-tabs>
<clr-tab>
<button clrTabLink (click)="selectedTab = TabsEnum.Rules">Rules</button>
<clr-tab-content *clrIfActive="selectedTab === TabsEnum.Rules">
</clr-tab-content>
</clr-tab>
<clr-tab>
<button clrTabLink (click)="selectedTab = TabsEnum.Data">Data</button>
<clr-tab-content *clrIfActive="selectedTab === TabsEnum.Data">
</clr-tab-content>
</clr-tab>
</clr-tabs>
<ng-container *ngTemplateOutlet="actionButtons"></ng-container>
<div class="clr-row m-0 mb-10-i viewerTitle">
<h3 class="d-flex clr-col-12 clr-justify-content-center mt-5-i">
{{ selectedXLMap.id }}
</h3>
<i class="d-flex clr-col-12 clr-justify-content-center mt-5-i">{{
selectedXLMap.description
}}</i>
<h5 class="d-flex clr-col-12 clr-justify-content-center mt-5-i">
Rules Source:
<a
cds-text="labelLink"
class="ml-10"
[routerLink]="'/view/data/' + rulesSource"
>
{{ rulesSource }}
</a>
</h5>
<h5 class="d-flex clr-col-12 clr-justify-content-center mt-5-i">
Target dataset:
<a
cds-text="labelLink"
class="ml-10"
[routerLink]="'/view/data/' + selectedXLMap.targetDS"
>
{{ selectedXLMap.targetDS }}
</a>
</h5>
</div>
<div class="clr-flex-1">
<hot-table
hotId="hotInstance"
id="hot-table"
[multiColumnSorting]="true"
[viewportRowRenderingOffset]="50"
[data]="selectedTab === TabsEnum.Rules ? xlmapRules : xlData"
[colHeaders]="
selectedTab === TabsEnum.Rules ? xlmapRulesHeaders : xlUploadHeader
"
[columns]="
selectedTab === TabsEnum.Rules ? xlmapRulesColumns : xlUploadColumns
"
[filters]="true"
[height]="'100%'"
stretchH="all"
[modifyColWidth]="maxWidthChecker"
[cells]="getCellConfiguration"
[maxRows]="hotTableMaxRows"
[manualColumnResize]="true"
[rowHeaders]="rowHeaders"
[rowHeaderWidth]="15"
[rowHeights]="20"
[licenseKey]="hotTableLicenseKey"
>
</hot-table>
</div>
</div>
<clr-modal
appFileDrop
(fileOver)="fileOverBase($event)"
(fileDrop)="getFileDesc($event, true)"
[uploader]="uploader"
[clrModalSize]="'xl'"
[clrModalStaticBackdrop]="false"
[clrModalClosable]="true"
[(clrModalOpen)]="showUploadModal"
class="relative"
>
<h3 class="modal-title">Upload File</h3>
<div class="modal-body">
<div class="drop-area">
<span>Drop file anywhere to upload!</span>
</div>
<div class="clr-col-md-12">
<div class="clr-row card-block mt-15 d-flex justify-content-between">
<div class="clr-col-md-3 filterBtn">
<span class="filterBtn w-100">
<label
for="file-upload"
class="btn btn-sm btn-outline profile-buttons w-100"
>
Browse
</label>
</span>
<input
hidden
#fileUploadInput
id="file-upload"
type="file"
appFileSelect
[uploader]="uploader"
(change)="getFileDesc($event)"
/>
</div>
</div>
</div>
</div>
</clr-modal>
<clr-modal [(clrModalOpen)]="submitLimitNotice">
<h3 class="modal-title">Notice</h3>
<div class="modal-body">
<p class="m-0">
Due to current licence, only
{{ licenceState.value.submit_rows_limit }} rows in a file will be
submitted. To remove the restriction, contact
support&#64;datacontroller.io
</p>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-sm btn-primary"
(click)="submitLimitNotice = false"
>
Cancel
</button>
<button
type="button"
class="btn btn-sm btn-primary"
(click)="submit(); submitLimitNotice = false"
>
Submit
</button>
</div>
</clr-modal>
</div>
<ng-template #actionButtons>
<div class="clr-row m-0 clr-justify-content-center">
<div
*ngIf="status === StatusEnum.ReadyToUpload"
class="d-flex clr-justify-content-center clr-col-12 clr-col-lg-4"
>
<button
type="button"
class="btn btn-sm btn-success btn-block mr-0"
(click)="onShowUploadModal()"
>
<clr-icon shape="upload"></clr-icon>
<span>Upload</span>
</button>
</div>
<div
*ngIf="status === StatusEnum.ReadyToSubmit"
class="d-flex clr-justify-content-center clr-col-12 clr-col-lg-4"
>
<button
type="button"
class="btn btn-sm btn-success btn-block mr-0"
(click)="submitExcel()"
>
<clr-icon shape="upload"></clr-icon>
<span>Submit</span>
</button>
</div>
<div
*ngIf="status === StatusEnum.ReadyToSubmit"
class="d-flex clr-justify-content-center clr-col-12 clr-col-lg-4"
>
<button
type="button"
class="btn btn-sm btn-outline-danger btn-block mr-0"
(click)="discardExtractedData()"
>
<clr-icon shape="times"></clr-icon>
<span>Discard</span>
</button>
</div>
</div>
</ng-template>

View File

@ -0,0 +1,77 @@
.card {
margin-top: 0;
flex: 1;
display: flex;
flex-direction: column;
}
clr-tree-node button {
white-space: nowrap;
}
.no-table-selected {
position: relative;
}
.header-row {
.title-col {
display: flex;
align-items: center;
}
.options-col {
display: flex;
justify-content: flex-end;
}
}
.sw {
margin: 1rem 0rem 0.5rem 1rem;
}
.viewerTitle {
text-align: center;
}
.cardFlex {
display: flex;
justify-content: center;
}
.content-area {
padding: 0.5rem !important;
display: flex;
flex-direction: column;
}
hot-table {
::ng-deep {
.primaryKeyHeaderStyle {
background: #306b006e;
}
}
}
.drop-area {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
display: flex;
justify-content: center;
margin: 1px;
border: 2px dashed #fff;
z-index: -1;
span {
font-size: 20px;
margin-top: 20px;
color: #fff;
}
}

View File

@ -0,0 +1,490 @@
import {
AfterContentInit,
AfterViewInit,
Component,
ElementRef,
HostBinding,
OnInit,
QueryList,
ViewChildren
} from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { UploadFile } from '@sasjs/adapter'
import * as XLSX from '@sheet/crypto'
import { XLMapListItem, globals } from '../_globals'
import { FileUploader } from '../models/FileUploader.class'
import {
EventService,
LicenceService,
LoggerService,
SasService,
SasStoreService
} from '../services'
import { getCellAddress, getFinishingCell } from './utils/xl.utils'
import { blobToFile, byteArrayToBinaryString } from './utils/file.utils'
interface XLMapRule {
XLMAP_ID: string
XLMAP_SHEET: string
XLMAP_RANGE_ID: string
XLMAP_START: string
XLMAP_FINISH: string
}
interface XLUploadEntry {
LOAD_REF: string
XLMAP_ID: string
XLMAP_RANGE_ID: string
ROW_NO: number
COL_NO: number
VALUE_TXT: string
}
enum Status {
NoMapSelected,
FetchingRules,
ReadyToUpload,
ExtractingData,
ReadyToSubmit,
SubmittingExtractedData,
Submitting
}
enum Tabs {
Rules,
Data
}
@Component({
selector: 'app-xlmap',
templateUrl: './xlmap.component.html',
styleUrls: ['./xlmap.component.scss']
})
export class XLMapComponent implements AfterContentInit, AfterViewInit, OnInit {
@HostBinding('class.content-container') contentContainerClass = true
@ViewChildren('fileUploadInput')
fileUploadInputCompList: QueryList<ElementRef> = new QueryList()
StatusEnum = Status
TabsEnum = Tabs
public selectedTab = Tabs.Rules
public rulesSource = globals.dcLib + '.MPE_XLMAP_RULES'
public xlmaps: XLMapListItem[] = []
public selectedXLMap: XLMapListItem | undefined = undefined
public searchString = ''
public xlmapsLoading = true
public isLoading = false
public isLoadingDesc = ''
public status = Status.NoMapSelected
public xlmapRulesHeaders = [
'XLMAP_SHEET',
'XLMAP_RANGE_ID',
'XLMAP_START',
'XLMAP_FINISH'
]
public xlmapRulesColumns = [
{
data: 'XLMAP_SHEET'
},
{
data: 'XLMAP_RANGE_ID'
},
{
data: 'XLMAP_START'
},
{
data: 'XLMAP_FINISH'
}
]
public xlmapRules: XLMapRule[] = []
public xlUploadHeader = ['XLMAP_RANGE_ID', 'ROW_NO', 'COL_NO', 'VALUE_TXT']
public xlUploadColumns = [
{
data: 'XLMAP_RANGE_ID'
},
{
data: 'ROW_NO'
},
{
data: 'COL_NO'
},
{
data: 'VALUE_TXT'
}
]
public xlData: XLUploadEntry[] = []
public showUploadModal = false
public hasBaseDropZoneOver = false
public filename = ''
public submitLimitNotice = false
public uploader: FileUploader = new FileUploader()
public licenceState = this.licenceService.licenceState
public hotTableLicenseKey: string | undefined = undefined
public hotTableMaxRows =
this.licenceState.value.viewer_rows_allowed || Infinity
constructor(
private eventService: EventService,
private licenceService: LicenceService,
private loggerService: LoggerService,
private route: ActivatedRoute,
private router: Router,
private sasStoreService: SasStoreService,
private sasService: SasService
) {}
public xlmapOnClick(xlmap: XLMapListItem) {
if (xlmap.id !== this.selectedXLMap?.id) {
this.selectedXLMap = xlmap
this.xlData = []
this.filename = ''
this.uploader.queue = []
if (this.fileUploadInputCompList.first) {
this.fileUploadInputCompList.first.nativeElement.value = ''
}
this.selectedTab = Tabs.Rules
this.viewXLMapRules()
this.router.navigateByUrl('/home/files/' + xlmap.id)
}
}
public xlmapListOnFilter() {
if (this.searchString.length > 0) {
const array: XLMapListItem[] = globals.xlmaps
this.xlmaps = array.filter((item) =>
item.id.toLowerCase().includes(this.searchString.toLowerCase())
)
} else {
this.xlmaps = globals.xlmaps
}
}
public isActiveXLMap(id: string) {
return this.selectedXLMap?.id === id
}
public maxWidthChecker(width: any, col: any) {
if (width > 200) return 200
else return width
}
public getCellConfiguration() {
return { readOnly: true }
}
public rowHeaders() {
return ' '
}
public onShowUploadModal() {
this.showUploadModal = true
}
/**
* Called by FileDropDirective
* @param e true if file is dragged over the drop zone
*/
public fileOverBase(e: boolean): void {
this.hasBaseDropZoneOver = e
}
public getFileDesc(event: any, dropped = false) {
const file = dropped ? event[0] : event.target.files[0]
if (!file) return
const filename = file.name
this.filename = filename
const fileType = filename.slice(
filename.lastIndexOf('.') + 1,
filename.lastIndexOf('.') + 4
)
if (fileType.toLowerCase() === 'xls') {
this.showUploadModal = false
this.isLoading = true
this.isLoadingDesc = 'Extracting Data'
this.status = Status.ExtractingData
const reader = new FileReader()
reader.onload = async (theFile: any) => {
/* read workbook */
const bstr = byteArrayToBinaryString(theFile.target.result)
let wb: XLSX.WorkBook | undefined = undefined
const xlsxOptions: XLSX.ParsingOptions = {
type: 'binary',
cellDates: false,
cellFormula: true,
cellStyles: true,
cellNF: false,
cellText: false
}
try {
wb = XLSX.read(bstr, {
...xlsxOptions
})
} catch (err: any) {
this.eventService.showAbortModal(
null,
err,
undefined,
'Error reading file'
)
}
if (!wb) {
this.isLoading = false
this.isLoadingDesc = ''
this.status = Status.ReadyToUpload
this.uploader.queue.pop()
return
}
this.extractData(wb)
return
}
reader.readAsArrayBuffer(file)
} else {
this.isLoading = false
this.isLoadingDesc = ''
this.status = Status.ReadyToUpload
this.showUploadModal = true
this.uploader.queue.pop()
const abortMsg =
'Invalid file type "<b>' +
this.filename +
'</b>". Please upload excel file.'
this.eventService.showAbortModal(null, abortMsg)
}
}
public discardExtractedData() {
this.isLoading = false
this.isLoadingDesc = ''
this.status = Status.ReadyToUpload
this.xlData = []
this.selectedTab = Tabs.Rules
this.filename = ''
this.uploader.queue = []
if (this.fileUploadInputCompList.first) {
this.fileUploadInputCompList.first.nativeElement.value = ''
}
}
/**
* Submits attached excel file that is in preview mode
*/
public submitExcel() {
if (this.licenceState.value.submit_rows_limit !== Infinity) {
this.submitLimitNotice = true
return
}
this.submit()
}
public submit() {
if (!this.selectedXLMap || !this.xlData.length) return
this.status = Status.Submitting
this.isLoading = true
this.isLoadingDesc = 'Submitting extracted data'
const filesToUpload: UploadFile[] = []
for (const file of this.uploader.queue) {
filesToUpload.push({
file: file,
fileName: file.name
})
}
const csvContent =
Object.keys(this.xlData[0]).join(',') +
'\n' +
this.xlData
.slice(0, this.licenceState.value.submit_rows_limit)
.map((row: any) => Object.values(row).join(','))
.join('\n')
const blob = new Blob([csvContent], { type: 'application/csv' })
const file: File = blobToFile(blob, this.filename + '.csv')
filesToUpload.push({
file: file,
fileName: file.name
})
const uploadUrl = 'services/editors/loadfile'
this.sasService
.uploadFile(uploadUrl, filesToUpload, {
table: this.selectedXLMap.targetDS
})
.then((res: any) => {
if (res.sasjsAbort) {
const abortRes = res
const abortMsg = abortRes.sasjsAbort[0].MSG
const macMsg = abortRes.sasjsAbort[0].MAC
this.eventService.showAbortModal('', abortMsg, {
SYSWARNINGTEXT: abortRes.SYSWARNINGTEXT,
SYSERRORTEXT: abortRes.SYSERRORTEXT,
MAC: macMsg
})
} else if (res.sasparams) {
const params = res.sasparams[0]
const tableId = params.DSID
this.router.navigateByUrl('/stage/' + tableId)
}
})
.catch((err: any) => {
this.eventService.catchResponseError('file upload', err)
})
.finally(() => {
this.status = Status.ReadyToSubmit
this.isLoading = false
this.isLoadingDesc = ''
})
}
public extractData(wb: XLSX.WorkBook) {
const extractedData: XLUploadEntry[] = []
this.xlmapRules.forEach((rule) => {
let sheetName = rule.XLMAP_SHEET
// if sheet name is not an absolute name rather an index string like /1, /2, etc
// we extract the index and find absolute sheet name for specified index
if (sheetName.startsWith('/')) {
const temp = sheetName.split('/')[1]
const sheetIndex = parseInt(temp) - 1
sheetName = wb.SheetNames[sheetIndex]
}
const sheet = wb.Sheets[sheetName]
const arrayOfObjects = <any[]>XLSX.utils.sheet_to_json(sheet, {
raw: true,
header: 'A',
blankrows: true
})
const start = getCellAddress(rule.XLMAP_START, arrayOfObjects)
const finish = getFinishingCell(start, rule.XLMAP_FINISH, arrayOfObjects)
const a1Range = `${start}:${finish}`
const range = XLSX.utils.decode_range(a1Range)
const rangedData = <any[]>XLSX.utils.sheet_to_json(sheet, {
raw: true,
range: a1Range,
header: 'A',
blankrows: true
})
for (let i = 0; i < rangedData.length; i++) {
const row = rangedData[i]
// `range.s.c` is the index of first column in the range
// `range.e.c` is the index of last column in the range
// we'll iterate from first column to last column and
// extract value where defined and push to extracted data array
for (let j = range.s.c, x = 0; j <= range.e.c; j++, x++) {
const col = XLSX.utils.encode_col(j)
if (col in row) {
// in excel's R1C1 notation indexing starts from 1 but in JS it starts from 0
// therefore, we'll have to add 1 to rows and cols
extractedData.push({
LOAD_REF: '0',
XLMAP_ID: rule.XLMAP_ID,
XLMAP_RANGE_ID: rule.XLMAP_RANGE_ID,
ROW_NO: i + 1,
COL_NO: x + 1,
VALUE_TXT: row[col]
})
}
}
}
})
this.status = Status.ReadyToSubmit
this.isLoading = false
this.isLoadingDesc = ''
this.xlData = extractedData
this.selectedTab = Tabs.Data
}
async viewXLMapRules() {
if (!this.selectedXLMap) return
this.isLoading = true
this.isLoadingDesc = 'Loading excel rules'
this.status = Status.FetchingRules
await this.sasStoreService
.getXLMapRules(this.selectedXLMap.id)
.then((res) => {
this.xlmapRules = res.xlmaprules
this.status = Status.ReadyToUpload
})
.catch((err) => {
this.loggerService.error(err)
})
this.isLoading = false
this.isLoadingDesc = ''
}
private load() {
this.xlmaps = globals.xlmaps
this.xlmapsLoading = false
const id = this.route.snapshot.params['id']
if (id) {
const xlmapListItem = this.xlmaps.find((item) => item.id === id)
if (xlmapListItem) {
this.selectedXLMap = xlmapListItem
this.viewXLMapRules()
}
}
}
ngOnInit() {
this.licenceService.hot_license_key.subscribe(
(hot_license_key: string | undefined) => {
this.hotTableLicenseKey = hot_license_key
}
)
}
ngAfterViewInit() {
return
}
ngAfterContentInit(): void {
if (globals.editor.startupSet) {
this.load()
} else {
this.eventService.onStartupDataLoaded.subscribe(() => {
this.load()
})
}
}
}

View File

@ -0,0 +1,31 @@
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 { registerAllModules } from 'handsontable/registry'
import { AppSharedModule } from '../app-shared.module'
import { DirectivesModule } from '../directives/directives.module'
import { XLMapRouteComponent } from '../routes/xlmap-route/xlmap-route.component'
import { DcTreeModule } from '../shared/dc-tree/dc-tree.module'
import { XLMapRoutingModule } from './xlmap-routing.module'
import { XLMapComponent } from './xlmap.component'
// register Handsontable's modules
registerAllModules()
@NgModule({
declarations: [XLMapRouteComponent, XLMapComponent],
imports: [
HotTableModule,
XLMapRoutingModule,
FormsModule,
ClarityModule,
AppSharedModule,
CommonModule,
DcTreeModule,
DirectivesModule
],
exports: [XLMapComponent]
})
export class XLMapModule {}

View File

@ -15,7 +15,7 @@ try {
writeFileSync( writeFileSync(
file, file,
`//IMPORTANT: THIS FILE IS AUTO GENERATED BASED ON LICENCE.MD FILE!\nexport const EULA = \`\n${licence}\n\``, `//IMPORTANT: THIS FILE IS AUTO GENERATED BASED ON LICENCE.MD FILE!\nexport const EULA = \`\n${licence}\n\`\n`,
{ encoding: 'utf-8' } { encoding: 'utf-8' }
) )

View File

@ -288,8 +288,8 @@ function resolveDateString(data, ca, component, width, key) {
resolved = hop.call(obj, width) resolved = hop.call(obj, width)
? obj[width] ? obj[width]
: hop.call(obj, alts[width][0]) : hop.call(obj, alts[width][0])
? obj[alts[width][0]] ? obj[alts[width][0]]
: obj[alts[width][1]] : obj[alts[width][1]]
// `key` wouldn't be specified for components 'dayPeriods' // `key` wouldn't be specified for components 'dayPeriods'
return key != null ? resolved[key] : resolved return key != null ? resolved[key] : resolved

View File

@ -57,7 +57,7 @@
> >
</sasjs> </sasjs>
<body class="m-0"> <body cds-theme="light" class="m-0">
<my-app></my-app> <my-app></my-app>
</body> </body>
</html> </html>

View File

@ -1,16 +1,20 @@
/* You can add global styles to this file, and also import other style files */ /* You can add global styles to this file, and also import other style files */
@import '~handsontable/dist/handsontable.full.css'; @import '~handsontable/dist/handsontable.full.css';
@import "~@clr/ui/clr-ui.min.css"; @import '~@clr/icons/clr-icons.min.css';
@import "~@clr/icons/clr-icons.min.css";
@font-face{ @import '@cds/core/global.min.css';
@import '@cds/core/styles/theme.dark.min.css';
@import '@clr/ui/clr-ui.min.css';
@font-face {
font-family: text-security-disc; font-family: text-security-disc;
src: url("https://raw.githubusercontent.com/noppa/text-security/master/dist/text-security-disc.woff"); src: url('https://raw.githubusercontent.com/noppa/text-security/master/dist/text-security-disc.woff');
} }
body, html { body,
font-weight: 400!important; html {
font-weight: 400 !important;
padding: 0; padding: 0;
margin: 0; margin: 0;
@ -28,8 +32,16 @@ button {
} }
} }
[cds-text=label] {
color: var(--cds-global-typography-color-200);
}
[cds-text=labelLink] {
line-height: 1.8 !important;
}
// Custom loading spinner // Custom loading spinner
.slider{ .slider {
position: absolute; position: absolute;
width: 320px; width: 320px;
margin-left: 75px; margin-left: 75px;
@ -38,33 +50,45 @@ button {
overflow-x: hidden; overflow-x: hidden;
} }
.line{ .line {
position:absolute; position: absolute;
opacity: 0.4; opacity: 0.4;
background:#73D544; background: #73d544;
width:150%; width: 150%;
height:5px; height: 5px;
} }
.subline{ .subline {
position:absolute; position: absolute;
background:#73D544; background: #73d544;
height:5px; height: 5px;
} }
.inc{ .inc {
animation: increase 2s infinite; animation: increase 2s infinite;
} }
.dec{ .dec {
animation: decrease 2s 0.5s infinite; animation: decrease 2s 0.5s infinite;
} }
@keyframes increase { @keyframes increase {
from { left: -5%; width: 5%; } from {
to { left: 130%; width: 100%;} left: -5%;
width: 5%;
}
to {
left: 130%;
width: 100%;
}
} }
@keyframes decrease { @keyframes decrease {
from { left: -80%; width: 80%; } from {
to { left: 110%; width: 10%;} left: -80%;
width: 80%;
}
to {
left: 110%;
width: 10%;
}
} }
// Custo loading spinner end // Custo loading spinner end
@ -248,6 +272,10 @@ button {
margin-right: 5px; margin-right: 5px;
} }
.mr-5i {
margin-right: 5px !important;
}
.mr-10 { .mr-10 {
margin-right: 10px; margin-right: 10px;
} }
@ -276,6 +304,10 @@ button {
margin-bottom: 10px; margin-bottom: 10px;
} }
.mb-10-i {
margin-bottom: 10px !important;
}
.mb-20 { .mb-20 {
margin-bottom: 20px; margin-bottom: 20px;
} }
@ -321,11 +353,11 @@ button {
} }
.color-dark-gray { .color-dark-gray {
color: #495967 color: #495967;
} }
.color-darker-gray{ .color-darker-gray {
color: #314351 color: #314351;
} }
.color-white { .color-white {
@ -333,7 +365,7 @@ button {
} }
.color-white-i { .color-white-i {
color: white !important color: white !important;
} }
.color-green { .color-green {
@ -341,15 +373,15 @@ button {
} }
.color-dc-green { .color-dc-green {
color: #81b440 color: #81b440;
} }
.color-red { .color-red {
color: #e45454 color: #e45454;
} }
.color-orange { .color-orange {
color: #E67E22; color: #e67e22;
} }
.color-blue { .color-blue {
@ -357,7 +389,7 @@ button {
} }
.color-yellow { .color-yellow {
color: #f1c40f color: #f1c40f;
} }
.cursor-pointer { .cursor-pointer {
@ -501,7 +533,7 @@ button {
} }
.z-index-highest { .z-index-highest {
z-index: 10000000 z-index: 10000000;
} }
.vertical-align-middle { .vertical-align-middle {
@ -519,35 +551,36 @@ button {
} }
.progresStatic { .progresStatic {
margin-top:-6px!important; margin-top: -6px !important;
position: absolute!important; position: absolute !important;
z-index: 10000!important; z-index: 10000 !important;
} }
.progress, .progress-static { .progress,
.progress-static {
background-color: #f5f6fe; background-color: #f5f6fe;
border-radius: 0; border-radius: 0;
font-size: inherit; font-size: inherit;
height: 6px; height: 6px;
margin: 0; margin: 0;
max-height: .583333rem; max-height: 0.583333rem;
min-height: .166667rem; min-height: 0.166667rem;
overflow: hidden; overflow: hidden;
display: block; display: block;
width: calc(100% - 63px); width: calc(100% - 63px);
} }
.progress.loop:after { .progress.loop:after {
-webkit-animation: clr-progress-looper 1.5s ease-in-out infinite; -webkit-animation: clr-progress-looper 1.5s ease-in-out infinite;
animation: clr-progress-looper 1.5s ease-in-out infinite; animation: clr-progress-looper 1.5s ease-in-out infinite;
content: " "; content: ' ';
top: .166667rem; top: 0.166667rem;
bottom: 0; bottom: 0;
left: 0; left: 0;
position: absolute; position: absolute;
display: block; display: block;
background-color: #60b515; background-color: #60b515;
width: 75%; width: 75%;
} }
// Fix for clarity bug, should be addressed when clarity is updated // Fix for clarity bug, should be addressed when clarity is updated
@ -570,9 +603,9 @@ button {
} }
.alert-app-level.alert-danger { .alert-app-level.alert-danger {
background: #D94B2E; background: #d94b2e;
color: #fff; color: #fff;
border: none; border: none;
} }
.card-header { .card-header {
@ -581,7 +614,7 @@ button {
.select select:focus { .select select:focus {
border-bottom: 1px solid #495967; border-bottom: 1px solid #495967;
background: linear-gradient(180deg,transparent 95%,#495a67 0) no-repeat; background: linear-gradient(180deg, transparent 95%, #495a67 0) no-repeat;
} }
.clr-treenode-children { .clr-treenode-children {
@ -597,7 +630,9 @@ button {
background: #d8e3e9; background: #d8e3e9;
} }
clr-select-container .clr-control-container, clr-select-container .clr-control-container .clr-select-wrapper, clr-select-container select { clr-select-container .clr-control-container,
clr-select-container .clr-control-container .clr-select-wrapper,
clr-select-container select {
width: 100%; width: 100%;
} }
@ -605,42 +640,55 @@ tbody {
font-weight: 400; font-weight: 400;
} }
h3, h4 { h3,
color: #585858; h4 {
font-weight: 400; color: #585858;
letter-spacing: normal; font-weight: 400;
line-height: 1rem; letter-spacing: normal;
margin-top: 1rem; line-height: 1rem;
margin-bottom: 0; margin-top: 1rem;
/* text-transform: uppercase; */ margin-bottom: 0;
/* text-transform: uppercase; */
} }
h1, h2 { h1,
color: #585858; h2 {
font-weight: 400; color: #585858;
/* font-family: Metropolis,Avenir Next,Helvetica Neue,Arial,sans-serif; */ font-weight: 400;
letter-spacing: normal; /* font-family: Metropolis,Avenir Next,Helvetica Neue,Arial,sans-serif; */
line-height: 2rem; letter-spacing: normal;
margin-top: 1rem; line-height: 2rem;
margin-bottom: 0; margin-top: 1rem;
/* text-transform: uppercase; */ margin-bottom: 0;
/* text-transform: uppercase; */
} }
clr-icon.is-info { clr-icon.is-info {
fill: #80b441; fill: #80b441;
} }
.datagrid-host, .datagrid-overlay-wrapper { .datagrid-host,
display: -webkit-box; .datagrid-overlay-wrapper {
display: -ms-flexbox; display: -webkit-box;
display: -webkit-box!important; display: -ms-flexbox;
-webkit-box-direction: normal; display: -webkit-box !important;
-webkit-box-direction: normal;
} }
.btn.btn-danger, .btn.btn-warning { .btn .clr-loading-btn-content {
border-color: #ef4f2e; justify-content: center;
background-color: #D94B2E; }
color: #fff;
.btn.btn-danger,
.btn.btn-warning {
border-color: #ef4f2e;
background-color: #d94b2e;
color: #fff;
}
// Vertical align fix for small buttons with icons
.btn.btn-sm:has(clr-icon) {
line-height: 2;
} }
.d-none { .d-none {
@ -685,11 +733,16 @@ clr-icon.is-info {
} }
.handsontable td.htInvalid { .handsontable td.htInvalid {
background: #e62700ad!important; background: #e62700ad !important;
border: 1px solid red !important; border: 1px solid red !important;
color: #ffffff!important; color: #ffffff !important;
} }
.margin-top-20{
.handsontable .numericListbox {
text-align: right;
}
.margin-top-20 {
margin-top: 20px; margin-top: 20px;
} }
.hidden { .hidden {
@ -823,7 +876,7 @@ clr-icon.is-info {
} }
.datagrid-body { .datagrid-body {
padding-bottom: 2rem!important; padding-bottom: 2rem !important;
} }
.abortMsg { .abortMsg {
@ -831,16 +884,15 @@ clr-icon.is-info {
font-family: monospace; font-family: monospace;
} }
#graph svg { #graph svg {
height: 100%; height: 100%;
width: 100%; width: 100%;
} }
.no-table-selected { .no-table-selected {
display:flex; display: flex;
justify-content:center; justify-content: center;
flex-direction:column; flex-direction: column;
align-items: center; align-items: center;
position: absolute; position: absolute;
background: white; background: white;
@ -851,16 +903,15 @@ clr-icon.is-info {
} }
.copyRight { .copyRight {
background:#495967!important; background: #495967 !important;
color: #fff; color: #fff;
display:flex !important; display: flex !important;
justify-content:center; justify-content: center;
align-items: center; align-items: center;
padding: 5px 0px 4px 0px; padding: 5px 0px 4px 0px;
z-index: 100; z-index: 100;
} }
.nav-tree > clr-tree-node.clr-expanded { .nav-tree > clr-tree-node.clr-expanded {
display: inline-block !important; display: inline-block !important;
} }
@ -903,13 +954,13 @@ clr-tree-node {
} }
.tree-search-wrapper { .tree-search-wrapper {
position: relative; position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
clr-input-container { clr-input-container {
margin: 0; margin: 0;
} }
clr-icon { clr-icon {
position: absolute; position: absolute;
@ -956,7 +1007,8 @@ input::-ms-clear {
overflow: hidden !important; overflow: hidden !important;
} }
.clr-treenode-content .clr-icon, .clr-treenode-content clr-icon { .clr-treenode-content .clr-icon,
.clr-treenode-content clr-icon {
min-width: 16px; min-width: 16px;
min-height: 16px; min-height: 16px;
} }
@ -985,12 +1037,12 @@ input::-ms-clear {
} }
.loadingSpinner { .loadingSpinner {
height:70vh; height: 70vh;
flex: 1; flex: 1;
display:flex; display: flex;
justify-content: center; justify-content: center;
flex-direction:column; flex-direction: column;
align-items:center; align-items: center;
} }
.disable-password-manager { .disable-password-manager {
@ -1025,7 +1077,8 @@ hr.light {
position: relative; position: relative;
min-width: 170px; min-width: 170px;
clr-icon, .spinner { clr-icon,
.spinner {
position: absolute; position: absolute;
right: 19px; right: 19px;
top: 0px; top: 0px;
@ -1063,7 +1116,7 @@ hr.light {
} }
/* Firefox */ /* Firefox */
input[type=number] { input[type='number'] {
-moz-appearance: textfield; -moz-appearance: textfield;
} }
} }
@ -1076,4 +1129,4 @@ hr.light {
.link-it { .link-it {
cursor: pointer; cursor: pointer;
text-decoration: underline; text-decoration: underline;
} }

9250
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "dcfrontend", "name": "dcfrontend",
"version": "6.2.5", "version": "6.8.1",
"description": "Data Controller", "description": "Data Controller",
"devDependencies": { "devDependencies": {
"@saithodev/semantic-release-gitea": "^2.1.0", "@saithodev/semantic-release-gitea": "^2.1.0",
@ -9,8 +9,7 @@
"@semantic-release/npm": "11.0.0", "@semantic-release/npm": "11.0.0",
"@semantic-release/git": "^10.0.1", "@semantic-release/git": "^10.0.1",
"@semantic-release/release-notes-generator": "^11.0.4", "@semantic-release/release-notes-generator": "^11.0.4",
"commit-and-tag-version": "^11.2.2", "commit-and-tag-version": "^11.2.2"
"prettier": "3.0.0"
}, },
"scripts": { "scripts": {
"install": "cd client && npm i && cd ../sas && npm i", "install": "cd client && npm i && cd ../sas && npm i",

View File

@ -14,6 +14,10 @@ _webout = `{"SYSDATE" : "26SEP22"
"APPROVER": "sasdemo" "APPROVER": "sasdemo"
} }
] ]
, "histparams":
[
{"HIST":100 ,"STARTROW":1 ,"NOBS":3 }
]
,"_DEBUG" : "" ,"_DEBUG" : ""
,"_METAUSER": "sasdemo@SAS" ,"_METAUSER": "sasdemo@SAS"
,"_METAPERSON": "sasdemo" ,"_METAPERSON": "sasdemo"

View File

@ -83,6 +83,12 @@ _webout = `{"SYSDATE" : "26SEP22"
"DC_RESTRICT_EDITRECORD": "NO" "DC_RESTRICT_EDITRECORD": "NO"
} }
] ]
,"xlmaps":
[
["BASEL-CR2" ,"" ,"DC695588.MPE_XLMAP_DATA" ]
,["BASEL-KM1" ,"Basel 3 Key Metrics report" ,"DC695588.MPE_XLMAP_DATA" ]
,["SAMPLE" ,"" ,"DC695588.MPE_XLMAP_DATA" ]
]
,"_DEBUG" : "" ,"_DEBUG" : ""
,"_METAUSER": "sasdemo@SAS" ,"_METAUSER": "sasdemo@SAS"
,"_METAPERSON": "sasdemo" ,"_METAPERSON": "sasdemo"

29
sas/package-lock.json generated
View File

@ -7,7 +7,7 @@
"name": "dc-sas", "name": "dc-sas",
"dependencies": { "dependencies": {
"@sasjs/cli": "^4.11.1", "@sasjs/cli": "^4.11.1",
"@sasjs/core": "^4.48.1" "@sasjs/core": "^4.52.0"
} }
}, },
"node_modules/@coolaj86/urequest": { "node_modules/@coolaj86/urequest": {
@ -116,9 +116,9 @@
"integrity": "sha512-Grwydm5GxBsYk238PZw41XPjXVVQ9vWcvfZ06L2P0bQbvK0sGn7l69JA7H5MGr3QcaLpiD4Kg70cAh7PgE+JOw==" "integrity": "sha512-Grwydm5GxBsYk238PZw41XPjXVVQ9vWcvfZ06L2P0bQbvK0sGn7l69JA7H5MGr3QcaLpiD4Kg70cAh7PgE+JOw=="
}, },
"node_modules/@sasjs/core": { "node_modules/@sasjs/core": {
"version": "4.48.1", "version": "4.52.0",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.48.1.tgz", "resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.52.0.tgz",
"integrity": "sha512-oT0lfoVa0ZAEhWveQgHL+gepobgrUhDDAH6fjNreGXlUUf8FmhQvbdS0X5o/dFjq6WgwjzD0FAC5J/ceXCqQhQ==" "integrity": "sha512-r+yQYSTYdvNPJ82n2xW/rbJd/cJpk3HuFhCa49RTDlDWjXMXJlymvGB0ltv3/L8rNk4+TwoyMzb7cJYnILi6ag=="
}, },
"node_modules/@sasjs/lint": { "node_modules/@sasjs/lint": {
"version": "2.3.1", "version": "2.3.1",
@ -229,12 +229,6 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/tough-cookie": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.3.tgz",
"integrity": "sha512-THo502dA5PzG/sfQH+42Lw3fvmYkceefOspdCwpHRul8ik2Jv1K8I5OZz1AT3/rs46kwgMCe9bSBmDLYkkOMGg==",
"peer": true
},
"node_modules/abab": { "node_modules/abab": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
@ -1834,9 +1828,9 @@
} }
}, },
"@sasjs/core": { "@sasjs/core": {
"version": "4.48.1", "version": "4.52.0",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.48.1.tgz", "resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.52.0.tgz",
"integrity": "sha512-oT0lfoVa0ZAEhWveQgHL+gepobgrUhDDAH6fjNreGXlUUf8FmhQvbdS0X5o/dFjq6WgwjzD0FAC5J/ceXCqQhQ==" "integrity": "sha512-r+yQYSTYdvNPJ82n2xW/rbJd/cJpk3HuFhCa49RTDlDWjXMXJlymvGB0ltv3/L8rNk4+TwoyMzb7cJYnILi6ag=="
}, },
"@sasjs/lint": { "@sasjs/lint": {
"version": "2.3.1", "version": "2.3.1",
@ -1933,12 +1927,6 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"@types/tough-cookie": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.3.tgz",
"integrity": "sha512-THo502dA5PzG/sfQH+42Lw3fvmYkceefOspdCwpHRul8ik2Jv1K8I5OZz1AT3/rs46kwgMCe9bSBmDLYkkOMGg==",
"peer": true
},
"abab": { "abab": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
@ -2965,8 +2953,7 @@
"ws": { "ws": {
"version": "8.13.0", "version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA=="
"requires": {}
}, },
"xml": { "xml": {
"version": "1.0.1", "version": "1.0.1",

View File

@ -14,7 +14,8 @@
"sas9e": "sasjs request services/admin/makedata -d deploy/makeDataSas9.json -t sas9 ", "sas9e": "sasjs request services/admin/makedata -d deploy/makeDataSas9.json -t sas9 ",
"sas9f": "sasjs request services/admin/refreshtablelineage -t sas9 ", "sas9f": "sasjs request services/admin/refreshtablelineage -t sas9 ",
"sas9g": "sasjs request services/admin/refreshcatalog -t sas9", "sas9g": "sasjs request services/admin/refreshcatalog -t sas9",
"4gl": "npm run cpfavicon && sasjs cbd -t 4gl && sasjs request services/admin/makedata -d deploy/makeData4GL.json -l sasjsresults/makedata_4gl.log -o sasjsresults/makedata_4gl.json -t 4gl", "4gl": "npm run cpfavicon && sasjs cbd -t 4gl && npm run 4glmakedata",
"4glmakedata": "sasjs request services/admin/makedata -d deploy/makeData4GL.json -l sasjsresults/makedata_4gl.log -o sasjsresults/makedata_4gl.json -t 4gl",
"server": "npm run cpfavicon && sasjs cbd -t server && npm run serverdata", "server": "npm run cpfavicon && sasjs cbd -t server && npm run serverdata",
"server-mihajlo": "npm run cpfavicon && sasjs cbd -t server-mihajlo && npm run serverdata-mihajlo", "server-mihajlo": "npm run cpfavicon && sasjs cbd -t server-mihajlo && npm run serverdata-mihajlo",
"serverdata-mihajlo": "sasjs request services/admin/makedata -d deploy/makeDataServer.json -l sasjsresults/makedata_server.log -o sasjsresults/makedata_server.json -t server-mihajlo", "serverdata-mihajlo": "sasjs request services/admin/makedata -d deploy/makeDataServer.json -l sasjsresults/makedata_server.log -o sasjsresults/makedata_server.json -t server-mihajlo",
@ -28,6 +29,6 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@sasjs/cli": "^4.11.1", "@sasjs/cli": "^4.11.1",
"@sasjs/core": "^4.48.1" "@sasjs/core": "^4.52.0"
} }
} }

View File

@ -0,0 +1,18 @@
/**
@file
@brief DDL for MPE_XLMAP_DATA
@version 9.3
@author 4GL Apps Ltd
@copyright 4GL Apps Ltd
**/
create table &curlib..MPE_XLMAP_DATA(
LOAD_REF char(32) not null,
XLMAP_ID char(32) not null,
XLMAP_RANGE_ID char(32) not null,
ROW_NO num not null,
COL_NO num not null,
VALUE_TXT char(4000),
constraint pk_MPE_XLMAP_DATA
primary key(LOAD_REF, XLMAP_ID, XLMAP_RANGE_ID, ROW_NO, COL_NO));

View File

@ -0,0 +1,17 @@
/**
@file
@brief DDL for mpe_xlmap_info
@version 9.3
@author 4GL Apps Ltd
@copyright 4GL Apps Ltd
**/
create table &curlib..mpe_xlmap_info(
tx_from num not null,
XLMAP_ID char(32) not null,
XLMAP_DESCRIPTION char(1000) not null,
XLMAP_TARGETLIBDS char(41) not null,
tx_to num not null,
constraint pk_mpe_xlmap_info
primary key(tx_from,XLMAP_ID));

View File

@ -0,0 +1,19 @@
/**
@file
@brief DDL for mpe_xlmap_rules
@version 9.3
@author 4GL Apps Ltd
@copyright 4GL Apps Ltd
**/
create table &curlib..mpe_xlmap_rules(
tx_from num not null,
XLMAP_ID char(32) not null,
XLMAP_RANGE_ID char(32) not null,
XLMAP_SHEET char(32) not null,
XLMAP_START char(1000) not null,
XLMAP_FINISH char(1000),
tx_to num not null,
constraint pk_mpe_xlmap_rules
primary key(tx_from,XLMAP_ID,XLMAP_RANGE_ID));

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