Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
96066c66cb | |||
7997b77158 | |||
d1966bcdc5 | |||
7b5bbe024d | |||
4d84f15aca | |||
928937daab | |||
3bd8d247e5 | |||
cf6c9dd5f2 | |||
ff55cbbaad | |||
3eda4e2c58 | |||
c3af97ef57 | |||
31d4e5c727 | |||
fbbcf90956 | |||
f522038b8d | |||
ace599b39f | |||
963562621d | |||
5171d07441 | |||
9a0b9573d5 | |||
4733311ef3 | |||
432450a15b | |||
47638becc0 | |||
bdd3a95685 | |||
38601346a5 |
@ -21,8 +21,17 @@ jobs:
|
|||||||
- name: Lint check
|
- name: Lint check
|
||||||
run: npm run lint:check
|
run: npm run lint:check
|
||||||
|
|
||||||
- name: Licence checker
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
cd client
|
cd client
|
||||||
npm ci
|
npm ci
|
||||||
|
# Install sheet
|
||||||
|
wget ${{ secrets.SHEETLINK }}
|
||||||
|
mv ${{ secrets.SHEETNAME }} ${{ secrets.SHEETNAME }}.tgz
|
||||||
|
npm i ${{ secrets.SHEETNAME }}.tgz
|
||||||
|
# End
|
||||||
|
|
||||||
|
- name: Licence checker
|
||||||
|
run: |
|
||||||
|
cd client
|
||||||
npm run license-checker
|
npm run license-checker
|
@ -34,7 +34,14 @@ jobs:
|
|||||||
CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }}
|
CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }}
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: |
|
||||||
|
npm ci
|
||||||
|
cd client
|
||||||
|
# Install sheet
|
||||||
|
wget ${{ secrets.SHEETLINK }}
|
||||||
|
mv ${{ secrets.SHEETNAME }} ${{ secrets.SHEETNAME }}.tgz
|
||||||
|
npm i ${{ secrets.SHEETNAME }}.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
|
||||||
@ -86,7 +93,14 @@ jobs:
|
|||||||
CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }}
|
CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }}
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: |
|
||||||
|
npm ci
|
||||||
|
cd client
|
||||||
|
# Install sheet
|
||||||
|
wget ${{ secrets.SHEETLINK }}
|
||||||
|
mv ${{ secrets.SHEETNAME }} ${{ secrets.SHEETNAME }}.tgz
|
||||||
|
npm i ${{ secrets.SHEETNAME }}.tgz
|
||||||
|
# End
|
||||||
|
|
||||||
# Install pm2 and prepare SASJS server
|
# Install pm2 and prepare SASJS server
|
||||||
- run: npm i -g pm2
|
- run: npm i -g pm2
|
||||||
@ -185,6 +199,11 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd client
|
cd client
|
||||||
npm ci
|
npm ci
|
||||||
|
# Install sheet
|
||||||
|
wget ${{ secrets.SHEETLINK }}
|
||||||
|
mv ${{ secrets.SHEETNAME }} ${{ secrets.SHEETNAME }}.tgz
|
||||||
|
npm i ${{ secrets.SHEETNAME }}.tgz
|
||||||
|
# End
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
- name: Build SAS9 EBI Release
|
- name: Build SAS9 EBI Release
|
||||||
|
30
CHANGELOG.md
30
CHANGELOG.md
@ -1,3 +1,33 @@
|
|||||||
|
# [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)
|
## [6.6.1](https://git.datacontroller.io/dc/dc/compare/v6.6.0...v6.6.1) (2024-02-19)
|
||||||
|
|
||||||
|
|
||||||
|
7
client/package-lock.json
generated
7
client/package-lock.json
generated
@ -23,7 +23,7 @@
|
|||||||
"@handsontable/angular": "^13.1.0",
|
"@handsontable/angular": "^13.1.0",
|
||||||
"@sasjs/adapter": "4.10.2",
|
"@sasjs/adapter": "4.10.2",
|
||||||
"@sasjs/utils": "^3.4.0",
|
"@sasjs/utils": "^3.4.0",
|
||||||
"@sheet/crypto": "1.20211122.1",
|
"@sheet/crypto": "file:../../../RESOURCES/libs/0fae52415ec93a6dd3d5bd33c73b6dc124a07496be568a7aba6d033ec65df60b.tgz",
|
||||||
"@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",
|
||||||
@ -4652,7 +4652,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/@sheet/crypto": {
|
"node_modules/@sheet/crypto": {
|
||||||
"version": "1.20211122.1",
|
"version": "1.20211122.1",
|
||||||
"resolved": "https://pylon.sheetjs.com:54111/@sheet%2fcrypto/-/crypto-1.20211122.1.tgz",
|
"resolved": "file:../../../RESOURCES/libs/0fae52415ec93a6dd3d5bd33c73b6dc124a07496be568a7aba6d033ec65df60b.tgz",
|
||||||
"integrity": "sha512-G3/HWyzFUYbbVQoQIa+KSeMOhFnK492Ep595FXbzWN9IGZSwuvFl4saEyMl8R8pE2Al5YgSZuR9MpDpx3f7Izg==",
|
"integrity": "sha512-G3/HWyzFUYbbVQoQIa+KSeMOhFnK492Ep595FXbzWN9IGZSwuvFl4saEyMl8R8pE2Al5YgSZuR9MpDpx3f7Izg==",
|
||||||
"bin": {
|
"bin": {
|
||||||
"xlsx": "bin/xlsx.njs"
|
"xlsx": "bin/xlsx.njs"
|
||||||
@ -23681,8 +23681,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@sheet/crypto": {
|
"@sheet/crypto": {
|
||||||
"version": "1.20211122.1",
|
"version": "file:../../../RESOURCES/libs/0fae52415ec93a6dd3d5bd33c73b6dc124a07496be568a7aba6d033ec65df60b.tgz",
|
||||||
"resolved": "https://pylon.sheetjs.com:54111/@sheet%2fcrypto/-/crypto-1.20211122.1.tgz",
|
|
||||||
"integrity": "sha512-G3/HWyzFUYbbVQoQIa+KSeMOhFnK492Ep595FXbzWN9IGZSwuvFl4saEyMl8R8pE2Al5YgSZuR9MpDpx3f7Izg=="
|
"integrity": "sha512-G3/HWyzFUYbbVQoQIa+KSeMOhFnK492Ep595FXbzWN9IGZSwuvFl4saEyMl8R8pE2Al5YgSZuR9MpDpx3f7Izg=="
|
||||||
},
|
},
|
||||||
"@sideway/address": {
|
"@sideway/address": {
|
||||||
|
@ -51,7 +51,6 @@
|
|||||||
"@handsontable/angular": "^13.1.0",
|
"@handsontable/angular": "^13.1.0",
|
||||||
"@sasjs/adapter": "4.10.2",
|
"@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",
|
||||||
|
@ -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)
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
@ -940,13 +940,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)
|
||||||
@ -961,20 +978,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])
|
||||||
}
|
}
|
||||||
@ -1929,13 +1956,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
|
||||||
})
|
})
|
||||||
@ -2030,13 +2057,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
|
||||||
})
|
})
|
||||||
@ -2679,13 +2706,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
|
||||||
})
|
})
|
||||||
@ -2919,6 +2946,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) => {
|
||||||
@ -3274,28 +3332,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) => {
|
||||||
|
@ -878,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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) => {
|
||||||
|
@ -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')
|
||||||
|
}
|
||||||
|
}
|
@ -713,6 +713,11 @@ clr-icon.is-info {
|
|||||||
border: 1px solid red !important;
|
border: 1px solid red !important;
|
||||||
color: #ffffff !important;
|
color: #ffffff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.handsontable .numericListbox {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
.margin-top-20 {
|
.margin-top-20 {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dcfrontend",
|
"name": "dcfrontend",
|
||||||
"version": "6.6.1",
|
"version": "6.7.0",
|
||||||
"description": "Data Controller",
|
"description": "Data Controller",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@saithodev/semantic-release-gitea": "^2.1.0",
|
"@saithodev/semantic-release-gitea": "^2.1.0",
|
||||||
|
15
sas/package-lock.json
generated
15
sas/package-lock.json
generated
@ -229,12 +229,6 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/tough-cookie": {
|
|
||||||
"version": "4.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
|
|
||||||
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
|
|
||||||
"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",
|
||||||
@ -1933,12 +1927,6 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/tough-cookie": {
|
|
||||||
"version": "4.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
|
|
||||||
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
|
|
||||||
"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",
|
||||||
|
@ -1874,6 +1874,16 @@ insert into &lib..MPE_VALIDATIONS set
|
|||||||
,rule_value="services/validations/columns_in_libds"
|
,rule_value="services/validations/columns_in_libds"
|
||||||
,rule_active=1
|
,rule_active=1
|
||||||
,tx_to='31DEC5999:23:59:59'dt;
|
,tx_to='31DEC5999:23:59:59'dt;
|
||||||
|
/* test softselect on numeric var (should be ordered numerically) */
|
||||||
|
insert into &lib..MPE_VALIDATIONS set
|
||||||
|
tx_from=0
|
||||||
|
,base_lib="&lib"
|
||||||
|
,base_ds="MPE_X_TEST"
|
||||||
|
,base_col="SOME_BESTNUM"
|
||||||
|
,rule_type='SOFTSELECT'
|
||||||
|
,rule_value="&lib..MPE_X_TEST.SOME_BESTNUM"
|
||||||
|
,rule_active=1
|
||||||
|
,tx_to='31DEC5999:23:59:59'dt;
|
||||||
insert into &lib..MPE_VALIDATIONS set
|
insert into &lib..MPE_VALIDATIONS set
|
||||||
tx_from=0
|
tx_from=0
|
||||||
,base_lib="&lib"
|
,base_lib="&lib"
|
||||||
|
@ -631,9 +631,14 @@ create table dqdata as
|
|||||||
select distinct "&&base_col&x" as base_col length=32
|
select distinct "&&base_col&x" as base_col length=32
|
||||||
,"&source" as rule_value length=74
|
,"&source" as rule_value length=74
|
||||||
,cats(&col) as rule_data length=1000
|
,cats(&col) as rule_data length=1000
|
||||||
,0 as selectbox_order
|
,&col as tmp_order
|
||||||
from &lib..&ds
|
from &lib..&ds
|
||||||
order by 1;
|
order by tmp_order;
|
||||||
|
/* ensure both numerics and char vals are ordered correctly */
|
||||||
|
data work.dqdata&x (drop=tmp_order);
|
||||||
|
set work.dqdata&x;
|
||||||
|
selectbox_order=_n_;
|
||||||
|
run;
|
||||||
%mp_abort(iftrue= (&syscc ne 0)
|
%mp_abort(iftrue= (&syscc ne 0)
|
||||||
,mac=&_program
|
,mac=&_program
|
||||||
,msg=%str(syscc=&syscc when selecting &&base_col&x from &orig_libds)
|
,msg=%str(syscc=&syscc when selecting &&base_col&x from &orig_libds)
|
||||||
|
Reference in New Issue
Block a user