Compare commits
113 Commits
v6.1.0
...
developmen
Author | SHA1 | Date |
---|---|---|
Mihajlo Medjedovic | b4e5dd74d9 | |
allan | 64746b0aae | |
Mihajlo Medjedovic | 16ae77c804 | |
Mihajlo Medjedovic | 481c14f066 | |
allan | 357b9849e7 | |
Mihajlo Medjedovic | 0c8a9eef32 | |
Mihajlo Medjedovic | 112b1d0da4 | |
Mihajlo Medjedovic | a05007416a | |
allan | 9f7dd55583 | |
Allan | 11b06f6416 | |
Allan | adb7eb7755 | |
Mihajlo Medjedovic | b776b80728 | |
Mihajlo Medjedovic | 73a149ea7b | |
Mihajlo Medjedovic | ef8784093b | |
semantic-release-bot | b30c788e3d | |
mihajlo | 23899bdff3 | |
Mihajlo Medjedovic | 8bd0dd22c2 | |
semantic-release-bot | c55b00c74f | |
mihajlo | c895f509b0 | |
Mihajlo Medjedovic | 5968915331 | |
Mihajlo Medjedovic | 44ffc082f6 | |
semantic-release-bot | b716ae5675 | |
mihajlo | 01a0b59494 | |
Mihajlo Medjedovic | 8ebc3da0bb | |
Mihajlo Medjedovic | 133577a4fa | |
Mihajlo Medjedovic | a19615db41 | |
Mihajlo Medjedovic | 32b212a6bf | |
semantic-release-bot | 00ec4529cd | |
mihajlo | 102d03888f | |
mihajlo | 9f8247320e | |
Mihajlo Medjedovic | ef871de30e | |
semantic-release-bot | b3a15ce26b | |
allan | 270695aec2 | |
allan | ad7392a326 | |
Allan | 92a50a42e2 | |
semantic-release-bot | a3a8856d8c | |
allan | 150c19b1b0 | |
allan | f04c51ee4e | |
Allan | c4338bf957 | |
Allan | 5b06f4ede8 | |
Allan | e7ab2cc956 | |
Allan | 5ebf8a66f7 | |
Allan | 3d4e886b9b | |
Allan | a00d31caf3 | |
allan | 40fe707287 | |
Allan | 8296be01ba | |
semantic-release-bot | dbeb003292 | |
allan | f048501c48 | |
allan | 498350b3f3 | |
Mihajlo Medjedovic | 91e82c9c65 | |
Mihajlo Medjedovic | 24067ea82b | |
semantic-release-bot | aa7deddba0 | |
allan | b2d13203d1 | |
mihajlo | 19c1092b5b | |
Mihajlo Medjedovic | 94ab949df8 | |
Mihajlo Medjedovic | 9eb2451c2f | |
zver | 6e521bfa3e | |
Mihajlo Medjedovic | 239720fe0c | |
Mihajlo Medjedovic | 8571e01e44 | |
Mihajlo Medjedovic | 6f482ec6d9 | |
Mihajlo Medjedovic | 5e30dc0f89 | |
semantic-release-bot | 3d76d12c86 | |
allan | 93f1b81d70 | |
allan | e2b65ddd82 | |
allan | b64bbe91d4 | |
Allan | 1f2ce55f24 | |
Mihajlo Medjedovic | 921157da9e | |
allan | 413acf7d05 | |
Mihajlo Medjedovic | 725f75aa74 | |
Mihajlo Medjedovic | c64ab8a577 | |
Mihajlo Medjedovic | 1154c99e0a | |
dcbot | 5bcdef77b8 | |
semantic-release-bot | e4a0089102 | |
Mihajlo Medjedovic | ba022d8a35 | |
semantic-release-bot | c8ac859d2e | |
allan | d6fd72e880 | |
allan | 61cc360c85 | |
Allan | 08e39c4fca | |
allan | 8c2ee441fc | |
allan | 0a7d23c763 | |
Mihajlo Medjedovic | 7b54fff26e | |
Mihajlo Medjedovic | cd3e0f614b | |
Mihajlo Medjedovic | 81c0aec202 | |
Mihajlo Medjedovic | 3193bdd720 | |
Mihajlo Medjedovic | b9a12454e1 | |
Mihajlo Medjedovic | 01a857f7c6 | |
Mihajlo Medjedovic | 2bb2eee80e | |
Mihajlo Medjedovic | c054ea500d | |
Mihajlo Medjedovic | d7f8201246 | |
Mihajlo Medjedovic | 622cfcc6fe | |
Mihajlo Medjedovic | 303240e4d2 | |
Mihajlo Medjedovic | a8b849aede | |
Mihajlo Medjedovic | ca281b70c9 | |
Mihajlo Medjedovic | e056ece223 | |
allan | 0ae35214fb | |
Mihajlo Medjedovic | 34ffac39cb | |
Mihajlo Medjedovic | 52d4b3eefc | |
Mihajlo Medjedovic | 908d2761f2 | |
mihajlo | 7c98ad8c5b | |
Mihajlo Medjedovic | 5bb55e6484 | |
Mihajlo Medjedovic | e48e47bc63 | |
allan | 2a3f4f755c | |
Mihajlo Medjedovic | ddfae9227c | |
mihajlo | c74378423d | |
Mihajlo Medjedovic | 0e020623a5 | |
Mihajlo Medjedovic | eeb4000efe | |
mihajlo | 2053c858ac | |
Mihajlo Medjedovic | 4c2c5d5526 | |
Mihajlo Medjedovic | 7ccc745eb2 | |
Mihajlo Medjedovic | e93367fb42 | |
allan | 250fb1e8cf | |
Mihajlo Medjedovic | 1c2b87940d | |
Allan | 25fed203c6 |
|
@ -1,5 +1,5 @@
|
|||
name: Build
|
||||
run-name: Building and testing DC
|
||||
run-name: Running Lint Check
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
|
@ -18,21 +18,8 @@ jobs:
|
|||
env:
|
||||
NPMRC: ${{ secrets.NPMRC}}
|
||||
|
||||
- run: apt-get update
|
||||
- run: wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
|
||||
- run: apt install -y ./google-chrome*.deb;
|
||||
- run: export CHROME_BIN=/usr/bin/google-chrome
|
||||
- run: npm run lint:check
|
||||
# Install dependencies~
|
||||
- run: npm ci
|
||||
# Audit should fail and stop the CI if critical vulnerability found
|
||||
- run: npm audit --audit-level=critical
|
||||
- run: |
|
||||
cd ./sas
|
||||
npm audit --audit-level=critical
|
||||
- run: |
|
||||
cd ./client
|
||||
npm audit --audit-level=critical
|
||||
npm test -- --no-watch --no-progress --browsers=ChromeHeadlessCI
|
||||
npm run postinstall
|
||||
npm run build
|
||||
cd client
|
||||
npm ci
|
||||
npm run license-checker
|
|
@ -6,6 +6,56 @@ on:
|
|||
- development
|
||||
|
||||
jobs:
|
||||
Build-production-and-ng-test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Write .npmrc file
|
||||
run: |
|
||||
touch client/.npmrc
|
||||
echo '${{ secrets.NPMRC}}' > client/.npmrc
|
||||
|
||||
- name: Install Chrome for Angular tests
|
||||
run: |
|
||||
apt-get update
|
||||
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
|
||||
apt install -y ./google-chrome*.deb;
|
||||
export CHROME_BIN=/usr/bin/google-chrome
|
||||
|
||||
- name: Write cypress credentials
|
||||
run: echo "$CYPRESS_CREDS" > ./client/cypress.env.json
|
||||
shell: bash
|
||||
env:
|
||||
CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Check audit
|
||||
# Audit should fail and stop the CI if critical vulnerability found
|
||||
run: |
|
||||
npm audit --audit-level=critical --omit=dev
|
||||
cd ./sas
|
||||
npm audit --audit-level=critical --omit=dev
|
||||
cd ../client
|
||||
npm audit --audit-level=critical --omit=dev
|
||||
|
||||
- name: Angular Tests
|
||||
run: |
|
||||
cd client
|
||||
npm test -- --no-watch --no-progress --browsers=ChromeHeadlessCI
|
||||
|
||||
- name: Angular Production Build
|
||||
run: |
|
||||
cd client
|
||||
npm run postinstall
|
||||
npm run build
|
||||
|
||||
Build-and-test-development:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
@ -34,7 +84,9 @@ jobs:
|
|||
env:
|
||||
CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }}
|
||||
|
||||
- run: npm ci
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
# Install pm2 and prepare SASJS server
|
||||
- run: npm i -g pm2
|
||||
- run: curl -L https://github.com/sasjs/server/releases/latest/download/linux.zip > linux.zip
|
||||
|
@ -77,6 +129,17 @@ jobs:
|
|||
# Start frontend and run cypress
|
||||
npm start & npx wait-on http://localhost:4200 && npx cypress run --browser chrome --spec "cypress/e2e/liveness.cy.ts,cypress/e2e/editor.cy.ts,cypress/e2e/excel.cy.ts,cypress/e2e/filtering.cy.ts,cypress/e2e/licensing.cy.ts"
|
||||
|
||||
- name: Zip Cypress videos
|
||||
if: always()
|
||||
run: |
|
||||
zip -r cypress-videos ./client/cypress/videos
|
||||
|
||||
- name: Cypress videos artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: cypress-videos.zip
|
||||
path: cypress-videos.zip
|
||||
|
||||
Build-and-test-development-latest-adapter:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
@ -106,7 +169,9 @@ jobs:
|
|||
env:
|
||||
CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }}
|
||||
|
||||
- run: npm ci
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
# Install pm2 and prepare SASJS server
|
||||
- run: npm i -g pm2
|
||||
- run: curl -L https://github.com/sasjs/server/releases/latest/download/linux.zip > linux.zip
|
||||
|
@ -125,7 +190,6 @@ jobs:
|
|||
npm install -g @sasjs/cli
|
||||
npm install -g replace-in-files-cli
|
||||
sasjs cbd -t server-ci
|
||||
# sasjs request services/admin/makedata -t server-ci -d ./deploy/makeData4GL.json -c ./deploy/requestConfig.json -o ./output.json
|
||||
|
||||
- name: Install ZIP
|
||||
run: |
|
||||
|
@ -148,4 +212,15 @@ jobs:
|
|||
replace-in-files --regex='"hosturl".*' --replacement='hosturl:"http://localhost:4200",' ./cypress.config.ts
|
||||
cat ./cypress.config.ts
|
||||
# Start frontend and run cypress
|
||||
npm start & npx wait-on http://localhost:4200 && npx cypress run --browser chrome --spec "cypress/e2e/liveness.cy.ts,cypress/e2e/editor.cy.ts,cypress/e2e/excel.cy.ts,cypress/e2e/filtering.cy.ts,cypress/e2e/licensing.cy.ts"
|
||||
npm start & npx wait-on http://localhost:4200 && npx cypress run --browser chrome --spec "cypress/e2e/liveness.cy.ts,cypress/e2e/editor.cy.ts,cypress/e2e/excel.cy.ts,cypress/e2e/filtering.cy.ts,cypress/e2e/licensing.cy.ts"
|
||||
|
||||
- name: Zip Cypress videos
|
||||
if: always()
|
||||
run: |
|
||||
zip -r cypress-videos ./client/cypress/videos
|
||||
|
||||
- name: Cypress videos artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: cypress-videos-latest-adapter.zip
|
||||
path: cypress-videos.zip
|
|
@ -22,35 +22,35 @@ jobs:
|
|||
env:
|
||||
NPMRC: ${{ secrets.NPMRC}}
|
||||
|
||||
- name: Install ZIP and SASjs CLI
|
||||
- name: Install packages
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install zip -y
|
||||
# sasjs cli is used to compile & build the SAS services
|
||||
npm i -g @sasjs/cli
|
||||
# test
|
||||
|
||||
- name: Install JQ for parsing JSON
|
||||
run: |
|
||||
apt-get update
|
||||
# jq is used to parse the release JSON
|
||||
apt-get install jq -y
|
||||
|
||||
- name: Install Semantic Release and plugins and create Release
|
||||
- name: Create Empty Release (assets are posted later)
|
||||
run: |
|
||||
npm i
|
||||
npm i -g semantic-release
|
||||
# We do a semantic-release DRY RUN to make the job fail if there are no changes to release
|
||||
GITEA_TOKEN=${{ secrets.RELEASE_TOKEN }} GITEA_URL=https://git.datacontroller.io semantic-release --dry-run | grep -q "There are no relevant changes, so no new version is released." && exit 1
|
||||
GITEA_TOKEN=${{ secrets.RELEASE_TOKEN }} GITEA_URL=https://git.datacontroller.io semantic-release
|
||||
|
||||
- name: Frontend Build
|
||||
description: Must be created AFTER the release as the version (git tag) is used in the interface
|
||||
run: |
|
||||
cd client
|
||||
npm ci
|
||||
npm run build
|
||||
|
||||
- name: Build SAS9 EBI Release
|
||||
description: compile SAS 9 services, remove tests & create deployment program
|
||||
description: Compile SAS 9 services, remove tests & create deployment program
|
||||
run: |
|
||||
cd sas
|
||||
npm ci
|
||||
npm i
|
||||
sasjs c -t sas9
|
||||
rm -rf sasjsbuild/tests
|
||||
sasjs b -t sas9
|
||||
|
@ -64,7 +64,7 @@ jobs:
|
|||
cp sasjsbuild/mysas9deploy.sas ./sas9.sas
|
||||
|
||||
- name: Build SASjs Server Release
|
||||
description: compile Base (SASjs) services, remove tests & create deployment JSON
|
||||
description: Compile Base (SASjs) services, remove tests & create deployment JSON
|
||||
run: |
|
||||
cd sas
|
||||
cp sasjs/utils/favicon.ico ../client/dist/favicon.ico
|
||||
|
@ -98,12 +98,17 @@ jobs:
|
|||
- name: Release Typedoc
|
||||
run: |
|
||||
cd client
|
||||
npm run typedoc
|
||||
# deploy docs
|
||||
npm -g install cloudron-surfer
|
||||
npm run compodoc:build
|
||||
surfer put --token ${{ secrets.TSDOC_TOKEN }} --server webdoc.datacontroller.io documentation/* /
|
||||
|
||||
- name: Upload assets to release
|
||||
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_BODY=`curl -k 'https://git.datacontroller.io/api/v1/repos/dc/dc/releases/latest?access_token=${{ secrets.RELEASE_TOKEN }}' | jq -r '.body'`
|
||||
# Update body
|
||||
curl --data '{"draft": false,"body":"'"$RELEASE_BODY\n\nFor installation instructions, please visit https://docs.datacontroller.io/"'"}' -X PATCH --header 'Content-Type: application/json' -k https://git.datacontroller.io/api/v1/repos/dc/dc/releases/$RELEASE_ID?access_token=${{ secrets.RELEASE_TOKEN }}
|
||||
# Upload assets
|
||||
URL="https://git.datacontroller.io/api/v1/repos/dc/dc/releases/$RELEASE_ID/assets?access_token=${{ secrets.RELEASE_TOKEN }}"
|
||||
curl -k $URL -F attachment=@frontend.zip
|
||||
curl -k $URL -F attachment=@sas/demostream_sas9.sas
|
||||
|
|
|
@ -10,6 +10,7 @@ client/src/environments/version.ts
|
|||
client/cypress/screenshots
|
||||
client/cypress/results
|
||||
client/cypress/videos
|
||||
client/documentation
|
||||
cypress.env.json
|
||||
sasjsbuild
|
||||
sasjsresults
|
||||
|
|
|
@ -6,11 +6,13 @@
|
|||
"@semantic-release/commit-analyzer",
|
||||
"@semantic-release/release-notes-generator",
|
||||
"@semantic-release/changelog",
|
||||
"@semantic-release/npm",
|
||||
[
|
||||
"@semantic-release/git",
|
||||
{
|
||||
"assets": [
|
||||
"CHANGELOG.md"
|
||||
"CHANGELOG.md",
|
||||
"package.json"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
|
31
CHANGELOG.md
31
CHANGELOG.md
|
@ -1,3 +1,34 @@
|
|||
## [6.2.2](https://git.datacontroller.io/dc/dc/compare/v6.2.1...v6.2.2) (2023-10-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* updated SheetJS (crypto) to the latest ([8bd0dd2](https://git.datacontroller.io/dc/dc/commit/8bd0dd22c258911672303869e4df893a98e93575))
|
||||
|
||||
## [6.2.1](https://git.datacontroller.io/dc/dc/compare/v6.2.0...v6.2.1) (2023-10-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* approve, history and submit pages grouped in review module ([e056ece](https://git.datacontroller.io/dc/dc/commit/e056ece2234ef6aab050f6a5b1f8de633b163d91))
|
||||
* closes [#39](https://git.datacontroller.io/dc/dc/issues/39) upcase issue in MPE_SECURITY ([a00d31c](https://git.datacontroller.io/dc/dc/commit/a00d31caf3c5634cd61a4700fb175e76856edbb6))
|
||||
* handsontable v13 ([6f482ec](https://git.datacontroller.io/dc/dc/commit/6f482ec6d909907a304ef9975262889e2370035f))
|
||||
* latest adapter ([5e30dc0](https://git.datacontroller.io/dc/dc/commit/5e30dc0f892fab2af41f4ea56e30f27ec3b3912e))
|
||||
* sasjs/cli and sasjs/core updated to the latest ([8571e01](https://git.datacontroller.io/dc/dc/commit/8571e01e44a8cb6df9d150d271c34bb75bffdf31))
|
||||
* updating editors/stagedata to address issues in particular viya configurations as described in issue [#33](https://git.datacontroller.io/dc/dc/issues/33) ([94ab949](https://git.datacontroller.io/dc/dc/commit/94ab949df8c75072525751a2156b7a32c2e641dc))
|
||||
* updating logic for REPLACE loadtype ([1f2ce55](https://git.datacontroller.io/dc/dc/commit/1f2ce55f249f4af56f0cacdec47e69246cd47431))
|
||||
|
||||
# [6.2.0](https://git.datacontroller.io/dc/dc/compare/v6.1.0...v6.2.0) (2023-08-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* re-enabling full REPLACE uploads ([08e39c4](https://git.datacontroller.io/dc/dc/commit/08e39c4fca570406f9aad3d907cb04596421d074))
|
||||
|
||||
### Features
|
||||
|
||||
* support for European numeric formats ([e48e47b](https://git.datacontroller.io/dc/dc/commit/e48e47bc635452b59e107b235e597c26e748875e))
|
||||
|
||||
# [6.1.0](https://git.datacontroller.io/dc/dc/compare/v6.0.0...v6.1.0) (2023-07-25)
|
||||
|
||||
|
||||
|
|
|
@ -53,6 +53,17 @@ npm run lint:fix
|
|||
Typedoc is used for generating typescript documentation based on the code.
|
||||
That part is automated and beign done as a part of CI job.
|
||||
|
||||
# Release
|
||||
Release is automated as a part of CI job. Workflow file: `.gitea/workflows/release.yaml`.
|
||||
It will run automatically when branch merged to the `main` branch.
|
||||
IMPORTANT!
|
||||
If release job fails, after it has been created empty release and a tag, we must not re-run the relase job until we removed the newly create GIT TAG and RELEASE.
|
||||
To remove the git tag run:
|
||||
```
|
||||
git push -d origin vX.X.X
|
||||
```
|
||||
To remove the release, you need to do it with repo administration over at [https://git.datacontroller.io/dc/dc](https://git.datacontroller.io/dc/dc)
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
## Makedata service "could not create directory" error
|
||||
|
|
|
@ -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@13.0.0;handsontable@13.0.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.6.0;jackspeak@2.2.0;path-scurry@1.7.0'
|
||||
},
|
||||
(error, json) => {
|
||||
if (error) {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "dc-client",
|
||||
"description": "dc-client",
|
||||
"name": "data_controller-client",
|
||||
"description": "DataController Client",
|
||||
"angular-cli": {},
|
||||
"scripts": {
|
||||
"start": "node --max_old_space_size=4096 node_modules/@angular/cli/bin/ng serve",
|
||||
|
@ -29,7 +29,9 @@
|
|||
"cy:run": "cypress run",
|
||||
"audit:prod": "npm audit --omit=dev",
|
||||
"sasdocs": "sasjs doc && ./sasjs/utils/deploydocs.sh",
|
||||
"typedoc": "typedoc --options typedoc.json && cd ../dc-devdocs"
|
||||
"compodoc:build": "compodoc -p tsconfig.doc.json --name 'Data Controller Client'",
|
||||
"compodoc:build-and-serve": "compodoc -p tsconfig.doc.json -s --name 'Data Controller Client'",
|
||||
"compodoc:serve": "compodoc -s --name 'Data Controller Client'"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
@ -46,9 +48,9 @@
|
|||
"@clr/angular": "^13.17.0",
|
||||
"@clr/icons": "^13.0.2",
|
||||
"@clr/ui": "^13.17.0",
|
||||
"@handsontable/angular": "^13.0.0",
|
||||
"@sasjs/adapter": "4.3.6",
|
||||
"@sasjs/utils": "^3.3.0",
|
||||
"@handsontable/angular": "^13.1.0",
|
||||
"@sasjs/adapter": "4.10.1",
|
||||
"@sasjs/utils": "^3.4.0",
|
||||
"@sheet/crypto": "1.20211122.1",
|
||||
"@types/d3-graphviz": "^2.6.7",
|
||||
"@types/text-encoding": "0.0.35",
|
||||
|
@ -58,7 +60,7 @@
|
|||
"crypto-js": "^3.3.0",
|
||||
"d3-graphviz": "^5.0.2",
|
||||
"fs-extra": "^7.0.1",
|
||||
"handsontable": "^13.0.0",
|
||||
"handsontable": "^13.1.0",
|
||||
"https-browserify": "1.0.0",
|
||||
"hyperformula": "^2.5.0",
|
||||
"iconv-lite": "^0.5.0",
|
||||
|
@ -69,7 +71,6 @@
|
|||
"ngx-clipboard": "^16.0.0",
|
||||
"ngx-json-viewer": "file:libraries/ngx-json-viewer-3.2.1.tgz",
|
||||
"nodejs": "0.0.0",
|
||||
"numbro": "^2.1.1",
|
||||
"os-browserify": "0.3.0",
|
||||
"rxjs": "^7.8.0",
|
||||
"save-svg-as-png": "^1.4.17",
|
||||
|
@ -88,6 +89,8 @@
|
|||
"@angular-eslint/template-parser": "16.0.3",
|
||||
"@angular/cli": "^16.1.0",
|
||||
"@angular/compiler-cli": "^16.1.2",
|
||||
"@babel/plugin-proposal-private-methods": "^7.18.6",
|
||||
"@compodoc/compodoc": "^1.1.21",
|
||||
"@cypress/webpack-preprocessor": "^5.17.1",
|
||||
"@types/core-js": "^2.5.5",
|
||||
"@types/crypto-js": "^4.0.1",
|
||||
|
@ -120,9 +123,10 @@
|
|||
"rimraf": "3.0.2",
|
||||
"ts-loader": "^9.2.8",
|
||||
"ts-node": "^3.3.0",
|
||||
"typedoc": "^0.23.24",
|
||||
"typedoc": "^0.24.8",
|
||||
"typedoc-plugin-external-module-name": "^4.0.6",
|
||||
"typescript": "~4.9.4",
|
||||
"wait-on": "^6.0.1",
|
||||
"watch": "^1.0.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import { QueryClause } from './models/TableData'
|
||||
|
||||
/**
|
||||
* Filtering cache info, to be reused when filtering modal is re-open
|
||||
*/
|
||||
interface FilterCache {
|
||||
cols: any[]
|
||||
vals: any[]
|
||||
|
@ -10,12 +13,18 @@ interface FilterCache {
|
|||
query: QueryClause[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Filtering cache info in the open viewboxes, to be reused when filtering modal is re-open
|
||||
*/
|
||||
interface ViewboxCache {
|
||||
[key: number]: {
|
||||
filter: FilterCache
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial values when no cached values stored
|
||||
*/
|
||||
export const initFilter: { filter: FilterCache } = {
|
||||
filter: {
|
||||
cols: <any[]>[],
|
||||
|
@ -28,6 +37,13 @@ export const initFilter: { filter: FilterCache } = {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cached filtering values across whole app (editor, viewer, viewboxes)
|
||||
* Cached lineage libraries, tables
|
||||
* Cached metadata tree
|
||||
* Cached usernav tree
|
||||
* Cached viyaApi collections, search and selected endpoint
|
||||
*/
|
||||
export const globals: {
|
||||
rootParam: string
|
||||
editor: any
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
<div class="content-area">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex flex-column justify-content-center">
|
||||
<h3 class="text-center">
|
||||
You succesfully edited table
|
||||
<span class="color-blue font-weight-700">{{ libds }}</span>
|
||||
</h3>
|
||||
<p class="text-center">
|
||||
<b>Please choose from the following actions</b>
|
||||
</p>
|
||||
<div class="row d-flex justify-content-center mt-20">
|
||||
<button
|
||||
class="btn btn-sm btn-outline text-center"
|
||||
(click)="submittedTableScreen()"
|
||||
>
|
||||
Go to submitted table screen
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-outline text-center"
|
||||
(click)="viewerTableScreen()"
|
||||
>
|
||||
Go to base table screen
|
||||
</button>
|
||||
<button
|
||||
id="approvalBtn"
|
||||
class="btn btn-sm btn-success-outline text-center"
|
||||
(click)="approveTableScreen()"
|
||||
>
|
||||
Go to approvals screen
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-info-outline text-center"
|
||||
(click)="goBack()"
|
||||
>
|
||||
Go back to editor
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,50 +0,0 @@
|
|||
import { AfterViewInit, Component, OnInit } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
|
||||
@Component({
|
||||
selector: 'app-actions',
|
||||
templateUrl: './actions.component.html',
|
||||
styleUrls: ['./actions.component.scss'],
|
||||
host: {
|
||||
class: 'content-container'
|
||||
}
|
||||
})
|
||||
export class ActionsComponent implements OnInit, AfterViewInit {
|
||||
public dsid: any
|
||||
public libds: string | undefined
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
public submittedTableScreen() {
|
||||
this.router.navigateByUrl('/stage/' + this.dsid)
|
||||
}
|
||||
|
||||
public approveTableScreen() {
|
||||
this.router.navigateByUrl('/approve/approveDet/' + this.dsid)
|
||||
}
|
||||
|
||||
public viewerTableScreen() {
|
||||
this.router.navigateByUrl('/view/data/' + this.libds)
|
||||
}
|
||||
|
||||
public goBack() {
|
||||
this.router.navigateByUrl('/editor/' + this.libds)
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.dsid = this.route.snapshot.params['dsid']
|
||||
this.libds = this.route.snapshot.params['libds']
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
let approvalBtn: any = window.document.getElementById('approvalBtn')
|
||||
if (!!approvalBtn) {
|
||||
approvalBtn.focus()
|
||||
}
|
||||
}, 700)
|
||||
}
|
||||
}
|
|
@ -169,7 +169,7 @@
|
|||
<clr-dropdown-menu *clrIfOpen clrPosition="bottom-left">
|
||||
<a [routerLink]="['/view']" clrDropdownItem>VIEW</a>
|
||||
<a [routerLink]="['/home']" clrDropdownItem>EDIT</a>
|
||||
<a [routerLink]="['/submitted']" clrDropdownItem>REVIEW</a>
|
||||
<a [routerLink]="['/review/submitted']" clrDropdownItem>REVIEW</a>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
</div>
|
||||
|
@ -192,7 +192,7 @@
|
|||
>EDIT</a
|
||||
>
|
||||
<a
|
||||
[routerLink]="['/submitted']"
|
||||
[routerLink]="['/review/submitted']"
|
||||
[class.active]="
|
||||
router.url.includes('submitted') ||
|
||||
router.url.includes('approve') ||
|
||||
|
@ -224,7 +224,7 @@
|
|||
<ul class="nav">
|
||||
<li class="nav-item">
|
||||
<a
|
||||
[routerLink]="['/submitted']"
|
||||
[routerLink]="['/review/submitted']"
|
||||
class="nav-link nav-text"
|
||||
routerLinkActive="active"
|
||||
>SUBMIT</a
|
||||
|
@ -232,15 +232,16 @@
|
|||
</li>
|
||||
<li class="nav-item">
|
||||
<a
|
||||
[routerLink]="['/approve']"
|
||||
[routerLink]="['/review/approve']"
|
||||
class="nav-link nav-text"
|
||||
[class.active]="router.url.includes('approve')"
|
||||
routerLinkActive="active"
|
||||
>APPROVE</a
|
||||
>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a
|
||||
[routerLink]="['/history']"
|
||||
[routerLink]="['/review/history']"
|
||||
class="nav-link nav-text"
|
||||
routerLinkActive="active"
|
||||
>HISTORY</a
|
||||
|
|
|
@ -57,24 +57,15 @@ export class AppComponent {
|
|||
private elementRef: ElementRef
|
||||
) {
|
||||
this.parseDcAdapterSettings()
|
||||
|
||||
/**
|
||||
* Prints app info in the console such as:
|
||||
* - Adapter versions
|
||||
* - App version
|
||||
* - Build timestamp
|
||||
*
|
||||
*/
|
||||
;(window as any).appinfo = () => {
|
||||
const licenseKeyData = this.licenceService.getLicenseKeyData()
|
||||
|
||||
if (licenseKeyData) {
|
||||
const expiry_date = moment(
|
||||
licenseKeyData.valid_until,
|
||||
'YYYY-MM-DD'
|
||||
).startOf('day')
|
||||
const current_date = moment().startOf('day')
|
||||
const daysToExpiry = expiry_date.diff(current_date, 'days')
|
||||
|
||||
licenseKeyData.valid_until += ` (${daysToExpiry} ${
|
||||
daysToExpiry === 1 ? 'day' : 'days'
|
||||
} remaining)`
|
||||
|
||||
if (isNaN(daysToExpiry)) licenseKeyData.valid_until = 'Unlimited'
|
||||
}
|
||||
|
||||
console.table({
|
||||
'Adapter version': VERSION.adapterVersion || 'n/a',
|
||||
'App version': (VERSION.tag || '').replace('v', ''),
|
||||
|
@ -87,7 +78,12 @@ export class AppComponent {
|
|||
|
||||
this.subscribeToLicenseEvents()
|
||||
|
||||
/**
|
||||
* Fetches git tag ang git hash from `version.ts` file
|
||||
* It's placed in the user drop down.
|
||||
*/
|
||||
this.commitVer = (VERSION.tag || '').replace('v', '') + '.' + VERSION.hash
|
||||
|
||||
router.events.subscribe((val) => {
|
||||
this.routeUrl = this.router.url
|
||||
|
||||
|
@ -127,8 +123,10 @@ export class AppComponent {
|
|||
this.subscribeToAppActive()
|
||||
this.subscribeToDemoLimitModal()
|
||||
|
||||
/* In Viya streaming apps, content is served within an iframe. This code
|
||||
makes that iframe "full screen" so it looks like a regular window. */
|
||||
/**
|
||||
* In Viya streaming apps, content is served within an iframe. This code
|
||||
* makes that iframe "full screen" so it looks like a regular window.
|
||||
*/
|
||||
if (window.frameElement) {
|
||||
window.frameElement.setAttribute(
|
||||
'style',
|
||||
|
@ -143,6 +141,9 @@ export class AppComponent {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses adapter settings that are found in the <sasjs> tag inside index.html
|
||||
*/
|
||||
private parseDcAdapterSettings() {
|
||||
const sasjsElement = document.querySelector('sasjs')
|
||||
|
||||
|
@ -180,9 +181,14 @@ export class AppComponent {
|
|||
this.appService.sasServiceInit()
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens licence page with the active licence problem
|
||||
* Problem details are encoded in the url
|
||||
*/
|
||||
public licenceProblemDetails(url: string) {
|
||||
this.router.navigateByUrl(url)
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on string provided we return true, false or null
|
||||
* True -> Compute API
|
||||
|
@ -199,6 +205,12 @@ export class AppComponent {
|
|||
return value === 'true' || false
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for an `demo limit` event that will show the `Feature locked modal`
|
||||
* For exmaple when in editor upload feature is not enabled
|
||||
* When user tries to upload the excel, editor component will trgger this event
|
||||
* And stop the execution of file upload code.
|
||||
*/
|
||||
public subscribeToDemoLimitModal() {
|
||||
this.eventService.onDemoLimitModalShow.subscribe((featureName: string) => {
|
||||
this.demoLimitNotice = {
|
||||
|
@ -208,6 +220,10 @@ export class AppComponent {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for licence events so banner can be displayed.
|
||||
* App is free tier, licence will expire, is expired or is invalid
|
||||
*/
|
||||
public subscribeToLicenseEvents() {
|
||||
this.licenceService.isAppFreeTier.subscribe((isAppFreeTier: boolean) => {
|
||||
this.freeTierBanner = isAppFreeTier
|
||||
|
@ -227,6 +243,10 @@ export class AppComponent {
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for an event that will activate od deactivate full application.
|
||||
* Based on licence key prcoessing result
|
||||
*/
|
||||
public subscribeToAppActive() {
|
||||
this.licenceService.isAppActivated.subscribe((value: any) => {
|
||||
this.appActive = value
|
||||
|
@ -248,31 +268,51 @@ export class AppComponent {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* When startupservice request is finished with valid response, this event will
|
||||
* make sure loading screen is gone.
|
||||
*/
|
||||
public subscribeToStartupData() {
|
||||
this.eventService.onStartupDataLoaded.subscribe(() => {
|
||||
this.startupDataLoaded = true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens requests modal when requested from event service
|
||||
*/
|
||||
public subscribeToRequestsModal() {
|
||||
this.eventService.onRequestsModalOpen.subscribe((value: boolean) => {
|
||||
this.requestsModal = true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes abort modal with matching ID (there could be multiple abort modals open)
|
||||
*/
|
||||
public closeAbortModal(abortId: number) {
|
||||
let abortIndex = this.sasjsAborts.findIndex((abort) => abort.id === abortId)
|
||||
this.sasjsAborts.splice(abortIndex, 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles sidebar when requested from event service
|
||||
*/
|
||||
public toggleSidebar() {
|
||||
this.eventService.toggleSidebar()
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not current route includes the route from param
|
||||
* @param route route to check
|
||||
*/
|
||||
public isMainRoute(route: string): boolean {
|
||||
return this.router.url.includes(route)
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a page for updating the licence.
|
||||
*/
|
||||
public openLicencingPage() {
|
||||
this.router.navigateByUrl('/licensing/update')
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
declare module 'save-svg-as-png'
|
||||
|
||||
declare module 'numbro/dist/languages.min'
|
||||
declare interface Navigator {
|
||||
msSaveBlob: (blob: any, defaultName?: string) => boolean
|
||||
}
|
||||
|
|
|
@ -11,33 +11,16 @@ import { NotFoundComponent } from './not-found/not-found.component'
|
|||
|
||||
import { SasStoreService } from './services/sas-store.service'
|
||||
import { SharedModule } from './shared/shared.module'
|
||||
// import { EditorComponent } from './editor/editor.component'
|
||||
import { ActionsComponent } from './actions/actions.component'
|
||||
|
||||
import { AppSharedModule } from './app-shared.module'
|
||||
import { ApproveDetailsComponent } from './approve-details/approve-details.component'
|
||||
import { ApproveComponent } from './approve/approve.component'
|
||||
import { DeployComponent } from './deploy/deploy.component'
|
||||
import { AutomaticComponent } from './deploy/sections/automatic/automatic.component'
|
||||
import { ManualComponent } from './deploy/sections/manual/manual.component'
|
||||
import { SasjsConfiguratorComponent } from './deploy/sections/sasjs-configurator/sasjs-configurator.component'
|
||||
import { GroupComponent } from './group/group.component'
|
||||
import { HistoryComponent } from './history/history.component'
|
||||
import { LicensingComponent } from './licensing/licensing.component'
|
||||
import { LineageComponent } from './lineage/lineage.component'
|
||||
import { MetadataComponent } from './metadata/metadata.component'
|
||||
import { PipesModule } from './pipes/pipes.module'
|
||||
import { RoleComponent } from './role/role.component'
|
||||
import { ApproveRouteComponent } from './routes/approve-route/approve-route.component'
|
||||
import { HistoryRouteComponent } from './routes/history-route/history-route.component'
|
||||
import { ReviewRouteComponent } from './routes/review-route/review-route.component'
|
||||
import { LicensingGuard } from './routes/licensing.guard'
|
||||
import { UsernavRouteComponent } from './routes/usernav-route/usernav-route.component'
|
||||
import { AppService } from './services/app.service'
|
||||
import { InfoModalComponent } from './shared/abort-modal/info-modal.component'
|
||||
import { RequestsModalComponent } from './shared/requests-modal/requests-modal.component'
|
||||
import { SubmitterComponent } from './submitter/submitter.component'
|
||||
import { UserComponent } from './user/user.component'
|
||||
import { HomeModule } from './home/home.module'
|
||||
import { SystemComponent } from './system/system.component'
|
||||
import { DirectivesModule } from './directives/directives.module'
|
||||
import { ViyaApiExplorerComponent } from './viya-api-explorer/viya-api-explorer.component'
|
||||
import { NgxJsonViewerModule } from 'ngx-json-viewer'
|
||||
|
@ -46,27 +29,11 @@ import { NgxJsonViewerModule } from 'ngx-json-viewer'
|
|||
declarations: [
|
||||
AppComponent,
|
||||
NotFoundComponent,
|
||||
ApproveComponent,
|
||||
ApproveDetailsComponent,
|
||||
ActionsComponent,
|
||||
HistoryComponent,
|
||||
LineageComponent,
|
||||
SubmitterComponent,
|
||||
ApproveRouteComponent,
|
||||
HistoryRouteComponent,
|
||||
MetadataComponent,
|
||||
ReviewRouteComponent,
|
||||
ReviewRouteComponent,
|
||||
UsernavRouteComponent,
|
||||
UserComponent,
|
||||
GroupComponent,
|
||||
RoleComponent,
|
||||
RequestsModalComponent,
|
||||
DeployComponent,
|
||||
InfoModalComponent,
|
||||
LicensingComponent,
|
||||
ManualComponent,
|
||||
AutomaticComponent,
|
||||
SasjsConfiguratorComponent,
|
||||
SystemComponent,
|
||||
ViyaApiExplorerComponent
|
||||
],
|
||||
imports: [
|
||||
|
@ -84,7 +51,7 @@ import { NgxJsonViewerModule } from 'ngx-json-viewer'
|
|||
DirectivesModule,
|
||||
NgxJsonViewerModule
|
||||
],
|
||||
providers: [AppService, SasStoreService, ApproveComponent, LicensingGuard],
|
||||
providers: [AppService, SasStoreService, LicensingGuard],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {}
|
||||
|
|
|
@ -7,22 +7,20 @@ import { ModuleWithProviders } from '@angular/core'
|
|||
import { Routes, RouterModule } from '@angular/router'
|
||||
|
||||
import { HomeComponent } from './home/home.component'
|
||||
import { ApproveComponent } from './approve/approve.component'
|
||||
import { ApproveDetailsComponent } from './approve-details/approve-details.component'
|
||||
import { ActionsComponent } from './actions/actions.component'
|
||||
import { HistoryComponent } from './history/history.component'
|
||||
import { NotFoundComponent } from './not-found/not-found.component'
|
||||
import { SubmitterComponent } from './submitter/submitter.component'
|
||||
|
||||
import { ApproveRouteComponent } from './routes/approve-route/approve-route.component'
|
||||
import { DeployComponent } from './deploy/deploy.component'
|
||||
import { LicensingComponent } from './licensing/licensing.component'
|
||||
import { LicensingGuard } from './routes/licensing.guard'
|
||||
import { ReviewRouteComponent } from './routes/review-route/review-route.component'
|
||||
import { StageModule } from './stage/stage.module'
|
||||
import { EditorModule } from './editor/editor.module'
|
||||
import { ViewerModule } from './viewer/viewer.module'
|
||||
import { SystemComponent } from './system/system.component'
|
||||
import { ReviewModule } from './review/review.module'
|
||||
import { DeployModule } from './deploy/deploy.module'
|
||||
import { LicensingModule } from './licensing/licensing.module'
|
||||
import { SystemModule } from './system/system.module'
|
||||
|
||||
/**
|
||||
* Defining routes
|
||||
*/
|
||||
export const ROUTES: Routes = [
|
||||
{ path: '', redirectTo: 'home', pathMatch: 'full' },
|
||||
{
|
||||
|
@ -30,23 +28,28 @@ export const ROUTES: Routes = [
|
|||
loadChildren: () => ViewerModule
|
||||
},
|
||||
{
|
||||
path: 'approve',
|
||||
component: ApproveRouteComponent,
|
||||
/**
|
||||
* Load review module (approve, history, submitted)
|
||||
*/
|
||||
path: 'review',
|
||||
component: ReviewRouteComponent,
|
||||
children: [
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'toapprove' },
|
||||
{ path: 'toapprove', component: ApproveComponent },
|
||||
{ path: 'approveDet/:tableId', component: ApproveDetailsComponent },
|
||||
{ path: 'submitted', component: SubmitterComponent }
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () => ReviewModule
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'licensing/:action',
|
||||
component: LicensingComponent,
|
||||
canActivate: [LicensingGuard],
|
||||
canDeactivate: [LicensingGuard]
|
||||
path: 'licensing',
|
||||
loadChildren: () => LicensingModule
|
||||
},
|
||||
{ path: 'home', component: HomeComponent },
|
||||
{
|
||||
/**
|
||||
* Load editor module with subroutes
|
||||
*/
|
||||
path: 'editor',
|
||||
loadChildren: () => EditorModule
|
||||
},
|
||||
|
@ -54,16 +57,20 @@ export const ROUTES: Routes = [
|
|||
path: 'stage',
|
||||
loadChildren: () => StageModule
|
||||
},
|
||||
{ path: 'system', component: SystemComponent },
|
||||
{ path: 'actions/:libds/:dsid', component: ActionsComponent },
|
||||
{ path: 'history', component: HistoryComponent },
|
||||
{ path: 'submitted', component: SubmitterComponent },
|
||||
{ path: 'submitted/:tableId', component: SubmitterComponent },
|
||||
{ path: 'deploy', component: DeployComponent },
|
||||
{ path: 'deploy/manualdeploy', component: DeployComponent },
|
||||
{
|
||||
path: 'system',
|
||||
loadChildren: () => SystemModule
|
||||
},
|
||||
{
|
||||
path: 'deploy',
|
||||
loadChildren: () => DeployModule
|
||||
},
|
||||
{ path: '**', component: NotFoundComponent }
|
||||
]
|
||||
|
||||
/**
|
||||
* Exporting routes
|
||||
*/
|
||||
export const ROUTING: ModuleWithProviders<RouterModule> = RouterModule.forRoot(
|
||||
ROUTES,
|
||||
{ useHash: true }
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import { NgModule } from '@angular/core'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { DeployComponent } from './deploy.component'
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', component: DeployComponent },
|
||||
{ path: 'manualdeploy', component: DeployComponent }
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class DeployRoutingModule {}
|
|
@ -78,6 +78,9 @@ export class DeployComponent implements OnInit {
|
|||
this.setDeployDefaults()
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting default values used for deploy request
|
||||
*/
|
||||
public setDeployDefaults() {
|
||||
this.dcPath = this.dcAdapterSettings?.dcPath || ''
|
||||
this.selectedAdminGroup = this.dcAdapterSettings?.adminGroup || ''
|
||||
|
@ -86,6 +89,9 @@ export class DeployComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepting terms of service shows next screen
|
||||
*/
|
||||
public termsAgreeChange() {
|
||||
if (!this.autodeploy) {
|
||||
this.getAdminGroups()
|
||||
|
@ -94,6 +100,9 @@ export class DeployComponent implements OnInit {
|
|||
this.step++
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches admin groups from VIYA to be selected for a backend deploy
|
||||
*/
|
||||
public getAdminGroups() {
|
||||
fetch(
|
||||
this.sasJsConfig.serverUrl + '/identities/groups?sortBy=name&limit=5000',
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { DeployComponent } from './deploy.component'
|
||||
import { AutomaticComponent } from './sections/automatic/automatic.component'
|
||||
import { ManualComponent } from './sections/manual/manual.component'
|
||||
import { SasjsConfiguratorComponent } from './sections/sasjs-configurator/sasjs-configurator.component'
|
||||
import { ClarityModule } from '@clr/angular'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { DeployRoutingModule } from './deploy-routing.module'
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
DeployComponent,
|
||||
AutomaticComponent,
|
||||
ManualComponent,
|
||||
SasjsConfiguratorComponent
|
||||
],
|
||||
imports: [CommonModule, FormsModule, ClarityModule, DeployRoutingModule]
|
||||
})
|
||||
export class DeployModule {}
|
|
@ -55,6 +55,13 @@ export class AutomaticComponent implements OnInit {
|
|||
|
||||
ngOnInit(): void {}
|
||||
|
||||
/**
|
||||
* Executes sas.json file to deploy the backend
|
||||
* Method will first try to run the `auto deploy`
|
||||
* If that fails the rest of the code is ignored.
|
||||
* If request is successfull, method will continue to try
|
||||
* to create database if checkbox is toggled on
|
||||
*/
|
||||
public async executeJson() {
|
||||
this.autodeploying = true
|
||||
this.isSubmittingJson = true
|
||||
|
@ -99,6 +106,9 @@ export class AutomaticComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the `makedata` request sending the ADMIN and DCPATH values
|
||||
*/
|
||||
public createDatabase() {
|
||||
let data = {
|
||||
fromjs: [
|
||||
|
|
|
@ -52,6 +52,9 @@ export class ManualComponent implements OnInit {
|
|||
|
||||
ngOnInit(): void {}
|
||||
|
||||
/**
|
||||
* FIXME: Remove
|
||||
*/
|
||||
public async executableContext() {
|
||||
// getExecutableContexts now need AuthConfig parameter which we don't have on web
|
||||
// this.contextsLoading = true
|
||||
|
@ -65,10 +68,16 @@ export class ManualComponent implements OnInit {
|
|||
// this.contextsLoading = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes sas.json file attached to the input
|
||||
*/
|
||||
public clearUploadInput(event: Event) {
|
||||
this.deployService.clearUploadInput(event)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads attached SAS file to be sent to sas for execution (backend deploy)
|
||||
*/
|
||||
public onSasFileChange(event: any) {
|
||||
this.preloadedFile = false
|
||||
|
||||
|
@ -93,12 +102,18 @@ export class ManualComponent implements OnInit {
|
|||
fileReader.readAsText(file)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads attached JSON file to be sent to sas for execution (backend deploy)
|
||||
*/
|
||||
public async onJsonFileChange(event: any) {
|
||||
let file = event.target.files[0]
|
||||
|
||||
this.jsonFile = await this.deployService.readFile(file)
|
||||
}
|
||||
|
||||
/**
|
||||
* Appending precode lines to the attached sas or json file for backend deploy
|
||||
*/
|
||||
public addPrecodeLines() {
|
||||
let headerLines = [
|
||||
`%let context=${this.selectedContext};`,
|
||||
|
@ -110,6 +125,9 @@ export class ManualComponent implements OnInit {
|
|||
this.linesOfCode.unshift(...headerLines)
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloadng file with precode included
|
||||
*/
|
||||
public downloadSasPrecodeFile() {
|
||||
let linesAsText = this.linesOfCode.join('\n')
|
||||
let filename = this.fileName.split('.')[0]
|
||||
|
@ -117,6 +135,9 @@ export class ManualComponent implements OnInit {
|
|||
this.downloadFile(linesAsText, filename, 'sas')
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for downloading log and repsonse as a file
|
||||
*/
|
||||
public downloadFile(
|
||||
content: any,
|
||||
filename: string,
|
||||
|
@ -125,10 +146,17 @@ export class ManualComponent implements OnInit {
|
|||
this.deployService.downloadFile(content, filename, extension)
|
||||
}
|
||||
|
||||
/**
|
||||
* Saving dcpath to localstorage
|
||||
* FIXME: maybe it'snot necessary
|
||||
*/
|
||||
public saveDcPath() {
|
||||
localStorage.setItem('deploy_dc_loc', this.dcPath)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send sas.json to be executed (deploying backend)
|
||||
*/
|
||||
public async executeJson() {
|
||||
this.isSubmittingJson = true
|
||||
|
||||
|
@ -162,6 +190,9 @@ export class ManualComponent implements OnInit {
|
|||
this.isSubmittingJson = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Send sas file to be executed (deploying backend)
|
||||
*/
|
||||
public async executeSAS() {
|
||||
this.executingScript = true
|
||||
this.jobLog = ''
|
||||
|
@ -194,6 +225,10 @@ export class ManualComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Running makedata service
|
||||
* @param newTab open and run in new tab
|
||||
*/
|
||||
public createDatabase(newTab: boolean = true) {
|
||||
if (newTab) {
|
||||
let url =
|
||||
|
|
|
@ -5,7 +5,6 @@ import { ServerType } from '@sasjs/utils/types/serverType'
|
|||
import { DcAdapterSettings } from 'src/app/models/DcAdapterSettings'
|
||||
import { SASGroup } from 'src/app/models/sas/public-getgroups.model'
|
||||
import { SASjsApiServerInfo } from 'src/app/models/sasjs-api/SASjsApiServerInfo.model'
|
||||
import { HelperService } from 'src/app/services/helper.service'
|
||||
import { SasService } from 'src/app/services/sas.service'
|
||||
import { SasjsService } from 'src/app/services/sasjs.service'
|
||||
|
||||
|
@ -51,6 +50,9 @@ export class SasjsConfiguratorComponent implements OnInit {
|
|||
this.getServerInfo()
|
||||
}
|
||||
|
||||
/**
|
||||
* Fethes the sasjs server instance info
|
||||
*/
|
||||
getServerInfo() {
|
||||
this.sasjsService
|
||||
.getServerInfo()
|
||||
|
@ -59,6 +61,9 @@ export class SasjsConfiguratorComponent implements OnInit {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches user groups from the `usernav/usergroupsbymember` service
|
||||
*/
|
||||
getUserGroups() {
|
||||
this.loading = true
|
||||
|
||||
|
@ -99,6 +104,9 @@ export class SasjsConfiguratorComponent implements OnInit {
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creating database
|
||||
*/
|
||||
makeData() {
|
||||
// const _debug = "&_debug=131"; //debug on
|
||||
const _debug = ' ' //debug off
|
||||
|
|
|
@ -14,7 +14,9 @@ export class DragNdropDirective {
|
|||
@Output() fileDropped = new EventEmitter<any>()
|
||||
@Output() fileDraggedOver = new EventEmitter<any>()
|
||||
|
||||
// Dragover listener
|
||||
/**
|
||||
* Dragover listener
|
||||
*/
|
||||
@HostListener('dragover', ['$event'])
|
||||
onDragOver(event: any) {
|
||||
event.preventDefault()
|
||||
|
@ -26,7 +28,9 @@ export class DragNdropDirective {
|
|||
}
|
||||
}
|
||||
|
||||
// Dragleave listener
|
||||
/**
|
||||
* Dragleave listener
|
||||
*/
|
||||
@HostListener('dragleave', ['$event'])
|
||||
public onDragLeave(event: any) {
|
||||
event.preventDefault()
|
||||
|
@ -34,7 +38,9 @@ export class DragNdropDirective {
|
|||
this.fileOver = false
|
||||
}
|
||||
|
||||
// Drop listener
|
||||
/**
|
||||
* Drop listener
|
||||
*/
|
||||
@HostListener('drop', ['$event'])
|
||||
public ondrop(event: any) {
|
||||
event.preventDefault()
|
||||
|
@ -48,6 +54,9 @@ export class DragNdropDirective {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks wether dragging element contain files
|
||||
*/
|
||||
private containsFiles(event: any) {
|
||||
if (event && event.dataTransfer && event.dataTransfer.types) {
|
||||
for (let i = 0; i < event.dataTransfer.types.length; i++) {
|
||||
|
|
|
@ -22,6 +22,9 @@ export class FileDropDirective {
|
|||
this.element = element
|
||||
}
|
||||
|
||||
/**
|
||||
* Dragging element drop event
|
||||
*/
|
||||
@HostListener('drop', ['$event'])
|
||||
onDrop(event: DragEvent): void {
|
||||
this._preventAndStop(event)
|
||||
|
@ -39,6 +42,9 @@ export class FileDropDirective {
|
|||
this.fileDrop.emit(fileList)
|
||||
}
|
||||
|
||||
/**
|
||||
* Dragging element drag over event
|
||||
*/
|
||||
@HostListener('dragover', ['$event'])
|
||||
onDragOver(event: DragEvent): void {
|
||||
this._preventAndStop(event)
|
||||
|
@ -59,6 +65,10 @@ export class FileDropDirective {
|
|||
this.fileOver.emit(false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent propagation trough elements and stop default behavior
|
||||
* For particular event
|
||||
*/
|
||||
protected _preventAndStop(event: MouseEvent): void {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
|
|
@ -21,6 +21,9 @@ export class FileSelectDirective {
|
|||
this.element = element
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if files exist in the input after input change
|
||||
*/
|
||||
isEmptyAfterSelection(): boolean {
|
||||
return !!this.element.nativeElement.attributes.multiple
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
export interface RowValidation {
|
||||
valid: boolean
|
||||
invalidError: string
|
||||
rowNumber?: number
|
||||
colName?: string
|
||||
value?: string
|
||||
}
|
|
@ -59,6 +59,12 @@ export class EditRecordComponent implements OnInit {
|
|||
|
||||
ngOnInit(): void {}
|
||||
|
||||
/**
|
||||
* Runs native HOT validator against cell value
|
||||
* @param cellValidation column rules
|
||||
* @param cellValue value in the cell that is beign validated
|
||||
* @returns Promise boolean - wether valid or invalid
|
||||
*/
|
||||
async validateRecordCol(
|
||||
cellValidation: any,
|
||||
cellValue: any
|
||||
|
@ -74,6 +80,12 @@ export class EditRecordComponent implements OnInit {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when date field in the record change
|
||||
* Function will parse date and format to string
|
||||
* @param date picker value
|
||||
* @param colKey column name (key)
|
||||
*/
|
||||
recordDateChange(date: Date, colKey: string) {
|
||||
let cellValidation = this.currentRecordValidator?.getRule(colKey)
|
||||
let format = cellValidation ? cellValidation.dateFormat : ''
|
||||
|
@ -82,24 +94,38 @@ export class EditRecordComponent implements OnInit {
|
|||
this.currentRecord[colKey] = moment(date).format(format)
|
||||
}
|
||||
|
||||
isRecordModalInvalid(): boolean {
|
||||
return this.currentRecordInvalidCols.length > 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Close edit record modal and apply changes by emitting output event
|
||||
*/
|
||||
confirmRecordEdit() {
|
||||
if (this.currentRecordInvalidCols.length < 1) {
|
||||
this.onRecordChange.emit(this.currentRecord)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close edit record modal without applying the changes
|
||||
*/
|
||||
closeRecordEdit() {
|
||||
this.onRecordEditClose.emit()
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitting output event when dropdown (autocomplete) input in any col change
|
||||
* @param colName column name (key)
|
||||
* @param col column index
|
||||
*/
|
||||
onRecordDropdownChange(colName: string, col: number) {
|
||||
this.onRecordDropdownChanged.emit({ colName, col })
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitting output event when input is focused (clicked on) so we can run a `dynamic cell validation`
|
||||
* Since that bit must be run from the parent component (editor.component)
|
||||
* Result is then applied in the `cellValidation` variable and automatically updated in this component.
|
||||
* @param event input event
|
||||
* @param colName column name (key)
|
||||
*/
|
||||
onRecordInputFocus(event: any, colName: number) {
|
||||
this.onRecordInputFocused.emit({ event, colName })
|
||||
}
|
||||
|
|
|
@ -385,7 +385,6 @@
|
|||
[class.hidden]="hotTable.hidden"
|
||||
[licenseKey]="hotTable.licenseKey"
|
||||
>
|
||||
<!--[licenseKey]=""-->
|
||||
</hot-table>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -15,7 +15,15 @@ import { Subject, Subscription } from 'rxjs'
|
|||
import { SasStoreService } from '../services/sas-store.service'
|
||||
|
||||
import * as XLSX from '@sheet/crypto'
|
||||
|
||||
/**
|
||||
* Used in combination with buffer
|
||||
*/
|
||||
const iconv = require('iconv-lite')
|
||||
/**
|
||||
* In combination with `iconv` is used for encoding json data captured with sheet js from excel file into a file again
|
||||
* Which will be send to backend
|
||||
*/
|
||||
const Buffer = require('buffer/').Buffer
|
||||
type AOA = any[][]
|
||||
|
||||
|
@ -63,6 +71,8 @@ import {
|
|||
} from './utils/renderers.utils'
|
||||
import { isStringDecimal, isStringNumber } from './utils/types.utils'
|
||||
import { LicenceService } from '../services/licence.service'
|
||||
import * as numbro from 'numbro'
|
||||
import * as languages from 'numbro/dist/languages.min'
|
||||
|
||||
@Component({
|
||||
selector: 'app-editor',
|
||||
|
@ -362,12 +372,19 @@ export class EditorComponent implements OnInit, AfterViewInit {
|
|||
private cdf: ChangeDetectorRef,
|
||||
private hotRegisterer: HotTableRegisterer
|
||||
) {
|
||||
const lang = languages[window.navigator.language]
|
||||
if (lang)
|
||||
numbro.default.registerLanguage(languages[window.navigator.language])
|
||||
|
||||
this.hotRegisterer = new HotTableRegisterer()
|
||||
|
||||
this.parseRestrictions()
|
||||
this.setRestrictions()
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare feature restrictions based on licence key
|
||||
*/
|
||||
private parseRestrictions() {
|
||||
this.restrictions.restrictAddRecord =
|
||||
this.licenceState.value.addRecord === false
|
||||
|
@ -377,6 +394,10 @@ export class EditorComponent implements OnInit, AfterViewInit {
|
|||
this.licenceState.value.fileUpload === false
|
||||
}
|
||||
|
||||
/**
|
||||
* Applying prepared restrictions
|
||||
* @param overrideRestrictions can be used to apply and override specific restrictions
|
||||
*/
|
||||
private setRestrictions(overrideRestrictions?: EditorRestrictions) {
|
||||
if (overrideRestrictions) {
|
||||
this.restrictions = {
|
||||
|
@ -396,6 +417,9 @@ export class EditorComponent implements OnInit, AfterViewInit {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disabling add row button based on wether rows limit is present
|
||||
*/
|
||||
private checkRowLimit() {
|
||||
if (this.columnLevelSecurityFlag) return
|
||||
|
||||
|
@ -410,12 +434,19 @@ export class EditorComponent implements OnInit, AfterViewInit {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resetting filter variables
|
||||
*/
|
||||
public resetFilter() {
|
||||
if (this.queryFilterCompList.first) {
|
||||
this.queryFilterCompList.first.resetFilter()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Openning file upload modal
|
||||
* If feature is locked, `feature locked` modal will be shown
|
||||
*/
|
||||
public onShowUploadModal() {
|
||||
if (this.restrictions.restrictFileUpload) {
|
||||
this.eventService.showDemoLimitModal('File Upload')
|
||||
|
@ -433,6 +464,10 @@ export class EditorComponent implements OnInit, AfterViewInit {
|
|||
if (!this.uploadPreview) 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
|
||||
}
|
||||
|
@ -614,6 +649,10 @@ export class EditorComponent implements OnInit, AfterViewInit {
|
|||
return returnObj
|
||||
}
|
||||
|
||||
/**
|
||||
* When excel is password protected we will display the password promppt for user to type password in.
|
||||
* @returns Password user input or undefined if discarded by user
|
||||
*/
|
||||
public promptExcelPassword(): Promise<string | undefined> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.filePasswordModal = true
|
||||
|
@ -639,6 +678,13 @@ export class EditorComponent implements OnInit, AfterViewInit {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses attached file, to be uploaded
|
||||
* If attached file is CSV it will be send to backend straight away
|
||||
* If attached file is EXCEL it will be displayed in the table, in preview mode
|
||||
* @param event file drop event
|
||||
* @param dropped whether it's dropped or added by browse button
|
||||
*/
|
||||
public getFileDesc(event: any, dropped: boolean = false) {
|
||||
this.excelUploadState = 'Loading'
|
||||
this.excelFileParsing = true
|
||||
|
@ -1004,6 +1050,9 @@ export class EditorComponent implements OnInit, AfterViewInit {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits attached excel file that is in preview mode
|
||||
*/
|
||||
public submitExcel() {
|
||||
if (this.licenceState.value.submit_rows_limit !== Infinity) {
|
||||
this.submitLimitNotice = true
|
||||
|
@ -1013,6 +1062,9 @@ export class EditorComponent implements OnInit, AfterViewInit {
|
|||
this.getFile()
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will run validations and upload all of the pending files that are in the uploader queue
|
||||
*/
|
||||
public getFile() {
|
||||
if (this.checkInvalid()) {
|
||||
this.eventService.showAbortModal(null, 'Invalid values are present.')
|
||||
|
@ -1085,6 +1137,9 @@ export class EditorComponent implements OnInit, AfterViewInit {
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* After excel file is attached and parsed, this function will display it's content in the HOT table in read only mode
|
||||
*/
|
||||
public getPendingExcelPreview() {
|
||||
this.queryTextSaved = this.queryText
|
||||
this.queryText = ''
|
||||
|
@ -1136,46 +1191,12 @@ export class EditorComponent implements OnInit, AfterViewInit {
|
|||
this.excelFileParsing = false
|
||||
this.excelUploadState = null
|
||||
})
|
||||
|
||||
/**
|
||||
* This is half validation feature to speed up file upload
|
||||
* Currently disabled but will leave it here in case it needs to be re-enabled
|
||||
*/
|
||||
// this.excelUploadState = 'Validating-DQ'
|
||||
|
||||
// this.validateRowsOnPendingExcel(
|
||||
// async (rowValidation: RowValidation | undefined) => {
|
||||
// if (rowValidation) {
|
||||
// this.eventService.showAbortModal(
|
||||
// 'Excel validation',
|
||||
// `Please fix the data and re-submit the file. Invalid data details: <br><br> Row: ${rowValidation.rowNumber} <br> Column: ${rowValidation.colName} <br> Reason: <strong>${rowValidation.invalidError}</strong> <br> Invalid value: ${rowValidation.value}`
|
||||
// )
|
||||
|
||||
// this.excelFileParsing = false
|
||||
// this.excelUploadState = null
|
||||
// } else {
|
||||
// this.excelUploadState = 'Validating-HOT'
|
||||
|
||||
// hot.updateSettings(
|
||||
// {
|
||||
// data: this.dataSource
|
||||
// },
|
||||
// false
|
||||
// )
|
||||
// hot.render()
|
||||
|
||||
// hot.validateCells(() => {
|
||||
// this.showUploadModal = false
|
||||
// this.uploadPreview = true
|
||||
|
||||
// this.excelFileParsing = false
|
||||
// this.excelUploadState = null
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops the attached excel file
|
||||
* @param discardData wheter to discard data parsed from the file or to keep it in the table after dropping a attached excel file
|
||||
*/
|
||||
public discardPendingExcel(discardData?: boolean) {
|
||||
this.hotInstance.updateSettings({
|
||||
maxRows: this.licenceState.value.editor_rows_allowed
|
||||
|
@ -1199,6 +1220,10 @@ export class EditorComponent implements OnInit, AfterViewInit {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops attached excel file, keeps it's data in the DC table
|
||||
* User can now edit the table and submit. Witout the file present.
|
||||
*/
|
||||
public previewTableEditConfirm() {
|
||||
this.discardPendingExcel()
|
||||
this.convertToCorrectTypes(this.dataSource)
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import { DcValidation } from 'src/app/shared/dc-validator/models/dc-validation.model'
|
||||
|
||||
/**
|
||||
* Wrapper for DC Validation because we need `noLinkOption` property
|
||||
* to be used as a flag to show/hide button that generates link for the
|
||||
* edit record modal
|
||||
*/
|
||||
export interface EditRecordModal extends DcValidation {
|
||||
noLinkOption: boolean
|
||||
[key: string]: any
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
export interface CellValidation {
|
||||
data: string
|
||||
length: number
|
||||
type?: string
|
||||
source: string[]
|
||||
format?: number
|
||||
validator?: any
|
||||
valid?: boolean
|
||||
renderer?: any
|
||||
dateFormat?: string
|
||||
readOnly?: boolean
|
||||
desc?: string
|
||||
correctFormat?: boolean
|
||||
/**
|
||||
* Key for accessing object fields, any type because it can be
|
||||
* any of the types interface have
|
||||
*/
|
||||
[key: string]: any
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
export enum ColumnType {
|
||||
string = 'string',
|
||||
number = 'number'
|
||||
}
|
||||
|
||||
export interface ColumnInterface {
|
||||
id: number | undefined
|
||||
name: string | undefined
|
||||
type: ColumnType | undefined
|
||||
length: number | undefined
|
||||
}
|
||||
|
||||
export class Column implements ColumnInterface {
|
||||
public id: number | undefined
|
||||
public name: string | undefined
|
||||
public type: ColumnType | undefined
|
||||
public length: number | undefined
|
||||
public static fromPlainObject(obj: object) {
|
||||
return Object.assign(new Column(), obj)
|
||||
}
|
||||
|
||||
constructor(id?: number, name?: string, type?: ColumnType, length?: number) {
|
||||
this.id = id
|
||||
this.name = name
|
||||
this.type = type
|
||||
this.length = length
|
||||
}
|
||||
|
||||
get hsType() {
|
||||
return (
|
||||
(this.type === ColumnType.string && 'text') ||
|
||||
(this.type === ColumnType.number && 'numeric') ||
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Model for the dynamic cell validation in the editor
|
||||
* (sending whole row to the backend service and recieving data for the cell dropdown)
|
||||
*/
|
||||
export interface DynamicExtendedCellValidation {
|
||||
DISPLAY_INDEX: number
|
||||
DISPLAY_TYPE: string
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
/**
|
||||
* Edit record modal - input has been focused event
|
||||
*/
|
||||
export interface EditRecordInputFocusedEvent {
|
||||
event: any
|
||||
colName: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit record modal - dropdown has been changed event
|
||||
*/
|
||||
export interface EditRecordDropdownChangeEvent {
|
||||
colName: string
|
||||
col: number
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
/**
|
||||
* Editor restrictions model (based on the licencing)
|
||||
*/
|
||||
export interface EditorRestrictions {
|
||||
restrictEditRecord?: boolean // Feature is locked but edit/add record buttons are visible so when user clicks he gets the `locked feature modal`
|
||||
restrictAddRecord?: boolean // Same as editRecord, but for addRecord
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
import { Column, ColumnType } from './models/column'
|
||||
|
||||
export enum TableType {
|
||||
INPUT = 'In',
|
||||
OUTPUT = 'Out'
|
||||
}
|
||||
|
||||
export interface TableInterface {
|
||||
id: number | undefined
|
||||
name: string | undefined
|
||||
data: Array<Object>
|
||||
columns: Array<Column>
|
||||
type: TableType | undefined
|
||||
}
|
||||
|
||||
export class Table implements TableInterface {
|
||||
public id: number | undefined
|
||||
public name: string | undefined
|
||||
public data: Array<any>
|
||||
public columns: Array<Column> = []
|
||||
public type: TableType | undefined
|
||||
|
||||
public static fromPlainObject(obj: any) {
|
||||
obj.columns = obj.columns.map((column: object) => {
|
||||
return Column.fromPlainObject(column)
|
||||
})
|
||||
|
||||
return Object.assign(new Table(), obj)
|
||||
}
|
||||
|
||||
constructor(
|
||||
id?: number,
|
||||
name?: string,
|
||||
type?: TableType,
|
||||
data?: Array<Object>,
|
||||
columns?: Array<Column>
|
||||
) {
|
||||
this.id = id
|
||||
this.name = name
|
||||
this.type = type
|
||||
|
||||
this.data = data || [{}]
|
||||
this.columns = columns || []
|
||||
}
|
||||
|
||||
public getNextColumnId(): number {
|
||||
let highestIdColumn: any = this.columns.sort(
|
||||
(cA: any, cB: any) => cA.id - cB.id
|
||||
)[this.columns.length - 1]
|
||||
return (highestIdColumn && highestIdColumn.id + 1) || 0
|
||||
}
|
||||
|
||||
public addColumn(column: Column) {
|
||||
this.columns.push(column)
|
||||
|
||||
this.data.forEach((row: any) => {
|
||||
if (column.name) {
|
||||
row[column.name] = column.type === ColumnType.string ? '' : null
|
||||
}
|
||||
})
|
||||
|
||||
return column
|
||||
}
|
||||
|
||||
public removeColumn(colInd: any) {
|
||||
this.data.forEach((row) => {
|
||||
delete row[this.columns[colInd].name!]
|
||||
})
|
||||
this.columns.splice(colInd, 1)
|
||||
}
|
||||
}
|
|
@ -1,3 +1,6 @@
|
|||
/**
|
||||
* Converting date object to the UTC time string
|
||||
*/
|
||||
export const dateToUtcTime = (date: Date) => {
|
||||
let timeStr = ('0' + date.getUTCHours()).slice(-2) + ':'
|
||||
timeStr = timeStr + ('0' + date.getUTCMinutes()).slice(-2) + ':'
|
||||
|
@ -5,6 +8,9 @@ export const dateToUtcTime = (date: Date) => {
|
|||
return timeStr
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts date object to the time string
|
||||
*/
|
||||
export const dateToTime = (date: Date) => {
|
||||
let timeStr = ('0' + date.getHours()).slice(-2) + ':'
|
||||
timeStr = timeStr + ('0' + date.getMinutes()).slice(-2) + ':'
|
||||
|
@ -12,6 +18,9 @@ export const dateToTime = (date: Date) => {
|
|||
return timeStr
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts date object to the YYYY-MM-DD
|
||||
*/
|
||||
export const dateFormat = (date: Date) => {
|
||||
return (
|
||||
date.getFullYear() +
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
import { Col } from 'src/app/shared/dc-validator/models/col.model'
|
||||
|
||||
/**
|
||||
* Converts excel date serial number to JS date
|
||||
*/
|
||||
export const excelDateToJSDate = (serial: number) => {
|
||||
return new Date(Math.round((serial - 25569) * 86400 * 1000))
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsing table columns for the HOT in editor
|
||||
* Converts array of objects into array of strings, every string is column name (key)
|
||||
* @param data array of objects (columns data)
|
||||
*/
|
||||
export const parseTableColumns = (data: Col[]): string[] => {
|
||||
const columns: string[] = []
|
||||
|
||||
|
@ -16,6 +24,12 @@ export const parseTableColumns = (data: Col[]): string[] => {
|
|||
return columns
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures headers that are not found in the current table but is found in the uploaded file data
|
||||
* @param data
|
||||
* @param headers
|
||||
* @returns string array of missing headers
|
||||
*/
|
||||
export const getMissingHeaders = (data: any, headers: any) => {
|
||||
const missingHeaders: string[] = []
|
||||
const remainingHeaders: string[] = []
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Custom renderer for HOT cell
|
||||
* Used to show error icon
|
||||
*/
|
||||
export const errorRenderer = (
|
||||
instance: any,
|
||||
td: any,
|
||||
|
@ -14,6 +18,10 @@ export const errorRenderer = (
|
|||
return td
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom renderer for HOT cell
|
||||
* Used to revert cell back to original state (no spinner, no error)
|
||||
*/
|
||||
export const noSpinnerRenderer = (
|
||||
instance: any,
|
||||
td: any,
|
||||
|
@ -28,7 +36,11 @@ export const noSpinnerRenderer = (
|
|||
return td
|
||||
}
|
||||
|
||||
// Spinner shown whilst waiting for SAS to respond
|
||||
/**
|
||||
* Custom renderer for HOT cell
|
||||
* Used to show loading spinner in the cell
|
||||
* (Spinner shown whilst waiting for SAS to respond)
|
||||
*/
|
||||
export const spinnerRenderer = (
|
||||
instance: any,
|
||||
td: any,
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import { NgModule } from '@angular/core'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { LicensingGuard } from '../routes/licensing.guard'
|
||||
import { LicensingComponent } from './licensing.component'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: ':action',
|
||||
component: LicensingComponent,
|
||||
canActivate: [LicensingGuard],
|
||||
canDeactivate: [LicensingGuard]
|
||||
}
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class LicensingRoutingModule {}
|
|
@ -0,0 +1,20 @@
|
|||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
|
||||
import { LicensingRoutingModule } from './licensing-routing.module'
|
||||
import { ClarityModule } from '@clr/angular'
|
||||
import { SharedModule } from '../shared/shared.module'
|
||||
import { LicensingComponent } from './licensing.component'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
|
||||
@NgModule({
|
||||
declarations: [LicensingComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ClarityModule,
|
||||
LicensingRoutingModule,
|
||||
SharedModule
|
||||
]
|
||||
})
|
||||
export class LicensingModule {}
|
|
@ -0,0 +1,5 @@
|
|||
export interface ErrorBody {
|
||||
message: string
|
||||
details: any
|
||||
raw: any
|
||||
}
|
|
@ -137,6 +137,11 @@ export class QueryComponent
|
|||
public whereClause: string | undefined
|
||||
public logicOperators: Array<string> = ['AND', 'OR']
|
||||
|
||||
/**
|
||||
* Temporary values array used in pickers
|
||||
* because they need particular format to work
|
||||
* before sending values to backend, values are parsed
|
||||
*/
|
||||
public queryDateTime: QueryDateTime[] = []
|
||||
|
||||
public currentClauseIndex: number = -1
|
||||
|
@ -158,6 +163,11 @@ export class QueryComponent
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and sets temporary values selected with DATETIME or TIME picker
|
||||
* Those values are used for picker to work with format it lieks
|
||||
* Later before sending values to backend, values are parsed
|
||||
*/
|
||||
getQueryDateTime(clauseIndex: number, queryIndex: number): QueryDateTime {
|
||||
let existingQueryDateTime = this.queryDateTime.find(
|
||||
(x) => x.clauseIndex === clauseIndex && x.queryIndex === queryIndex
|
||||
|
@ -178,10 +188,16 @@ export class QueryComponent
|
|||
return existingQueryDateTime
|
||||
}
|
||||
|
||||
/**
|
||||
* When toggling pickers feature we reset the temp picker values array
|
||||
*/
|
||||
usePickersChange() {
|
||||
this.queryDateTime = []
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all variables used for filtering
|
||||
*/
|
||||
public resetFilter() {
|
||||
this.whereString = undefined
|
||||
this.whereClause = undefined
|
||||
|
@ -210,6 +226,10 @@ export class QueryComponent
|
|||
this.whereClauseFn(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* `Globals` are used to store filtering state (variables) as a caching feature
|
||||
* until browser reloads
|
||||
*/
|
||||
public setToGlobals() {
|
||||
if (!this.caching) return
|
||||
|
||||
|
@ -237,6 +257,10 @@ export class QueryComponent
|
|||
console.log('globals', globals)
|
||||
}
|
||||
|
||||
/**
|
||||
* `Globals` are used to store filtering state (variables) as a caching feature
|
||||
* until browser reloads
|
||||
*/
|
||||
public getFromGlobals() {
|
||||
if (!this.caching) return
|
||||
|
||||
|
@ -269,6 +293,11 @@ export class QueryComponent
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets filtering multiple caluses group logic (and / or)
|
||||
*
|
||||
* @param groupLogic to set
|
||||
*/
|
||||
public setGroupLogic(groupLogic: any) {
|
||||
this.groupLogic = groupLogic
|
||||
this.clauses.groupLogic = groupLogic
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { ActivatedRoute } from '@angular/router'
|
||||
import { SasStoreService } from '../services/sas-store.service'
|
||||
import { SasStoreService } from '../../services/sas-store.service'
|
||||
import { Component, AfterViewInit, OnDestroy } from '@angular/core'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { Router } from '@angular/router'
|
||||
import { EventService } from '../services/event.service'
|
||||
import { EventService } from '../../services/event.service'
|
||||
import {
|
||||
AuditorsPostdataSASResponse,
|
||||
Param
|
||||
} from '../models/sas/auditors-postdata.model'
|
||||
} from '../../models/sas/auditors-postdata.model'
|
||||
|
||||
interface ChangesObj {
|
||||
ind: any
|
||||
|
@ -89,7 +89,7 @@ export class ApproveDetailsComponent implements AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
public goToApprovalsList() {
|
||||
this.route.navigateByUrl('/approve')
|
||||
this.route.navigateByUrl('/review/approve')
|
||||
}
|
||||
|
||||
public getTable(tableId: any) {
|
||||
|
@ -136,7 +136,7 @@ export class ApproveDetailsComponent implements AfterViewInit, OnDestroy {
|
|||
await this.sasStoreService
|
||||
.rejecting(rejParams, 'BrowserParams', 'approvers/rejection')
|
||||
.then((res: any) => {
|
||||
this.route.navigateByUrl('/history')
|
||||
this.route.navigateByUrl('/review/history')
|
||||
})
|
||||
.catch((err: any) => {
|
||||
this.acceptLoading = false
|
||||
|
@ -156,7 +156,7 @@ export class ApproveDetailsComponent implements AfterViewInit, OnDestroy {
|
|||
await this.sasStoreService
|
||||
.approveTable(approveParams, 'SASControlTable', 'auditors/postdata')
|
||||
.then((res: any) => {
|
||||
this.route.navigateByUrl('/history')
|
||||
this.route.navigateByUrl('/review/history')
|
||||
})
|
||||
.catch((err: any) => {
|
||||
this.acceptLoading = false
|
||||
|
@ -164,7 +164,7 @@ export class ApproveDetailsComponent implements AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
public goToSubmitList() {
|
||||
this.route.navigateByUrl('/submitted')
|
||||
this.route.navigateByUrl('/review/submitted')
|
||||
}
|
||||
|
||||
public async callChangesInfo(tableId: any) {
|
|
@ -1,8 +1,8 @@
|
|||
import { Component, OnInit, ChangeDetectorRef } from '@angular/core'
|
||||
import { SasStoreService } from '../services/sas-store.service'
|
||||
import { SasStoreService } from '../../services/sas-store.service'
|
||||
import { Router } from '@angular/router'
|
||||
import { SasService } from '../services/sas.service'
|
||||
import { EventService } from '../services/event.service'
|
||||
import { SasService } from '../../services/sas.service'
|
||||
import { EventService } from '../../services/event.service'
|
||||
|
||||
interface ApproveData {
|
||||
tableId: string
|
||||
|
@ -45,7 +45,7 @@ export class ApproveComponent implements OnInit {
|
|||
if (this.approveList !== undefined) {
|
||||
this.tableId = this.approveList[ind].tableId
|
||||
this.route.navigateByUrl(
|
||||
'approve/approveDet/' + this.approveList[ind].tableId
|
||||
'review/approveDet/' + this.approveList[ind].tableId
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
import { Component, OnInit } from '@angular/core'
|
||||
|
||||
import { SasStoreService } from '../services/sas-store.service'
|
||||
import { Router } from '@angular/router'
|
||||
import { SasService } from '../services/sas.service'
|
||||
import { EventService } from '../services/event.service'
|
||||
import { SASjsConfig } from '@sasjs/adapter'
|
||||
import { LicenceService } from '../services/licence.service'
|
||||
import {
|
||||
LicenceService,
|
||||
SasStoreService,
|
||||
EventService,
|
||||
SasService
|
||||
} from 'src/app/services'
|
||||
|
||||
@Component({
|
||||
selector: 'app-history',
|
|
@ -0,0 +1,22 @@
|
|||
import { CommonModule } from '@angular/common'
|
||||
import { NgModule } from '@angular/core'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { ApproveDetailsComponent } from './approve-details/approve-details.component'
|
||||
import { ApproveComponent } from './approve/approve.component'
|
||||
import { SubmitterComponent } from './submitter/submitter.component'
|
||||
import { HistoryComponent } from './history/history.component'
|
||||
|
||||
const ROUTES: Routes = [
|
||||
{ path: 'approve', component: ApproveComponent },
|
||||
{ path: 'approveDet/:tableId', component: ApproveDetailsComponent },
|
||||
{ path: 'submitted', component: SubmitterComponent },
|
||||
{ path: 'submitted/:tableId', component: SubmitterComponent },
|
||||
{ path: 'history', component: HistoryComponent }
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [CommonModule, RouterModule.forChild(ROUTES)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class ReviewRoutingModule {}
|
|
@ -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 { DirectivesModule } from '../directives/directives.module'
|
||||
import { SharedModule } from '../shared/shared.module'
|
||||
import { ApproveDetailsComponent } from './approve-details/approve-details.component'
|
||||
import { ApproveComponent } from './approve/approve.component'
|
||||
import { ReviewRoutingModule } from './review-routing.module'
|
||||
import { SubmitterComponent } from './submitter/submitter.component'
|
||||
import { HistoryComponent } from './history/history.component'
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
ApproveComponent,
|
||||
ApproveDetailsComponent,
|
||||
SubmitterComponent,
|
||||
HistoryComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReviewRoutingModule,
|
||||
ClarityModule,
|
||||
HotTableModule.forRoot(),
|
||||
DirectivesModule,
|
||||
SharedModule
|
||||
]
|
||||
})
|
||||
export class ReviewModule {}
|
|
@ -1,9 +1,7 @@
|
|||
import { Component, AfterViewInit, OnInit } from '@angular/core'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { SasStoreService } from '../services/sas-store.service'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { SasService } from '../services/sas.service'
|
||||
import { EventService } from '../services/event.service'
|
||||
import { SasStoreService, EventService, SasService } from '../../services'
|
||||
|
||||
interface SubmitterData {
|
||||
tableId: string
|
||||
|
@ -46,7 +44,7 @@ export class SubmitterComponent implements OnInit, AfterViewInit {
|
|||
}
|
||||
|
||||
public goToDetails(table_id: any) {
|
||||
this.router.navigateByUrl('/submitted/' + table_id)
|
||||
this.router.navigateByUrl('/review/submitted/' + table_id)
|
||||
}
|
||||
|
||||
public getDetails(sub: any, index: any) {
|
|
@ -1,15 +0,0 @@
|
|||
import { Component, OnInit } from '@angular/core'
|
||||
|
||||
@Component({
|
||||
selector: 'app-approve-route',
|
||||
templateUrl: './approve-route.component.html',
|
||||
styleUrls: ['./approve-route.component.scss'],
|
||||
host: {
|
||||
class: 'content-container'
|
||||
}
|
||||
})
|
||||
export class ApproveRouteComponent implements OnInit {
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
<router-outlet></router-outlet>
|
|
@ -1,12 +0,0 @@
|
|||
import { Component, OnInit } from '@angular/core'
|
||||
|
||||
@Component({
|
||||
selector: 'app-edit-route',
|
||||
templateUrl: './edit-route.component.html',
|
||||
styleUrls: ['./edit-route.component.scss']
|
||||
})
|
||||
export class EditRouteComponent implements OnInit {
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
<router-outlet></router-outlet>
|
|
@ -1,12 +0,0 @@
|
|||
import { Component, OnInit } from '@angular/core'
|
||||
|
||||
@Component({
|
||||
selector: 'app-history-route',
|
||||
templateUrl: './history-route.component.html',
|
||||
styleUrls: ['./history-route.component.scss']
|
||||
})
|
||||
export class HistoryRouteComponent implements OnInit {
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import { Component, OnInit } from '@angular/core'
|
||||
|
||||
@Component({
|
||||
selector: 'app-review-route',
|
||||
templateUrl: './review-route.component.html',
|
||||
styleUrls: ['./review-route.component.scss'],
|
||||
host: {
|
||||
class: 'content-container'
|
||||
}
|
||||
})
|
||||
export class ReviewRouteComponent implements OnInit {
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {}
|
||||
}
|
|
@ -17,6 +17,17 @@ export class HelperService {
|
|||
console.log('Is IE or Edge?', this.isMicrosoft)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a JavaScript date object to a SAS Date or Datetime, given the logic below:
|
||||
*
|
||||
* A JS Date contains the number of _milliseconds_ since 01/01/1970
|
||||
* A SAS Date contains the number of _days_ since 01/01/1960
|
||||
* A SAS Datetime contains the number of _seconds_ since 01/01/1960
|
||||
*
|
||||
* @param jsDate JS Date to be converted. The type is instance of `Date`
|
||||
* @param unit Unit in which to return the SAS Date / datetime, eg `sasdate | sasdatetime`
|
||||
* @returns SAS Date value based on `unit` param
|
||||
*/
|
||||
public convertJsDateToSasDate(
|
||||
jsDate: string | Date,
|
||||
unit: string = 'days'
|
||||
|
@ -63,6 +74,17 @@ export class HelperService {
|
|||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a SAS Date or Datetime to a JavaScript date object, given the logic below:
|
||||
*
|
||||
* A JS Date contains the number of _milliseconds_ since 01/01/1970
|
||||
* A SAS Date contains the number of _days_ since 01/01/1960
|
||||
* A SAS Datetime contains the number of _seconds_ since 01/01/1960
|
||||
*
|
||||
* @param sasValue SAS Date or Datetime to be converted. The type could be `number` or `string.
|
||||
* @param unit Unit from which to convert the SAS Date / Datetime, eg `sasdate | sasdatetime`
|
||||
* @returns JavaScript Date object
|
||||
*/
|
||||
public convertSasDaysToJsDate(
|
||||
sasValue: number | string,
|
||||
unit: string = 'days'
|
||||
|
@ -87,6 +109,11 @@ export class HelperService {
|
|||
return new Date(msNegativeTenYears + sasValue * msInDay)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param array all elements in the clarity tree
|
||||
* @param arrToFilter sub array in the tree to be filtered for example `tables`
|
||||
*/
|
||||
public treeOnFilter(array: any, arrToFilter: string) {
|
||||
let search = array['searchString'] ? array['searchString'] : ''
|
||||
let arrToFilterArray = arrToFilter.split('.')[0]
|
||||
|
|
|
@ -40,6 +40,16 @@ export class SasStoreService {
|
|||
private loggerService: LoggerService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Wrapper for making request to service
|
||||
* Should be removed, as it's redundant now
|
||||
* TODO: Refactor to call editors/getdata directly
|
||||
* @param tableData
|
||||
* @param tableName
|
||||
* @param program
|
||||
* @param libds
|
||||
* @returns
|
||||
*/
|
||||
public async callService(
|
||||
tableData: Array<any>,
|
||||
tableName: string,
|
||||
|
@ -60,6 +70,15 @@ export class SasStoreService {
|
|||
return response
|
||||
}
|
||||
|
||||
/**
|
||||
* Calling editors/stagedata - saving table data, sending request to backend
|
||||
* @param tableParams params to send to backend
|
||||
* @param tableData data to be updated
|
||||
* @param tableName name of the table to be updated
|
||||
* @param program service against which we send request
|
||||
* @param $dataFormats column data formats recieved from backend, sending it back
|
||||
* @returns adapter.request() response
|
||||
*/
|
||||
public async updateTable(
|
||||
tableParams: any,
|
||||
tableData: any,
|
||||
|
@ -86,6 +105,13 @@ export class SasStoreService {
|
|||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* Sending request to 'approvers/getapprovals' to fetch approvals list
|
||||
* @param tableData sending to backend table data
|
||||
* @param tableName sending to backend table name
|
||||
* @param program service to run request on
|
||||
* @returns HTTP Response
|
||||
*/
|
||||
public async getApprovals(
|
||||
tableData: any,
|
||||
tableName: string,
|
||||
|
@ -96,6 +122,13 @@ export class SasStoreService {
|
|||
let res: any = await this.sasService.request(program, tables)
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* Interceptor for loading of the submitted details
|
||||
* @param detail submitter
|
||||
* @param index submitter index
|
||||
* @param data submit data
|
||||
*/
|
||||
public async sendDetails(detail: any, index: any, data: any) {
|
||||
let details = Object.assign({ sub: true }, detail)
|
||||
let subData = data[index]
|
||||
|
@ -105,11 +138,20 @@ export class SasStoreService {
|
|||
}
|
||||
this.submittDetail.next(allData)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns All submits
|
||||
*/
|
||||
public async getSubmitts() {
|
||||
let res: any = await this.sasService.request('editors/getsubmits', null)
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns All libraries
|
||||
*/
|
||||
public async viewLibs() {
|
||||
return this.sasService.request('public/viewlibs', null)
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import { DcAdapterSettings } from '../models/DcAdapterSettings'
|
|||
import { AppStoreService } from './app-store.service'
|
||||
import { LoggerService } from './logger.service'
|
||||
import { RequestWrapperOptions } from '../models/RequestWrapperOptions'
|
||||
import { ErrorBody } from '../models/ErrorBody'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
@ -39,6 +40,11 @@ export class SasService {
|
|||
private router: Router
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Same as `setup` function in the sasjs.service, this is the constructor replacement.
|
||||
* This function is being called by `app.service`.
|
||||
* Because of timing and dependency issues
|
||||
*/
|
||||
public sasServiceInit() {
|
||||
this.dcAdapterSettings = this.appStoreService.getDcAdapterSettings()
|
||||
|
||||
|
@ -80,6 +86,16 @@ export class SasService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runing a backend request against a service.
|
||||
* Function also handles the displaying of success or error modals.
|
||||
*
|
||||
* @param url service to run reuqest against
|
||||
* @param data to be sent to backend service
|
||||
* @param config additional parameters to force eg. { debug: false }
|
||||
* @param wrapperOptions used to suppress error or success abort modals after request is finished
|
||||
* @returns
|
||||
*/
|
||||
public request(
|
||||
url: string,
|
||||
data: any,
|
||||
|
@ -197,6 +213,14 @@ export class SasService {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a file to the backend, using the adapter upload function.
|
||||
*
|
||||
* @param sasService Service to which the file will be sent
|
||||
* @param files Files to be sent
|
||||
* @param params Aditional parameters eg. { debug: false }
|
||||
* @returns HTTP Response
|
||||
*/
|
||||
public uploadFile(sasService: string, files: UploadFile[], params: any) {
|
||||
return this.sasjsAdapter.uploadFile(sasService, files, params)
|
||||
}
|
||||
|
@ -487,9 +511,3 @@ export class SasService {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ErrorBody {
|
||||
message: string
|
||||
details: any
|
||||
raw: any
|
||||
}
|
||||
|
|
|
@ -25,6 +25,12 @@ export class SasjsService {
|
|||
private appStoreService: AppStoreService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* This function is replacing the constructor.
|
||||
* The reason for this is timing issues, other services eg. sas.service, app-store.service
|
||||
* must be initialized before this bit of code is executed.
|
||||
* This function is being called by `sas.service`
|
||||
*/
|
||||
setup() {
|
||||
const adapterConfig = this.appStoreService.getDcAdapterSettings()
|
||||
|
||||
|
@ -32,10 +38,18 @@ export class SasjsService {
|
|||
this.driveUrl = `${this.url}/drive`
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns Sasjs/server information
|
||||
*/
|
||||
getServerInfo(): Observable<SASjsApiServerInfo> {
|
||||
return this.http.get<SASjsApiServerInfo>(`${this.url}/info`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets file contents on a given path
|
||||
* @param filePath path to the file
|
||||
*/
|
||||
getFileFromDrive(filePath: string) {
|
||||
return this.http.get(
|
||||
`${this.driveUrl}/file/?_filePath=${filePath}`,
|
||||
|
@ -43,6 +57,11 @@ export class SasjsService {
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets folder contents on a given path
|
||||
* @param folderPath path to the folder
|
||||
* @returns
|
||||
*/
|
||||
getFolderContentsFromDrive(
|
||||
folderPath: string
|
||||
): Observable<SASjsApiDriveFolderContents> {
|
||||
|
|
|
@ -41,6 +41,14 @@ export class InfoModalComponent implements OnInit {
|
|||
this.data = newData
|
||||
}
|
||||
|
||||
/**
|
||||
* Wheter or not to show the `Open configurator button`
|
||||
* Button used for navigating to the `configuration` page
|
||||
* Only for SAS9
|
||||
* @param sasService backend service that caused this info modal to be shown
|
||||
* Decision is made based on that service path
|
||||
* @returns
|
||||
*/
|
||||
showConfiguratorButton(sasService: string | null) {
|
||||
const sasjsConfig = this.sasService.getSasjsConfig()
|
||||
|
||||
|
@ -54,6 +62,9 @@ export class InfoModalComponent implements OnInit {
|
|||
this.onConfirmModalClick.emit()
|
||||
}
|
||||
|
||||
/**
|
||||
* Only on SAS9, opening a backend configurator/deploy page
|
||||
*/
|
||||
openConfigurator() {
|
||||
this.eventService.startupDataLoaded()
|
||||
this.router.navigateByUrl('/deploy')
|
||||
|
|
|
@ -19,6 +19,7 @@ import { mergeColsRules } from './utils/mergeColsRules'
|
|||
import { parseColType } from './utils/parseColType'
|
||||
import { dqValidate } from './validations/dq-validation'
|
||||
import { specialMissingNumericValidator } from './validations/hot-custom-validators'
|
||||
import { applyNumericFormats } from './utils/applyNumericFormats'
|
||||
|
||||
export class DcValidator {
|
||||
private rules: DcValidation[] = []
|
||||
|
@ -41,6 +42,7 @@ export class DcValidator {
|
|||
this.hotInstance = hotInstance
|
||||
this.rules = parseColType(sasparams.COLTYPE)
|
||||
this.rules = mergeColsRules(cols, this.rules, $dataFormats)
|
||||
this.rules = applyNumericFormats(this.rules)
|
||||
this.dqrules = dqRules
|
||||
this.dqdata = dqData
|
||||
this.primaryKeys = sasparams.PK.split(' ')
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import { DcValidation } from '../models/dc-validation.model'
|
||||
import * as languages from 'numbro/dist/languages.min'
|
||||
/**
|
||||
* Applying the numeric formats based on the browser locale/language
|
||||
* So that correct decimal separators are applied.
|
||||
* For example european format (thousand dot, decimal comma): 1.000,00
|
||||
*
|
||||
* @param rules Cell Validation rules to be updated
|
||||
* Those rules are passed in the `columns` property Of handsontable settings.
|
||||
*/
|
||||
export const applyNumericFormats = (rules: DcValidation[]): DcValidation[] => {
|
||||
const lang = languages[window.navigator.language]
|
||||
|
||||
if (!lang) return rules
|
||||
|
||||
for (let rule of rules) {
|
||||
if (rule.type === 'numeric')
|
||||
rule.numericFormat = {
|
||||
pattern: '0,0',
|
||||
culture: window.navigator.language // use this for EUR (German),
|
||||
// more cultures available on http://numbrojs.com/languages.html
|
||||
}
|
||||
}
|
||||
|
||||
return rules
|
||||
}
|
|
@ -6,7 +6,8 @@ import { DcValidation } from '../models/dc-validation.model'
|
|||
* Merging old validation params from sasparams with cols params
|
||||
* @param sasparams sasparams coming from SAS
|
||||
* @param cols cols coming from SAS
|
||||
* @param rules rules to be updated
|
||||
* @param rules Cell Validation rules to be updated
|
||||
* Those rules are passed in the `columns` property Of handsontable settings.
|
||||
* @returns
|
||||
*/
|
||||
export const mergeColsRules = (
|
||||
|
|
|
@ -55,14 +55,14 @@
|
|||
<a
|
||||
*ngIf="isMainRoute('approve')"
|
||||
clrVerticalNavLink
|
||||
routerLink="/approve/submitted"
|
||||
routerLink="/review/approve/submitted"
|
||||
routerLinkActive="active"
|
||||
>Submitted</a
|
||||
>
|
||||
<a
|
||||
*ngIf="isMainRoute('approve')"
|
||||
clrVerticalNavLink
|
||||
routerLink="/approve/toapprove"
|
||||
routerLink="/review/approve/toapprove"
|
||||
routerLinkActive="active"
|
||||
>To Approve</a
|
||||
>
|
||||
|
|
|
@ -47,7 +47,7 @@ export class StageComponent implements OnInit {
|
|||
}
|
||||
|
||||
public approveTableScreen() {
|
||||
this.route.navigateByUrl('/approve/approveDet/' + this.table_id)
|
||||
this.route.navigateByUrl('/review/approveDet/' + this.table_id)
|
||||
}
|
||||
|
||||
public viewerTableScreen() {
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import { NgModule } from '@angular/core'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { SystemComponent } from './system.component'
|
||||
|
||||
const routes: Routes = [{ path: '', component: SystemComponent }]
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class SystemRoutingModule {}
|
|
@ -0,0 +1,12 @@
|
|||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
|
||||
import { SystemRoutingModule } from './system-routing.module'
|
||||
import { SystemComponent } from './system.component'
|
||||
import { ClarityModule } from '@clr/angular'
|
||||
|
||||
@NgModule({
|
||||
declarations: [SystemComponent],
|
||||
imports: [CommonModule, SystemRoutingModule, ClarityModule]
|
||||
})
|
||||
export class SystemModule {}
|
|
@ -201,16 +201,28 @@ export class ViewerComponent implements AfterContentInit, AfterViewInit {
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Open viewboxes modal
|
||||
*/
|
||||
public newViewbox() {
|
||||
this.viewboxOpen = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Resetting filter variables
|
||||
*/
|
||||
public resetFilter() {
|
||||
if (this.queryFilterCompList.first) {
|
||||
this.queryFilterCompList.first.resetFilter()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Searching table against particular string, data is comming from backend.
|
||||
* There is also a toggle that will search for a numeric values
|
||||
*
|
||||
* @param inputElement input from which search string is captured
|
||||
*/
|
||||
public async searchTable(inputElement: any) {
|
||||
this.searchLoading = true
|
||||
|
||||
|
@ -249,6 +261,9 @@ export class ViewerComponent implements AfterContentInit, AfterViewInit {
|
|||
this.searchLoading = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Re sending request to backend and re-setting data in the HOT
|
||||
*/
|
||||
public reloadTableData() {
|
||||
this.viewData(this.urlFilterPk || 0)
|
||||
}
|
||||
|
@ -278,6 +293,9 @@ export class ViewerComponent implements AfterContentInit, AfterViewInit {
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* FIXME: Should be removed, not used
|
||||
*/
|
||||
public filterFn(input: string) {
|
||||
let libraries = this.libraries
|
||||
this.libraries = libraries.filter(
|
||||
|
@ -286,6 +304,9 @@ export class ViewerComponent implements AfterContentInit, AfterViewInit {
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads file from backend, against `getrawdata` service, link is created and open in new tab
|
||||
*/
|
||||
public downloadData() {
|
||||
let storage = this.sasjsConfig.serverUrl
|
||||
let metaData = this.sasjsConfig.appLoc
|
||||
|
@ -320,6 +341,9 @@ export class ViewerComponent implements AfterContentInit, AfterViewInit {
|
|||
this.openDownload = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads file from backend, against `getddl` service, link is created and open in new tab
|
||||
*/
|
||||
public downloadDDL() {
|
||||
let libref = this.lib
|
||||
let ds = this.table
|
||||
|
@ -346,15 +370,27 @@ export class ViewerComponent implements AfterContentInit, AfterViewInit {
|
|||
this.openDownload = false
|
||||
}
|
||||
|
||||
/**
|
||||
* When clicked on textarea in the Web Query Modal, this function will
|
||||
* select all text inside.
|
||||
* @param evt textarea which contains the web query text
|
||||
*/
|
||||
public onCliCommandFocus(evt: any): void {
|
||||
evt.preventDefault()
|
||||
evt.target.select()
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the edit page of a viewing table
|
||||
*/
|
||||
public editTable() {
|
||||
this.router.navigateByUrl('/editor/' + this.libTab)
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to show/hide the edit table button
|
||||
* @returns Wheter currently viewed table is edtiable
|
||||
*/
|
||||
public tableEditExists() {
|
||||
let editTables: any = {}
|
||||
editTables = globals.editor.libsAndTables
|
||||
|
@ -367,12 +403,18 @@ export class ViewerComponent implements AfterContentInit, AfterViewInit {
|
|||
return editTables[currentLibrary].includes(currentTable)
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the lineage of a viewing table
|
||||
*/
|
||||
public goToLineage() {
|
||||
let routeUri = this.tableuri!.split('\\')[1]
|
||||
let lineageUrl = `/view/lineage/${routeUri}/REVERSE`
|
||||
this.router.navigateByUrl(lineageUrl)
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays web query modal
|
||||
*/
|
||||
public showWebQuery() {
|
||||
this.webQuery = true
|
||||
let filter_pk: number
|
||||
|
|
|
@ -13,9 +13,22 @@ import { SharedModule } from '../shared/shared.module'
|
|||
import { ViewboxesModule } from '../shared/viewboxes/viewboxes.module'
|
||||
import { QueryModule } from '../query/query.module'
|
||||
import { DirectivesModule } from '../directives/directives.module'
|
||||
import { UserComponent } from '../user/user.component'
|
||||
import { RoleComponent } from '../role/role.component'
|
||||
import { GroupComponent } from '../group/group.component'
|
||||
import { LineageComponent } from '../lineage/lineage.component'
|
||||
import { MetadataComponent } from '../metadata/metadata.component'
|
||||
|
||||
@NgModule({
|
||||
declarations: [ViewerComponent, ViewRouteComponent],
|
||||
declarations: [
|
||||
ViewerComponent,
|
||||
ViewRouteComponent,
|
||||
UserComponent,
|
||||
RoleComponent,
|
||||
GroupComponent,
|
||||
LineageComponent,
|
||||
MetadataComponent
|
||||
],
|
||||
imports: [
|
||||
ViewboxesModule,
|
||||
CommonModule,
|
||||
|
|
|
@ -5,27 +5,17 @@ Licence Agreement for Data Controller for SAS®
|
|||
|
||||
Copyright (c) Bowe IO Ltd
|
||||
|
||||
Data Controller is a software distributed by 4GL Apps, a brand owned by Bowe IO Ltd, a UK Limited Company headquarted in 29 Oldfield Rd, Cumbria, registered by companies house under number 08777171, VAT number: 203914240
|
||||
Data Controller software is distributed by 4GL Apps, a brand owned by Bowe IO Ltd, a UK Limited Company headquarted in 29 Oldfield Rd, Cumbria, registered at Companies House with company number 08777171, VAT number: 203914240
|
||||
|
||||
This software is protected by applicable copyright laws, including international treaties, and dual-
|
||||
licensed – depending on whether your use for commercial purposes, meaning intended for or
|
||||
resulting in commercial advantage or monetary compensation, or not.
|
||||
This software is protected by applicable copyright laws, including international treaties, and dual-licensed – depending on whether your use for commercial purposes, meaning intended for or resulting in commercial advantage or monetary compensation, or not.
|
||||
|
||||
If your use is strictly personal or solely for evaluation purposes, meaning for the purposes of testing
|
||||
the suitability, performance, and usefulness of this software outside the production environment,
|
||||
you agree to be bound by the terms included in the "licence-non-commercial-datacontroller.md" file.
|
||||
If your use is strictly personal or solely for evaluation purposes, meaning for the purposes of testing the suitability, performance, and usefulness of this software outside the production environment, you agree to be bound by the terms included in the "licence-non-commercial-datacontroller.md" file available here: https://git.datacontroller.io/dc/dc/src/branch/main/licence-non-commercial-datacontroller.md
|
||||
|
||||
Your use of this software for commercial purposes is subject to the terms included in an applicable
|
||||
license agreement.
|
||||
Your use of this software for commercial purposes is subject to the terms included in an applicable license agreement.
|
||||
|
||||
In any case, you must not make any such use of this software as to develop software which may be
|
||||
considered competitive with this software.
|
||||
In any case, you must not make any such use of this software as to develop software which may be considered competitive with this software.
|
||||
|
||||
UNLESS EXPRESSLY AGREED OTHERWISE, 4GL APPS PROVIDES THIS SOFTWARE ON AN "AS IS"
|
||||
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, AND IN NO EVENT AND UNDER NO
|
||||
LEGAL THEORY, SHALL 4GL APPS BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT,
|
||||
INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER ARISING FROM
|
||||
USE OR INABILITY TO USE THIS SOFTWARE.
|
||||
UNLESS EXPRESSLY AGREED OTHERWISE, 4GL APPS PROVIDES THIS SOFTWARE ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, AND IN NO EVENT AND UNDER NO LEGAL THEORY, SHALL 4GL APPS BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER ARISING FROM USE OR INABILITY TO USE THIS SOFTWARE.
|
||||
|
||||
|
||||
`
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{"include":["src/**/*.ts"],"exclude":["src/**/*.spec.ts"]}
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"out": "../dc-devdocs",
|
||||
"tsconfig": "./tsconfig.app.json",
|
||||
"entryPointStrategy": "expand",
|
||||
"entryPoints": [
|
||||
"./src"
|
||||
],
|
||||
"exclude": "**/*+(index|.spec|.e2e).ts",
|
||||
"externalPattern": "**/node_modules/**",
|
||||
"excludeExternals": true,
|
||||
"excludePrivate": true
|
||||
}
|
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
|
@ -1,11 +1,12 @@
|
|||
{
|
||||
"name": "dcfrontend",
|
||||
"version": "6.0.0",
|
||||
"version": "6.2.2",
|
||||
"description": "Data Controller",
|
||||
"devDependencies": {
|
||||
"@saithodev/semantic-release-gitea": "^2.1.0",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/commit-analyzer": "^10.0.1",
|
||||
"@semantic-release/npm": "11.0.0",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"@semantic-release/release-notes-generator": "^11.0.4",
|
||||
"commit-and-tag-version": "^11.2.2",
|
||||
|
@ -23,5 +24,10 @@
|
|||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.datacontroller.io/dc/dc.git"
|
||||
}
|
||||
},
|
||||
"private": true,
|
||||
"//": [
|
||||
"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"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
"": {
|
||||
"name": "dc-sas",
|
||||
"dependencies": {
|
||||
"@sasjs/cli": "^4.4.2",
|
||||
"@sasjs/core": "^4.46.4"
|
||||
"@sasjs/cli": "^4.11.1",
|
||||
"@sasjs/core": "^4.48.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@coolaj86/urequest": {
|
||||
|
@ -29,9 +29,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@sasjs/adapter": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-4.5.1.tgz",
|
||||
"integrity": "sha512-WeKDMfCivBywxDZ6t0jng78ZBPoMk8RIHKFTNDDmvuvmXq5Mr5oqZ0r5lRPB863XkGOeVi6UIEI1+JawZ2TlWQ==",
|
||||
"version": "4.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-4.10.1.tgz",
|
||||
"integrity": "sha512-/z6eR+3nNaLPyycK8YmpF+GAWNy0zgdl8n4cv4r45hjVBulPHVop7oj57JM/0uIPVOTT2V9IwrMCT/sFPq++vw==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@sasjs/utils": "2.52.0",
|
||||
|
@ -78,21 +78,21 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@sasjs/cli": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-4.4.2.tgz",
|
||||
"integrity": "sha512-o1Qp+L7vJOH9dbsEPJK6GaQR7yWW+W0BAI1rrD55+Ij3USMCcdWcRJAOvFxwS8Gflq5BuNrVqa39rg4RK0ZVEQ==",
|
||||
"version": "4.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-4.11.1.tgz",
|
||||
"integrity": "sha512-aI8V3YJGFXcY9OlNas0h8ZrajLIRPn4KmGkaOLHTDa8rZ2SOtj6W5by1RwyPB9iPIHcqW3JV3OgmtEB048zuYQ==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@sasjs/adapter": "4.5.1",
|
||||
"@sasjs/adapter": "4.10.1",
|
||||
"@sasjs/core": "4.46.3",
|
||||
"@sasjs/lint": "2.3.1",
|
||||
"@sasjs/utils": "3.3.0",
|
||||
"adm-zip": "0.5.9",
|
||||
"@sasjs/utils": "3.4.0",
|
||||
"adm-zip": "0.5.10",
|
||||
"chalk": "4.1.2",
|
||||
"dotenv": "16.0.3",
|
||||
"esm": "3.2.25",
|
||||
"find": "0.3.0",
|
||||
"js-base64": "3.7.2",
|
||||
"js-base64": "3.7.5",
|
||||
"jsdom": "22.1.0",
|
||||
"jwt-decode": "3.1.2",
|
||||
"lodash.groupby": "4.6.0",
|
||||
|
@ -116,9 +116,9 @@
|
|||
"integrity": "sha512-Grwydm5GxBsYk238PZw41XPjXVVQ9vWcvfZ06L2P0bQbvK0sGn7l69JA7H5MGr3QcaLpiD4Kg70cAh7PgE+JOw=="
|
||||
},
|
||||
"node_modules/@sasjs/core": {
|
||||
"version": "4.46.4",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.46.4.tgz",
|
||||
"integrity": "sha512-Q4UiOEYEHWCYn4ak+2BaKnrusLauyvKK/Hq4Y4RwJOfwA2MSjOzJSV8fDpbhnY1Dyubbk4SChA6yAL8lc0hn1Q=="
|
||||
"version": "4.48.0",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.48.0.tgz",
|
||||
"integrity": "sha512-KaAvfTPv1UrP0I1fREDYjkfa7FRM9+yCseGXGLYylD30oH7BBOwLc7o/BkhRjjDvrBFoiJMjAOVKULhmkHz9zQ=="
|
||||
},
|
||||
"node_modules/@sasjs/lint": {
|
||||
"version": "2.3.1",
|
||||
|
@ -166,9 +166,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@sasjs/utils": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-3.3.0.tgz",
|
||||
"integrity": "sha512-ZJ+c2d/rEoF340Ay3TZrXO4c2ain7AvSzkRuKG2H2qxwIlQQTk/9Rbknmy0mo3Y/QRScBYl0Fw5xSZ8SMHjljg==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-3.4.0.tgz",
|
||||
"integrity": "sha512-KHkuOcbwKdD9HrgwKYrMPPuKoFzOAEyNpUjHHhyefxRlrpLwTaf08nYQXFBUhqbWuS+hPRqFLozx45x+xExgyQ==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@fast-csv/format": "4.3.5",
|
||||
|
@ -229,12 +229,6 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/tough-cookie": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz",
|
||||
"integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/abab": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
|
||||
|
@ -255,9 +249,9 @@
|
|||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/adm-zip": {
|
||||
"version": "0.5.9",
|
||||
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz",
|
||||
"integrity": "sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg==",
|
||||
"version": "0.5.10",
|
||||
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz",
|
||||
"integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==",
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
}
|
||||
|
@ -662,9 +656,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
|
||||
"integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
|
@ -945,9 +939,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/js-base64": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz",
|
||||
"integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ=="
|
||||
"version": "3.7.5",
|
||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.5.tgz",
|
||||
"integrity": "sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA=="
|
||||
},
|
||||
"node_modules/jsdom": {
|
||||
"version": "22.1.0",
|
||||
|
@ -1755,9 +1749,9 @@
|
|||
}
|
||||
},
|
||||
"@sasjs/adapter": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-4.5.1.tgz",
|
||||
"integrity": "sha512-WeKDMfCivBywxDZ6t0jng78ZBPoMk8RIHKFTNDDmvuvmXq5Mr5oqZ0r5lRPB863XkGOeVi6UIEI1+JawZ2TlWQ==",
|
||||
"version": "4.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-4.10.1.tgz",
|
||||
"integrity": "sha512-/z6eR+3nNaLPyycK8YmpF+GAWNy0zgdl8n4cv4r45hjVBulPHVop7oj57JM/0uIPVOTT2V9IwrMCT/sFPq++vw==",
|
||||
"requires": {
|
||||
"@sasjs/utils": "2.52.0",
|
||||
"axios": "0.27.2",
|
||||
|
@ -1798,20 +1792,20 @@
|
|||
}
|
||||
},
|
||||
"@sasjs/cli": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-4.4.2.tgz",
|
||||
"integrity": "sha512-o1Qp+L7vJOH9dbsEPJK6GaQR7yWW+W0BAI1rrD55+Ij3USMCcdWcRJAOvFxwS8Gflq5BuNrVqa39rg4RK0ZVEQ==",
|
||||
"version": "4.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-4.11.1.tgz",
|
||||
"integrity": "sha512-aI8V3YJGFXcY9OlNas0h8ZrajLIRPn4KmGkaOLHTDa8rZ2SOtj6W5by1RwyPB9iPIHcqW3JV3OgmtEB048zuYQ==",
|
||||
"requires": {
|
||||
"@sasjs/adapter": "4.5.1",
|
||||
"@sasjs/adapter": "4.10.1",
|
||||
"@sasjs/core": "4.46.3",
|
||||
"@sasjs/lint": "2.3.1",
|
||||
"@sasjs/utils": "3.3.0",
|
||||
"adm-zip": "0.5.9",
|
||||
"@sasjs/utils": "3.4.0",
|
||||
"adm-zip": "0.5.10",
|
||||
"chalk": "4.1.2",
|
||||
"dotenv": "16.0.3",
|
||||
"esm": "3.2.25",
|
||||
"find": "0.3.0",
|
||||
"js-base64": "3.7.2",
|
||||
"js-base64": "3.7.5",
|
||||
"jsdom": "22.1.0",
|
||||
"jwt-decode": "3.1.2",
|
||||
"lodash.groupby": "4.6.0",
|
||||
|
@ -1834,9 +1828,9 @@
|
|||
}
|
||||
},
|
||||
"@sasjs/core": {
|
||||
"version": "4.46.4",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.46.4.tgz",
|
||||
"integrity": "sha512-Q4UiOEYEHWCYn4ak+2BaKnrusLauyvKK/Hq4Y4RwJOfwA2MSjOzJSV8fDpbhnY1Dyubbk4SChA6yAL8lc0hn1Q=="
|
||||
"version": "4.48.0",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.48.0.tgz",
|
||||
"integrity": "sha512-KaAvfTPv1UrP0I1fREDYjkfa7FRM9+yCseGXGLYylD30oH7BBOwLc7o/BkhRjjDvrBFoiJMjAOVKULhmkHz9zQ=="
|
||||
},
|
||||
"@sasjs/lint": {
|
||||
"version": "2.3.1",
|
||||
|
@ -1878,9 +1872,9 @@
|
|||
}
|
||||
},
|
||||
"@sasjs/utils": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-3.3.0.tgz",
|
||||
"integrity": "sha512-ZJ+c2d/rEoF340Ay3TZrXO4c2ain7AvSzkRuKG2H2qxwIlQQTk/9Rbknmy0mo3Y/QRScBYl0Fw5xSZ8SMHjljg==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-3.4.0.tgz",
|
||||
"integrity": "sha512-KHkuOcbwKdD9HrgwKYrMPPuKoFzOAEyNpUjHHhyefxRlrpLwTaf08nYQXFBUhqbWuS+hPRqFLozx45x+xExgyQ==",
|
||||
"requires": {
|
||||
"@fast-csv/format": "4.3.5",
|
||||
"@types/fs-extra": "9.0.13",
|
||||
|
@ -1933,12 +1927,6 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/tough-cookie": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz",
|
||||
"integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==",
|
||||
"peer": true
|
||||
},
|
||||
"abab": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
|
||||
|
@ -1961,9 +1949,9 @@
|
|||
}
|
||||
},
|
||||
"adm-zip": {
|
||||
"version": "0.5.9",
|
||||
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz",
|
||||
"integrity": "sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg=="
|
||||
"version": "0.5.10",
|
||||
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz",
|
||||
"integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ=="
|
||||
},
|
||||
"agent-base": {
|
||||
"version": "6.0.2",
|
||||
|
@ -2243,9 +2231,9 @@
|
|||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
|
||||
"integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q=="
|
||||
},
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
|
@ -2429,9 +2417,9 @@
|
|||
}
|
||||
},
|
||||
"js-base64": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz",
|
||||
"integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ=="
|
||||
"version": "3.7.5",
|
||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.5.tgz",
|
||||
"integrity": "sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA=="
|
||||
},
|
||||
"jsdom": {
|
||||
"version": "22.1.0",
|
||||
|
@ -2965,8 +2953,7 @@
|
|||
"ws": {
|
||||
"version": "8.13.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
|
||||
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
|
||||
"requires": {}
|
||||
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA=="
|
||||
},
|
||||
"xml": {
|
||||
"version": "1.0.1",
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@sasjs/cli": "^4.4.2",
|
||||
"@sasjs/core": "^4.46.4"
|
||||
"@sasjs/cli": "^4.11.1",
|
||||
"@sasjs/core": "^4.48.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,33 @@
|
|||
/**
|
||||
@file
|
||||
@brief Checks the level of access a user has to the MP Editor
|
||||
@brief Checks group access level for a table or library
|
||||
@details In order for a user to be able to EDIT or APPROVE a table they must
|
||||
be in a metadata group that has been granted access to that table in the
|
||||
&mpelib..mpe_security table. Alternatively, they may be in the
|
||||
&mpeadmins group (has overall access).
|
||||
be in a group that has been granted access to that table in the
|
||||
MPE_SECURITY table. Alternatively, they may be in the &mpeadmins
|
||||
group (which has full access to everything).
|
||||
|
||||
@param [in] base_table The base table to check for
|
||||
@param [in] user= The user for which the access level should be returned. If
|
||||
not provided, the mf_user() result is used instead.
|
||||
@param [in] access_level= (APPROVE) access_level (per MPE_SECURITY) reqd.
|
||||
Valid values:
|
||||
@li EDIT
|
||||
@li APPROVE
|
||||
@li VIEW
|
||||
@param [in] cntl_lib_var= (MPELIB) The name of a global macro variable that
|
||||
contains the libref in which the MPE_SECURITY table is stored
|
||||
@param [out] outds= (MED_ACCESSCHECK) Output WORK table containing all the
|
||||
groups for which the user is granted the particular ACCESS_LEVEL.
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_abort.sas
|
||||
@li mf_getuniquename.sas
|
||||
@li mf_getuser.sas
|
||||
@li mf_verifymacvars.sas
|
||||
@li mpe_getgroups.sas
|
||||
@li mp_dropmembers.sas
|
||||
|
||||
@param [in] access_level= access_level (per &mpelib..mp_editor_security) reqd
|
||||
|
||||
@returns outds A table containing all the groups that user is a member of,
|
||||
which are granted the access_level requested.
|
||||
<h4> Related Macros </h4>
|
||||
@li mpe_accesscheck.test.sas
|
||||
|
||||
@version 9.2
|
||||
@author 4GL Apps Ltd
|
||||
|
@ -25,65 +37,64 @@
|
|||
**/
|
||||
|
||||
%macro mpe_accesscheck(
|
||||
base_table /* base table to check for */
|
||||
base_table
|
||||
,outds=med_accesscheck /* WORK table to contain access details */
|
||||
,user= /* metadata user to check for */
|
||||
,access_level=APPROVE
|
||||
,cntl_lib_var=MPELIB
|
||||
);
|
||||
|
||||
%if &user= %then %let user=%mf_getuser();
|
||||
|
||||
%if %index(&outds,.) %then %do;
|
||||
%local lib ds;
|
||||
%let lib=%scan(&outds,1,.);
|
||||
%let ds=%scan(&outds,2,.);
|
||||
%if %upcase(&lib) ne WORK %then %do;
|
||||
%mp_abort(msg=outds should be a WORK table
|
||||
,mac=mpe_accesscheck);
|
||||
%end;
|
||||
%end;
|
||||
%else %let ds=&outds;
|
||||
%mp_abort(
|
||||
iftrue=(%index(&outds,.)>0 and %upcase(%scan(&outds,1,.)) ne WORK)
|
||||
,mac=mpe_accesscheck
|
||||
,msg=%str(outds should be a WORK table)
|
||||
)
|
||||
|
||||
%mp_abort(
|
||||
iftrue=(%mf_verifymacvars(base_table user access_level)=0)
|
||||
,mac=bitemporal_dataloader
|
||||
,msg=%str(Missing base_table/user access_level)
|
||||
,mac=mpe_accesscheck
|
||||
,msg=%str(Missing base_table/user access_level variables)
|
||||
)
|
||||
|
||||
/* ensure any existing table is dropped */
|
||||
%mp_dropmembers(&ds)
|
||||
/* make unique temp table vars */
|
||||
%local tempds1 tempds2;
|
||||
%let tempds1=%mf_getuniquename(prefix=usergroups);
|
||||
%let tempds2=%mf_getuniquename(prefix=tablegroups);
|
||||
|
||||
/* create a new table for temp use */
|
||||
data; run;
|
||||
%local tempds; %let tempds=&syslast;
|
||||
|
||||
/* overwrite with the list of groups */
|
||||
%mpe_getgroups(user=&user,outds=&tempds);
|
||||
/* get list of user groups */
|
||||
%mpe_getgroups(user=&user,outds=&tempds1)
|
||||
|
||||
/* get list of groups with access for that table */
|
||||
proc sql;
|
||||
create table &tempds2 as
|
||||
select distinct sas_group
|
||||
from &&&cntl_lib_var...mpe_security
|
||||
where &dc_dttmtfmt. lt tx_to
|
||||
and access_level="&access_level"
|
||||
and (
|
||||
(libref="%scan(&base_table,1,.)" and upcase(dsn)="%scan(&base_table,2,.)")
|
||||
or (libref="%scan(&base_table,1,.)" and dsn="*ALL*")
|
||||
or (libref="*ALL*")
|
||||
);
|
||||
%if &_debug ge 131 %then %do;
|
||||
data _null_;
|
||||
set &tempds;
|
||||
set &tempds1;
|
||||
putlog (_all_)(=);
|
||||
run;
|
||||
data _null_;
|
||||
set &tempds2;
|
||||
putlog (_all_)(=);
|
||||
run;
|
||||
%end;
|
||||
|
||||
proc sql;
|
||||
create table &outds as
|
||||
select * from &tempds
|
||||
select * from &tempds1
|
||||
where groupname="&mpeadmins"
|
||||
or groupname in
|
||||
(select sas_group from &mpelib..mpe_security
|
||||
where &dc_dttmtfmt. lt tx_to
|
||||
and access_level="&access_level"
|
||||
& (
|
||||
(libref="%scan(&base_table,1,.)" and dsn="%scan(&base_table,2,.)")
|
||||
or (libref="%scan(&base_table,1,.)" and dsn="*ALL*")
|
||||
or (libref="*ALL*")
|
||||
)
|
||||
);
|
||||
or groupname in (select * from &tempds2);
|
||||
|
||||
%put base_table=&base_table;
|
||||
%put libref=%scan(&base_table,1,.);
|
||||
%put dsn=%scan(&base_table,2,.);
|
||||
%put access_level=&access_level;
|
||||
%put &sysmacroname: base_table=&base_table;
|
||||
%put &sysmacroname: access_level=&access_level;
|
||||
%mend mpe_accesscheck;
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
@file
|
||||
@brief Testing mpe_accesscheck macro
|
||||
@details Checking functionality of mpe_accesscheck.sas macro
|
||||
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getuniquename.sas
|
||||
@li mf_getuser.sas
|
||||
@li mp_assertdsobs.sas
|
||||
@li mpe_getgroups.sas
|
||||
@li mpe_accesscheck.sas
|
||||
|
||||
|
||||
@author 4GL Apps Ltd
|
||||
@copyright 4GL Apps Ltd. This code may only be used within Data Controller
|
||||
and may not be re-distributed or re-sold without the express permission of
|
||||
4GL Apps Ltd.
|
||||
|
||||
**/
|
||||
|
||||
/* get the groups this user is actually a member of */
|
||||
%mpe_getgroups(user=%mf_getuser(),outds=work.groups)
|
||||
|
||||
data _null_;
|
||||
set work.groups;
|
||||
call symputx('groupname',groupname);
|
||||
run;
|
||||
|
||||
/* create demo MPE_SECURITY table */
|
||||
data work.mpe_security;
|
||||
if 0 then set &dc_libref..mpe_security;
|
||||
do access_level='EDIT','APPROVE','VIEW','SIGNOFF','AUDIT';
|
||||
LIBREF='SOMELIB';
|
||||
DSN='SOMEDS';
|
||||
sas_group="&groupname";
|
||||
tx_from=0;
|
||||
tx_to='31dec4999:23:59:59'dt;
|
||||
output;
|
||||
end;
|
||||
run;
|
||||
|
||||
%let WRK=WORK;
|
||||
|
||||
%mpe_accesscheck(
|
||||
SOMELIB.SOMEDS
|
||||
,outds=work.test1
|
||||
,access_level=APPROVE
|
||||
,cntl_lib_var=WRK
|
||||
)
|
||||
%mp_assertdsobs(work.test1,
|
||||
desc=Test 1 - One record returned,
|
||||
test=EQUALS 1,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
%mpe_accesscheck(
|
||||
SOMELIB.INVALID
|
||||
,outds=work.test2
|
||||
,access_level=APPROVE
|
||||
,cntl_lib_var=WRK
|
||||
)
|
||||
%mp_assertdsobs(work.test2,
|
||||
desc=Test 12 - 0 records returned,
|
||||
test=EQUALS 0,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
|
@ -131,7 +131,7 @@ filename __out email ("&emails")
|
|||
txt=symget('SUBMITTED_TXT');
|
||||
put "Reason provided: " txt;
|
||||
put " ";
|
||||
put "This is an automated email by Data Controller for SAS®. For "
|
||||
put "This is an automated email by Data Controller for SAS. For "
|
||||
"documentation, please visit https://docs.datacontroller.io";
|
||||
run;
|
||||
%end;
|
||||
|
@ -144,7 +144,7 @@ filename __out email ("&emails")
|
|||
put "Please be advised that a change to table &alert_lib..&alert_ds has "
|
||||
"been approved by &from_user on the '&syshostname' SAS server.";
|
||||
put " ";
|
||||
put "This is an automated email by Data Controller for SAS®. For "
|
||||
put "This is an automated email by Data Controller for SAS. For "
|
||||
"documentation, please visit https://docs.datacontroller.io";
|
||||
run;
|
||||
%end;
|
||||
|
@ -165,7 +165,7 @@ filename __out email ("&emails")
|
|||
txt=symget('REVIEW_REASON_TXT');
|
||||
put "Reason provided: " txt;
|
||||
put " ";
|
||||
put "This is an automated email by Data Controller for SAS®. For "
|
||||
put "This is an automated email by Data Controller for SAS. For "
|
||||
"documentation, please visit https://docs.datacontroller.io";
|
||||
run;
|
||||
%end;
|
||||
|
|
|
@ -1014,7 +1014,8 @@ insert into &lib..mpe_selectbox set
|
|||
,buskey='LIBREF DSN ACCESS_LEVEL SAS_GROUP'
|
||||
,var_txfrom='TX_FROM'
|
||||
,var_txto='TX_TO'
|
||||
,notes='Shows which metadata groups can edit which tables'
|
||||
,notes='Determines which groups can view/edit/approve which tables'
|
||||
,post_edit_hook='services/hooks/mpe_security_postedit'
|
||||
;
|
||||
insert into &lib..mpe_tables
|
||||
set tx_from=0
|
||||
|
@ -1351,6 +1352,15 @@ insert into &lib..MPE_VALIDATIONS set
|
|||
,rule_value='UPCASE'
|
||||
,rule_active=1
|
||||
,tx_to='31DEC5999:23:59:59'dt;
|
||||
insert into &lib..MPE_VALIDATIONS set
|
||||
tx_from=0
|
||||
,base_lib="&lib"
|
||||
,base_ds="MPE_SECURITY"
|
||||
,base_col="LIBREF"
|
||||
,rule_type='CASE'
|
||||
,rule_value="UPCASE"
|
||||
,rule_active=1
|
||||
,tx_to='31DEC5999:23:59:59'dt;
|
||||
insert into &lib..MPE_VALIDATIONS set
|
||||
tx_from=0
|
||||
,base_lib="&lib"
|
||||
|
@ -1369,6 +1379,15 @@ insert into &lib..MPE_VALIDATIONS set
|
|||
,rule_value="&lib..MPE_TABLES.LIBREF"
|
||||
,rule_active=1
|
||||
,tx_to='31DEC5999:23:59:59'dt;
|
||||
insert into &lib..MPE_VALIDATIONS set
|
||||
tx_from=0
|
||||
,base_lib="&lib"
|
||||
,base_ds="MPE_SECURITY"
|
||||
,base_col="DSN"
|
||||
,rule_type='CASE'
|
||||
,rule_value="UPCASE"
|
||||
,rule_active=1
|
||||
,tx_to='31DEC5999:23:59:59'dt;
|
||||
insert into &lib..MPE_VALIDATIONS set
|
||||
tx_from=0
|
||||
,base_lib="&lib"
|
||||
|
@ -1733,12 +1752,12 @@ proc format library=&lib..mpe_x_catalog;
|
|||
6 = "Agree"
|
||||
7 = "Strongly Agree"
|
||||
;
|
||||
VALUE LIKERT7_A
|
||||
VALUE LIKERT7_ELEVEN
|
||||
1,2,3 = "Disagree"
|
||||
4 = "Neither Agree nor Disagree"
|
||||
5,6,7 = "Agree"
|
||||
;
|
||||
VALUE LIKERT7_B
|
||||
VALUE LIKERT7_SISTERS
|
||||
1-3 = "Disagree"
|
||||
4 = "Neither Agree nor Disagree"
|
||||
5-7 = "Agree"
|
||||
|
|
|
@ -141,6 +141,17 @@ run;
|
|||
|
||||
%mp_lockanytable(UNLOCK,lib=&lib,ds=&ds,ctl_ds=&dclib..mpe_lockanytable)
|
||||
%end;
|
||||
%else %do;
|
||||
/* is full replace so treat all staged records as new in diff screen */
|
||||
data work.outds_mod work.outds_add ;
|
||||
set work.&staging_ds;
|
||||
output work.outds_add;
|
||||
run;
|
||||
/* previous table will be considered fully deleted */
|
||||
data work.outds_del;
|
||||
set &lib..&ds;
|
||||
run;
|
||||
%end;
|
||||
%end;
|
||||
%else %if &loadtype=UPDATE %then %do;
|
||||
%bitemporal_dataloader(bus_from=,bus_to=
|
||||
|
|
|
@ -283,7 +283,7 @@
|
|||
"rejectUnauthorized": false,
|
||||
"allowInsecureRequests": true
|
||||
},
|
||||
"appLoc": "/Public/app/mihajlo",
|
||||
"appLoc": "/Public/app/dc2",
|
||||
"deployConfig": {
|
||||
"deployServicePack": true,
|
||||
"deployScripts": []
|
||||
|
|
|
@ -597,6 +597,7 @@ run;
|
|||
%if &LOADTYPE=REPLACE %then %do;
|
||||
data work.outds_add; run;
|
||||
data work.outds_mod; run;
|
||||
data work.outds_del; run;
|
||||
%end;
|
||||
libname approve "&mpelocapprovals/&TABLE";
|
||||
data; set &libds;stop;run;
|
||||
|
|
|
@ -186,20 +186,15 @@ options notes mprint;
|
|||
libname approve "&dir";
|
||||
|
||||
/* take copy of webin file */
|
||||
|
||||
data _null_;
|
||||
if symexist('_WEBIN_FILEREF1')
|
||||
then ref=symget('_WEBIN_FILEREF1');
|
||||
else if symexist('sasjs_tables') then do;
|
||||
rc=filename('ref',"%sysfunc(pathname(work))/&dsn.csv");
|
||||
ref='ref';
|
||||
end;
|
||||
if symexist('_WEBIN_FILEREF1') then ref=symget('_WEBIN_FILEREF1');
|
||||
else if symexist('sasjs_tables') then ref='0ref'; /* no fileref created */
|
||||
else ref='indata1';
|
||||
call symputx('ref',ref);
|
||||
putlog ref=;
|
||||
run;
|
||||
|
||||
%mp_binarycopy(inref=&ref, outloc="&dir/_WEBIN_FILEREF1.txt")
|
||||
%mp_binarycopy(inref=&ref,outloc="&dir/_WEBIN_FILEREF1.txt",iftrue=&ref ne 0ref)
|
||||
|
||||
|
||||
/* take copy of macvars */
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue