Compare commits

..

32 Commits

Author SHA1 Message Date
semantic-release-bot
113e0bbc3c chore(release): 7.2.5 [skip ci]
## [7.2.5](https://git.datacontroller.io/dc/dc/compare/v7.2.4...v7.2.5) (2025-12-09)

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

* removing localhost from index.html ([225e693](225e693d1f))
2025-08-08 17:35:02 +00:00
b43dfb5cf4 Merge pull request 'fix: removing localhost from index.html' (#187) from localhostfix into main
All checks were successful
Release / Build-production-and-ng-test (push) Successful in 3m54s
Release / Build-and-test-development (push) Successful in 8m25s
Release / release (push) Successful in 7m50s
Reviewed-on: #187
2025-08-08 17:19:17 +00:00
allan
225e693d1f fix: removing localhost from index.html
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 3m47s
Build / Build-and-test-development (pull_request) Successful in 8m27s
Lighthouse Checks / lighthouse (20.15.1) (pull_request) Successful in 18m41s
2025-08-08 18:08:49 +01:00
27 changed files with 3211 additions and 2162 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
* text=auto eol=lf

View File

@@ -10,7 +10,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v4
with:
node-version: 20.15.1
node-version: 24.5.0
- name: Install Google Chrome
run: |
@@ -58,7 +58,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v4
with:
node-version: 20.15.1
node-version: 24.5.0
- name: Write .npmrc file
run: |

View File

@@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.15.1]
node-version: [24.5.0]
steps:
- uses: actions/checkout@v3

View File

@@ -13,12 +13,12 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v4
with:
node-version: 20.14.0
node-version: 24.5.0
- name: Write .npmrc file
run: |
touch client/.npmrc
echo '${{ secrets.NPMRC}}' > client/.npmrc
touch client/.npmrc
echo '${{ secrets.NPMRC}}' > client/.npmrc
- name: Install Chrome for Angular tests
run: |
@@ -41,7 +41,7 @@ jobs:
npm ci
- name: Check audit
# Audit should fail and stop the CI if critical vulnerability found
# Audit should fail and stop the CI if critical vulnerability found
run: |
npm audit --audit-level=critical --omit=dev
cd ./sas
@@ -68,12 +68,12 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v4
with:
node-version: 20.14.0
node-version: 24.5.0
- name: Write .npmrc file
run: |
touch client/.npmrc
echo '${{ secrets.NPMRC}}' > client/.npmrc
touch client/.npmrc
echo '${{ secrets.NPMRC}}' > client/.npmrc
- run: apt-get update
- run: wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
@@ -158,7 +158,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v4
with:
node-version: 20.14.0
node-version: 24.5.0
- name: Write .npmrc file
run: |

View File

@@ -1,3 +1,44 @@
## [7.2.5](https://git.datacontroller.io/dc/dc/compare/v7.2.4...v7.2.5) (2025-12-09)
### Bug Fixes
* (build) rebuilt package-lock files ([bfbfd55](https://git.datacontroller.io/dc/dc/commit/bfbfd55fe7e2dff3ce707763a2c7939ff365318b))
* (deps) bump @sasjs/cli and @sasjs/core ([d7c7302](https://git.datacontroller.io/dc/dc/commit/d7c7302c12ac60f355ab9b3b1b461fcf7d0719b8))
* (deps) bumped @sasjs/core, @sasjs/cli, @sasjs/utils and @sasjs/adapter ([af1657e](https://git.datacontroller.io/dc/dc/commit/af1657e226a4efd22cc87401a3850c4a665c2680))
* configurable audit table on restore check ([26ce95f](https://git.datacontroller.io/dc/dc/commit/26ce95f7c1d2260f81c240cd6b058db154d997e4)), closes [#193](https://git.datacontroller.io/dc/dc/issues/193)
* improved testing ([fb3c49a](https://git.datacontroller.io/dc/dc/commit/fb3c49aa8bfdc6acf2ae3034b885010dcdce32a6))
* output values to intended macro variables ([43ae73c](https://git.datacontroller.io/dc/dc/commit/43ae73c5f3ad919394201f54984b61bb2a52fcfe))
## [7.2.4](https://git.datacontroller.io/dc/dc/compare/v7.2.3...v7.2.4) (2025-10-14)
### Bug Fixes
* ensure reload after applying licence key ([cb1978b](https://git.datacontroller.io/dc/dc/commit/cb1978bcaf23b0bf45b5d3b78b9707fd4e48a5f4))
* snyk report security patches ([387f512](https://git.datacontroller.io/dc/dc/commit/387f5122f1ea6dff55d23c9223f17737283a94d3))
## [7.2.3](https://git.datacontroller.io/dc/dc/compare/v7.2.2...v7.2.3) (2025-10-02)
### Bug Fixes
* opening second table in viewer throws an error ([6c6b1cb](https://git.datacontroller.io/dc/dc/commit/6c6b1cbf460e5291ec746af017e764b894fff8d5))
## [7.2.2](https://git.datacontroller.io/dc/dc/compare/v7.2.1...v7.2.2) (2025-09-23)
### Bug Fixes
* jsrsasign, @sasjs/cli bump ([365f129](https://git.datacontroller.io/dc/dc/commit/365f12996db3ef50a4f4f099d5af15696c43bb42))
## [7.2.1](https://git.datacontroller.io/dc/dc/compare/v7.2.0...v7.2.1) (2025-08-08)
### Bug Fixes
* removing localhost from index.html ([225e693](https://git.datacontroller.io/dc/dc/commit/225e693d1fd4381f2b8ce42fecb508f0a9e9dad8))
# [7.2.0](https://git.datacontroller.io/dc/dc/compare/v7.1.1...v7.2.0) (2025-08-08)

View File

@@ -10,7 +10,7 @@ const check = (cwd) => {
onlyAllow:
'AFLv2.1;Apache 2.0;Apache-2.0;Apache*;Artistic-2.0;0BSD;BSD*;BSD-2-Clause;BSD-3-Clause;CC0-1.0;CC-BY-3.0;CC-BY-4.0;ISC;MIT;MPL-2.0;ODC-By-1.0;Python-2.0;Unlicense;',
excludePackages:
'@cds/city@1.1.0;@handsontable/angular-wrapper@16.0.1;handsontable@16.0.1;hyperformula@2.7.1;hyperformula@3.0.0;jackspeak@3.4.3;path-scurry@1.11.1;package-json-from-dist@1.0.1'
'@cds/city@1.1.0;@handsontable/angular-wrapper@16.0.1;handsontable@^16.0.1;handsontable@16.2.0;hyperformula@2.7.1;hyperformula@3.0.0;hyperformula@3.1.0;jackspeak@3.4.3;path-scurry@1.11.1;package-json-from-dist@1.0.1'
},
(error, json) => {
if (error) {

3582
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -50,8 +50,8 @@
"@clr/icons": "^13.0.2",
"@clr/ui": "file:libraries/clr-ui-17.9.0.tgz",
"@handsontable/angular-wrapper": "16.0.1",
"@sasjs/adapter": "^4.12.2",
"@sasjs/utils": "^3.4.0",
"@sasjs/adapter": "^4.16.0",
"@sasjs/utils": "^3.5.3",
"@sheet/crypto": "file:libraries/sheet-crypto.tgz",
"@types/d3-graphviz": "^2.6.7",
"@types/text-encoding": "0.0.35",
@@ -66,7 +66,7 @@
"hyperformula": "^2.5.0",
"iconv-lite": "^0.5.0",
"jquery-datetimepicker": "^2.5.21",
"jsrsasign": "^10.2.0",
"jsrsasign": "^11.1.0",
"marked": "^5.0.0",
"moment": "^2.26.0",
"ngx-clipboard": "^16.0.0",

View File

@@ -171,23 +171,8 @@ export class EditRecordComponent implements OnInit {
}
public copyToClip(text: string) {
const modalElement = document.querySelector('#recordModalRef .modal-title')
if (modalElement) {
const selBox = document.createElement('textarea')
selBox.style.position = 'fixed'
selBox.style.left = '0'
selBox.style.top = '0'
selBox.style.opacity = '0'
selBox.style.zIndex = '5000'
selBox.value = text
modalElement.appendChild(selBox)
selBox.focus()
selBox.select()
document.execCommand('copy')
modalElement.removeChild(selBox)
this.generatedRecordUrl = text
}
navigator.clipboard.writeText(text)
this.generatedRecordUrl = text
}
async generateEditRecordUrl() {

View File

@@ -52,6 +52,7 @@ export class LicensingComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private router: Router,
private licenceService: LicenceService,
private sasService: SasService,
private appService: AppService
@@ -124,7 +125,9 @@ export class LicensingComponent implements OnInit {
res.adapterResponse.return[0] &&
res.adapterResponse.return[0].MSG === 'SUCCESS'
) {
location.replace(location.href.split('#')[0])
this.router.navigateByUrl('/').then(() => {
window.location.reload()
})
}
})
.finally(() => {

View File

@@ -746,28 +746,13 @@ export class LineageComponent {
return URL.createObjectURL(svg_blob)
}
private getSVGBlob() {
let svg: any = document.getElementById('graph')
let serializer = new XMLSerializer()
let svg_blob = new Blob([serializer.serializeToString(svg)], {
type: 'image/svg+xml'
})
return svg_blob
}
downloadSVG() {
d3Viz.graphviz('#graph').resetZoom()
if (navigator.appVersion.toString().indexOf('.NET') > 0) {
window.navigator.msSaveBlob(this.getSVGBlob(), this.constructName('svg'))
} else {
let downloadLink = document.createElement('a')
downloadLink.href = this.getSVGURL()
downloadLink.download = this.constructName('svg')
document.body.appendChild(downloadLink)
downloadLink.click()
document.body.removeChild(downloadLink)
}
let downloadLink = document.createElement('a')
downloadLink.href = this.getSVGURL()
downloadLink.download = this.constructName('svg')
downloadLink.click()
}
async downloadPNG() {
@@ -795,16 +780,11 @@ export class LineageComponent {
var a = document.createElement('a')
var blob = new Blob([csvArray], { type: 'text/csv' })
if (navigator.appVersion.toString().indexOf('.NET') > 0) {
window.navigator.msSaveBlob(blob, this.constructName('csv'))
} else {
var url = window.URL.createObjectURL(blob)
a.href = url
a.download = this.constructName('csv')
a.click()
window.URL.revokeObjectURL(url)
a.remove()
}
var url = window.URL.createObjectURL(blob)
a.href = url
a.download = this.constructName('csv')
a.click()
window.URL.revokeObjectURL(url)
}
private getDotUrl() {
@@ -813,23 +793,11 @@ export class LineageComponent {
return window.URL.createObjectURL(dot_blob)
}
private getDotBlob() {
let data = this.vizInput
let dot_blob = new Blob([data], { type: 'text/plain' })
return dot_blob
}
downloadDot() {
if (navigator.appVersion.toString().indexOf('.NET') > 0) {
window.navigator.msSaveBlob(this.getDotBlob(), this.constructName('txt'))
} else {
let downloadLink = document.createElement('a')
downloadLink.href = this.getDotUrl()
downloadLink.download = this.constructName('txt')
document.body.appendChild(downloadLink)
downloadLink.click()
document.body.removeChild(downloadLink)
}
let downloadLink = document.createElement('a')
downloadLink.href = this.getDotUrl()
downloadLink.download = this.constructName('txt')
downloadLink.click()
}
public showSvg() {

View File

@@ -38,9 +38,7 @@ class SubmittedFilter implements ClrDatagridStringFilterInterface<ApproveData> {
}
}
class SubmitReasonFilter
implements ClrDatagridStringFilterInterface<ApproveData>
{
class SubmitReasonFilter implements ClrDatagridStringFilterInterface<ApproveData> {
accepts(data: ApproveData, search: string): boolean {
return data.submitReason.toLowerCase().indexOf(search.toLowerCase()) >= 0
}

View File

@@ -38,9 +38,7 @@ class SubmitterFilter implements ClrDatagridStringFilterInterface<HistoryData> {
}
}
class SubmitReasonFilter
implements ClrDatagridStringFilterInterface<HistoryData>
{
class SubmitReasonFilter implements ClrDatagridStringFilterInterface<HistoryData> {
accepts(data: HistoryData, search: string): boolean {
return data.submittedReason.toLowerCase().indexOf(search.toLowerCase()) >= 0
}

View File

@@ -17,17 +17,13 @@ interface SubmitterData {
approver: string
}
class SubmittedFilter
implements ClrDatagridStringFilterInterface<SubmitterData>
{
class SubmittedFilter implements ClrDatagridStringFilterInterface<SubmitterData> {
accepts(data: SubmitterData, search: string): boolean {
return data.submitted.toLowerCase().indexOf(search.toLowerCase()) >= 0
}
}
class SubmitReasonFilter
implements ClrDatagridStringFilterInterface<SubmitterData>
{
class SubmitReasonFilter implements ClrDatagridStringFilterInterface<SubmitterData> {
accepts(data: SubmitterData, search: string): boolean {
return data.submitReason.toLowerCase().indexOf(search.toLowerCase()) >= 0
}

View File

@@ -1,8 +1,9 @@
import Handsontable from 'handsontable'
import Core from 'handsontable/core'
export class CustomAutocompleteEditor extends Handsontable.editors
.AutocompleteEditor {
export class CustomAutocompleteEditor
extends Handsontable.editors.AutocompleteEditor
{
constructor(instance: Core) {
super(instance)
}

View File

@@ -15,7 +15,6 @@ export interface DcColumnSettings {
export interface DcValidation extends HotColumnSettings, DcColumnSettings {}
export interface DcValidationRuleUpdate
extends Handsontable.ColumnSettings,
DcColumnSettings {
extends Handsontable.ColumnSettings, DcColumnSettings {
data?: string
}

View File

@@ -157,15 +157,30 @@ export class ViewerComponent
return ' '
},
afterGetColHeader: (col: number, th: any, headerLevel: number) => {
const column = this.hotInstance?.colToProp(col) as string
// CRITICAL: Prevent "colToProp method cannot be called because this Handsontable instance has been destroyed" error
// This callback can be triggered even after the instance is destroyed during rapid table switching
if (
!this.hotInstance ||
this.hotInstance.isDestroyed ||
this.isTableSwitching
) {
// Graceful fallback: apply only dark mode styling when instance is unavailable
th.classList.add(globals.handsontable.darkTableHeaderClass)
return
}
// header columns styling - primary keys
const isPKCol = column && this.headerPks.indexOf(column) > -1
try {
const column = this.hotInstance.colToProp(col) as string
if (isPKCol) th.classList.add('primaryKeyHeaderStyle')
const isPKCol = column && this.headerPks.indexOf(column) > -1
if (isPKCol) th.classList.add('primaryKeyHeaderStyle')
// Dark mode
th.classList.add(globals.handsontable.darkTableHeaderClass)
// Apply dark mode styling to all headers
th.classList.add(globals.handsontable.darkTableHeaderClass)
} catch (error) {
// Safety net: if colToProp() fails, still apply basic styling
th.classList.add(globals.handsontable.darkTableHeaderClass)
}
},
rowHeaderWidth: 15,
rowHeights: 20,
@@ -199,12 +214,21 @@ export class ViewerComponent
let colInfo: DataFormat | undefined
let textInfo = 'No info found'
if (this.hotInstance) {
const hotSelected: [number, number, number, number][] =
this.hotInstance.getSelected() || []
const selectedCol: number = hotSelected ? hotSelected[0][1] : -1
const colName = this.hotInstance?.colToProp(selectedCol)
colInfo = this.$dataFormats?.vars[colName]
if (
this.hotInstance &&
!this.hotInstance.isDestroyed &&
!this.isTableSwitching
) {
try {
const hotSelected: [number, number, number, number][] =
this.hotInstance.getSelected() || []
const selectedCol: number = hotSelected ? hotSelected[0][1] : -1
const colName = this.hotInstance.colToProp(selectedCol)
colInfo = this.$dataFormats?.vars[colName]
} catch (error) {
// Ignore errors during table switching
colInfo = undefined
}
if (colInfo)
textInfo = `LABEL: ${colInfo?.label}<br>TYPE: ${colInfo?.type}<br>LENGTH: ${colInfo?.length}<br>FORMAT: ${colInfo?.format}`
@@ -224,6 +248,13 @@ export class ViewerComponent
private hotInstance: Handsontable | null = null
public hotInstanceClickListener: boolean = false
// Race condition prevention for rapid table switching
private isTableSwitching: boolean = false
private switchingTimeout: any = null
// Prevents duplicate setupHot() calls within short time windows
private lastSetupTime: number = 0
public viewboxOpen: boolean = false
constructor(
@@ -505,17 +536,7 @@ export class ViewerComponent
}
public copyToClip() {
let selBox = document.createElement('textarea')
selBox.style.position = 'fixed'
selBox.style.left = '0'
selBox.style.top = '0'
selBox.style.opacity = '0'
selBox.value = this.webQueryText
document.body.appendChild(selBox)
selBox.focus()
selBox.select()
document.execCommand('copy')
document.body.removeChild(selBox)
navigator.clipboard.writeText(this.webQueryText)
}
public goToViewer() {
@@ -599,10 +620,24 @@ export class ViewerComponent
}
public onTableClick(libTable: any, library: any) {
this.lib = library.LIBRARYREF
this.table = libTable
this.selectLibTable(libTable)
this.viewData(0)
// OPTIMIZATION: Prevent race conditions and destroyed instance errors during rapid table switching
if (this.isTableSwitching) {
return
}
// Clear any existing timeout to prevent stale operations
if (this.switchingTimeout) {
clearTimeout(this.switchingTimeout)
}
// PERFORMANCE: Debounce table switches to prevent rapid successive calls
// This ensures only the final table selection is processed
this.switchingTimeout = setTimeout(() => {
this.lib = library.LIBRARYREF
this.table = libTable
this.selectLibTable(libTable)
this.viewData(0)
}, 50) // 50ms debounce - fast enough for good UX, slow enough to prevent issues
}
public async selectTable(lib: string, initial?: boolean, library?: any) {
@@ -721,6 +756,14 @@ export class ViewerComponent
}
public async viewData(filter_pk: number) {
// CRITICAL: Set switching flag to prevent concurrent operations and race conditions
// This prevents callbacks from accessing destroyed instances during table switching
this.isTableSwitching = true
// CLEANUP: Ensure any existing Handsontable instance is properly destroyed
// This prevents "instance destroyed" errors
this.cleanupHotInstance()
this.loadingTableView = true
let libDataset: any
@@ -961,14 +1004,25 @@ export class ViewerComponent
this.loadingTableView = false
//If we try to setup hot when no data is returned it errors `isDestoryed`.
//That is intorduced by HOT update
if (!this.noData && !this.noDataReqErr && libDataset) this.setupHot()
// Setup Handsontable after async operations complete
// Original issue: setupHot() called before API responses populated headerPks array
// Solution: Delay ensures both API paths (lines 328 & 886) have chance to set headerPks
setTimeout(() => {
if (!this.noData && !this.noDataReqErr && libDataset) {
this.setupHot()
}
}, 50) // Optimized from 100ms - fast enough for API completion, slow enough to prevent race conditions
// RACE CONDITION PREVENTION: Reset switching flag after setup completion
// This allows new table switches after current operation finishes
setTimeout(() => {
this.isTableSwitching = false
}, 300) // Optimized from 700ms to match reduced setup times
// Fix ARIA accessibility issues after data loading
setTimeout(() => {
this.fixAriaAccessibility()
}, 1500)
}, 500)
/**
* This is hacky fix for closing the nav drodpwon, when clicking on the handsontable area.
@@ -988,7 +1042,7 @@ export class ViewerComponent
})
this.hotInstanceClickListener = true
}
}, 2000)
}, 1000)
}
}
@@ -1116,12 +1170,74 @@ export class ViewerComponent
}
}
/**
* CRITICAL CLEANUP (workaround needed for HOT version 16 and above): Safely destroys Handsontable instances
*
* Purpose: Prevents "instance destroyed" errors and memory leaks during table switching
*
* Called from:
* - viewData() - before loading new table data
* - setupHot() - before creating new instance
* - ngOnDestroy() - component cleanup
*
* Safety features:
* - Checks if instance exists and is not already destroyed
* - Try-catch prevents errors during destruction
* - Sets instance to null to prevent stale references
*/
private cleanupHotInstance() {
if (this.hotInstance && !this.hotInstance.isDestroyed) {
try {
this.hotInstance.destroy()
} catch (error) {
console.warn('Error destroying Handsontable instance:', error)
}
}
this.hotInstance = null
}
/**
* PERFORMANCE: Configures Handsontable with enhanced error handling (workaround needed for HOT version 16 and above)
*
* 1. Duplicate call prevention (500ms window)
* 2. Reduced timeout delays (200ms + 50ms vs original 1000ms + 200ms)
* 3. Multiple validation checks to prevent race conditions
* 4. Forced render for immediate primary key styling
*
* Timeline: 50ms (viewData) + 200ms (main) + 50ms (component ready) = ~300ms total
* Previous: 100ms + 600ms + 100ms = 800ms (plus render delays = ~2 seconds)
*/
private setupHot() {
// DUPLICATE PREVENTION: Avoid multiple setup calls during rapid table switching
const now = Date.now()
if (now - this.lastSetupTime < 500) {
return
}
this.lastSetupTime = now
setTimeout(() => {
if (!this.loadingTableView && this.libDataset) {
// VALIDATION: Don't setup if we're currently switching tables or still loading
if (this.loadingTableView || !this.libDataset) {
return
}
// CLEANUP: Ensure clean slate before new setup
this.cleanupHotInstance()
// TIMING: Wait for Angular component to be ready (optimized from 100ms to 50ms)
setTimeout(() => {
// DOUBLE-CHECK: Ensure we're still in valid state after delays
if (
this.isTableSwitching ||
this.loadingTableView ||
!this.libDataset
) {
return
}
this.hotInstance = this.hotTableComponent?.hotInstance
if (this.hotInstance) {
if (this.hotInstance && !this.hotInstance.isDestroyed) {
this.hotInstance.updateSettings({
height: this.hotTable.height,
modifyColWidth: (width: any, col: any) => {
@@ -1129,15 +1245,29 @@ export class ViewerComponent
else return width
},
afterGetColHeader: (col: number, th: any) => {
const column = this.hotInstance?.colToProp(col) as string
// CRITICAL: Same safety checks as initial callback to prevent destroyed instance errors
if (
!this.hotInstance ||
this.hotInstance.isDestroyed ||
this.isTableSwitching
) {
th.classList.add(globals.handsontable.darkTableHeaderClass)
return
}
// header columns styling - primary keys
const isPKCol = column && this.headerPks.indexOf(column) > -1
try {
const column = this.hotInstance.colToProp(col) as string
if (isPKCol) th.classList.add('primaryKeyHeaderStyle')
// PRIMARY KEY STYLING: Apply special styling to PK columns (populated from API response)
const isPKCol = column && this.headerPks.indexOf(column) > -1
if (isPKCol) th.classList.add('primaryKeyHeaderStyle')
// Dark mode
th.classList.add(globals.handsontable.darkTableHeaderClass)
// DARK MODE: Apply to all headers
th.classList.add(globals.handsontable.darkTableHeaderClass)
} catch (error) {
// SAFETY NET: Ensure basic styling is always applied
th.classList.add(globals.handsontable.darkTableHeaderClass)
}
}
})
@@ -1153,14 +1283,18 @@ export class ViewerComponent
this.fixAriaAccessibility()
}, 50)
})
}
}
// Fix ARIA accessibility issues after table setup
setTimeout(() => {
this.fixAriaAccessibility()
}, 500)
}, 1000)
// Force immediate render to apply primary key styling
// Without this, styling would wait for ~2 seconds to be applied
// With this, styling appears in ~300ms total (workaround needed for HOT version 16 and above)
setTimeout(() => {
if (this.hotInstance && !this.hotInstance.isDestroyed) {
this.hotInstance.render()
}
}, 10)
}
}, 50) // Optimized Angular component readiness delay
}, 200) // Optimized main setup delay (was 600ms)
}
async loadWithParameters() {
@@ -1233,13 +1367,27 @@ export class ViewerComponent
}
ngOnDestroy() {
// Clean up the MutationObserver
// Proper component destruction to prevent memory leaks and errors
// Prevent any new operations during cleanup
this.isTableSwitching = true
// Clear any pending debounced table switches
if (this.switchingTimeout) {
clearTimeout(this.switchingTimeout)
this.switchingTimeout = null
}
// Safely destroy Handsontable instance
this.cleanupHotInstance()
// Clean up ARIA accessibility observers
if (this.ariaObserver) {
this.ariaObserver.disconnect()
this.ariaObserver = undefined
}
// Clean up the interval
// Clear ARIA check intervals
if (this.ariaCheckInterval) {
clearInterval(this.ariaCheckInterval)
this.ariaCheckInterval = undefined

View File

@@ -44,7 +44,7 @@
-->
<sasjs
serverUrl="http://localhost:5000"
serverUrl=""
appLoc="/Public/app/devtest"
serverType="SASJS"
loginMechanism="Redirected"

559
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "dcfrontend",
"version": "7.2.0",
"version": "7.2.5",
"description": "Data Controller",
"devDependencies": {
"@saithodev/semantic-release-gitea": "^2.1.0",
@@ -10,7 +10,7 @@
"@semantic-release/npm": "11.0.0",
"@semantic-release/release-notes-generator": "^11.0.4",
"commit-and-tag-version": "^11.2.2",
"prettier": "3.6.2"
"prettier": "^3.7.4"
},
"scripts": {
"install": "cd client && npm i && cd ../sas && npm i",
@@ -32,6 +32,5 @@
"//": [
"Readme",
"We must set private: true so that semantic-release/npm plugin will update the package.json version but not try to release it as NPM package"
],
"dependencies": {}
]
}

377
sas/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -28,7 +28,7 @@
},
"private": true,
"dependencies": {
"@sasjs/cli": "^4.12.10",
"@sasjs/core": "^4.59.5"
"@sasjs/cli": "^4.12.15",
"@sasjs/core": "^4.59.9"
}
}

View File

@@ -39,15 +39,35 @@
%let &outresult=NO;
%let &outreason=NOTFOUND;
/* check if there is actually a version to restore */
%local libds;
proc sql noprint;
select upcase(cats(base_lib,'.',base_ds)) into: libds
from &dc_libref..mpe_submit
where TABLE_ID="&load_ref";
/**
* check if there is actually a version to restore
*/
%local audtab;
proc sql noprint;
select coalescec(audit_libds,"&dc_libref..MPE_AUDIT") into: audtab
from &dclib..MPE_TABLES
where &dc_dttmtfmt. lt tx_to
and libref="%scan(&libds,1,.)" and dsn="%scan(&libds,2,.)";
%if "&audtab"="0" %then %do;
%let &outresult=NO;
%let &outreason= &libds has no audit table configured;
%return;
%end;
%local chk;
%let chk=0;
proc sql noprint;
select count(*) into: chk from &dc_libref..mpe_audit
select count(*) into: chk from &audtab
where load_ref="&load_ref";
%if &chk=0 %then %do;
%let allow_restore=NO;
%let reason=No entry for &load_ref in MPE_AUDIT;
%let &outresult=NO;
%let &outreason=No entry for &load_ref in &audtab;
%return;
%end;
@@ -64,24 +84,19 @@
where groupname="&dc_admin_group";
%if &is_admin>0 %then %do;
%let allow_restore=YES;
%let reason=IS ADMIN;
%let &outresult=YES;
%let &outreason=IS ADMIN;
%return;
%end;
/* check if user has basic access */
%local libds;
proc sql noprint;
select cats(base_lib,'.',base_ds) into: libds
from &mpelib..mpe_submit
where TABLE_ID="&load_ref";
%mpe_accesscheck(&libds,outds=work.access_check
,user=&user
,access_level=EDIT
)
%if %mf_nobs(access_check)=0 %then %do;
%let allow_restore=NO;
%let reason=No access in MPE_TABLES;
%let &outresult=NO;
%let &outreason=No access in MPE_TABLES;
%return;
%end;
@@ -97,8 +112,8 @@
and CLS_LIBREF="%upcase(&base_lib)"
and CLS_TABLE="%upcase(&base_ds)";
%if %mf_nobs(work.cls_rules)>0 %then %do;
%let allow_restore=NO;
%let reason=User has restrictions in MPE_COLUMN_LEVEL_SECURITY;
%let &outresult=NO;
%let &outreason=User has restrictions in MPE_COLUMN_LEVEL_SECURITY;
data _null_;
set work.cls_rules;
putlog (_all_)(=);
@@ -119,8 +134,8 @@
and rls_table="&base_ds"
and rls_active=1;
%if %mf_nobs(work.rls_rules)>0 %then %do;
%let allow_restore=NO;
%let reason=User has restrictions in MPE_ROW_LEVEL_SECURITY;
%let &outresult=NO;
%let &outreason=User has restrictions in MPE_ROW_LEVEL_SECURITY;
data _null_;
set work.rls_rules;
putlog (_all_)(=);
@@ -129,7 +144,7 @@
%return;
%end;
%else %do;
%let allow_restore=YES;
%let reason=CHECKS PASSED;
%let &outresult=YES;
%let &outreason=CHECKS PASSED;
%end;
%mend mpe_checkrestore;

View File

@@ -35,14 +35,12 @@ run;
%dc_getsettings()
%put checking it is restorable;
%global allow_restore reason;
%mp_assertscope(SNAPSHOT)
%mpe_checkrestore(&loadref,outresult=ALLOW_RESTORE,outreason=REASON)
%mp_assertscope(COMPARE,
desc=Checking macro variables against previous snapshot,
ignorelist=ALLOW_RESTORE REASON
MCLIB0_JADP1LEN MCLIB0_JADP2LEN MCLIB0_JADPNUM MCLIB0_JADVLEN
MCLIB2_JADP1LEN MCLIB2_JADP2LEN MCLIB2_JADPNUM MCLIB2_JADVLEN
MC0_JADP1LEN MC0_JADP2LEN MC0_JADP3LEN MC0_JADPNUM MC0_JADVLEN
)
%mp_assert(

View File

@@ -94,7 +94,7 @@
},
{
"name": "viya",
"serverUrl": "https://viya-f0g8ht62vq.engage.sas.com",
"serverUrl": "",
"serverType": "SASVIYA",
"httpsAgentOptions": {
"allowInsecureRequests": false

View File

@@ -3,6 +3,7 @@
@brief testing getchangeinfo service
<h4> SAS Macros </h4>
@li mp_assert.sas
@li mp_assertcolvals.sas
@li mf_getuniquefileref.sas

View File

@@ -43,8 +43,8 @@ data _null_;
cnt=find(_pgm,'/tests/');
if cnt=0 then cnt=find(_pgm,'/services/');
if cnt=0 then cnt=find(_pgm,'/jobs/');
put cnt= apploc= _pgm=;
apploc=substr(_pgm,1,cnt-1);
put cnt= apploc= _pgm=;
call symputx('apploc',apploc);
end;
run;