Compare commits

...

42 Commits

Author SHA1 Message Date
ca281b70c9 chore(git): Merge branch 'development' into tsdoc
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 22s
2023-08-01 14:52:53 +02:00
e056ece223 fix: approve, history and submit pages grouped in review module
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 1m5s
Using compodoc instead of typedoc because of better angular support.
2023-08-01 14:50:04 +02:00
0ae35214fb Merge pull request 'Removed ng build and ng test from PR action' (#17) from ci into development
Reviewed-on: #17
Reviewed-by: allan <allan@4gl.io>
2023-07-31 12:05:32 +00:00
34ffac39cb ci: PR will only run lint check
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 23s
2023-07-31 13:13:00 +02:00
2a3f4f755c Merge pull request 'Releasing of tsdoc.datacontroller.io' (#15) from ci into development
All checks were successful
Test / Build-and-test-development (push) Successful in 11m20s
Test / Build-and-test-development-latest-adapter (push) Successful in 11m23s
Reviewed-on: #15
Reviewed-by: allan <allan@4gl.io>
2023-07-27 19:34:16 +00:00
ddfae9227c chore(git): Merge branch 'development' into ci
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 7m31s
2023-07-27 19:26:06 +02:00
c74378423d Merge pull request 'chore: improving the descriptions of the steps in the release.yaml file' (#11) from releasedoc into development
All checks were successful
Test / Build-and-test-development (push) Successful in 11m25s
Test / Build-and-test-development-latest-adapter (push) Successful in 11m25s
Reviewed-on: #11
Reviewed-by: mihajlo <mihajlo@4gl.io>
2023-07-27 17:24:51 +00:00
0e020623a5 chore(git): Merge branch 'development' into releasedoc
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 7m34s
2023-07-27 19:23:57 +02:00
eeb4000efe ci: releasing typedoc 2023-07-27 19:20:14 +02:00
2053c858ac Merge pull request 'Added cypress videos to the build artifacts' (#14) from ci into development
All checks were successful
Test / Build-and-test-development (push) Successful in 11m29s
Test / Build-and-test-development-latest-adapter (push) Successful in 11m34s
Reviewed-on: #14
Reviewed-by: allan <allan@4gl.io>
2023-07-27 12:58:08 +00:00
4c2c5d5526 style: lint
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 7m38s
2023-07-27 13:49:13 +02:00
7ccc745eb2 chore(git): Merge branch 'development' into ci
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 1m2s
2023-07-27 13:15:39 +02:00
e93367fb42 ci: capturing cypress videos
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 1m3s
2023-07-27 13:09:16 +02:00
250fb1e8cf Merge pull request 'ci: release' (#13) from ci into development
Some checks failed
Test / Build-and-test-development (push) Failing after 12m32s
Test / Build-and-test-development-latest-adapter (push) Successful in 11m15s
Reviewed-on: #13
Reviewed-by: allan <allan@4gl.io>
2023-07-26 19:02:05 +00:00
1c2b87940d ci: release
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 7m30s
2023-07-26 13:39:04 +02:00
25fed203c6 chore: improving the descriptions of the steps in the release.yaml file
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 7m29s
2023-07-25 21:36:10 +01:00
218b15918d Merge branch 'main' into development
Some checks reported warnings
Test / Build-and-test-development-latest-adapter (push) Has been cancelled
Test / Build-and-test-development (push) Has been cancelled
Build / Build-and-ng-test (pull_request) Has been cancelled
2023-07-25 14:41:44 +00:00
19489a68a7 chore: merge commit
Some checks reported warnings
Test / Build-and-test-development-latest-adapter (push) Has been cancelled
Test / Build-and-test-development (push) Has been cancelled
Build / Build-and-ng-test (pull_request) Has been cancelled
2023-07-25 15:39:55 +01:00
7d7061a78a chore: adding pdf version of the non commercial licence 2023-07-25 15:38:47 +01:00
1aceb297ba Merge pull request 'Merging Development' (#5) from development into main
Some checks failed
Release / release (push) Failing after 1m53s
Reviewed-on: #5
2023-07-25 14:19:46 +00:00
cdee7480c2 Merge pull request 'CI Integration and Cypress test fixing' (#9) from ci into development
Some checks reported warnings
Test / Build-and-test-development (push) Has been cancelled
Test / Build-and-test-development-latest-adapter (push) Has been cancelled
Build / Build-and-ng-test (pull_request) Successful in 7m38s
Reviewed-on: #9
Reviewed-by: allan <allan@4gl.io>
2023-07-25 13:54:21 +00:00
bb2fb235aa chore(git): Merge branch 'ci' of ssh://git.datacontroller.io:29419/dc/dc into ci
Some checks reported warnings
Build / Build-and-ng-test (pull_request) Has been cancelled
2023-07-25 15:51:47 +02:00
b2f88e203a chore: ci and repo links 2023-07-25 15:45:11 +02:00
c2f1af99ef Merge branch 'development' into ci
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 7m32s
2023-07-25 13:36:03 +00:00
00e9dff1d9 chore: removed woodpecker ci
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 1m2s
2023-07-25 14:21:32 +02:00
82d74e6fee Merge pull request 'fix: missing mf_existds dependency in bitemporal_dataloader' (#7) from issue4 into development
Some checks failed
Test / Build-and-test-development (push) Failing after 4m54s
Test / Build-and-test-development-latest-adapter (push) Failing after 4m52s
Build / Build-and-ng-test (pull_request) Successful in 7m30s
Reviewed-on: #7
2023-07-25 10:06:41 +00:00
f1e2770574 Merge branch 'development' into issue4
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 7m32s
2023-07-25 10:06:15 +00:00
5ce1701657 fix: missing mf_existds dependency in bitemporal_dataloader
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 7m28s
2023-07-25 11:05:01 +01:00
2f79487aea fix: release script, excel upload duplicate primary keys, cypress fix 2023-07-25 11:34:58 +02:00
fefdfaf17b chore: readme tidyup
Some checks reported warnings
Test / Build-and-test-development (push) Has been cancelled
Test / Build-and-test-development-latest-adapter (push) Has been cancelled
Build / Build-and-ng-test (pull_request) Successful in 7m32s
2023-07-25 08:56:07 +00:00
c1e6f71430 chore: line spacing
Some checks reported warnings
Test / Build-and-test-development (push) Has been cancelled
Test / Build-and-test-development-latest-adapter (push) Has been cancelled
Build / Build-and-ng-test (pull_request) Has been cancelled
2023-07-25 08:52:27 +00:00
a8310fa63f Merge pull request 'fix: reducing audit data volumes. Closes #4' (#6) from issue4 into development
Some checks failed
Test / Build-and-test-development (push) Failing after 4m55s
Test / Build-and-test-development-latest-adapter (push) Failing after 4m48s
Build / Build-and-ng-test (pull_request) Successful in 7m34s
Reviewed-on: #6
2023-07-25 08:50:32 +00:00
e28ac814b8 chore: readme update
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 7m34s
2023-07-25 09:48:49 +01:00
45fed59a65 chore: readme
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 7m34s
2023-07-25 09:47:27 +01:00
310399112b chore: licence updates 2023-07-25 09:09:57 +01:00
01238fc8a9 chore: newline in _eula.ts
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 7m27s
2023-07-25 09:05:57 +01:00
365e46c568 chore: postdata comment 2023-07-25 09:05:28 +01:00
8d336e5b46 chore: updating tests
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 1m0s
2023-07-21 14:23:22 +01:00
54fe7013b1 fix: reducing audit data volumes. Closes #4
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 1m12s
2023-07-20 21:29:34 +01:00
8dc40bdd4e feat: full format deletion, closes #2
Some checks failed
Test / Build-and-test-development (push) Failing after 6m16s
Test / Build-and-test-development-latest-adapter (push) Failing after 6m22s
Build / Build-and-ng-test (pull_request) Failing after 1m12s
2023-07-20 16:12:59 +01:00
d88ab8bf58 ci: removed legacy-peer-deps flag
Some checks failed
Test / Build-and-test-development (push) Failing after 6m27s
Test / Build-and-test-development-latest-adapter (push) Failing after 6m25s
2023-07-14 12:27:33 +02:00
67030ab033 chore: contributing
Some checks reported warnings
Test / Build-and-test-development (push) Has started running
Test / Build-and-test-development-latest-adapter (push) Has been cancelled
2023-07-13 14:11:50 +02:00
75 changed files with 13490 additions and 4478 deletions

View File

@ -1,5 +1,5 @@
name: Build name: Build
run-name: Building and testing DC run-name: Running Lint Check
on: [pull_request] on: [pull_request]
jobs: jobs:
@ -18,21 +18,4 @@ jobs:
env: env:
NPMRC: ${{ secrets.NPMRC}} NPMRC: ${{ secrets.NPMRC}}
- run: apt-get update - run: npm run lint:check
- 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 --legacy-peer-deps
# 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

View File

@ -6,6 +6,53 @@ on:
- development - development
jobs: 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
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
- 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
cd ./sas
npm audit --audit-level=critical
cd ./client
npm audit --audit-level=critical
- name: Angular Tests
run: |
npm test -- --no-watch --no-progress --browsers=ChromeHeadlessCI
- name: Angular Production Build
run: |
npm run postinstall
npm run build
Build-and-test-development: Build-and-test-development:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -16,10 +63,9 @@ jobs:
node-version: 18 node-version: 18
- name: Write .npmrc file - name: Write .npmrc file
run: echo "$NPMRC" > client/.npmrc run: |
shell: bash touch client/.npmrc
env: echo '${{ secrets.NPMRC}}' > client/.npmrc
NPMRC: ${{ secrets.NPMRC}}
- run: apt-get update - run: apt-get update
- run: wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb - run: wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
@ -28,18 +74,16 @@ jobs:
- run: apt-get update -y - run: apt-get update -y
- run: apt-get -y install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb - run: apt-get -y install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb
- run: apt -y install jq - run: apt -y install jq
# - run: 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
# - run: eval $(ssh-agent -s)
# - run: echo "$ssh_key" | tr -d '\r' | ssh-add -
- name: Write cypress credentials - name: Write cypress credentials
run: echo "$CYPRESS_CREDS" > ./client/cypress.env.json run: echo "$CYPRESS_CREDS" > ./client/cypress.env.json
shell: bash shell: bash
env: env:
CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }} CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }}
# - run: mkdir -p ~/.ssh - name: Install dependencies
# - run: chmod 700 ~/.ssh run: npm ci
- run: npm ci --legacy-peer-deps
# Install pm2 and prepare SASJS server # Install pm2 and prepare SASJS server
- run: npm i -g pm2 - run: npm i -g pm2
- run: curl -L https://github.com/sasjs/server/releases/latest/download/linux.zip > linux.zip - run: curl -L https://github.com/sasjs/server/releases/latest/download/linux.zip > linux.zip
@ -49,44 +93,49 @@ jobs:
- run: echo NODE_PATH=node >> .env - run: echo NODE_PATH=node >> .env
- run: echo CORS=enable >> .env - run: echo CORS=enable >> .env
- run: echo WHITELIST=http://localhost:4200 >> .env - run: echo WHITELIST=http://localhost:4200 >> .env
# - run: echo "SERVER_URL=$server_url" >> .env - run: cat .env
# - run: echo "SERVER_TYPE=$server_type" >> .env
# - run: echo "CLIENT=$client_sasjs" >> .env
# - run: echo "ACCESS_TOKEN=$access_token_sasjs" >> .env
# - run: echo "REFRESH_TOKEN=$refresh_token_sasjs" >> .env
# - run: cat .env
- run: pm2 start api-linux --wait-ready - run: pm2 start api-linux --wait-ready
- name: Deploy mocked services - name: Deploy mocked services
run: | run: |
cd ./sas/mocks/sasjs cd ./sas/mocks/sasjs
npm install --legacy-peer-deps -g @sasjs/cli npm install -g @sasjs/cli
npm install --legacy-peer-deps -g replace-in-files-cli npm install -g replace-in-files-cli
sasjs cbd -t server-ci sasjs cbd -t server-ci
# sasjs request services/admin/makedata -t server-ci -d ./deploy/makeData4GL.json -c ./deploy/requestConfig.json -o ./output.json # sasjs request services/admin/makedata -t server-ci -d ./deploy/makeData4GL.json -c ./deploy/requestConfig.json -o ./output.json
- name: Prepare frontend - name: Install ZIP
run: |
apt-get update
apt-get install zip
- name: Prepare and run frontend and cypress
run: | run: |
cd ./client cd ./client
# mv ./cypress.env.example.json ./cypress.env.json mv ./cypress.env.example.json ./cypress.env.json
# replace-in-files --regex='"username".*' --replacement='"username":"'$cypress_username_sasjs'",' ./cypress.env.json replace-in-files --regex='"username".*' --replacement='"username":"'${{ secrets.CYPRESS_USERNAME_SASJS }}'",' ./cypress.env.json
# replace-in-files --regex='"password".*' --replacement='"password":"'$cypress_pwd_sasjs'" ' ./cypress.env.json replace-in-files --regex='"password".*' --replacement='"password":"'${{ secrets.CYPRESS_PWD_SASJS }}'" ' ./cypress.env.json
cat ./cypress.env.json cat ./cypress.env.json
npm run postinstall npm run postinstall
# Prepare index.html to SASJS local # Prepare index.html to SASJS local
replace-in-files --regex='serverUrl=".*?"' --replacement='serverUrl="http://localhost:5000"' ./src/index.html replace-in-files --regex='serverUrl=".*?"' --replacement='serverUrl="http://localhost:5000"' ./src/index.html
replace-in-files --regex='appLoc=".*?"' --replacement='appLoc="/Public/app/devtest"' ./src/index.html replace-in-files --regex='appLoc=".*?"' --replacement='appLoc="/Public/app/devtest"' ./src/index.html
replace-in-files --regex='serverType=".*?"' --replacement='serverType="SASJS"' ./src/index.html replace-in-files --regex='serverType=".*?"' --replacement='serverType="SASJS"' ./src/index.html
# Prepare and deploy SASJS version
# replace-in-files --regex='serverurl=".*?"' --replacement='serverUrl="http://localhost:5000"' ./dist/index.html
# replace-in-files --regex='apploc=".*?"' --replacement='appLoc="/30.SASApps/app/devtest"' ./dist/index.html
# replace-in-files --regex='servertype=".*?"' --replacement='serverType="SASJS"' ./dist/index.html
# scp -o stricthostkeychecking=no -r ./dist/* dcgitlab@sas.4gl.io:/var/www/html/dcviya/development/newadapter
replace-in-files --regex='"hosturl".*' --replacement='hosturl:"http://localhost:4200",' ./cypress.config.ts replace-in-files --regex='"hosturl".*' --replacement='hosturl:"http://localhost:4200",' ./cypress.config.ts
# replace-in-files --regex='"appLocation".*' --replacement='appLocation:"/dcviya/development/newadapter",' ./cypress.config.ts
cat ./cypress.config.ts cat ./cypress.config.ts
# Start frontend and run cypress # Start frontend and run cypress
npm start & npx wait-on http://localhost:4200 && ./run-cypress-tests.sh 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: Build-and-test-development-latest-adapter:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -110,18 +159,16 @@ jobs:
- run: apt-get update -y - run: apt-get update -y
- run: apt-get -y install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb - run: apt-get -y install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb
- run: apt -y install jq - run: apt -y install jq
# - run: 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
# - run: eval $(ssh-agent -s)
# - run: echo "$ssh_key" | tr -d '\r' | ssh-add -
- name: Write cypress credentials - name: Write cypress credentials
run: echo "$CYPRESS_CREDS" > ./client/cypress.env.json run: echo "$CYPRESS_CREDS" > ./client/cypress.env.json
shell: bash shell: bash
env: env:
CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }} CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }}
# - run: mkdir -p ~/.ssh - name: Install dependencies
# - run: chmod 700 ~/.ssh run: npm ci
- run: npm ci --legacy-peer-deps
# Install pm2 and prepare SASJS server # Install pm2 and prepare SASJS server
- run: npm i -g pm2 - run: npm i -g pm2
- run: curl -L https://github.com/sasjs/server/releases/latest/download/linux.zip > linux.zip - run: curl -L https://github.com/sasjs/server/releases/latest/download/linux.zip > linux.zip
@ -131,42 +178,46 @@ jobs:
- run: echo NODE_PATH=node >> .env - run: echo NODE_PATH=node >> .env
- run: echo CORS=enable >> .env - run: echo CORS=enable >> .env
- run: echo WHITELIST=http://localhost:4200 >> .env - run: echo WHITELIST=http://localhost:4200 >> .env
# - run: echo "SERVER_URL=$server_url" >> .env - run: cat .env
# - run: echo "SERVER_TYPE=$server_type" >> .env
# - run: echo "CLIENT=$client_sasjs" >> .env
# - run: echo "ACCESS_TOKEN=$access_token_sasjs" >> .env
# - run: echo "REFRESH_TOKEN=$refresh_token_sasjs" >> .env
# - run: cat .env
- run: pm2 start api-linux --wait-ready - run: pm2 start api-linux --wait-ready
- name: Deploy mocked services - name: Deploy mocked services
run: | run: |
cd ./sas/mocks/sasjs cd ./sas/mocks/sasjs
npm install --legacy-peer-deps -g @sasjs/cli npm install -g @sasjs/cli
npm install --legacy-peer-deps -g replace-in-files-cli npm install -g replace-in-files-cli
sasjs cbd -t server-ci 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: Prepare frontend - name: Install ZIP
run: |
apt-get update
apt-get install zip
- name: Prepare and run frontend and cypress
run: | run: |
cd ./client cd ./client
# mv ./cypress.env.example.json ./cypress.env.json mv ./cypress.env.example.json ./cypress.env.json
# replace-in-files --regex='"username".*' --replacement='"username":"'$cypress_username_sasjs'",' ./cypress.env.json replace-in-files --regex='"username".*' --replacement='"username":"'${{ secrets.CYPRESS_USERNAME_SASJS }}'",' ./cypress.env.json
# replace-in-files --regex='"password".*' --replacement='"password":"'$cypress_pwd_sasjs'" ' ./cypress.env.json replace-in-files --regex='"password".*' --replacement='"password":"'${{ secrets.CYPRESS_PWD_SASJS }}'" ' ./cypress.env.json
cat ./cypress.env.json cat ./cypress.env.json
npm run postinstall npm run postinstall
npm install --legacy-peer-deps @sasjs/adapter@latest npm install @sasjs/adapter@latest
# Prepare index.html to SASJS local # Prepare index.html to SASJS local
replace-in-files --regex='serverUrl=".*?"' --replacement='serverUrl="http://localhost:5000"' ./src/index.html replace-in-files --regex='serverUrl=".*?"' --replacement='serverUrl="http://localhost:5000"' ./src/index.html
replace-in-files --regex='appLoc=".*?"' --replacement='appLoc="/Public/app/devtest"' ./src/index.html replace-in-files --regex='appLoc=".*?"' --replacement='appLoc="/Public/app/devtest"' ./src/index.html
replace-in-files --regex='serverType=".*?"' --replacement='serverType="SASJS"' ./src/index.html replace-in-files --regex='serverType=".*?"' --replacement='serverType="SASJS"' ./src/index.html
# Prepare and deploy SASJS version
# replace-in-files --regex='serverurl=".*?"' --replacement='serverUrl="http://localhost:5000"' ./dist/index.html
# replace-in-files --regex='apploc=".*?"' --replacement='appLoc="/30.SASApps/app/devtest"' ./dist/index.html
# replace-in-files --regex='servertype=".*?"' --replacement='serverType="SASJS"' ./dist/index.html
# scp -o stricthostkeychecking=no -r ./dist/* dcgitlab@sas.4gl.io:/var/www/html/dcviya/development/newadapter
replace-in-files --regex='"hosturl".*' --replacement='hosturl:"http://localhost:4200",' ./cypress.config.ts replace-in-files --regex='"hosturl".*' --replacement='hosturl:"http://localhost:4200",' ./cypress.config.ts
# replace-in-files --regex='"appLocation".*' --replacement='appLocation:"/dcviya/development/newadapter",' ./cypress.config.ts
cat ./cypress.config.ts cat ./cypress.config.ts
# Start frontend and run cypress # Start frontend and run cypress
npm start & npx wait-on http://localhost:4200 && ./run-cypress-tests.sh 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

View File

@ -22,29 +22,94 @@ jobs:
env: env:
NPMRC: ${{ secrets.NPMRC}} NPMRC: ${{ secrets.NPMRC}}
- name: Install ZIP - name: Install packages
run: | run: |
apt-get update apt-get update
apt-get install zip apt-get install zip -y
# sasjs cli is used to compile & build the SAS services
npm i -g @sasjs/cli
# jq is used to parse the release JSON
apt-get install jq -y
- name: release-build - name: Create Empty Release (assets are posted later)
run: |
npm i
npm i -g semantic-release
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: | run: |
cd client cd client
npm ci npm ci
npm run build npm run build
zip -r dist.zip ./dist
- name: Install Semantic Release and plugins - name: Build SAS9 EBI Release
description: Compile SAS 9 services, remove tests & create deployment program
run: | run: |
npm i cd sas
npm i -g semantic-release npm ci
sasjs c -t sas9
rm -rf sasjsbuild/tests
sasjs b -t sas9
cp sasjsbuild/mysas9deploy.sas ./demostream_sas9.sas
#
# remove streamed component and rebuild SAS 9 services
#
rm -rf sasjsbuild/services/web9
rm sasjsbuild/services/clickme.sas
sasjs b -t sas9
cp sasjsbuild/mysas9deploy.sas ./sas9.sas
- name: Release - name: Build SASjs Server Release
description: Compile Base (SASjs) services, remove tests & create deployment JSON
run: | run: |
GITEA_TOKEN=${{ secrets.RELEASE_TOKEN }} GITEA_URL=https://git.datacontroller.io semantic-release cd sas
cp sasjs/utils/favicon.ico ../client/dist/favicon.ico
sasjs c -t server
rm -rf sasjsbuild/tests
sasjs b -t server
cp sasjsbuild/server.json.zip ./sasjs_server.json.zip
- name: Build Viya Release
description: compile Viya Streaming Deploy (without tests)
run: |
cd sas
sasjs c -t viya
rm -rf sasjsbuild/tests
sed -i -e 's/servertype="SASJS"/servertype="SASVIYA"/g' sasjsbuild/services/clickme.html
sasjs b -t viya
cp sasjsbuild/viya.sas ./demostream_viya.sas
# compile Viya Full deploy (without web)
rm -rf sasjsbuild/services/web
rm sasjsbuild/services/clickme.html
sasjs b -t viya
cp sasjsbuild/viya.sas ./viya.sas
- name: Zip Frontend (including viya.json for full viya deploy)
run: |
cd sas
cp sasjsbuild/viya.json ../client/dist
cd ..
zip -r frontend.zip ./client/dist
- name: Release Typedoc - name: Release Typedoc
run: | run: |
cd client cd client
npm run typedoc npm run compodoc:build
# deploy docs surfer put --token ${{ secrets.TSDOC_TOKEN }} --server tsdoc.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": true,"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
curl -k $URL -F attachment=@sas/demostream_viya.sas
curl -k $URL -F attachment=@sas/sasjs_server.json.zip
curl -k $URL -F attachment=@sas/sas9.sas
curl -k $URL -F attachment=@sas/viya.sas

View File

@ -1,22 +1,44 @@
{ {
"branches": ["main"], "branches": [
"main"
],
"plugins": [ "plugins": [
"@semantic-release/commit-analyzer", "@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator", "@semantic-release/release-notes-generator",
"@semantic-release/changelog", "@semantic-release/changelog",
[ [
"@semantic-release/git", "@semantic-release/git",
{ {
"assets": [ "assets": [
"CHANGELOG.md" "CHANGELOG.md"
] ]
} }
], ],
["@saithodev/semantic-release-gitea", { [
"giteaUrl": "https://git.datacontroller.io", "@saithodev/semantic-release-gitea",
"assets": [ {
{"path": "client/dist.zip"} "giteaUrl": "https://git.datacontroller.io",
] "assets": [
}] {
"path": "sas/demostream_sas9.sas"
},
{
"path": "sas/demostream_viya.sas"
},
{
"path": "sas/frontend.zip"
},
{
"path": "sas/sasjs_server.json.zip"
},
{
"path": "sas/sas9.sas"
},
{
"path": "sas/viya.sas"
}
]
}
]
] ]
} }

View File

@ -1,9 +0,0 @@
pipeline:
build:
image: debian
commands:
- echo "This is the build step"
a-test-step:
image: debian
commands:
- echo "Testing.."

View File

@ -1,6 +1,9 @@
# Data Controller # Data Controller
# Contributing # Contributing
## Workflow guidelines
[Wiki Page](https://git.datacontroller.io/dc/dc/wiki/Git-Workflow)
## Dependencies that requires licences ## Dependencies that requires licences
[SheetJS Pro Version](https://www.npmjs.com/package/sheetjs) [SheetJS Pro Version](https://www.npmjs.com/package/sheetjs)

View File

@ -3,25 +3,15 @@ Licence Agreement for Data Controller for SAS®
Copyright (c) Bowe IO Ltd 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- 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.
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 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
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.
Your use of this software for commercial purposes is subject to the terms included in an applicable Your use of this software for commercial purposes is subject to the terms included in an applicable license agreement.
license agreement.
In any case, you must not make any such use of this software as to develop software which may be 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.
considered competitive with this software.
UNLESS EXPRESSLY AGREED OTHERWISE, 4GL APPS PROVIDES THIS SOFTWARE ON AN "AS IS" 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.
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.

View File

@ -0,0 +1,30 @@
# Data Controller for SAS
**Control your manual data modifications!**
_Alternatives to Data Controller include:_
* 💾 Developing / testing / deploying / scheduling overnight batch jobs to load files from shared drives
* 🔒 Opening (and locking) datasets in Enterprise Guide or SAS® Table Viewer to perform direct updates
* ❓ Asking a #DBA to run validated code after a change management process
* 🌐 Building & maintaining your own custom web application
* 🏃 Running #SAS or #SQL updates in production
_Problems with the above include:_
* Legacy 'black box' solutions with little to no testing, documentation or support
* End users requiring direct write access to critical data sources in production
* Upload routines that must be manually modified when the data model changes
* Breaches due to unnecessary parties having access to the data
* Inability to trace who made a change, when, and why
* Reliance on key individuals to perform updates
* Building bespoke ETL for every new data source
* High risk of manual error / data corruption
Data Controller for SAS® solves all these issues in a simple-to-install, user-friendly, secure, documented, battle-tested web application. Available on Viya, SAS 9 EBI, and [SASjs Server](https://server.sasjs.io).
For more information:
* Main site: https://datacontroller.io
* Docs: https://docs.datacontroller.io
* Code: https://code.datacontroller.io

View File

@ -1,17 +1,20 @@
import { defineConfig } from 'cypress' import { defineConfig } from "cypress";
export default defineConfig({ export default defineConfig({
reporter: 'mochawesome', reporter: "mochawesome",
reporterOptions: { reporterOptions: {
reportDir: 'cypress/results', reportDir: "cypress/results",
overwrite: false, overwrite: false,
html: true, html: true,
json: false, json: false,
}, },
chromeWebSecurity: false, chromeWebSecurity: false,
defaultCommandTimeout: 30000, defaultCommandTimeout: 30000,
env: { env: {
hosturl:"http://localhost:4200", hosturl: "http://localhost:4200",
appLocation: "", appLocation: "",
site_id_SAS9: "70221618", site_id_SAS9: "70221618",
site_id_SASVIYA: "70253615", site_id_SASVIYA: "70253615",
@ -23,6 +26,12 @@ export default defineConfig({
debug: false, debug: false,
screenshotOnRunFailure: false, screenshotOnRunFailure: false,
longerCommandTimeout: 50000, longerCommandTimeout: 50000,
testLicenceUserLimits: false testLicenceUserLimits: false,
} },
})
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
});

View File

@ -0,0 +1,250 @@
const username = Cypress.env('username')
const password = Cypress.env('password')
const hostUrl = Cypress.env('hosturl')
const appLocation = Cypress.env('appLocation')
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
const serverType = Cypress.env('serverType')
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
const downloadsFolder = Cypress.config('downloadsFolder')
import { deleteDownloadsFolder } from '../util/deleteDownloadFolder'
context('download files test: ', function () {
this.beforeAll(() => {
cy.visit(`${hostUrl}/SASLogon/logout`)
cy.loginAndUpdateValidKey()
})
this.beforeEach(() => {
cy.visit(hostUrl + appLocation)
cy.get('input.username').type(username)
cy.get('input.password').type(password)
cy.get('.login-group button').click()
visitPage('home')
})
this.afterEach(() => {
deleteDownloadsFolder()
})
it('1 | downloads audit file', (done) => {
visitPage('approve/toapprove')
cy.get('.app-loading', { timeout: longerCommandTimeout })
.should('not.exist')
.then(() => {
cy.get('.btn.btn-success')
.should('be.visible')
.then((buttons) => {
buttons[0].click()
const id = buttons[0].id
checkForFileDownloaded(id, 'zip', () => done())
})
})
})
it('2 | downloads viewer csv', (done) => {
visitPage('view/data')
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openDownloadModal(() => {
cy.get('select')
.select('CSV')
.then(() => {
cy.get('.btn.btn-sm.btn-success-outline').then((button) => {
button.trigger('click')
const id = button[0].id
checkForFileDownloaded(id, 'csv', () => done())
})
})
})
})
it('3 | downloads viewer excel', (done) => {
visitPage('view/data')
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openDownloadModal(() => {
cy.get('select')
.select('Excel')
.then(() => {
cy.get('.btn.btn-sm.btn-success-outline').then((button) => {
button.trigger('click')
const id = button[0].id
checkForFileDownloaded(id, 'xlsx', () => done())
})
})
})
})
it('4 | downloads viewer SAS Datalines', (done) => {
visitPage('view/data')
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openDownloadModal(() => {
cy.get('select')
.select('SAS Datalines')
.then(() => {
cy.get('.btn.btn-sm.btn-success-outline').then((button) => {
button.trigger('click')
const id = button[0].id
checkForFileDownloaded(id, 'sas', () => done())
})
})
})
})
it('5 | downloads viewer SAS DDL', (done) => {
visitPage('view/data')
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openDownloadModal(() => {
cy.get('select')
.select('SAS DDL')
.then(() => {
cy.get('.btn.btn-sm.btn-success-outline').then((button) => {
button.trigger('click')
const id = button[0].id
checkForFileDownloaded(id, 'ddl', () => done(), '_')
})
})
})
})
it('6 | downloads viewer TSQL DDL', (done) => {
visitPage('view/data')
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openDownloadModal(() => {
cy.get('select')
.select('TSQL DDL')
.then(() => {
cy.get('.btn.btn-sm.btn-success-outline').then((button) => {
button.trigger('click')
const id = button[0].id
checkForFileDownloaded(id, 'ddl', () => done(), '_')
})
})
})
})
it('7 | downloads viewer PGSQL DDL', (done) => {
visitPage('view/data')
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openDownloadModal(() => {
cy.get('select')
.select('PGSQL DDL')
.then(() => {
cy.get('.btn.btn-sm.btn-success-outline').then((button) => {
button.trigger('click')
const id = button[0].id
checkForFileDownloaded(id, 'ddl', () => done(), '_')
})
})
})
})
this.afterEach(() => {
cy.visit(`${hostUrl}/SASLogon/logout`)
})
})
const visitPage = (url: string) => {
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
}
const checkForFileDownloaded = (
id: string,
extension: string,
callback?: any,
libDivider: string = '.'
) => {
cy.on('url:changed', (newUrl) => {
console.log('newUrl', newUrl)
})
id = id.replace('.', libDivider)
const filename = downloadsFolder + '/' + id + '.' + extension
// browser might take a while to download the file,
// so use "cy.readFile" to retry until the file exists
// and has length - and we assume that it has finished downloading then
cy.readFile(filename, { timeout: longerCommandTimeout })
.should('have.length.gt', 10)
.then((file) => {
if (callback) callback()
})
}
const openDownloadModal = (callback?: any) => {
cy.get('.btn.btn-sm.btn-outline.filterSide.dropdown-toggle')
.click()
.then(() => {
cy.get('clr-dropdown-menu button').then((buttons) => {
for (let button of buttons) {
if (button.innerText.toLowerCase().includes('download')) {
button.click()
if (callback) callback()
}
}
})
})
}
const openTableFromTree = (libNameIncludes: string, tablename: string) => {
cy.get('.app-loading', { timeout: longerCommandTimeout })
.should('not.exist')
.then(() => {
cy.get('.nav-tree clr-tree > clr-tree-node', {
timeout: longerCommandTimeout
}).then((treeNodes: any) => {
let viyaLib
for (let node of treeNodes) {
if (node.innerText.toLowerCase().includes(libNameIncludes)) {
viyaLib = node
break
}
}
console.log('viyaLib', viyaLib)
cy.get(viyaLib).within(() => {
cy.get(
'.clr-tree-node-content-container .clr-treenode-content p'
).click()
cy.get('.clr-treenode-link').then((innerNodes: any) => {
for (let innerNode of innerNodes) {
if (innerNode.innerText.toLowerCase().includes(tablename)) {
innerNode.click()
break
}
}
})
})
})
})
}

View File

@ -0,0 +1,257 @@
const username = Cypress.env('username')
const password = Cypress.env('password')
const hostUrl = Cypress.env('hosturl')
const appLocation = Cypress.env('appLocation')
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
const serverType = Cypress.env('serverType')
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
const fixturePath = 'excels_general/'
context('editor tests: ', function () {
this.beforeAll(() => {
cy.visit(`${hostUrl}/SASLogon/logout`)
cy.loginAndUpdateValidKey()
})
this.beforeEach(() => {
cy.visit(hostUrl + appLocation)
// cy.get('input.username').type(username)
// cy.get('input.password').type(password)
// cy.get('.login-group button').click()
visitPage('home')
})
it('1 | Submits duplicate primary keys', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_datadictionary')
attachExcelFile('MPE_DATADICTIONARY_duplicate_keys.xlsx', () => {
clickOnUploadPreview(() => {
confirmEditPreviewFile(() => {
submitTable(() => {
cy.get('.modal-body').then((modalBody: any) => {
if (modalBody[0].innerText.includes(`Duplicates found:`)) {
done()
}
})
})
})
})
})
})
it('2 | Submits null cells which must not be null', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
clickOnEdit(() => {
cy.get('.btn.btn-sm.btn-icon.btn-outline-danger', {
timeout: longerCommandTimeout
}).then(() => {
cy.get('.ht_master tbody tr').then((rows: any) => {
cy.get(rows[1].childNodes[2])
.dblclick({ force: true })
.then(() => {
cy.focused()
.clear()
.type('{enter}')
.then(() => {
submitTable(() => {
cy.get('.modal-body').then((modalBody: any) => {
if (
modalBody[0].innerHTML
.toLowerCase()
.includes(`invalid values are present`)
) {
done()
}
})
})
})
})
})
})
})
})
it('3 | Gets basic dynamic cell validation', () => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
clickOnEdit(() => {
cy.get('.btn.btn-sm.btn-icon.btn-outline-danger', {
timeout: longerCommandTimeout
}).then(() => {
cy.get('.ht_master tbody tr').then((rows: any) => {
cy.get(rows[1].childNodes[5])
.click({ force: true })
.then(($td) => {
cy.get('.htAutocompleteArrow', { withinSubject: $td }).should(
'exist'
)
})
})
})
})
})
it('4 | Gets advanced dynamic cell validation', () => {
openTableFromTree(libraryToOpenIncludes, 'mpe_tables')
clickOnEdit(() => {
cy.get('.btn.btn-sm.btn-icon.btn-outline-danger', {
timeout: longerCommandTimeout
}).then(() => {
cy.get('.ht_master tbody tr').then((rows: any) => {
cy.get(rows[1].childNodes[3])
.click({ force: true })
.then(($td) => {
cy.get('.htAutocompleteArrow', { withinSubject: $td }).should(
'exist'
)
cy.get('.htAutocompleteArrow', {
withinSubject: rows[1].childNodes[7]
}).should('exist')
cy.get('.htAutocompleteArrow', {
withinSubject: rows[1].childNodes[8]
}).should('exist')
})
})
})
})
})
this.afterEach(() => {
// cy.visit(`${hostUrl}/SASLogon/logout`)
})
})
const clickOnEdit = (callback?: any) => {
cy.get('.btnCtrl button.btn-primary', { timeout: longerCommandTimeout })
.click()
.then(() => {
if (callback) callback()
})
}
const openTableFromTree = (libNameIncludes: string, tablename: string) => {
cy.get('.app-loading', { timeout: longerCommandTimeout })
.should('not.exist')
.then(() => {
cy.get('.nav-tree clr-tree > clr-tree-node', {
timeout: longerCommandTimeout
}).then((treeNodes: any) => {
let viyaLib
for (let node of treeNodes) {
if (node.innerText.toLowerCase().includes(libNameIncludes)) {
viyaLib = node
break
}
}
cy.get(viyaLib).within(() => {
cy.get('.clr-tree-node-content-container > button').click()
cy.get('.clr-treenode-link').then((innerNodes: any) => {
for (let innerNode of innerNodes) {
if (innerNode.innerText.toLowerCase().includes(tablename)) {
innerNode.click()
break
}
}
})
})
})
})
}
const attachExcelFile = (excelFilename: string, callback?: any) => {
cy.get('.buttonBar button:last-child')
.click()
.then(() => {
cy.get('input[type="file"]#file-upload')
.attachFile(`/${fixturePath}/${excelFilename}`)
.then(() => {
cy.get('.modal-footer .btn.btn-primary').then((modalBtn) => {
modalBtn.click()
if (callback) callback()
})
})
})
}
const clickOnUploadPreview = (callback?: any) => {
cy.get('.buttonBar button.btn-primary.btn-upload-preview')
.click()
.then(() => {
if (callback) callback()
})
}
const confirmEditPreviewFile = (callback?: any) => {
cy.get('.modal-footer button.btn-success-outline')
.click()
.then(() => {
if (callback) callback()
})
}
const submitTable = (callback?: any) => {
cy.get('.btnCtrl button.btn-primary')
.click()
.then(() => {
if (callback) callback()
})
}
const submitTableMessage = (callback?: any) => {
cy.get('.modal-footer .btn.btn-sm.btn-success-outline')
.click()
.then(() => {
if (callback) callback()
})
}
const submitExcel = (callback?: any) => {
cy.get('.buttonBar button.preview-submit')
.click()
.then(() => {
if (callback) callback()
})
}
const rejectExcel = (callback?: any) => {
cy.get('button', { timeout: longerCommandTimeout })
.should('contain', 'Go to approvals screen')
.then((allButtons: any) => {
for (let approvalButton of allButtons) {
if (
approvalButton.innerText
.toLowerCase()
.includes('go to approvals screen')
) {
approvalButton.click()
break
}
}
cy.get('button.btn-danger')
.should('exist')
.should('not.be.disabled')
.click()
.then(() => {
cy.get('.modal-footer button.btn-success-outline')
.click()
.then(() => {
cy.get('app-history')
.should('exist')
.then(() => {
if (callback) callback()
})
})
})
})
}
const visitPage = (url: string) => {
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
}

View File

@ -0,0 +1,537 @@
import { Callbacks } from 'cypress/types/jquery/index'
const username = Cypress.env('username')
const password = Cypress.env('password')
const hostUrl = Cypress.env('hosturl')
const appLocation = Cypress.env('appLocation')
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
const serverType = Cypress.env('serverType')
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
const fixturePath = 'excels/'
context('excel tests: ', function () {
this.beforeAll(() => {
cy.visit(`${hostUrl}/SASLogon/logout`)
cy.loginAndUpdateValidKey(true)
})
this.beforeEach(() => {
cy.visit(hostUrl + appLocation)
// cy.get('input.username').type(username)
// cy.get('input.password').type(password)
// cy.get('.login-group button').click()
visitPage('home')
colorLog(
`TEST START ---> ${
Cypress.mocha.getRunner().suite.ctx.currentTest.title
}`,
'#3498DB'
)
})
it('1 | Uploads regular Excel file', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('regular_excel.xlsx', () => {
submitExcel()
rejectExcel(done)
})
})
it('2 | Uploads Excel with data on the 7th tab', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('7th_tab_excel.xlsx', () => {
submitExcel()
rejectExcel(done)
})
})
it('3 | Uploads Excel with missing columns (should fail)', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('missing_columns_excel.xlsx', () => {
cy.get('.abortMsg', { timeout: longerCommandTimeout })
.should('exist')
.then((elements: any) => {
if (elements[0]) {
if (elements[0].innerText.toLowerCase().includes('missing')) done()
}
})
})
})
it('4 | Uploads Excel with formulas', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_datadictionary')
attachExcelFile('formulas_excel.xlsx', () => {
checkResultOfFormulaUpload(done)
})
})
it('5 | Uploads Excel with no data rows', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('nodata_rows_excel.xlsx', () => {
cy.get('.abortMsg', { timeout: longerCommandTimeout })
.should('exist')
.then((elements: any) => {
if (elements[0]) {
if (
elements[0].innerText
.toLowerCase()
.includes('no relevant data found')
)
done()
}
})
})
})
it('6 | Uploads Excel with a table that is surrounded by other data', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('surrounded_data_excel.xlsx', () => {
submitExcel()
rejectExcel(done)
})
})
it('7 | Uploads Excel with a extra columns in the middle', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('extra_column_excel.xlsx', () => {
submitExcel()
rejectExcel(done)
})
})
it('8 | Uploads Excel with a duplicate column', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('duplicate_column_excel.xlsx', () => {
cy.get('.abortMsg', { timeout: longerCommandTimeout })
.should('exist')
.then((elements: any) => {
if (elements[0]) {
if (elements[0].innerText.toLowerCase().includes('missing')) done()
}
})
})
})
it('9 | Uploads Excel with a duplicate row', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('duplicate_row_excel.xlsx', () => {
submitExcel(() => {
cy.get('.duplicate-keys-modal', { timeout: longerCommandTimeout })
.should('exist')
.then((elements: any) => {
if (elements[0]) {
if (elements[0].innerText.toLowerCase().includes('duplicates'))
done()
}
})
})
})
})
it('10 | Uploads Excel with a mixed content', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('mixed_content_excel.xlsx', () => {
submitExcel(() => {
cy.get('.modal-body').then((modalBody: any) => {
if (
modalBody[0].innerHTML
.toLowerCase()
.includes(`invalid values are present`)
) {
done()
}
})
})
})
})
it('11 | Uploads Excel with a blank columns', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('blank_columns_excel.xlsx', () => {
cy.get('.abortMsg', { timeout: longerCommandTimeout })
.should('exist')
.then((elements: any) => {
if (elements[0]) {
if (elements[0].innerText.toLowerCase().includes('missing')) done()
}
})
})
})
it('12 | Uploads Excel xls extension', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('regular_excel_xls.xls', () => {
submitExcel()
rejectExcel(done)
})
})
// For some strange reason this file breaks cypress. When uploaded manually in DC it is working.
// it('13 | Uploads Excel xlsm extension', (done) => {
// openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
// attachExcelFile('regular_excel_macro.xlsm', () => {
// submitExcel()
// rejectExcel(done)
// })
// })
it('14 | Uploads Excel with composite primary key', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_datadictionary')
attachExcelFile('MPE_DATADICTIONARY_composite_keys.xlsx', () => {
submitExcel()
rejectExcel(done)
})
})
it('15 | Uploads Excel with missing row (empty table)', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_datadictionary')
attachExcelFile('MPE_DATADICTIONARY_missing_row.xlsx', () => {
cy.get('.abortMsg', { timeout: longerCommandTimeout })
.should('exist')
.then((elements: any) => {
if (elements[0]) {
if (
elements[0].innerText
.toLowerCase()
.includes('no relevant data found')
)
done()
}
})
})
})
it('16 | Uploads Excel with merged cells', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_datadictionary')
attachExcelFile('MPE_DATADICTIONARY_merged_cells.xlsx', () => {
submitExcel()
rejectExcel(done)
})
})
it('17 | Check uploaded values from excel with xls extension', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('regular_excel_xls.xls', () => {
checkResultOfXLSUpload(done)
})
})
it('18 | Uploads Excel with missing row (empty table)', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('blank_column_with_header.xlsx', () => {
cy.get('.btn-upload-preview', { timeout: 60000 })
.should('be.visible')
.then(() => {
cy.get('#hotInstance', { timeout: 30000 })
.find('div.ht_master.handsontable')
.find('div.wtHolder')
.find('div.wtHider')
.find('div.wtSpreader')
.find('table.htCore')
.find('tbody')
.then((data) => {
let allEmpty = true
for (let col = 0; col < data[0].children.length; col++) {
const cell: any = data[0].children[col].children[5]
if (cell.innerText !== '') {
allEmpty = false
break
}
}
if (allEmpty) done()
})
})
})
})
it('19 | Uploads Excel with data on random sheet surrounded with all empty cells', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('surrounded_data_all_cells_empty_excel.xlsx', () => {
checkResultOfXLSUpload(done)
})
})
it('20 | Uploads Excel with data surrounded with empty cells ', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('surrounded_data_empty_cells_excel.xlsx', () => {
checkResultOfXLSUpload(done)
})
})
it('21 | Uploads regular Excel file with first row marked for Delete (yes)', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
attachExcelFile('regular_excel_with_delete.xlsx', () => {
cy.get('.btn-upload-preview', { timeout: 60000 })
.should('be.visible')
.then(() => {
cy.get('#hotInstance', { timeout: 30000 })
.find('div.ht_master.handsontable')
.find('div.wtHolder')
.find('div.wtHider')
.find('div.wtSpreader')
.find('table.htCore')
.find('tbody')
.then((data: JQuery<HTMLTableSectionElement>) => {
const firstRowFirstCol: Partial<HTMLElement> =
data[0].children[0].children[1]
if (
firstRowFirstCol.innerText &&
!firstRowFirstCol.innerText.toLowerCase().includes('yes')
) {
done('Delete? column from file not applied')
}
})
.then(() => {
submitExcel()
rejectExcel(done)
})
})
})
})
// Large files break Cypress
// it ('? | Uploads Excel with size of 5MB', (done) => {
// attachExcelFile('5mb_excel.xlsx', () => {
// submitExcel();
// rejectExcel(done);
// });
// })
// it ('? | Uploads Excel with size of 15MB', (done) => {
// attachExcelFile('15mb_excel.xlsx', () => {
// submitExcel();
// rejectExcel(done);
// });
// })
//Large files tests end
this.afterEach(() => {
colorLog(`TEST END -------------`, '#3498DB')
// cy.visit(`${hostUrl}/SASLogon/logout`)
})
})
const openTableFromTree = (libNameIncludes: string, tablename: string) => {
cy.get('.app-loading', { timeout: longerCommandTimeout })
.should('not.exist')
.then(() => {
cy.get('.nav-tree clr-tree > clr-tree-node', {
timeout: longerCommandTimeout
}).then((treeNodes: any) => {
let viyaLib
for (let node of treeNodes) {
if (node.innerText.toLowerCase().includes(libNameIncludes)) {
viyaLib = node
break
}
}
cy.get(viyaLib).within(() => {
cy.get('.clr-tree-node-content-container > button').click()
cy.get('.clr-treenode-link').then((innerNodes: any) => {
for (let innerNode of innerNodes) {
if (innerNode.innerText.toLowerCase().includes(tablename)) {
innerNode.click()
break
}
}
})
})
})
})
}
const attachExcelFile = (excelFilename: string, callback?: any) => {
cy.get('.buttonBar button:last-child')
.should('exist')
.click()
.then(() => {
cy.get('input[type="file"]#file-upload')
.attachFile(`/${fixturePath}/${excelFilename}`)
.then(() => {
cy.get('.clr-abort-modal .modal-title').then((modalTitle) => {
if (!modalTitle[0].innerHTML.includes('Abort Message')) {
cy.get('.modal-footer .btn.btn-primary').then((modalBtn) => {
modalBtn.click()
if (callback) callback()
})
} else {
if (callback) callback()
}
})
})
})
}
const submitExcel = (callback?: any) => {
cy.get('.buttonBar button.preview-submit', { timeout: longerCommandTimeout })
.click()
.then(() => {
if (callback) callback()
})
}
const rejectExcel = (callback?: any) => {
cy.get('button', { timeout: longerCommandTimeout })
.should('contain', 'Go to approvals screen')
.then((allButtons: any) => {
for (let approvalButton of allButtons) {
if (
approvalButton.innerText
.toLowerCase()
.includes('go to approvals screen')
) {
approvalButton.click()
break
}
}
cy.get('button.btn-danger')
.should('exist')
.should('not.be.disabled')
.click()
.then(() => {
cy.get('.modal-footer button.btn-success-outline')
.click()
.then(() => {
cy.get('app-history')
.should('exist')
.then(() => {
if (callback) callback()
})
})
})
})
}
const acceptExcel = (callback?: any) => {
cy.get('button', { timeout: longerCommandTimeout })
.should('contain', 'Go to approvals screen')
.then((allButtons: any) => {
for (let approvalButton of allButtons) {
if (
approvalButton.innerText
.toLowerCase()
.includes('go to approvals screen')
) {
approvalButton.click()
break
}
}
cy.get('#acceptBtn')
.should('exist')
.should('not.be.disabled')
.click()
.then(() => {
if (callback) {
callback()
}
})
})
}
const checkResultOfFormulaUpload = (callback?: any) => {
cy.get('#hotInstance', { timeout: longerCommandTimeout })
.find('div.ht_master.handsontable')
.find('div.wtHolder')
.find('div.wtHider')
.find('div.wtSpreader')
.find('table.htCore')
.find('tbody')
.then((data) => {
const cell: any = data[0].children[0].children[5]
expect(cell.innerText).to.equal('=1+1')
if (callback) callback()
})
}
const checkResultOfXLSUpload = (callback?: any) => {
cy.viewport(1280, 720)
cy.get('#hotInstance', { timeout: 30000 })
.find('div.ht_master.handsontable')
.find('div.wtHolder')
.find('div.wtHider')
.find('div.wtSpreader')
.find('table.htCore')
.find('tbody')
.then((data) => {
let cell: any = data[0].children[0].children[2]
expect(cell.innerText).to.equal('0')
cell = data[0].children[0].children[3]
expect(cell.innerText).to.equal('this is dummy data changed in excel')
cell = data[0].children[0].children[4]
expect(cell.innerText).to.equal('▼\nOption 1')
cell = data[0].children[0].children[5]
expect(cell.innerText).to.equal('42')
cell = data[0].children[0].children[6]
expect(cell.innerText).to.equal('▼\n1960-02-12')
// When CI detached browser screen is smaller, below cells are not visible so test fails
// Commenting it out now until we figure out workaround
// cell = data[0].children[0].children[7]
// expect(cell.innerText).to.equal('▼\n1960-01-01 00:00:42')
// cell = data[0].children[0].children[8]
// expect(cell.innerText).to.equal('00:00:42')
if (callback) callback()
})
cy.get('#hotInstance', { timeout: 30000 })
.find('div.ht_master.handsontable')
.find('div.wtHolder')
.scrollTo('right')
.find('div.wtHider')
.find('div.wtSpreader')
.find('table.htCore')
.find('tbody')
.then((data) => {
let cell: any = data[0].children[0].children[1]
cell = data[0].children[0].children[9]
expect(cell.innerText).to.equal('44')
if (callback) callback()
})
}
const visitPage = (url: string) => {
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
}
const colorLog = (msg: string, color: string) => {
console.log('%c' + msg, 'color:' + color + ';font-weight:bold;')
}

View File

@ -0,0 +1,383 @@
const username = Cypress.env('username')
const password = Cypress.env('password')
const hostUrl = Cypress.env('hosturl')
const appLocation = Cypress.env('appLocation')
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
const serverType = Cypress.env('serverType')
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
const fixturePath = 'excels_general/'
context('filtering tests: ', function () {
this.beforeAll(() => {
cy.visit(`${hostUrl}/SASLogon/logout`, { timeout: longerCommandTimeout })
cy.loginAndUpdateValidKey()
})
this.beforeEach(() => {
cy.visit(hostUrl + appLocation, { timeout: longerCommandTimeout })
// cy.get('input.username').type(username)
// cy.get('input.password').type(password)
// cy.get('.login-group button').click()
visitPage('home')
})
it('1 | filter char field', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openFilterPopup(() => {
setFilterWithValue('SOME_CHAR', 'this is dummy data', 'value', () => {
checkInfoBarIncludes(
`AND,AND,0,SOME_CHAR,=,"'this is dummy data'"`,
(includes: boolean) => {
if (includes) done()
}
)
})
})
})
it('2 | filter number field', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openFilterPopup(() => {
setFilterWithValue('SOME_NUM', '42', 'value', () => {
checkInfoBarIncludes(`AND,AND,0,SOME_NUM,=,42`, (includes: boolean) => {
if (includes) done()
})
})
})
})
it('3 | filter time field', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openFilterPopup(() => {
setFilterWithValue('SOME_TIME', '00:00:42', 'time', () => {
checkInfoBarIncludes(
`AND,AND,0,SOME_TIME,=,42`,
(includes: boolean) => {
if (includes) done()
}
)
})
})
})
it('3.1 | Non picker - filter time field', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openFilterPopup(() => {
setFilterWithValue('SOME_TIME', '42', 'value', () => {
checkInfoBarIncludes(
`AND,AND,0,SOME_TIME,=,42`,
(includes: boolean) => {
if (includes) done()
}
)
})
}, false)
})
it('4 | filter date field', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openFilterPopup(() => {
setFilterWithValue('SOME_DATE', '12/02/1960', 'date', () => {
checkInfoBarIncludes(
`AND,AND,0,SOME_DATE,=,42`,
(includes: boolean) => {
if (includes) done()
}
)
})
})
})
it('4.1 | Non picker - filter date field', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openFilterPopup(() => {
setFilterWithValue('SOME_DATE', '42', 'value', () => {
checkInfoBarIncludes(
`AND,AND,0,SOME_DATE,=,42`,
(includes: boolean) => {
if (includes) done()
}
)
})
}, false)
})
it('5 | filter datetime field', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openFilterPopup(() => {
setFilterWithValue(
'SOME_DATETIME',
'01/01/1960 00:00:42',
'datetime',
() => {
checkInfoBarIncludes(
`AND,AND,0,SOME_DATETIME,=,42`,
(includes: boolean) => {
if (includes) done()
}
)
}
)
})
})
it('5.1 | Non picker - filter datetime field', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openFilterPopup(() => {
setFilterWithValue('SOME_DATETIME', '42', 'value', () => {
checkInfoBarIncludes(
`AND,AND,0,SOME_DATETIME,=,42`,
(includes: boolean) => {
if (includes) done()
}
)
})
}, false)
})
it('6 | filter date field IN', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openFilterPopup(() => {
setFilterWithValue('SOME_DATE', '', 'in', () => {
checkInfoBarIncludes(
`AND,AND,0,SOME_DATE,IN,(0)`,
(includes: boolean) => {
if (includes) done()
}
)
})
})
})
it('7 | filter bestnum field BETWEEN', (done) => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
openFilterPopup(() => {
setFilterWithValue('SOME_BESTNUM', '0-10', 'between', () => {
checkInfoBarIncludes(
`AND,AND,0,SOME_BESTNUM,BETWEEN,0 AND 10`,
(includes: boolean) => {
if (includes) done()
}
)
})
})
})
this.afterEach(() => {
// cy.visit(`${hostUrl}/SASLogon/logout`)
})
})
const checkInfoBarIncludes = (text: string, callback: any) => {
cy.get('.infoBar b', { timeout: longerCommandTimeout }).then((el: any) => {
const includes = el[0].innerText.toLowerCase().includes(text.toLowerCase())
if (callback) callback(includes)
})
}
const openFilterPopup = (
callback?: any,
usePickers: boolean = true,
isViewerFiltering: boolean = false
) => {
const filterButton = isViewerFiltering
? '.btn-outline.filterSide'
: '.btnCtrl .btnView'
cy.get(filterButton, { timeout: longerCommandTimeout }).then(
(optionsButton: any) => {
optionsButton.click()
if (isViewerFiltering) {
cy.wait(300)
cy.get('.dropdown-menu button').then(async (dropdownButtons: any) => {
let filterButton = null
for (let btn of dropdownButtons) {
if (btn.innerText.toLowerCase().includes('filter')) {
filterButton = btn
break
}
}
if (filterButton) {
filterButton.click()
if (usePickers) turnOnPickers()
if (callback) callback()
return
}
})
}
if (usePickers) turnOnPickers()
if (callback) callback()
}
)
}
const turnOnPickers = () => {
cy.get('#usePickers')
.should('exist')
.then((picker: any) => {
picker[0].click()
})
}
const setFilterWithValue = (
variableValue: string,
valueString: string,
valueField: 'value' | 'time' | 'date' | 'datetime' | 'in' | 'between',
callback?: any
) => {
cy.wait(600)
cy.focused().type(variableValue)
cy.wait(100)
// cy.focused().trigger('input')
cy.get('.variable-col .autocomplete-wrapper', { withinSubject: null })
.first()
.trigger('keydown', { key: 'ArrowDown' })
cy.get('.variable-col .autocomplete-wrapper', {
withinSubject: null
}).trigger('keydown', { key: 'Enter' })
cy.focused().tab()
cy.wait(100)
if (valueField === 'in') {
cy.focused().select(valueField.toUpperCase()).trigger('change')
} else if (valueField === 'between') {
cy.focused().select(valueField.toUpperCase()).trigger('change')
} else {
cy.focused().tab()
cy.wait(100)
}
switch (valueField) {
case 'value': {
cy.focused().type(valueString)
break
}
case 'time': {
cy.focused().type(valueString)
break
}
case 'date': {
cy.focused().type(valueString)
cy.focused().tab()
break
}
case 'datetime': {
const date = valueString.split(' ')[0]
const time = valueString.split(' ')[1]
cy.focused().type(date)
cy.focused().tab()
cy.focused().tab()
cy.focused().type(time)
break
}
case 'in': {
cy.get('.checkbox-vals').then(() => {
cy.focused().tab()
cy.focused().click()
cy.get('.no-values')
.should('not.exist')
.then(() => {
cy.get('.in-values-modal clr-checkbox-wrapper input').then((inputs: any) => {
inputs[0].click()
cy.get('.in-values-modal .modal-footer button').click()
cy.get('.modal-footer .btn-success-outline').click()
if (callback) callback()
})
})
})
break
}
case 'between': {
cy.focused().tab()
const start = valueString.split('-')[0]
const end = valueString.split('-')[1]
cy.focused().type(start)
cy.focused().tab()
cy.focused().type(end)
}
default: {
break
}
}
cy.wait(100)
cy.focused().tab()
cy.wait(100)
cy.focused().tab()
cy.wait(100)
cy.focused().tab()
cy.wait(100)
cy.focused().tab()
cy.wait(100)
cy.focused().click()
if (callback) callback()
}
const openTableFromTree = (libNameIncludes: string, tablename: string) => {
cy.get('.app-loading', { timeout: longerCommandTimeout })
.should('not.exist')
.then(() => {
cy.get('.nav-tree clr-tree > clr-tree-node', {
timeout: longerCommandTimeout
}).then((treeNodes: any) => {
let viyaLib
for (let node of treeNodes) {
if (new RegExp(libNameIncludes).test(node.innerText.toLowerCase())) {
viyaLib = node
break
}
}
cy.get(viyaLib).within(() => {
cy.get('.clr-tree-node-content-container p').click()
cy.get('.clr-treenode-link').then((innerNodes: any) => {
for (let innerNode of innerNodes) {
if (innerNode.innerText.toLowerCase().includes(tablename)) {
innerNode.click()
break
}
}
})
})
})
})
}
const visitPage = (url: string) => {
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
}

View File

@ -0,0 +1,731 @@
import { arrayBufferToBase64 } from './../util/helper-functions'
import * as moment from 'moment'
const username = Cypress.env('username')
const password = Cypress.env('password')
const hostUrl = Cypress.env('hosturl')
const appLocation = Cypress.env('appLocation')
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
const fixturePath = 'excels_general/'
const serverType = Cypress.env('serverType')
const site_id = Cypress.env(`site_id_${serverType}`)
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
const testLicenceUserLimits = Cypress.env('testLicenceUserLimits')
/** IMPORTANT NOTICE
* Before running tests, make sure that table `MPE_USERS` is present
*/
interface EditConfigTableCells {
varName: string
varValue: string
}
context('licensing tests: ', function () {
this.beforeAll(() => {
// cy.visit(`${hostUrl}/SASLogon/logout`)
cy.loginAndUpdateValidKey()
})
this.beforeEach(() => {
cy.visit(hostUrl + appLocation)
// cy.get('input.username').type(username)
// cy.get('input.password').type(password)
// cy.get('.login-group button').click()
visitPage('home')
})
it('1 | key valid, not expired', (done) => {
let keyData = {
valid_until: moment().add(1, 'year').format('YYYY-MM-DD'),
users_allowed: 4,
hot_license_key: '',
demo: false,
site_id: site_id
}
let keys: { licenseKey: any; activationKey: any }
generateKeys(keyData, (keysGen: any) => {
keys = keysGen
cy.wait(2000)
isLicensingPage((result: boolean) => {
if (result) {
inputLicenseKeyPage(keys.licenseKey, keys.activationKey)
cy.wait(2000)
}
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(10000)
}
visitPage('home')
cy.get('.nav-tree clr-tree > clr-tree-node', {
timeout: longerCommandTimeout
}).then((treeNodes: any) => {
done()
})
})
})
})
})
it('2 | Key will expire in less then 14 days, not free tier', (done) => {
// make 2 separate for this one
let keyData = {
valid_until: moment().add(10, 'day').format('YYYY-MM-DD'),
users_allowed: 4,
hot_license_key: '',
demo: false,
site_id: site_id
}
let keys: { licenseKey: any; activationKey: any }
generateKeys(keyData, (keysGen: any) => {
keys = keysGen
console.log('keys', keys)
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
updateLicenseKeyQuick(keysGen, () => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
verifyLicensingWarning('This license key will expire in ', () => {
done()
})
})
})
})
})
})
it('3 | key expired, free tier works', (done) => {
let keyData = {
valid_until: moment().subtract(1, 'day').format('YYYY-MM-DD'),
users_allowed: 4,
hot_license_key: '',
demo: false,
site_id: site_id
}
let keys: { licenseKey: any; activationKey: any }
generateKeys(keyData, (keysGen: any) => {
keys = keysGen
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
cy.wait(2000)
updateLicenseKeyQuick(keysGen, () => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
verifyLicensingPage(
'Licence key is expired - please contact',
(success: boolean) => {
if (success) {
verifyLicensingWarning(
'(FREE Tier) - Problem with licence',
() => {
done()
}
)
}
}
)
})
})
})
})
})
it('4 | key invalid, free tier works', (done) => {
let keyData = {
valid_until: moment().subtract(1, 'day').format('YYYY-MM-DD'),
users_allowed: 4,
hot_license_key: '',
demo: false,
site_id: site_id
}
let keys: { licenseKey: any; activationKey: any }
generateKeys(keyData, (keysGen: any) => {
keys = keysGen
keys.activationKey = 'invalid' + keys.activationKey
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
cy.wait(2000)
updateLicenseKeyQuick(keysGen, () => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
verifyLicensingPage(
'Licence key is invalid - please contact',
(success: boolean) => {
if (success) {
verifyLicensingWarning(
'(FREE Tier) - Problem with licence',
() => {
done()
}
)
}
}
)
})
})
})
})
})
it('5 | key for wrong organisation, free tier works', (done) => {
let keyData = {
valid_until: moment().add(1, 'year').format('YYYY-MM-DD'),
users_allowed: 4,
hot_license_key: '',
demo: false,
site_id: 100
}
let keys: { licenseKey: any; activationKey: any }
generateKeys(keyData, (keysGen: any) => {
keys = keysGen
keys.activationKey = keys.activationKey
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
cy.wait(2000)
updateLicenseKeyQuick(keysGen, () => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
verifyLicensingPage(
'SYSSITE (below) is not found',
(success: boolean) => {
if (success) {
verifyLicensingWarning(
'(FREE Tier) - Problem with licence',
() => {
done()
}
)
}
}
)
})
})
})
})
})
if (testLicenceUserLimits) {
it('4 | User try to register when limit is reached', (done) => {
let keyData = {
valid_until: moment().add(1, 'month').format('YYYY-MM-DD'),
users_allowed: 10,
hot_license_key: '',
demo: false,
site_id: site_id
}
let keyData2 = {
valid_until: moment().add(1, 'month').format('YYYY-MM-DD'),
users_allowed: 1,
hot_license_key: '',
demo: false,
site_id: site_id
}
generateKeys(keyData, (keysGen: any) => {
generateKeys(keyData2, (keysGen2: any) => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
updateLicenseKeyQuick(keysGen, () => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
updateLicenseKeyQuick(keysGen2, () => {
cy.wait(2000)
const random = Cypress._.random(0, 1000)
const newUser = {
username: `randomusername${random}notregistered`,
last_seen_at: moment().add(1, 'month').format('YYYY-MM-DD'),
registered_at: moment().add(1, 'month').format('YYYY-MM-DD')
}
updateUsersTable(
{ deleteAll: true, newUsers: [newUser] },
() => {
logout(() => {
cy.visit(hostUrl + appLocation)
// cy.get('input.username').type(username)
// cy.get('input.password').type(password)
// cy.get('.login-group button').click()
visitPage('home')
cy.wait(2000)
verifyLicensingPage(
'The registered number of users reached the limit specified for your licence.',
(success: boolean) => {
if (success) done()
}
)
})
}
)
})
})
})
})
})
})
})
it('5 | Show warning banner when limit is exceeded', (done) => {
let keyData = {
valid_until: moment().add(1, 'month').format('YYYY-MM-DD'),
users_allowed: 10,
hot_license_key: '',
demo: false,
site_id: site_id
}
let keyData2 = {
valid_until: moment().add(1, 'month').format('YYYY-MM-DD'),
users_allowed: 1,
hot_license_key: '',
demo: false,
site_id: site_id
}
generateKeys(keyData, (keysGen: any) => {
generateKeys(keyData2, (keysGen2: any) => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
updateLicenseKeyQuick(keysGen, () => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
const random = Cypress._.random(0, 1000)
const newUser = {
username: `randomusername${random}`,
last_seen_at: moment().add(1, 'month').format('YYYY-MM-DD'),
registered_at: moment().add(1, 'month').format('YYYY-MM-DD')
}
updateUsersTable(
{ deleteAll: true, keep: username, newUsers: [newUser] },
() => {
updateLicenseKeyQuick(keysGen2, () => {
cy.wait(2000)
verifyLicensingWarning(
'The registered number of users exceeds the limit specified for your license.',
() => {
done()
}
)
})
}
)
})
})
})
})
})
})
}
this.afterEach(() => {
// cy.visit(`${hostUrl}/SASLogon/logout`)
})
})
const logout = (callback?: any) => {
cy.get('.header-actions .dropdown-toggle')
.click()
.then(() => {
cy.get('.header-actions .dropdown-menu > .separator')
.next()
.click()
.then(() => {
if (callback) callback()
})
})
}
const acceptTermsIfPresented = (callback?: any) => {
cy.url().then((url: string) => {
if (url.includes('licensing/register')) {
cy.get('.card-block')
.scrollTo('bottom')
.then(() => {
cy.get('#checkbox1')
.click()
.then(() => {
if (callback) callback(true)
})
})
} else {
if (callback) callback(false)
}
})
}
const isLicensingPage = (callback: any) => {
return cy.url().then((url: string) => {
callback(
url.includes('#/licensing/') && !url.includes('licensing/register')
)
})
}
const verifyLicensingPage = (text: string, callback: any) => {
// visitPage('home')
cy.wait(1000)
isLicensingPage((result: boolean) => {
if (result) {
cy.get('p.key-error')
.should('contain', text)
.then((treeNodes: any) => {
callback(true)
})
}
})
}
const verifyLicensingWarning = (text: string, callback: any) => {
visitPage('home')
cy.wait(1000)
cy.get("div[role='alert'] .alert-text")
.invoke('text')
.should('contain', text)
.then(() => {
callback()
})
}
const inputLicenseKeyPage = (licenseKey: string, activationKey: string) => {
cy.get('button').contains('Paste licence').click()
cy.get('.license-key-form textarea', { timeout: longerCommandTimeout })
.invoke('val', licenseKey)
.trigger('input')
.should('not.be.undefined')
cy.get('.activation-key-form textarea', { timeout: longerCommandTimeout })
.invoke('val', activationKey)
.trigger('input')
.should('not.be.undefined')
cy.get('button.apply-keys').click()
}
const updateUsersTable = (options: any, callback?: any) => {
visitPage('home')
openTableFromTree(libraryToOpenIncludes, 'mpe_users')
clickOnEdit(() => {
cy.get('.ht_master tbody tr').then((rows: any) => {
if (options.deleteAll) {
for (let row of rows) {
const user_id = row.childNodes[2]
if (!options.keep || user_id.innerText !== options.keep) {
cy.get(row.childNodes[1])
.dblclick()
.then(() => {
cy.focused().type('{selectall}').type('Yes').type('{enter}')
})
}
}
}
if (options.newUsers && options.newUsers.length) {
for (let newUser of options.newUsers) {
clickOnAddRow(() => {
cy.get('#hotInstance tbody tr:last-child').then((rows: any) => {
cy.get(rows[0].childNodes[2])
.dblclick()
.then(() => {
cy.focused()
.type('{selectall}')
.type(newUser.username)
.type('{enter}')
})
// cy.get(rows[0].childNodes[3])
// .dblclick()
// .then(() => {
// cy.focused()
// .type('{selectall}')
// .type(newUser.last_seen_at)
// .type('{enter}')
// })
// cy.get(rows[0].childNodes[4])
// .dblclick()
// .then(() => {
// cy.focused()
// .type('{selectall}')
// .type(newUser.registered_at)
// .type('{enter}')
// })
submitTable(() => {
cy.wait(2000)
approveTable(callback)
})
})
})
}
}
})
})
}
const changeLicenseKeyTable = (keys: any, callback?: any) => {
visitPage('home')
openTableFromTree(libraryToOpenIncludes, 'mpe_config')
clickOnEdit(() => {
editTableField(
[
{ varName: 'DC_ACTIVATION_KEY', varValue: keys.activationKey },
{ varName: 'DC_LICENCE_KEY', varValue: keys.licenseKey }
],
() => {
submitTable(() => {
cy.wait(2000)
approveTable(() => {
cy.reload()
if (callback) callback()
})
})
}
)
})
}
const updateLicenseKeyQuick = (keys: any, callback: any) => {
isLicensingPage((result: boolean) => {
if (!result) {
visitPage('licensing/update')
cy.wait(2000)
}
inputLicenseKeyPage(keys.licenseKey, keys.activationKey)
callback()
})
}
const generateKeys = async (licenseData: any, resultCallback?: any) => {
let keyPair = await window.crypto.subtle.generateKey(
{
name: 'RSA-OAEP',
modulusLength: 2024,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256'
},
true,
['encrypt', 'decrypt']
)
const encoded = new TextEncoder().encode(JSON.stringify(licenseData))
const cipher = await window.crypto.subtle
.encrypt(
{
name: 'RSA-OAEP'
},
keyPair.publicKey,
encoded
)
.then(
(value) => {
return value
},
(err) => {
console.log('Encrpyt error', err)
}
)
if (!cipher) {
alert('Encryptin keys failed')
throw new Error('Encryptin keys failed')
}
const privateKeyBytes = await window.crypto.subtle.exportKey(
'pkcs8',
keyPair.privateKey
)
const activationKey = await arrayBufferToBase64(privateKeyBytes)
const licenseKey = await arrayBufferToBase64(cipher)
if (resultCallback)
resultCallback({
activationKey,
licenseKey
})
}
const editTableField = (edits: EditConfigTableCells[], callback?: any) => {
cy.get('td').then((tdNodes: any) => {
for (let edit of edits) {
let correctRow = false
for (let node of tdNodes) {
if (correctRow) {
cy.get(node)
.dblclick()
.then(() => {
// textarea update on long keys
cy.focused().invoke('val', edit.varValue).type('{enter}')
})
correctRow = false
break
}
if (node.innerText.includes(edit.varName)) {
correctRow = true
}
}
}
if (callback) callback()
})
}
const openTableFromTree = (
libNameIncludes: string,
tablename: string,
callback?: any
) => {
cy.get('.app-loading', { timeout: longerCommandTimeout })
.should('not.exist')
.then(() => {
cy.get('.nav-tree clr-tree > clr-tree-node', {
timeout: longerCommandTimeout
}).then((treeNodes: any) => {
let viyaLib
for (let node of treeNodes) {
if (node.innerText.toLowerCase().includes(libNameIncludes)) {
viyaLib = node
break
}
}
cy.get(viyaLib).within(() => {
cy.get('.clr-tree-node-content-container > button').click()
cy.get('.clr-treenode-link').then((innerNodes: any) => {
for (let innerNode of innerNodes) {
if (innerNode.innerText.toLowerCase().includes(tablename)) {
innerNode.click()
if (callback) callback()
break
}
}
})
})
})
})
}
const clickOnAddRow = (callback?: any) => {
cy.get('.btnCtrl button.btn-success')
.click()
.then(() => {
if (callback) callback()
})
}
const clickOnEdit = (callback?: any) => {
cy.get('.btnCtrl button.btn-primary', { timeout: longerCommandTimeout })
.click()
.then(() => {
if (callback) callback()
})
}
const submitTable = (callback?: any) => {
cy.get('.btnCtrl button.btn-primary', { timout: longerCommandTimeout })
.click()
.then(() => {
cy.get(".modal.ng-star-inserted button[type='submit']")
.click()
.then(() => {
if (callback) callback()
})
})
}
const approveTable = (callback?: any) => {
cy.get('button', { timeout: longerCommandTimeout })
.should('contain', 'Go to approvals screen')
.then((allButtons: any) => {
for (let approvalButton of allButtons) {
if (
approvalButton.innerText
.toLowerCase()
.includes('go to approvals screen')
) {
approvalButton.click()
break
}
}
cy.get('button#acceptBtn', { timeout: longerCommandTimeout })
.should('exist')
.should('not.be.disabled')
.click()
.then(() => {
cy.get('app-history', { timeout: longerCommandTimeout })
.should('exist')
.then(() => {
if (callback) callback()
})
})
})
}
const visitPage = (url: string) => {
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
}

View File

@ -0,0 +1,157 @@
const username = Cypress.env('username')
const password = Cypress.env('password')
const hostUrl = Cypress.env('hosturl')
const appLocation = Cypress.env('appLocation')
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
const serverType = Cypress.env('serverType')
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
const fixturePath = 'excels/'
context('liveness tests: ', function () {
this.beforeAll(() => {
if (serverType !== 'SASJS') {
cy.visit(`${hostUrl}/SASLogon/logout`)
}
cy.loginAndUpdateValidKey(true)
})
this.beforeEach(() => {
cy.visit(hostUrl + appLocation)
// cy.get('input.username').type(username)
// cy.get('input.password').type(password)
// cy.get('.login-group button').click()
visitPage('home')
})
it('1 | Login and submit test', (done) => {
cy.get('.nav-tree clr-tree > clr-tree-node', {
timeout: longerCommandTimeout
}).then((treeNodes: any) => {
libraryExistsInTree('viya', treeNodes)
? openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
: openTableFromTree('dc', 'mpe_x_test')
attachExcelFile('regular_excel.xlsx', () => {
submitExcel()
rejectExcel(done)
})
})
})
/**
* Thist part will be needed if we add more tests in future
*/
// this.afterEach(() => {
// cy.visit('https://sas.4gl.io/SASLogon/logout');
// })
})
const visitPage = (url: string) => {
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
}
const libraryExistsInTree = (libName: string, nodes: any) => {
for (let node of nodes) {
if (node.innerText.toLowerCase().includes(libName.toLowerCase()))
return true
}
return false
}
const openTableFromTree = (
libNameIncludes: string,
tablename: string,
finish: any
) => {
cy.get('.app-loading', { timeout: longerCommandTimeout })
.should('not.exist')
.then(() => {
cy.get('.nav-tree clr-tree > clr-tree-node', {
timeout: longerCommandTimeout
}).then((treeNodes: any) => {
let viyaLib
for (let node of treeNodes) {
if (node.innerText.toLowerCase().includes(libNameIncludes)) {
viyaLib = node
break
}
}
if (!viyaLib && finish) finish(false)
cy.get(viyaLib).within(() => {
cy.get('.clr-tree-node-content-container > button').click()
cy.get('.clr-treenode-link').then((innerNodes: any) => {
for (let innerNode of innerNodes) {
if (innerNode.innerText.toLowerCase().includes(tablename)) {
innerNode.click()
if (finish) finish(true)
break
}
}
})
})
})
})
}
const attachExcelFile = (excelFilename: string, callback?: any) => {
cy.get('.buttonBar button:last-child')
.should('exist')
.click()
.then(() => {
cy.get('input[type="file"]#file-upload').attachFile(
`/${fixturePath}/${excelFilename}`
)
cy.get('.modal-footer .btn.btn-primary').then((modalBtn) => {
modalBtn.click()
if (callback) callback()
})
})
}
const submitExcel = (callback?: any) => {
cy.get('.buttonBar button.preview-submit', { timeout: longerCommandTimeout })
.click()
.then(() => {
if (callback) callback()
})
}
const rejectExcel = (callback?: any) => {
cy.get('button', { timeout: longerCommandTimeout })
.should('contain', 'Go to approvals screen')
.then((allButtons: any) => {
for (let approvalButton of allButtons) {
if (
approvalButton.innerText
.toLowerCase()
.includes('go to approvals screen')
) {
approvalButton.click()
break
}
}
cy.get('button.btn-danger')
.should('exist')
.should('not.be.disabled')
.click()
.then(() => {
cy.get('.modal-footer button.btn-success-outline')
.click()
.then(() => {
cy.get('app-history')
.should('exist')
.then(() => {
if (callback) callback()
})
})
})
})
}

View File

@ -0,0 +1,61 @@
const username = Cypress.env('username')
const password = Cypress.env('password')
const hostUrl = Cypress.env('hosturl')
const appLocation = Cypress.env('appLocation')
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
const serverType = Cypress.env('serverType')
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
const fixturePath = 'excels_general/'
context('metanav tests: ', function () {
this.beforeAll(() => {
cy.visit(`${hostUrl}/SASLogon/logout`)
cy.loginAndUpdateValidKey()
})
this.beforeEach(() => {
cy.visit(hostUrl + appLocation)
cy.get('input.username').type(username)
cy.get('input.password').type(password)
cy.get('.login-group button').click()
visitPage('view/metadata')
})
it('1 | Opens metadata object', (done) => {
openFirstMetadataFromTree(() => {
// BLOCKER
// For unkown reasons, .clr-accordion-header-button always null although it is present on the page.
cy.get('.clr-accordion-header-button').then((panelNodes: any) => {
panelNodes[0].querySelector('button').click()
})
})
})
this.afterEach(() => {
cy.visit(`${hostUrl}/SASLogon/logout`)
})
})
const openFirstMetadataFromTree = (callback?: any) => {
cy.get('.app-loading', { timeout: longerCommandTimeout })
.should('not.exist')
.then(() => {
cy.get('.nav-tree clr-tree > clr-tree-node', {
timeout: longerCommandTimeout
}).then((treeNodes: any) => {
let firstMetaNode
firstMetaNode = treeNodes[1]
cy.get(firstMetaNode).within(() => {
cy.get('.clr-treenode-content').click()
callback()
})
})
})
}
const visitPage = (url: string) => {
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
}

View File

@ -0,0 +1,655 @@
import { cloneDeep } from 'lodash-es'
const username = Cypress.env('username')
const password = Cypress.env('password')
const hostUrl = Cypress.env('hosturl')
const appLocation = Cypress.env('appLocation')
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
const serverType = Cypress.env('serverType')
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
context('editor tests: ', function () {
this.beforeAll(() => {
cy.visit(`${hostUrl}/SASLogon/logout`)
cy.loginAndUpdateValidKey(true)
})
this.beforeEach(() => {
cy.visit(hostUrl + appLocation)
cy.wait(2000)
cy.get('body').then(($body) => {
const usernameInput = $body.find('input.username')[0]
if (usernameInput && !Cypress.dom.isHidden(usernameInput)) {
cy.get('input.username').type(username)
cy.get('input.password').type(password)
cy.get('.login-group button').click()
}
})
visitPage('home')
})
it('1 | Add one viewbox', (done) => {
const viewbox_table = 'mpe_audit'
const columns = ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM']
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
cy.get('.viewbox-open').click()
openTableFromViewboxTree(libraryToOpenIncludes, [viewbox_table])
cy.get('.open-viewbox').then((viewboxNodes: any) => {
for (let viewboxNode of viewboxNodes) {
if (!viewboxNode.innerText.toLowerCase().includes(viewbox_table)) {
return
}
checkColumns(columns, () => {
done()
})
}
})
})
it('2 | Add two viewboxes', (done) => {
const viewboxes = [
{
viewbox_table: 'mpe_audit',
columns: ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM']
},
{
viewbox_table: 'mpe_alerts',
columns: [
'TX_FROM',
'ALERT_EVENT',
'ALERT_LIB',
'ALERT_DS',
'ALERT_USER'
]
}
]
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
cy.get('.viewbox-open').click()
openTableFromViewboxTree(
libraryToOpenIncludes,
viewboxes.map((viewbox) => viewbox.viewbox_table))
cy.get('.open-viewbox').then((viewboxNodes: any) => {
let found = 0
for (let viewboxNode of viewboxNodes) {
for (let viewbox of viewboxes) {
if (
viewboxNode.innerText.toLowerCase().includes(viewbox.viewbox_table)
)
found++
}
}
if (found < viewboxes.length) return
cy.get('.viewboxes-container .viewbox', { withinSubject: null }).then((viewboxNodes: any) => {
for (let viewboxNode of viewboxNodes) {
cy.get(viewboxNode).within(() => {
cy.get('.table-title').then((tableTitle) => {
const title = tableTitle[0].innerText
const viewbox = viewboxes.find((vb) =>
title.toLowerCase().includes(vb.viewbox_table)
)
if (viewbox) {
cy.get('.ht_master.handsontable .htCore thead tr').then(
(viewboxColNodes: any) => {
let allColsHtml = viewboxColNodes[0].innerHTML
for (let col of viewbox?.columns) {
if (!allColsHtml.includes(col)) return
}
done()
}
)
}
})
})
}
})
})
})
it('3 | Add viewbox, add columns', (done) => {
const viewbox_table = 'mpe_audit'
const columns = ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM']
const additionalColumns = ['IS_PK']
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
cy.get('.viewbox-open').click()
openTableFromViewboxTree(libraryToOpenIncludes, [viewbox_table])
cy.get('.open-viewbox').then((viewboxNodes: any) => {
for (let viewboxNode of viewboxNodes) {
if (!viewboxNode.innerText.toLowerCase().includes(viewbox_table)) {
return
}
openViewboxConfig(viewbox_table)
removeAllColumns()
addColumns(additionalColumns)
checkColumns([...columns, ...additionalColumns], () => {
done()
})
}
})
})
it('4 | Add viewbox, add columns and reorder', (done) => {
const viewbox_table = 'mpe_audit'
const columns = ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM']
const additionalColumns = ['IS_PK', 'MOVE_TYPE']
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
cy.get('.viewbox-open').click()
openTableFromViewboxTree(libraryToOpenIncludes, [viewbox_table])
cy.get('.open-viewbox').then((viewboxNodes: any) => {
for (let viewboxNode of viewboxNodes) {
if (!viewboxNode.innerText.toLowerCase().includes(viewbox_table)) {
return
}
openViewboxConfig(viewbox_table)
removeAllColumns()
addColumns(additionalColumns, () => {
cy.wait(1000)
//reorder
cy.get('.col-box.column-MOVE_TYPE')
.realMouseDown({ button: 'left', position: 'center' })
.realMouseMove(0, 10, { position: 'center' })
cy.wait(200) // In our case, we wait 200ms cause we have animations which we are sure that take this amount of time
cy.get('.col-box.column-IS_PK')
.realMouseMove(0, 0, { position: 'center' })
.realMouseUp()
//reorder end
cy.wait(500)
checkColumns([...columns, ...additionalColumns.reverse()], () => {
done()
})
})
}
})
})
it('5 | Add viewbox, add columns, reorder, remove column, add again', (done) => {
const viewbox_table = 'mpe_audit'
const columns = ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM']
const additionalColumns = ['IS_PK', 'MOVE_TYPE']
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
cy.get('.viewbox-open').click()
openTableFromViewboxTree(libraryToOpenIncludes, [viewbox_table])
cy.get('.open-viewbox').then((viewboxNodes: any) => {
for (let viewboxNode of viewboxNodes) {
if (!viewboxNode.innerText.toLowerCase().includes(viewbox_table)) {
return
}
viewboxNode.click()
removeAllColumns()
addColumns(additionalColumns, () => {
cy.wait(1000)
//reorder
cy.get('.col-box.column-MOVE_TYPE')
.realMouseDown({ button: 'left', position: 'center' })
.realMouseMove(0, 10, { position: 'center' })
cy.wait(200) // In our case, we wait 200ms cause we have animations which we are sure that take this amount of time
cy.get('.col-box.column-IS_PK')
.realMouseMove(0, 0, { position: 'center' })
.realMouseUp()
//reorder end
cy.wait(500)
checkColumns([...columns, ...additionalColumns.reverse()], () => {
const colToRemove = 'MOVE_TYPE'
removeColumn(colToRemove)
checkColumns(
[
...columns,
...additionalColumns.filter((col) => col !== colToRemove)
],
() => {
addColumns([colToRemove], () => {
checkColumns(
[...columns, ...additionalColumns.reverse()],
() => {
done()
}
)
})
}
)
})
})
}
})
})
it('6 | Add viewboxes, reload and check url restored configuration', (done) => {
const viewboxes = [
{
viewbox_table: 'mpe_audit',
columns: ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM'],
additionalColumns: ['IS_PK', 'MOVE_TYPE']
},
{
viewbox_table: 'mpe_alerts',
columns: [
'TX_FROM',
'ALERT_EVENT',
'ALERT_LIB',
'ALERT_DS',
'ALERT_USER'
],
additionalColumns: ['TX_TO']
}
]
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
cy.get('.viewbox-open').click()
openTableFromViewboxTree(libraryToOpenIncludes, [
viewboxes[0].viewbox_table,
viewboxes[1].viewbox_table
])
openViewboxConfig(viewboxes[0].viewbox_table)
cy.wait(500)
removeAllColumns()
addColumns(viewboxes[0].additionalColumns, () => {
cy.wait(1000)
if (viewboxes[0].viewbox_table === 'mpe_audit') {
cy.get('.col-box.column-MOVE_TYPE')
.realMouseDown({ button: 'left', position: 'center' })
.realMouseMove(0, 10, { position: 'center' })
cy.wait(200) // In our case, we wait 200ms cause we have animations which we are sure that take this amount of time
cy.get('.col-box.column-IS_PK')
.realMouseMove(0, 0, { position: 'center' })
.realMouseUp()
}
cy.wait(1000)
openViewboxConfig(viewboxes[1].viewbox_table)
addColumns(viewboxes[1].additionalColumns, () => {
cy.wait(1000).reload()
let result = 0
checkColumns(
[
...viewboxes[0].columns,
...cloneDeep(viewboxes[0].additionalColumns.reverse())
],
() => {
result++
if (result === 2) done()
}
)
checkColumns(
[...viewboxes[1].columns, ...viewboxes[1].additionalColumns],
() => {
result++
if (result === 2) done()
}
)
})
})
})
// We will enable this test when we figure out how to mock filtering
// it('7 | Add viewboxes and filter', () => {
// const viewboxes = ['mpe_x_test', 'mpe_validations']
// openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
// cy.get('.viewbox-open').click()
// openTableFromViewboxTree(libraryToOpenIncludes, viewboxes)
// cy.wait(1000)
// closeViewboxModal()
// cy.get('.viewboxes-container .viewbox', { withinSubject: null }).then(
// (viewboxNodes: any) => {
// for (let viewboxNode of viewboxNodes) {
// cy.get(viewboxNode).within(() => {
// cy.get('.table-title').then((title: any) => {
// cy.get('.hot-spinner')
// .should('not.exist')
// .then(() => {
// cy.get('clr-icon[shape="filter"]').then((filterButton) => {
// filterButton[0].click()
// })
// if (title[0].innerText.includes('MPE_X_TEST')) {
// setFilterWithValue(
// 'SOME_CHAR',
// 'this is dummy data',
// 'value',
// () => {
// cy.get('app-query', { withinSubject: null })
// .should('not.exist')
// .get('.ht_master.handsontable tbody tr')
// .then((rowNodes) => {
// const tr = rowNodes[0]
// expect(rowNodes).to.have.length(1)
// expect(tr.innerText).to.equal('0')
// })
// }
// )
// } else if (title[0].innerText.includes('MPE_VALIDATIONS')) {
// setFilterWithValue('BASE_COL', 'ALERT_LIB', 'value', () => {
// cy.get('app-query', { withinSubject: null })
// .should('not.exist')
// .get('.ht_master.handsontable tbody tr')
// .then((rowNodes) => {
// const tr = rowNodes[0]
// expect(rowNodes).to.have.length(1)
// expect(tr.innerText).to.contain('ALERT_LIB')
// })
// })
// }
// })
// })
// })
// }
// }
// )
// })
this.afterEach(() => {
// cy.visit(`${hostUrl}/SASLogon/logout`)
})
})
const removeAllColumns = () => {
cy.get('.configuration-wrapper clr-icon[shape="trash"]').then(removeNodes => {
for (let removeNode of removeNodes) {
removeNode.click()
}
})
}
const checkColumns = (columns: string[], callback: () => void) => {
cy.get('.viewboxes-container .viewbox', { withinSubject: null }).then(
(viewboxNodes: any) => {
for (let viewboxNode of viewboxNodes) {
cy.get(viewboxNode).within(() => {
cy.get('.ht_master.handsontable thead tr th').then(
(viewboxColNodes: any) => {
console.log('viewboxColNode', viewboxColNodes)
console.log('columns', columns)
for (let i = 0; i < viewboxColNodes.length; i++) {
const col = columns[i]|| ''
const colNode = viewboxColNodes[i]
if (
!colNode.innerHTML.toLowerCase().includes(col.toLowerCase())
)
return
}
callback()
}
)
})
}
}
)
}
const closeViewboxModal = () => {
cy.get('app-viewboxes .close', { withinSubject: null }).click()
}
const removeColumn = (column: string) => {
cy.get(`.col-box.column-${column} clr-icon`, { withinSubject: null }).click()
}
const addColumns = (columns: string[], callback?: () => void) => {
for (let i = 0; i < columns.length; i++) {
const column = columns[i]
cy.get('.cols-search input', { withinSubject: null }).type(column)
cy.get('.cols-search .autocomplete-wrapper', { withinSubject: null })
.first()
.trigger('keydown', { key: 'ArrowDown' })
cy.get('.cols-search .autocomplete-wrapper', { withinSubject: null })
.first()
.trigger('keydown', { key: 'Enter' })
.then(() => {
if (i === columns.length - 1 && callback) callback()
})
}
}
const openViewboxConfig = (viewbox_tablename: string) => {
cy.get('.open-viewbox').then((viewboxes: any) => {
for (let openViewbox of viewboxes) {
if (openViewbox.innerText.toLowerCase().includes(viewbox_tablename))
openViewbox.click()
}
})
}
const openTableFromTree = (libNameIncludes: string, tablename: string) => {
cy.get('.app-loading', { timeout: longerCommandTimeout })
.should('not.exist')
.then(() => {
cy.get('.nav-tree clr-tree > clr-tree-node', {
timeout: longerCommandTimeout
}).then((treeNodes: any) => {
let viyaLib
for (let node of treeNodes) {
if (new RegExp(libNameIncludes).test(node.innerText.toLowerCase())) {
viyaLib = node
break
}
}
cy.get(viyaLib).within(() => {
cy.get('.clr-tree-node-content-container p').click()
cy.get('.clr-treenode-link').then((innerNodes: any) => {
for (let innerNode of innerNodes) {
if (innerNode.innerText.toLowerCase().includes(tablename)) {
innerNode.click()
break
}
}
})
})
})
})
}
const setFilterWithValue = (
variableValue: string,
valueString: string,
valueField: 'value' | 'time' | 'date' | 'datetime' | 'in' | 'between',
callback?: any
) => {
cy.wait(600)
cy.focused().type(variableValue)
cy.wait(100)
// cy.focused().trigger('input')
cy.get('.variable-col .autocomplete-wrapper', { withinSubject: null })
.first()
.trigger('keydown', { key: 'ArrowDown' })
cy.get('.variable-col .autocomplete-wrapper', {
withinSubject: null
}).trigger('keydown', { key: 'Enter' })
cy.focused().tab()
cy.wait(100)
if (valueField === 'in') {
cy.focused().select(valueField.toUpperCase()).trigger('change')
} else if (valueField === 'between') {
cy.focused().select(valueField.toUpperCase()).trigger('change')
} else {
cy.focused().tab()
cy.wait(100)
}
switch (valueField) {
case 'value': {
cy.focused().type(valueString)
break
}
case 'time': {
cy.focused().type(valueString)
break
}
case 'date': {
cy.focused().type(valueString)
cy.focused().tab()
break
}
case 'datetime': {
const date = valueString.split(' ')[0]
const time = valueString.split(' ')[1]
cy.focused().type(date)
cy.focused().tab()
cy.focused().tab()
cy.focused().type(time)
break
}
case 'in': {
cy.get('.checkbox-vals').then(() => {
cy.focused().tab()
cy.focused().click()
cy.get('.no-values')
.should('not.exist')
.then(() => {
cy.get('clr-checkbox-wrapper input').then((inputs: any) => {
inputs[0].click()
cy.get('.in-values-modal .modal-footer button').click()
cy.get('.modal-footer .btn-success-outline').click()
if (callback) callback()
})
})
})
break
}
case 'between': {
cy.focused().tab()
const start = valueString.split('-')[0]
const end = valueString.split('-')[1]
cy.focused().type(start)
cy.focused().tab()
cy.focused().type(end)
}
default: {
break
}
}
cy.wait(100)
cy.focused().tab()
cy.wait(100)
cy.focused().tab()
cy.wait(100)
cy.focused().tab()
cy.wait(100)
cy.focused().tab()
cy.wait(100)
cy.focused().click()
if (callback) callback()
}
const openTableFromViewboxTree = (
libNameIncludes: string,
tablenames: string[]
) => {
cy.get('.add-new clr-tree > clr-tree-node', {
timeout: longerCommandTimeout
}).then((treeNodes: any) => {
let viyaLib
for (let node of treeNodes) {
if (node.innerText.toLowerCase().includes(libNameIncludes)) {
viyaLib = node
break
}
}
cy.get(viyaLib).within(() => {
cy.get('p')
.click()
.then(() => {
cy.get('.clr-treenode-link').then(async (innerNodes: any) => {
for (let innerNode of innerNodes) {
for (let tablename of tablenames) {
await pause(300)
if (innerNode.innerText.toLowerCase().includes(tablename)) {
innerNode.click()
}
}
}
})
})
})
})
}
const pause = (ms: number) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(null)
}, ms)
})
}
const visitPage = (url: string) => {
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
}

View File

@ -0,0 +1,23 @@
// ***********************************************************
// This example support/e2e.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')
import 'cypress-plugin-tab'
import "cypress-real-events"

4910
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "dc-client", "name": "data_controller-client",
"description": "dc-client", "description": "DataController Client",
"angular-cli": {}, "angular-cli": {},
"scripts": { "scripts": {
"start": "node --max_old_space_size=4096 node_modules/@angular/cli/bin/ng serve", "start": "node --max_old_space_size=4096 node_modules/@angular/cli/bin/ng serve",
@ -29,7 +29,9 @@
"cy:run": "cypress run", "cy:run": "cypress run",
"audit:prod": "npm audit --omit=dev", "audit:prod": "npm audit --omit=dev",
"sasdocs": "sasjs doc && ./sasjs/utils/deploydocs.sh", "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, "private": true,
"dependencies": { "dependencies": {
@ -88,7 +90,9 @@
"@angular-eslint/template-parser": "16.0.3", "@angular-eslint/template-parser": "16.0.3",
"@angular/cli": "^16.1.0", "@angular/cli": "^16.1.0",
"@angular/compiler-cli": "^16.1.2", "@angular/compiler-cli": "^16.1.2",
"@cypress/webpack-preprocessor": "^5.11.1", "@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/core-js": "^2.5.5",
"@types/crypto-js": "^4.0.1", "@types/crypto-js": "^4.0.1",
"@types/es6-shim": "^0.31.39", "@types/es6-shim": "^0.31.39",
@ -99,10 +103,10 @@
"@typescript-eslint/eslint-plugin": "^5.29.0", "@typescript-eslint/eslint-plugin": "^5.29.0",
"@typescript-eslint/parser": "^5.29.0", "@typescript-eslint/parser": "^5.29.0",
"core-js": "^2.5.4", "core-js": "^2.5.4",
"cypress": "^9.5.3", "cypress": "12.17.1",
"cypress-file-upload": "^5.0.8", "cypress-file-upload": "^5.0.8",
"cypress-plugin-tab": "^1.0.5", "cypress-plugin-tab": "^1.0.5",
"cypress-real-events": "^1.7.6", "cypress-real-events": "^1.8.1",
"es6-shim": "^0.35.5", "es6-shim": "^0.35.5",
"eslint": "^8.33.0", "eslint": "^8.33.0",
"git-describe": "^4.0.4", "git-describe": "^4.0.4",
@ -120,9 +124,10 @@
"rimraf": "3.0.2", "rimraf": "3.0.2",
"ts-loader": "^9.2.8", "ts-loader": "^9.2.8",
"ts-node": "^3.3.0", "ts-node": "^3.3.0",
"typedoc": "^0.23.24", "typedoc": "^0.24.8",
"typedoc-plugin-external-module-name": "^4.0.6",
"typescript": "~4.9.4", "typescript": "~4.9.4",
"wait-on": "^6.0.1", "wait-on": "^6.0.1",
"watch": "^1.0.2" "watch": "^1.0.2"
} }
} }

View File

@ -1,7 +0,0 @@
#!/bin/bash
npm run cy:run -- --browser chrome --spec "cypress/integration/liveness.tests.ts"
npm run cy:run -- --browser chrome --spec "cypress/integration/editor.tests.ts"
npm run cy:run -- --browser chrome --spec "cypress/integration/excel.tests.ts"
npm run cy:run -- --browser chrome --spec "cypress/integration/filtering.tests.ts"
npm run cy:run -- --browser chrome --spec "cypress/integration/licensing.tests.ts"

View File

@ -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>

View File

@ -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)
}
}

View File

@ -169,7 +169,7 @@
<clr-dropdown-menu *clrIfOpen clrPosition="bottom-left"> <clr-dropdown-menu *clrIfOpen clrPosition="bottom-left">
<a [routerLink]="['/view']" clrDropdownItem>VIEW</a> <a [routerLink]="['/view']" clrDropdownItem>VIEW</a>
<a [routerLink]="['/home']" clrDropdownItem>EDIT</a> <a [routerLink]="['/home']" clrDropdownItem>EDIT</a>
<a [routerLink]="['/submitted']" clrDropdownItem>REVIEW</a> <a [routerLink]="['/review/submitted']" clrDropdownItem>REVIEW</a>
</clr-dropdown-menu> </clr-dropdown-menu>
</clr-dropdown> </clr-dropdown>
</div> </div>
@ -192,7 +192,7 @@
>EDIT</a >EDIT</a
> >
<a <a
[routerLink]="['/submitted']" [routerLink]="['/review/submitted']"
[class.active]=" [class.active]="
router.url.includes('submitted') || router.url.includes('submitted') ||
router.url.includes('approve') || router.url.includes('approve') ||
@ -224,7 +224,7 @@
<ul class="nav"> <ul class="nav">
<li class="nav-item"> <li class="nav-item">
<a <a
[routerLink]="['/submitted']" [routerLink]="['/review/submitted']"
class="nav-link nav-text" class="nav-link nav-text"
routerLinkActive="active" routerLinkActive="active"
>SUBMIT</a >SUBMIT</a
@ -232,15 +232,16 @@
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a <a
[routerLink]="['/approve']" [routerLink]="['/review/approve']"
class="nav-link nav-text" class="nav-link nav-text"
[class.active]="router.url.includes('approve')"
routerLinkActive="active" routerLinkActive="active"
>APPROVE</a >APPROVE</a
> >
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a <a
[routerLink]="['/history']" [routerLink]="['/review/history']"
class="nav-link nav-text" class="nav-link nav-text"
routerLinkActive="active" routerLinkActive="active"
>HISTORY</a >HISTORY</a

View File

@ -12,29 +12,23 @@ import { NotFoundComponent } from './not-found/not-found.component'
import { SasStoreService } from './services/sas-store.service' import { SasStoreService } from './services/sas-store.service'
import { SharedModule } from './shared/shared.module' import { SharedModule } from './shared/shared.module'
// import { EditorComponent } from './editor/editor.component' // import { EditorComponent } from './editor/editor.component'
import { ActionsComponent } from './actions/actions.component'
import { AppSharedModule } from './app-shared.module' 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 { DeployComponent } from './deploy/deploy.component'
import { AutomaticComponent } from './deploy/sections/automatic/automatic.component' import { AutomaticComponent } from './deploy/sections/automatic/automatic.component'
import { ManualComponent } from './deploy/sections/manual/manual.component' import { ManualComponent } from './deploy/sections/manual/manual.component'
import { SasjsConfiguratorComponent } from './deploy/sections/sasjs-configurator/sasjs-configurator.component' import { SasjsConfiguratorComponent } from './deploy/sections/sasjs-configurator/sasjs-configurator.component'
import { GroupComponent } from './group/group.component' import { GroupComponent } from './group/group.component'
import { HistoryComponent } from './history/history.component'
import { LicensingComponent } from './licensing/licensing.component' import { LicensingComponent } from './licensing/licensing.component'
import { LineageComponent } from './lineage/lineage.component' import { LineageComponent } from './lineage/lineage.component'
import { MetadataComponent } from './metadata/metadata.component' import { MetadataComponent } from './metadata/metadata.component'
import { PipesModule } from './pipes/pipes.module' import { PipesModule } from './pipes/pipes.module'
import { RoleComponent } from './role/role.component' import { RoleComponent } from './role/role.component'
import { ApproveRouteComponent } from './routes/approve-route/approve-route.component' import { ReviewRouteComponent } from './routes/review-route/review-route.component'
import { HistoryRouteComponent } from './routes/history-route/history-route.component'
import { LicensingGuard } from './routes/licensing.guard' import { LicensingGuard } from './routes/licensing.guard'
import { UsernavRouteComponent } from './routes/usernav-route/usernav-route.component' import { UsernavRouteComponent } from './routes/usernav-route/usernav-route.component'
import { AppService } from './services/app.service' import { AppService } from './services/app.service'
import { InfoModalComponent } from './shared/abort-modal/info-modal.component' import { InfoModalComponent } from './shared/abort-modal/info-modal.component'
import { RequestsModalComponent } from './shared/requests-modal/requests-modal.component' import { RequestsModalComponent } from './shared/requests-modal/requests-modal.component'
import { SubmitterComponent } from './submitter/submitter.component'
import { UserComponent } from './user/user.component' import { UserComponent } from './user/user.component'
import { HomeModule } from './home/home.module' import { HomeModule } from './home/home.module'
import { SystemComponent } from './system/system.component' import { SystemComponent } from './system/system.component'
@ -46,14 +40,9 @@ import { NgxJsonViewerModule } from 'ngx-json-viewer'
declarations: [ declarations: [
AppComponent, AppComponent,
NotFoundComponent, NotFoundComponent,
ApproveComponent,
ApproveDetailsComponent,
ActionsComponent,
HistoryComponent,
LineageComponent, LineageComponent,
SubmitterComponent, ReviewRouteComponent,
ApproveRouteComponent, ReviewRouteComponent,
HistoryRouteComponent,
MetadataComponent, MetadataComponent,
UsernavRouteComponent, UsernavRouteComponent,
UserComponent, UserComponent,
@ -84,7 +73,7 @@ import { NgxJsonViewerModule } from 'ngx-json-viewer'
DirectivesModule, DirectivesModule,
NgxJsonViewerModule NgxJsonViewerModule
], ],
providers: [AppService, SasStoreService, ApproveComponent, LicensingGuard], providers: [AppService, SasStoreService, LicensingGuard],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })
export class AppModule {} export class AppModule {}

View File

@ -7,14 +7,9 @@ import { ModuleWithProviders } from '@angular/core'
import { Routes, RouterModule } from '@angular/router' import { Routes, RouterModule } from '@angular/router'
import { HomeComponent } from './home/home.component' 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 { NotFoundComponent } from './not-found/not-found.component'
import { SubmitterComponent } from './submitter/submitter.component'
import { ApproveRouteComponent } from './routes/approve-route/approve-route.component' import { ReviewRouteComponent } from './routes/review-route/review-route.component'
import { DeployComponent } from './deploy/deploy.component' import { DeployComponent } from './deploy/deploy.component'
import { LicensingComponent } from './licensing/licensing.component' import { LicensingComponent } from './licensing/licensing.component'
import { LicensingGuard } from './routes/licensing.guard' import { LicensingGuard } from './routes/licensing.guard'
@ -22,6 +17,7 @@ import { StageModule } from './stage/stage.module'
import { EditorModule } from './editor/editor.module' import { EditorModule } from './editor/editor.module'
import { ViewerModule } from './viewer/viewer.module' import { ViewerModule } from './viewer/viewer.module'
import { SystemComponent } from './system/system.component' import { SystemComponent } from './system/system.component'
import { ReviewModule } from './review/review.module'
export const ROUTES: Routes = [ export const ROUTES: Routes = [
{ path: '', redirectTo: 'home', pathMatch: 'full' }, { path: '', redirectTo: 'home', pathMatch: 'full' },
@ -30,13 +26,14 @@ export const ROUTES: Routes = [
loadChildren: () => ViewerModule loadChildren: () => ViewerModule
}, },
{ {
path: 'approve', path: 'review',
component: ApproveRouteComponent, component: ReviewRouteComponent,
children: [ children: [
{ path: '', pathMatch: 'full', redirectTo: 'toapprove' }, { path: '', pathMatch: 'full', redirectTo: 'toapprove' },
{ path: 'toapprove', component: ApproveComponent }, {
{ path: 'approveDet/:tableId', component: ApproveDetailsComponent }, path: '',
{ path: 'submitted', component: SubmitterComponent } loadChildren: () => ReviewModule
}
] ]
}, },
{ {
@ -55,10 +52,6 @@ export const ROUTES: Routes = [
loadChildren: () => StageModule loadChildren: () => StageModule
}, },
{ path: 'system', component: SystemComponent }, { 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', component: DeployComponent },
{ path: 'deploy/manualdeploy', component: DeployComponent }, { path: 'deploy/manualdeploy', component: DeployComponent },
{ path: '**', component: NotFoundComponent } { path: '**', component: NotFoundComponent }

View File

@ -597,7 +597,7 @@
</button> </button>
</div> </div>
</clr-modal> </clr-modal>
<clr-modal [(clrModalOpen)]="pkDups"> <clr-modal class="duplicate-keys-modal" [(clrModalOpen)]="pkDups">
<h3 class="modal-title">Error</h3> <h3 class="modal-title">Error</h3>
<div class="modal-body"> <div class="modal-body">
<p> <p>

View File

@ -1019,6 +1019,16 @@ export class EditorComponent implements OnInit, AfterViewInit {
return return
} }
this.validatePrimaryKeys()
if (this.duplicatePkIndexes.length !== 0) {
this.pkDups = true
this.submit = false
return
} else {
this.pkDups = false
}
this.uploadLoading = true this.uploadLoading = true
let filesToUpload: UploadFile[] = [] let filesToUpload: UploadFile[] = []

View File

@ -13,6 +13,7 @@
[(ngModel)]="usePickers" [(ngModel)]="usePickers"
(change)="usePickersChange()" (change)="usePickersChange()"
type="checkbox" type="checkbox"
id="usePickers"
/> />
<label class="clr-control-label"> Use pickers </label> <label class="clr-control-label"> Use pickers </label>
</clr-checkbox-wrapper> </clr-checkbox-wrapper>

View File

@ -1,13 +1,13 @@
import { ActivatedRoute } from '@angular/router' 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 { Component, AfterViewInit, OnDestroy } from '@angular/core'
import { Subscription } from 'rxjs' import { Subscription } from 'rxjs'
import { Router } from '@angular/router' import { Router } from '@angular/router'
import { EventService } from '../services/event.service' import { EventService } from '../../services/event.service'
import { import {
AuditorsPostdataSASResponse, AuditorsPostdataSASResponse,
Param Param
} from '../models/sas/auditors-postdata.model' } from '../../models/sas/auditors-postdata.model'
interface ChangesObj { interface ChangesObj {
ind: any ind: any
@ -89,7 +89,7 @@ export class ApproveDetailsComponent implements AfterViewInit, OnDestroy {
} }
public goToApprovalsList() { public goToApprovalsList() {
this.route.navigateByUrl('/approve') this.route.navigateByUrl('/review/approve')
} }
public getTable(tableId: any) { public getTable(tableId: any) {
@ -136,7 +136,7 @@ export class ApproveDetailsComponent implements AfterViewInit, OnDestroy {
await this.sasStoreService await this.sasStoreService
.rejecting(rejParams, 'BrowserParams', 'approvers/rejection') .rejecting(rejParams, 'BrowserParams', 'approvers/rejection')
.then((res: any) => { .then((res: any) => {
this.route.navigateByUrl('/history') this.route.navigateByUrl('/review/history')
}) })
.catch((err: any) => { .catch((err: any) => {
this.acceptLoading = false this.acceptLoading = false
@ -156,7 +156,7 @@ export class ApproveDetailsComponent implements AfterViewInit, OnDestroy {
await this.sasStoreService await this.sasStoreService
.approveTable(approveParams, 'SASControlTable', 'auditors/postdata') .approveTable(approveParams, 'SASControlTable', 'auditors/postdata')
.then((res: any) => { .then((res: any) => {
this.route.navigateByUrl('/history') this.route.navigateByUrl('/review/history')
}) })
.catch((err: any) => { .catch((err: any) => {
this.acceptLoading = false this.acceptLoading = false
@ -164,7 +164,7 @@ export class ApproveDetailsComponent implements AfterViewInit, OnDestroy {
} }
public goToSubmitList() { public goToSubmitList() {
this.route.navigateByUrl('/submitted') this.route.navigateByUrl('/review/submitted')
} }
public async callChangesInfo(tableId: any) { public async callChangesInfo(tableId: any) {

View File

@ -1,8 +1,8 @@
import { Component, OnInit, ChangeDetectorRef } from '@angular/core' 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 { Router } from '@angular/router'
import { SasService } from '../services/sas.service' import { SasService } from '../../services/sas.service'
import { EventService } from '../services/event.service' import { EventService } from '../../services/event.service'
interface ApproveData { interface ApproveData {
tableId: string tableId: string
@ -45,7 +45,7 @@ export class ApproveComponent implements OnInit {
if (this.approveList !== undefined) { if (this.approveList !== undefined) {
this.tableId = this.approveList[ind].tableId this.tableId = this.approveList[ind].tableId
this.route.navigateByUrl( this.route.navigateByUrl(
'approve/approveDet/' + this.approveList[ind].tableId 'review/approveDet/' + this.approveList[ind].tableId
) )
} }
} }

View File

@ -1,11 +1,8 @@
import { Component, OnInit } from '@angular/core' import { Component, OnInit } from '@angular/core'
import { SasStoreService } from '../services/sas-store.service'
import { Router } from '@angular/router' import { Router } from '@angular/router'
import { SasService } from '../services/sas.service'
import { EventService } from '../services/event.service'
import { SASjsConfig } from '@sasjs/adapter' import { SASjsConfig } from '@sasjs/adapter'
import { LicenceService } from '../services/licence.service' import { LicenceService, SasStoreService, EventService, SasService } from 'src/app/services'
@Component({ @Component({
selector: 'app-history', selector: 'app-history',

View File

@ -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 {}

View File

@ -0,0 +1,31 @@
import { CommonModule } from "@angular/common";
import { NgModule } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { ClarityModule } from "@clr/angular";
import { HotTableModule } from "@handsontable/angular";
import { 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 {}

View File

@ -1,9 +1,7 @@
import { Component, AfterViewInit, OnInit } from '@angular/core' import { Component, AfterViewInit, OnInit } from '@angular/core'
import { Subscription } from 'rxjs' import { Subscription } from 'rxjs'
import { SasStoreService } from '../services/sas-store.service'
import { ActivatedRoute, Router } from '@angular/router' import { ActivatedRoute, Router } from '@angular/router'
import { SasService } from '../services/sas.service' import { SasStoreService, EventService, SasService } from '../../services'
import { EventService } from '../services/event.service'
interface SubmitterData { interface SubmitterData {
tableId: string tableId: string
@ -46,7 +44,7 @@ export class SubmitterComponent implements OnInit, AfterViewInit {
} }
public goToDetails(table_id: any) { public goToDetails(table_id: any) {
this.router.navigateByUrl('/submitted/' + table_id) this.router.navigateByUrl('/review/submitted/' + table_id)
} }
public getDetails(sub: any, index: any) { public getDetails(sub: any, index: any) {

View File

@ -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() {}
}

View File

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

View File

@ -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() {}
}

View File

@ -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() {}
}

View File

@ -55,14 +55,14 @@
<a <a
*ngIf="isMainRoute('approve')" *ngIf="isMainRoute('approve')"
clrVerticalNavLink clrVerticalNavLink
routerLink="/approve/submitted" routerLink="/review/approve/submitted"
routerLinkActive="active" routerLinkActive="active"
>Submitted</a >Submitted</a
> >
<a <a
*ngIf="isMainRoute('approve')" *ngIf="isMainRoute('approve')"
clrVerticalNavLink clrVerticalNavLink
routerLink="/approve/toapprove" routerLink="/review/approve/toapprove"
routerLinkActive="active" routerLinkActive="active"
>To Approve</a >To Approve</a
> >

View File

@ -47,7 +47,7 @@ export class StageComponent implements OnInit {
} }
public approveTableScreen() { public approveTableScreen() {
this.route.navigateByUrl('/approve/approveDet/' + this.table_id) this.route.navigateByUrl('/review/approveDet/' + this.table_id)
} }
public viewerTableScreen() { public viewerTableScreen() {

View File

@ -5,27 +5,17 @@ Licence Agreement for Data Controller for SAS®
Copyright (c) Bowe IO Ltd 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- 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.
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 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
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.
Your use of this software for commercial purposes is subject to the terms included in an applicable Your use of this software for commercial purposes is subject to the terms included in an applicable license agreement.
license agreement.
In any case, you must not make any such use of this software as to develop software which may be 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.
considered competitive with this software.
UNLESS EXPRESSLY AGREED OTHERWISE, 4GL APPS PROVIDES THIS SOFTWARE ON AN "AS IS" 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.
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.
` `

1
client/tsconfig.doc.json Normal file
View File

@ -0,0 +1 @@
{"include":["src/**/*.ts"],"exclude":["src/**/*.spec.ts"]}

View File

@ -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
}

View File

@ -2,13 +2,13 @@
# Data Controller # Data Controller
_FREE FOR NON-COMMERCIAL LICENSE_ _FREE FOR NON-COMMERCIAL LICENSE_
_VERSION 1.0 OF July 11th, 2022_ _VERSION 1.0 OF July 25th, 2023_
## 1. Copyright notice ## 1. Copyright Notice
Copyright (c) Bowe IO Ltd, a UK Limited Company headquarted in 29 Oldfield Rd, Cumbria, registered by companies house under number 08777171, VAT number: 203914240 Copyright (c) Bowe IO Ltd, a UK Limited Company headquarted in 29 Oldfield Rd, Cumbria, registered by companies house under number 08777171, VAT number: 203914240
## 2. License terms and conditions ## 2. License Terms and Conditions
### 2.1. Grant of license. ### 2.1. Grant of License.
Subject to the terms and conditions of this license, the licensor as indicated in the copyright notice above (“Licensor”) hereby grants you a perpetual, irrevocable except in the event of any breach of this license, worldwide, non-exclusive, no-charge, royalty-free, and limited license to: Subject to the terms and conditions of this license, the licensor as indicated in the copyright notice above (“Licensor”) hereby grants you a perpetual, irrevocable except in the event of any breach of this license, worldwide, non-exclusive, no-charge, royalty-free, and limited license to:
(i) use Data Controller software (“Software”) and prepare original works of authorship based on or derived from the Software (“Derivative Works”); and (i) use Data Controller software (“Software”) and prepare original works of authorship based on or derived from the Software (“Derivative Works”); and
@ -38,15 +38,15 @@ and
(iv) if the Software includes a “notice” text file as part of its distribution, then any Derivative Works that you distribute must include a readable copy of the attribution notices contained within such notice file, excluding those notices that do not pertain to any part of the Derivative Works. The contents of the notice file are for informational purposes only and do not modify this license. (iv) if the Software includes a “notice” text file as part of its distribution, then any Derivative Works that you distribute must include a readable copy of the attribution notices contained within such notice file, excluding those notices that do not pertain to any part of the Derivative Works. The contents of the notice file are for informational purposes only and do not modify this license.
## 3. Miscellaneous ## 3. Miscellaneous
### 3.1. License key. ### 3.1. License Key.
To use the Deliverables, you shall apply a license key defined in the relevant Software documentation as from time to time amended. To use the Deliverables, you shall apply a license key defined in the relevant Software documentation as from time to time amended.
### 3.2. Trademarks. ### 3.2. Trademarks.
This license does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Deliverables and reproducing the content of the notice file. This license does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Deliverables and reproducing the content of the notice file.
## 4. Disclaimer and limitation ## 4. Disclaimer and Limitation
### 4.1. Disclaimer of warranty. ### 4.1. Disclaimer of Warranty.
UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, LICENSOR PROVIDES THE SOFTWARE ON AN “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. YOU ARE SOLELY RESPONSIBLE FOR DETERMINING THE APPROPRIATENESS OF USING THE DELIVERABLES AND ASSUME ANY RISKS ASSOCIATED WITH YOUR EXERCISE OF PERMISSIONS UNDER THIS LICENSE. UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, LICENSOR PROVIDES THE SOFTWARE ON AN “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. YOU ARE SOLELY RESPONSIBLE FOR DETERMINING THE APPROPRIATENESS OF USING THE DELIVERABLES AND ASSUME ANY RISKS ASSOCIATED WITH YOUR EXERCISE OF PERMISSIONS UNDER THIS LICENSE.
### 4.2. Limitation of liability. ### 4.2. Limitation of Liability.
IN NO EVENT AND UNDER NO LEGAL THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, UNLESS REQUIRED BY APPLICABLE LAW (SUCH AS DELIBERATE AND GROSSLY NEGLIGENT ACTS) OR AGREED TO IN WRITING, SHALL LICENSOR BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER ARISING AS A RESULT OF THIS LICENSE OR OUT OF THE USE OR INABILITY TO USE THE DELIVERABLES (INCLUDING BUT NOT LIMITED TO DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES), EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. IN NO EVENT AND UNDER NO LEGAL THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, UNLESS REQUIRED BY APPLICABLE LAW (SUCH AS DELIBERATE AND GROSSLY NEGLIGENT ACTS) OR AGREED TO IN WRITING, SHALL LICENSOR BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER ARISING AS A RESULT OF THIS LICENSE OR OUT OF THE USE OR INABILITY TO USE THE DELIVERABLES (INCLUDING BUT NOT LIMITED TO DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES), EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

View File

@ -0,0 +1,172 @@
%PDF-1.3
1 0 obj
<< /Type /Catalog
/Outlines 2 0 R
/Pages 3 0 R >>
endobj
2 0 obj
<< /Type /Outlines /Count 0 >>
endobj
3 0 obj
<< /Type /Pages
/Kids [6 0 R
10 0 R
]
/Count 2
/Resources <<
/ProcSet 4 0 R
/Font <<
/F1 8 0 R
/F2 9 0 R
>>
>>
/MediaBox [0.000 0.000 595.280 841.890]
>>
endobj
4 0 obj
[/PDF /Text ]
endobj
5 0 obj
<<
/Creator (DOMPDF)
/CreationDate (D:20230725143300+00'00')
/ModDate (D:20230725143300+00'00')
>>
endobj
6 0 obj
<< /Type /Page
/Parent 3 0 R
/Contents 7 0 R
>>
endobj
7 0 obj
<<
/Length 3484 >>
stream
0.000 0.000 0.000 rg
BT 34.016 768.985 Td /F1 24.0 Tf [(Data Controller)] TJ ET
BT 34.016 735.797 Td /F2 12.0 Tf [(FREE FOR NON-COMMERCIAL LICENSE)] TJ ET
0.000 0.000 0.000 RG
0.6 w 0 J [ ] 0 d
34.016 733.097 m 256.352 733.097 l S
BT 34.016 709.541 Td /F2 12.0 Tf [(VERSION 1.0 OF July 25th, 2023)] TJ ET
0.6 w 0 J [ ] 0 d
34.016 706.841 m 199.688 706.841 l S
BT 34.016 674.643 Td /F1 18.0 Tf [(1. Copyright Notice)] TJ ET
BT 34.016 644.021 Td /F2 12.0 Tf [(Copyright \(c\) Bowe IO Ltd, a UK Limited Company headquarted in 29 Oldfield Rd, Cumbria, registered by )] TJ ET
BT 34.016 629.765 Td /F2 12.0 Tf [(companies house under number 08777171, VAT number: 203914240)] TJ ET
BT 34.016 594.867 Td /F1 18.0 Tf [(2. License Terms and Conditions)] TJ ET
BT 34.016 562.307 Td /F1 14.0 Tf [(2.1. Grant of License.)] TJ ET
BT 34.016 535.566 Td /F2 12.0 Tf [(Subject to the terms and conditions of this license, the licensor as indicated in the copyright notice above )] TJ ET
BT 34.016 521.310 Td /F2 12.0 Tf [(\(<28>Licensor<6F>\) hereby grants you a perpetual, irrevocable except in the event of any breach of this license, )] TJ ET
BT 34.016 507.054 Td /F2 12.0 Tf [(worldwide, non-exclusive, no-charge, royalty-free, and limited license to:)] TJ ET
BT 34.016 480.798 Td /F2 12.0 Tf [(\(i\) use Data Controller software \(<28>Software<72>\) and prepare original works of authorship based on or derived )] TJ ET
BT 34.016 466.542 Td /F2 12.0 Tf [(from the Software \(<28>Derivative Works<6B>\); and)] TJ ET
BT 34.016 440.286 Td /F2 12.0 Tf [(\(ii\) reproduce and distribute copies of the Software and Derivative Works \(collectively, <20>Deliverables<65>\).)] TJ ET
BT 34.016 412.091 Td /F1 14.0 Tf [(2.2. Restrictions.)] TJ ET
BT 34.016 385.350 Td /F2 12.0 Tf [(Neither this license nor the Deliverables may be so used as to result in:)] TJ ET
BT 34.016 359.094 Td /F2 12.0 Tf [(\(i\) your direct or indirect commercial advantage or monetary gains \(<28>Commercial Purposes<65>\) except only if )] TJ ET
BT 34.016 344.838 Td /F2 12.0 Tf [(such use is for the sole purpose of testing the suitability, performance, and usefulness of the Deliverables for )] TJ ET
BT 34.016 330.582 Td /F2 12.0 Tf [(your business needs; or)] TJ ET
BT 34.016 304.326 Td /F2 12.0 Tf [(\(ii\) commercial, non-commercial, for profit or not for profit licensing, transfer or distribution of a Derivative )] TJ ET
BT 34.016 290.070 Td /F2 12.0 Tf [(Work that is competitive with the Software, contains the same or substantially similar functionality as the )] TJ ET
BT 34.016 275.814 Td /F2 12.0 Tf [(Software, or could likely result in third parties utilizing the Derivative Work in lieu of or in place of the )] TJ ET
BT 34.016 261.558 Td /F2 12.0 Tf [(Software \(<28>Competitive Purposes<65>\); or)] TJ ET
BT 34.016 235.302 Td /F2 12.0 Tf [(\(iii\) both Commercial Purposes and Competitive Purposes.)] TJ ET
BT 34.016 207.108 Td /F1 14.0 Tf [(2.3. Redistribution.)] TJ ET
BT 34.016 180.367 Td /F2 12.0 Tf [(You may reproduce and distribute copies of the Deliverables in any medium, with or without modifications, )] TJ ET
BT 34.016 166.111 Td /F2 12.0 Tf [(and in source or object form, if you meet the following conditions:)] TJ ET
BT 34.016 139.855 Td /F2 12.0 Tf [(\(i\) you must give any other recipients of the Deliverables a copy of this license; and)] TJ ET
BT 34.016 113.599 Td /F2 12.0 Tf [(\(ii\) you must cause any modified files to carry prominent notices stating that you changed the files; and)] TJ ET
endstream
endobj
8 0 obj
<< /Type /Font
/Subtype /Type1
/Name /F1
/BaseFont /Times-Bold
/Encoding /WinAnsiEncoding
>>
endobj
9 0 obj
<< /Type /Font
/Subtype /Type1
/Name /F2
/BaseFont /Times-Roman
/Encoding /WinAnsiEncoding
>>
endobj
10 0 obj
<< /Type /Page
/Parent 3 0 R
/Contents 11 0 R
>>
endobj
11 0 obj
<<
/Length 3942 >>
stream
0.000 0.000 0.000 rg
0.000 0.000 0.000 RG
0.6 w 0 J [ ] 0 d
BT 34.016 784.469 Td /F2 12.0 Tf [(\(iii\) you must retain, in the source form of any Derivative Works that you distribute, all copyright, )] TJ ET
BT 34.016 770.213 Td /F2 12.0 Tf [(trademark, and attribution notices from the source form of the Software, excluding those notices that do not )] TJ ET
BT 34.016 755.957 Td /F2 12.0 Tf [(pertain to any part of the Derivative Works; and)] TJ ET
BT 34.016 729.701 Td /F2 12.0 Tf [(\(iv\) if the Software includes a <20>notice<63> text file as part of its distribution, then any Derivative Works that you )] TJ ET
BT 34.016 715.445 Td /F2 12.0 Tf [(distribute must include a readable copy of the attribution notices contained within such notice file, excluding )] TJ ET
BT 34.016 701.189 Td /F2 12.0 Tf [(those notices that do not pertain to any part of the Derivative Works. The contents of the notice file are for )] TJ ET
BT 34.016 686.933 Td /F2 12.0 Tf [(informational purposes only and do not modify this license.)] TJ ET
BT 34.016 652.035 Td /F1 18.0 Tf [(3. Miscellaneous)] TJ ET
BT 34.016 619.475 Td /F1 14.0 Tf [(3.1. License Key.)] TJ ET
BT 34.016 592.734 Td /F2 12.0 Tf [(To use the Deliverables, you shall apply a license key defined in the relevant Software documentation as )] TJ ET
BT 34.016 578.478 Td /F2 12.0 Tf [(from time to time amended.)] TJ ET
BT 34.016 550.283 Td /F1 14.0 Tf [(3.2. Trademarks.)] TJ ET
BT 34.016 523.542 Td /F2 12.0 Tf [(This license does not grant permission to use the trade names, trademarks, service marks, or product names )] TJ ET
BT 34.016 509.286 Td /F2 12.0 Tf [(of the Licensor, except as required for reasonable and customary use in describing the origin of the )] TJ ET
BT 34.016 495.030 Td /F2 12.0 Tf [(Deliverables and reproducing the content of the notice file.)] TJ ET
BT 34.016 460.132 Td /F1 18.0 Tf [(4. Disclaimer and Limitation)] TJ ET
BT 34.016 427.572 Td /F1 14.0 Tf [(4.1. Disclaimer of Warranty.)] TJ ET
BT 34.016 400.831 Td /F2 12.0 Tf [(UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, LICENSOR PROVIDES )] TJ ET
BT 34.016 386.575 Td /F2 12.0 Tf [(THE SOFTWARE ON AN <20>AS IS<49> BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY )] TJ ET
BT 34.016 372.319 Td /F2 12.0 Tf [(KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY )] TJ ET
BT 34.016 358.063 Td /F2 12.0 Tf [(WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR )] TJ ET
BT 34.016 343.807 Td /F2 12.0 Tf [(FITNESS FOR A PARTICULAR PURPOSE. YOU ARE SOLELY RESPONSIBLE FOR DETERMINING )] TJ ET
BT 34.016 329.551 Td /F2 12.0 Tf [(THE APPROPRIATENESS OF USING THE DELIVERABLES AND ASSUME ANY RISKS )] TJ ET
BT 34.016 315.295 Td /F2 12.0 Tf [(ASSOCIATED WITH YOUR EXERCISE OF PERMISSIONS UNDER THIS LICENSE.)] TJ ET
BT 34.016 287.100 Td /F1 14.0 Tf [(4.2. Limitation of Liability.)] TJ ET
BT 34.016 260.359 Td /F2 12.0 Tf [(IN NO EVENT AND UNDER NO LEGAL THEORY, WHETHER IN TORT \(INCLUDING )] TJ ET
BT 34.016 246.103 Td /F2 12.0 Tf [(NEGLIGENCE\), CONTRACT, OR OTHERWISE, UNLESS REQUIRED BY APPLICABLE LAW )] TJ ET
BT 34.016 231.847 Td /F2 12.0 Tf [(\(SUCH AS DELIBERATE AND GROSSLY NEGLIGENT ACTS\) OR AGREED TO IN WRITING, )] TJ ET
BT 34.016 217.591 Td /F2 12.0 Tf [(SHALL LICENSOR BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT, INDIRECT, )] TJ ET
BT 34.016 203.335 Td /F2 12.0 Tf [(SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER ARISING AS A )] TJ ET
BT 34.016 189.079 Td /F2 12.0 Tf [(RESULT OF THIS LICENSE OR OUT OF THE USE OR INABILITY TO USE THE DELIVERABLES )] TJ ET
BT 34.016 174.823 Td /F2 12.0 Tf [(\(INCLUDING BUT NOT LIMITED TO DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, )] TJ ET
BT 34.016 160.567 Td /F2 12.0 Tf [(COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL )] TJ ET
BT 34.016 146.311 Td /F2 12.0 Tf [(DAMAGES OR LOSSES\), EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.)] TJ ET
endstream
endobj
xref
0 12
0000000000 65535 f
0000000009 00000 n
0000000074 00000 n
0000000120 00000 n
0000000291 00000 n
0000000320 00000 n
0000000434 00000 n
0000000497 00000 n
0000004033 00000 n
0000004141 00000 n
0000004250 00000 n
0000004315 00000 n
trailer
<<
/Size 12
/Root 1 0 R
/Info 5 0 R
>>
startxref
8310
%%EOF

View File

@ -1,8 +1,8 @@
{ {
"fromjs": [ "fromjs": [
{ {
"ADMIN": "dcgroup", "ADMIN": "AllUsers",
"DCPATH": "/opt/data/DataController" "DCPATH": "/opt/data/DataController"
} }
] ]
} }

View File

@ -1,4 +1,4 @@
{ {
"licenceKey": "CFG2LNF1SYGgRPTwegsgRolmXw0xQmDpbagjRTniIarWXFE+/eClT4m+64U2kL5BBj0p3UwS80+wEer+jaw5w4OX27mDfXAUvkv90k54ER6GP5C5Z/t4VYucQo3Kn8wfhfFOymjRVtKZaHyavl9hBaWGwohX7W+97gsj5pnI9a/63w55OKNJvgeZrCxR04qhRiuf9K1J5EQtKP+wfW/ERoX3R4dPWKANjyFZkURs2GL/UDCGS6nPWTS8grlcinFUhh4V3hIZmzlWc5FG97lrbxnmq4FsdEaey4z4l70H4Yf/52ZZqJzaOyRGeQXmUtpS", "licenceKey": "invalid",
"activationKey": "MIIEbQIBADANBgkqhkiG9w0BAQEFAASCBFcwggRTAgEAAoHxAM0ayGUZgn7ZBBoEDwGYbTaKf85WIGInISinE2WisHZv6AaivG/tNo/K+Ms7b2EnwvIg4vUKKbKtoD9E3Fk1B0LV2qmKlv+TcZQ0YXXhRguaHcewGcwof7aJr+iTrD4CoL/H7gs3JvVshCRq7bfX59dXDlVdm8TkDglQMTyxX+L3JirAII4xbbJ7cTNoBnzgyi4n2Aa0+zFlq2WDzRjECxdcAm79lipQRW/AOjPFxEYIjOl+Uzv9yxOGdrFLAJkCznhnWC+D28qCaM+vCEDDW9tvmSdf7nsnlY46xud6sNnVgO/1LvC3VGkZ5zNZCx9XRwIDAQABAoHwT0qjXjJWeKN9KnGXO46p6gPxFNvG+SsXbpfor8oNXjw0/xu6raqPBVf6htcbX/v3KZP9Ka4cIK9u3AbLCNGvVO9H8XNanMNrjVgStXe5lJKoIKK71mlxtifUkZ1FYVOywXGRXVSdAxRIoauU6xXU0zMcn3Po3F0tPpi/C5xCcz0eOjLCx+eJc3U2IZKsQ7lHO7azCT9woguj09/xVW0481T6zuF6e0Oq4MWlt0qJPkkuLcMF6ps+ObHUB3044VDcm6X7kZjtcwnj886Y6n9llTNzHiui4R5eMSzm1w57jwR5m/Q1AOTGCyiW1KLI1eIBAnkA8Ma4zvkn44w0E1Dlrq/97vdJsUiE7Ifs5DknRU81imm0+LP2yyX7tBRpVcvrVkH2x/qW3bHLHFdV5o9A262Wcqdk2ZkMHp+gD59OsrcbRBokiwqU6nagLbYbC7RA2PZRMR48s8bWAnzFae0P8FFJlmKUHggarUDnAnkA2hKrOplw1C0X2QVeVQOlU36PjayVJEwkxq30k0wg+q9F464TrraRr5uFqMrAmwGN5ziZQ1Jzx3R2uBHft67FwXJfGy21Hor47sb+f5VkAuXPurbp6K+sMWRfIehK9/G9Iju90Iv6Oto4uv/yfdWadxCLc8tYy4qhAngUA/EJA51VRSpvEKKHSwoI+3WczzJ9ly8SKc4h7Nu+jdsFcbBqYtXxumCnSTRfD0y8gxBXjZgc2wXBDNePa3a+QTwY+qgPQ6XCprOcF6yklKfFBzQp6YKXSjQlXO6nGpLVSnYxW64ettCSZaqVh6xeXAOEG5hcHrECeFTYEpqX/Ffwu2iKOCtnYblcckmyrcwTe/N41sFAS0x9SPnOToYZLhFett/3Eny8XBNr5+VTfQxK+a2f9qSmcPZUo0AVxnP9qeBst7O30dN2yh1g8RzAzIPjA0hT8mcJPIbHK5CqBU9Ee/H1hskChDhyzW7d3MxEQQJ4QB6FIMNVQQh05iIB64SzxVvpI/ggOKDGO2/b2zILsKWiw4NhppEphmhXvuJy5TGFYERoGydaW6dKfau6rDSWGNbjPuFwmkUTUjzh2gR5rCp0Ifapd6D5sWDFaI+F4gEUaxmC9yJL8i6/JpMx3LRut1g5dsH6chhT" "activationKey": "invalid"
} }

View File

@ -1,16 +1,6 @@
const path = require('path') const path = require('path')
let iwantFileText = '' let iwantFileText = ''
// let filterQuery1 = `AND,AND,0,SOME_CHAR,=,"'this is dummy data'"`
// let filterQuery2 = `AND,AND,0,SOME_NUM,=,42`
// let filterQuery3 = `AND,AND,0,SOME_TIME,=,00:00:42`
// let filterQuery4 = `AND,AND,0,SOME_TIME,=,42`
// let filterQuery5 = `AND,AND,0,SOME_DATE,=,42`
// let filterQuery6 = `AND,AND,0,SOME_DATE,=,42`
// let filterQuery7 = `AND,AND,0,SOME_DATETIME,=,42`
// let filterQuery8 = `AND,AND,0,SOME_DATETIME,=,42`
// let filterQuery9 = `AND,AND,0,SOME_DATE,IN,(0)`
// let filterQuery10 = `AND,AND,0,SOME_BESTNUM,BETWEEN,0 AND 10`
let appLoc = path.join(..._program.split('services')[0].split('/')) let appLoc = path.join(..._program.split('services')[0].split('/'))
const sessionStoragePath = path.resolve(__dirname, '..', '..', 'drive', 'files', appLoc, 'mock-storage') const sessionStoragePath = path.resolve(__dirname, '..', '..', 'drive', 'files', appLoc, 'mock-storage')

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,15 @@ _webout = `{"SYSDATE" : "26SEP22"
[ [
{ {
"MEMNAME": "MPE_X_TEST" "MEMNAME": "MPE_X_TEST"
},
{
"MEMNAME": "MPE_AUDIT"
},
{
"MEMNAME": "MPE_ALERTS"
},
{
"MEMNAME": "MPE_VALIDATIONS"
} }
] ]
, "libinfo": , "libinfo":

474
sas/package-lock.json generated
View File

@ -6,8 +6,8 @@
"": { "": {
"name": "dc-sas", "name": "dc-sas",
"dependencies": { "dependencies": {
"@sasjs/cli": "4.3.0", "@sasjs/cli": "^4.4.2",
"@sasjs/core": "^4.46.3" "@sasjs/core": "^4.46.4"
} }
}, },
"node_modules/@coolaj86/urequest": { "node_modules/@coolaj86/urequest": {
@ -29,9 +29,9 @@
} }
}, },
"node_modules/@sasjs/adapter": { "node_modules/@sasjs/adapter": {
"version": "4.3.4", "version": "4.5.1",
"resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-4.3.4.tgz", "resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-4.5.1.tgz",
"integrity": "sha512-hPLNPXqunbIXZY+rsRxwfiLehk8x9a0IG2/96geJqrgP4Xi705X2WqkTaCpJ+Lr9gFGk4PttdprokCl1yoHVNw==", "integrity": "sha512-WeKDMfCivBywxDZ6t0jng78ZBPoMk8RIHKFTNDDmvuvmXq5Mr5oqZ0r5lRPB863XkGOeVi6UIEI1+JawZ2TlWQ==",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@sasjs/utils": "2.52.0", "@sasjs/utils": "2.52.0",
@ -39,7 +39,7 @@
"axios-cookiejar-support": "1.0.1", "axios-cookiejar-support": "1.0.1",
"form-data": "4.0.0", "form-data": "4.0.0",
"https": "1.0.0", "https": "1.0.0",
"tough-cookie": "4.0.0" "tough-cookie": "4.1.3"
} }
}, },
"node_modules/@sasjs/adapter/node_modules/@sasjs/utils": { "node_modules/@sasjs/adapter/node_modules/@sasjs/utils": {
@ -78,22 +78,22 @@
} }
}, },
"node_modules/@sasjs/cli": { "node_modules/@sasjs/cli": {
"version": "4.3.0", "version": "4.4.2",
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-4.3.0.tgz", "resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-4.4.2.tgz",
"integrity": "sha512-LDObBUigusiBvq7SzkL49W5zel2YfjcmetdrftEnEdpWeVkVYYby77gJiW9iXdhrdVT+Hgrdubs6tA+RT0LskA==", "integrity": "sha512-o1Qp+L7vJOH9dbsEPJK6GaQR7yWW+W0BAI1rrD55+Ij3USMCcdWcRJAOvFxwS8Gflq5BuNrVqa39rg4RK0ZVEQ==",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@sasjs/adapter": "4.3.4", "@sasjs/adapter": "4.5.1",
"@sasjs/core": "4.45.4", "@sasjs/core": "4.46.3",
"@sasjs/lint": "2.3.0", "@sasjs/lint": "2.3.1",
"@sasjs/utils": "3.3.0", "@sasjs/utils": "3.3.0",
"adm-zip": "0.5.9", "adm-zip": "0.5.9",
"chalk": "4.1.2", "chalk": "4.1.2",
"dotenv": "10.0.0", "dotenv": "16.0.3",
"esm": "3.2.25", "esm": "3.2.25",
"find": "0.3.0", "find": "0.3.0",
"js-base64": "3.7.2", "js-base64": "3.7.2",
"jsdom": "21.1.0", "jsdom": "22.1.0",
"jwt-decode": "3.1.2", "jwt-decode": "3.1.2",
"lodash.groupby": "4.6.0", "lodash.groupby": "4.6.0",
"lodash.uniqby": "4.7.0", "lodash.uniqby": "4.7.0",
@ -111,19 +111,19 @@
} }
}, },
"node_modules/@sasjs/cli/node_modules/@sasjs/core": { "node_modules/@sasjs/cli/node_modules/@sasjs/core": {
"version": "4.45.4",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.45.4.tgz",
"integrity": "sha512-zf0foZHXNUb7hTHQAbGcQbHhiwX7qNK9g3PTT5XkjR8aDgcYeBpSxu1bEj7GTVvOmv49YF/3xrVZNh5J+38+DA=="
},
"node_modules/@sasjs/core": {
"version": "4.46.3", "version": "4.46.3",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.46.3.tgz", "resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.46.3.tgz",
"integrity": "sha512-Grwydm5GxBsYk238PZw41XPjXVVQ9vWcvfZ06L2P0bQbvK0sGn7l69JA7H5MGr3QcaLpiD4Kg70cAh7PgE+JOw==" "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=="
},
"node_modules/@sasjs/lint": { "node_modules/@sasjs/lint": {
"version": "2.3.0", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/@sasjs/lint/-/lint-2.3.0.tgz", "resolved": "https://registry.npmjs.org/@sasjs/lint/-/lint-2.3.1.tgz",
"integrity": "sha512-ac3l4RUf3+87tmi2VCebibxQNIdiyzJuRGErka/BKXu+F4pBhUG1Y8t0lNISLDk6fm5uJXHpYrR5zck8J5tCtQ==", "integrity": "sha512-vvoMXAicuewxyiBmF9VkMziQsAA6+MSKMnmcmAfnbR5cvBVSDYDh6X7sAeWXyjNfWxuBweyP9nv6//FRiWaRMQ==",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@sasjs/utils": "2.52.0", "@sasjs/utils": "2.52.0",
@ -254,34 +254,6 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
}, },
"node_modules/acorn": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
"integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/acorn-globals": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
"integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
"dependencies": {
"acorn": "^8.1.0",
"acorn-walk": "^8.0.2"
}
},
"node_modules/acorn-walk": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/adm-zip": { "node_modules/adm-zip": {
"version": "0.5.9", "version": "0.5.9",
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz", "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz",
@ -556,43 +528,33 @@
"resolved": "https://registry.npmjs.org/consola/-/consola-2.15.0.tgz", "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.0.tgz",
"integrity": "sha512-vlcSGgdYS26mPf7qNi+dCisbhiyDnrN1zaRbw3CSuc2wGOMEGGPsp46PdRG5gqXwgtJfjxDkxRNAgRPr1B77vQ==" "integrity": "sha512-vlcSGgdYS26mPf7qNi+dCisbhiyDnrN1zaRbw3CSuc2wGOMEGGPsp46PdRG5gqXwgtJfjxDkxRNAgRPr1B77vQ=="
}, },
"node_modules/cssom": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
"integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw=="
},
"node_modules/cssstyle": { "node_modules/cssstyle": {
"version": "2.3.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz",
"integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==",
"dependencies": { "dependencies": {
"cssom": "~0.3.6" "rrweb-cssom": "^0.6.0"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=14"
} }
}, },
"node_modules/cssstyle/node_modules/cssom": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="
},
"node_modules/csv-stringify": { "node_modules/csv-stringify": {
"version": "5.6.5", "version": "5.6.5",
"resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.6.5.tgz", "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.6.5.tgz",
"integrity": "sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A==" "integrity": "sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A=="
}, },
"node_modules/data-urls": { "node_modules/data-urls": {
"version": "3.0.2", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz",
"integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==",
"dependencies": { "dependencies": {
"abab": "^2.0.6", "abab": "^2.0.6",
"whatwg-mimetype": "^3.0.0", "whatwg-mimetype": "^3.0.0",
"whatwg-url": "^11.0.0" "whatwg-url": "^12.0.0"
}, },
"engines": { "engines": {
"node": ">=12" "node": ">=14"
} }
}, },
"node_modules/debug": { "node_modules/debug": {
@ -647,11 +609,11 @@
} }
}, },
"node_modules/dotenv": { "node_modules/dotenv": {
"version": "10.0.0", "version": "16.0.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
"integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==",
"engines": { "engines": {
"node": ">=10" "node": ">=12"
} }
}, },
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
@ -678,26 +640,6 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/escodegen": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
"integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
"dependencies": {
"esprima": "^4.0.1",
"estraverse": "^5.2.0",
"esutils": "^2.0.2"
},
"bin": {
"escodegen": "bin/escodegen.js",
"esgenerate": "bin/esgenerate.js"
},
"engines": {
"node": ">=6.0"
},
"optionalDependencies": {
"source-map": "~0.6.1"
}
},
"node_modules/esm": { "node_modules/esm": {
"version": "3.2.25", "version": "3.2.25",
"resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
@ -706,34 +648,6 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"engines": {
"node": ">=4.0"
}
},
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/eventemitter3": { "node_modules/eventemitter3": {
"version": "4.0.7", "version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
@ -1036,26 +950,23 @@
"integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ==" "integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ=="
}, },
"node_modules/jsdom": { "node_modules/jsdom": {
"version": "21.1.0", "version": "22.1.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.1.0.tgz", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz",
"integrity": "sha512-m0lzlP7qOtthD918nenK3hdItSd2I+V3W9IrBcB36sqDwG+KnUs66IF5GY7laGWUnlM9vTsD0W1QwSEBYWWcJg==", "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==",
"dependencies": { "dependencies": {
"abab": "^2.0.6", "abab": "^2.0.6",
"acorn": "^8.8.1", "cssstyle": "^3.0.0",
"acorn-globals": "^7.0.0", "data-urls": "^4.0.0",
"cssom": "^0.5.0", "decimal.js": "^10.4.3",
"cssstyle": "^2.3.0",
"data-urls": "^3.0.2",
"decimal.js": "^10.4.2",
"domexception": "^4.0.0", "domexception": "^4.0.0",
"escodegen": "^2.0.0",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"html-encoding-sniffer": "^3.0.0", "html-encoding-sniffer": "^3.0.0",
"http-proxy-agent": "^5.0.0", "http-proxy-agent": "^5.0.0",
"https-proxy-agent": "^5.0.1", "https-proxy-agent": "^5.0.1",
"is-potential-custom-element-name": "^1.0.1", "is-potential-custom-element-name": "^1.0.1",
"nwsapi": "^2.2.2", "nwsapi": "^2.2.4",
"parse5": "^7.1.1", "parse5": "^7.1.2",
"rrweb-cssom": "^0.6.0",
"saxes": "^6.0.0", "saxes": "^6.0.0",
"symbol-tree": "^3.2.4", "symbol-tree": "^3.2.4",
"tough-cookie": "^4.1.2", "tough-cookie": "^4.1.2",
@ -1063,12 +974,12 @@
"webidl-conversions": "^7.0.0", "webidl-conversions": "^7.0.0",
"whatwg-encoding": "^2.0.0", "whatwg-encoding": "^2.0.0",
"whatwg-mimetype": "^3.0.0", "whatwg-mimetype": "^3.0.0",
"whatwg-url": "^11.0.0", "whatwg-url": "^12.0.1",
"ws": "^8.11.0", "ws": "^8.13.0",
"xml-name-validator": "^4.0.0" "xml-name-validator": "^4.0.0"
}, },
"engines": { "engines": {
"node": ">=14" "node": ">=16"
}, },
"peerDependencies": { "peerDependencies": {
"canvas": "^2.5.0" "canvas": "^2.5.0"
@ -1079,28 +990,6 @@
} }
} }
}, },
"node_modules/jsdom/node_modules/tough-cookie": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
"integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
"dependencies": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
"universalify": "^0.2.0",
"url-parse": "^1.5.3"
},
"engines": {
"node": ">=6"
}
},
"node_modules/jsdom/node_modules/universalify": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/jsonfile": { "node_modules/jsonfile": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
@ -1258,9 +1147,9 @@
} }
}, },
"node_modules/nwsapi": { "node_modules/nwsapi": {
"version": "2.2.6", "version": "2.2.7",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.6.tgz", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz",
"integrity": "sha512-vSZ4miHQ4FojLjmz2+ux4B0/XA16jfwt/LBzIUftDpRd8tujHFkXjMyLwjS08fIZCzesj2z7gJukOKJwqebJAQ==" "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ=="
}, },
"node_modules/once": { "node_modules/once": {
"version": "1.4.0", "version": "1.4.0",
@ -1492,6 +1381,11 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/rrweb-cssom": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz",
"integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw=="
},
"node_modules/safe-buffer": { "node_modules/safe-buffer": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@ -1553,15 +1447,6 @@
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="
}, },
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/ssl-root-cas": { "node_modules/ssl-root-cas": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/ssl-root-cas/-/ssl-root-cas-1.3.1.tgz", "resolved": "https://registry.npmjs.org/ssl-root-cas/-/ssl-root-cas-1.3.1.tgz",
@ -1630,35 +1515,36 @@
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="
}, },
"node_modules/tough-cookie": { "node_modules/tough-cookie": {
"version": "4.0.0", "version": "4.1.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
"integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
"dependencies": { "dependencies": {
"psl": "^1.1.33", "psl": "^1.1.33",
"punycode": "^2.1.1", "punycode": "^2.1.1",
"universalify": "^0.1.2" "universalify": "^0.2.0",
"url-parse": "^1.5.3"
}, },
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/tough-cookie/node_modules/universalify": { "node_modules/tough-cookie/node_modules/universalify": {
"version": "0.1.2", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
"engines": { "engines": {
"node": ">= 4.0.0" "node": ">= 4.0.0"
} }
}, },
"node_modules/tr46": { "node_modules/tr46": {
"version": "3.0.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz",
"integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==",
"dependencies": { "dependencies": {
"punycode": "^2.1.1" "punycode": "^2.3.0"
}, },
"engines": { "engines": {
"node": ">=12" "node": ">=14"
} }
}, },
"node_modules/traverse-chain": { "node_modules/traverse-chain": {
@ -1745,15 +1631,15 @@
} }
}, },
"node_modules/whatwg-url": { "node_modules/whatwg-url": {
"version": "11.0.0", "version": "12.0.1",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz",
"integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==",
"dependencies": { "dependencies": {
"tr46": "^3.0.0", "tr46": "^4.1.1",
"webidl-conversions": "^7.0.0" "webidl-conversions": "^7.0.0"
}, },
"engines": { "engines": {
"node": ">=12" "node": ">=14"
} }
}, },
"node_modules/wrap-ansi": { "node_modules/wrap-ansi": {
@ -1869,16 +1755,16 @@
} }
}, },
"@sasjs/adapter": { "@sasjs/adapter": {
"version": "4.3.4", "version": "4.5.1",
"resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-4.3.4.tgz", "resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-4.5.1.tgz",
"integrity": "sha512-hPLNPXqunbIXZY+rsRxwfiLehk8x9a0IG2/96geJqrgP4Xi705X2WqkTaCpJ+Lr9gFGk4PttdprokCl1yoHVNw==", "integrity": "sha512-WeKDMfCivBywxDZ6t0jng78ZBPoMk8RIHKFTNDDmvuvmXq5Mr5oqZ0r5lRPB863XkGOeVi6UIEI1+JawZ2TlWQ==",
"requires": { "requires": {
"@sasjs/utils": "2.52.0", "@sasjs/utils": "2.52.0",
"axios": "0.27.2", "axios": "0.27.2",
"axios-cookiejar-support": "1.0.1", "axios-cookiejar-support": "1.0.1",
"form-data": "4.0.0", "form-data": "4.0.0",
"https": "1.0.0", "https": "1.0.0",
"tough-cookie": "4.0.0" "tough-cookie": "4.1.3"
}, },
"dependencies": { "dependencies": {
"@sasjs/utils": { "@sasjs/utils": {
@ -1912,21 +1798,21 @@
} }
}, },
"@sasjs/cli": { "@sasjs/cli": {
"version": "4.3.0", "version": "4.4.2",
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-4.3.0.tgz", "resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-4.4.2.tgz",
"integrity": "sha512-LDObBUigusiBvq7SzkL49W5zel2YfjcmetdrftEnEdpWeVkVYYby77gJiW9iXdhrdVT+Hgrdubs6tA+RT0LskA==", "integrity": "sha512-o1Qp+L7vJOH9dbsEPJK6GaQR7yWW+W0BAI1rrD55+Ij3USMCcdWcRJAOvFxwS8Gflq5BuNrVqa39rg4RK0ZVEQ==",
"requires": { "requires": {
"@sasjs/adapter": "4.3.4", "@sasjs/adapter": "4.5.1",
"@sasjs/core": "4.45.4", "@sasjs/core": "4.46.3",
"@sasjs/lint": "2.3.0", "@sasjs/lint": "2.3.1",
"@sasjs/utils": "3.3.0", "@sasjs/utils": "3.3.0",
"adm-zip": "0.5.9", "adm-zip": "0.5.9",
"chalk": "4.1.2", "chalk": "4.1.2",
"dotenv": "10.0.0", "dotenv": "16.0.3",
"esm": "3.2.25", "esm": "3.2.25",
"find": "0.3.0", "find": "0.3.0",
"js-base64": "3.7.2", "js-base64": "3.7.2",
"jsdom": "21.1.0", "jsdom": "22.1.0",
"jwt-decode": "3.1.2", "jwt-decode": "3.1.2",
"lodash.groupby": "4.6.0", "lodash.groupby": "4.6.0",
"lodash.uniqby": "4.7.0", "lodash.uniqby": "4.7.0",
@ -1941,21 +1827,21 @@
}, },
"dependencies": { "dependencies": {
"@sasjs/core": { "@sasjs/core": {
"version": "4.45.4", "version": "4.46.3",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.45.4.tgz", "resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.46.3.tgz",
"integrity": "sha512-zf0foZHXNUb7hTHQAbGcQbHhiwX7qNK9g3PTT5XkjR8aDgcYeBpSxu1bEj7GTVvOmv49YF/3xrVZNh5J+38+DA==" "integrity": "sha512-Grwydm5GxBsYk238PZw41XPjXVVQ9vWcvfZ06L2P0bQbvK0sGn7l69JA7H5MGr3QcaLpiD4Kg70cAh7PgE+JOw=="
} }
} }
}, },
"@sasjs/core": { "@sasjs/core": {
"version": "4.46.3", "version": "4.46.4",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.46.3.tgz", "resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.46.4.tgz",
"integrity": "sha512-Grwydm5GxBsYk238PZw41XPjXVVQ9vWcvfZ06L2P0bQbvK0sGn7l69JA7H5MGr3QcaLpiD4Kg70cAh7PgE+JOw==" "integrity": "sha512-Q4UiOEYEHWCYn4ak+2BaKnrusLauyvKK/Hq4Y4RwJOfwA2MSjOzJSV8fDpbhnY1Dyubbk4SChA6yAL8lc0hn1Q=="
}, },
"@sasjs/lint": { "@sasjs/lint": {
"version": "2.3.0", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/@sasjs/lint/-/lint-2.3.0.tgz", "resolved": "https://registry.npmjs.org/@sasjs/lint/-/lint-2.3.1.tgz",
"integrity": "sha512-ac3l4RUf3+87tmi2VCebibxQNIdiyzJuRGErka/BKXu+F4pBhUG1Y8t0lNISLDk6fm5uJXHpYrR5zck8J5tCtQ==", "integrity": "sha512-vvoMXAicuewxyiBmF9VkMziQsAA6+MSKMnmcmAfnbR5cvBVSDYDh6X7sAeWXyjNfWxuBweyP9nv6//FRiWaRMQ==",
"requires": { "requires": {
"@sasjs/utils": "2.52.0", "@sasjs/utils": "2.52.0",
"ignore": "5.2.4" "ignore": "5.2.4"
@ -2074,25 +1960,6 @@
} }
} }
}, },
"acorn": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
"integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw=="
},
"acorn-globals": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
"integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
"requires": {
"acorn": "^8.1.0",
"acorn-walk": "^8.0.2"
}
},
"acorn-walk": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA=="
},
"adm-zip": { "adm-zip": {
"version": "0.5.9", "version": "0.5.9",
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz", "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz",
@ -2280,24 +2147,12 @@
"resolved": "https://registry.npmjs.org/consola/-/consola-2.15.0.tgz", "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.0.tgz",
"integrity": "sha512-vlcSGgdYS26mPf7qNi+dCisbhiyDnrN1zaRbw3CSuc2wGOMEGGPsp46PdRG5gqXwgtJfjxDkxRNAgRPr1B77vQ==" "integrity": "sha512-vlcSGgdYS26mPf7qNi+dCisbhiyDnrN1zaRbw3CSuc2wGOMEGGPsp46PdRG5gqXwgtJfjxDkxRNAgRPr1B77vQ=="
}, },
"cssom": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
"integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw=="
},
"cssstyle": { "cssstyle": {
"version": "2.3.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz",
"integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==",
"requires": { "requires": {
"cssom": "~0.3.6" "rrweb-cssom": "^0.6.0"
},
"dependencies": {
"cssom": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="
}
} }
}, },
"csv-stringify": { "csv-stringify": {
@ -2306,13 +2161,13 @@
"integrity": "sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A==" "integrity": "sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A=="
}, },
"data-urls": { "data-urls": {
"version": "3.0.2", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz",
"integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==",
"requires": { "requires": {
"abab": "^2.0.6", "abab": "^2.0.6",
"whatwg-mimetype": "^3.0.0", "whatwg-mimetype": "^3.0.0",
"whatwg-url": "^11.0.0" "whatwg-url": "^12.0.0"
} }
}, },
"debug": { "debug": {
@ -2350,9 +2205,9 @@
} }
}, },
"dotenv": { "dotenv": {
"version": "10.0.0", "version": "16.0.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
"integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==" "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ=="
}, },
"emoji-regex": { "emoji-regex": {
"version": "8.0.0", "version": "8.0.0",
@ -2369,37 +2224,11 @@
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
}, },
"escodegen": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
"integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
"requires": {
"esprima": "^4.0.1",
"estraverse": "^5.2.0",
"esutils": "^2.0.2",
"source-map": "~0.6.1"
}
},
"esm": { "esm": {
"version": "3.2.25", "version": "3.2.25",
"resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
"integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==" "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA=="
}, },
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="
},
"esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
},
"eventemitter3": { "eventemitter3": {
"version": "4.0.7", "version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
@ -2605,26 +2434,23 @@
"integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ==" "integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ=="
}, },
"jsdom": { "jsdom": {
"version": "21.1.0", "version": "22.1.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.1.0.tgz", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz",
"integrity": "sha512-m0lzlP7qOtthD918nenK3hdItSd2I+V3W9IrBcB36sqDwG+KnUs66IF5GY7laGWUnlM9vTsD0W1QwSEBYWWcJg==", "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==",
"requires": { "requires": {
"abab": "^2.0.6", "abab": "^2.0.6",
"acorn": "^8.8.1", "cssstyle": "^3.0.0",
"acorn-globals": "^7.0.0", "data-urls": "^4.0.0",
"cssom": "^0.5.0", "decimal.js": "^10.4.3",
"cssstyle": "^2.3.0",
"data-urls": "^3.0.2",
"decimal.js": "^10.4.2",
"domexception": "^4.0.0", "domexception": "^4.0.0",
"escodegen": "^2.0.0",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"html-encoding-sniffer": "^3.0.0", "html-encoding-sniffer": "^3.0.0",
"http-proxy-agent": "^5.0.0", "http-proxy-agent": "^5.0.0",
"https-proxy-agent": "^5.0.1", "https-proxy-agent": "^5.0.1",
"is-potential-custom-element-name": "^1.0.1", "is-potential-custom-element-name": "^1.0.1",
"nwsapi": "^2.2.2", "nwsapi": "^2.2.4",
"parse5": "^7.1.1", "parse5": "^7.1.2",
"rrweb-cssom": "^0.6.0",
"saxes": "^6.0.0", "saxes": "^6.0.0",
"symbol-tree": "^3.2.4", "symbol-tree": "^3.2.4",
"tough-cookie": "^4.1.2", "tough-cookie": "^4.1.2",
@ -2632,27 +2458,9 @@
"webidl-conversions": "^7.0.0", "webidl-conversions": "^7.0.0",
"whatwg-encoding": "^2.0.0", "whatwg-encoding": "^2.0.0",
"whatwg-mimetype": "^3.0.0", "whatwg-mimetype": "^3.0.0",
"whatwg-url": "^11.0.0", "whatwg-url": "^12.0.1",
"ws": "^8.11.0", "ws": "^8.13.0",
"xml-name-validator": "^4.0.0" "xml-name-validator": "^4.0.0"
},
"dependencies": {
"tough-cookie": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
"integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
"requires": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
"universalify": "^0.2.0",
"url-parse": "^1.5.3"
}
},
"universalify": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="
}
} }
}, },
"jsonfile": { "jsonfile": {
@ -2774,9 +2582,9 @@
} }
}, },
"nwsapi": { "nwsapi": {
"version": "2.2.6", "version": "2.2.7",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.6.tgz", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz",
"integrity": "sha512-vSZ4miHQ4FojLjmz2+ux4B0/XA16jfwt/LBzIUftDpRd8tujHFkXjMyLwjS08fIZCzesj2z7gJukOKJwqebJAQ==" "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ=="
}, },
"once": { "once": {
"version": "1.4.0", "version": "1.4.0",
@ -2941,6 +2749,11 @@
"glob": "^7.1.3" "glob": "^7.1.3"
} }
}, },
"rrweb-cssom": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz",
"integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw=="
},
"safe-buffer": { "safe-buffer": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@ -2979,12 +2792,6 @@
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="
}, },
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"optional": true
},
"ssl-root-cas": { "ssl-root-cas": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/ssl-root-cas/-/ssl-root-cas-1.3.1.tgz", "resolved": "https://registry.npmjs.org/ssl-root-cas/-/ssl-root-cas-1.3.1.tgz",
@ -3038,28 +2845,29 @@
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="
}, },
"tough-cookie": { "tough-cookie": {
"version": "4.0.0", "version": "4.1.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
"integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
"requires": { "requires": {
"psl": "^1.1.33", "psl": "^1.1.33",
"punycode": "^2.1.1", "punycode": "^2.1.1",
"universalify": "^0.1.2" "universalify": "^0.2.0",
"url-parse": "^1.5.3"
}, },
"dependencies": { "dependencies": {
"universalify": { "universalify": {
"version": "0.1.2", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="
} }
} }
}, },
"tr46": { "tr46": {
"version": "3.0.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz",
"integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==",
"requires": { "requires": {
"punycode": "^2.1.1" "punycode": "^2.3.0"
} }
}, },
"traverse-chain": { "traverse-chain": {
@ -3131,11 +2939,11 @@
"integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==" "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="
}, },
"whatwg-url": { "whatwg-url": {
"version": "11.0.0", "version": "12.0.1",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz",
"integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==",
"requires": { "requires": {
"tr46": "^3.0.0", "tr46": "^4.1.1",
"webidl-conversions": "^7.0.0" "webidl-conversions": "^7.0.0"
} }
}, },

View File

@ -27,7 +27,7 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@sasjs/cli": "4.3.0", "@sasjs/cli": "^4.4.2",
"@sasjs/core": "^4.46.3" "@sasjs/core": "^4.46.4"
} }
} }

View File

@ -94,6 +94,7 @@ Areas for optimisation
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li bitemporal_closeouts.sas @li bitemporal_closeouts.sas
@li dc_assignlib.sas @li dc_assignlib.sas
@li mf_existds.sas
@li mf_existvar.sas @li mf_existvar.sas
@li mf_fmtdttm.sas @li mf_fmtdttm.sas
@li mf_getattrn.sas @li mf_getattrn.sas
@ -1390,9 +1391,11 @@ run;
,processed_dttm=&now ,processed_dttm=&now
,loadref=%superq(etlsource) ,loadref=%superq(etlsource)
) )
data _null_; /* exclude unchanged values in modified rows */
data work.mp_storediffs;
set work.mp_storediffs; set work.mp_storediffs;
putlog load_ref= libref= dsn= key_hash= tgtvar_nm=; if MOVE_TYPE="M" and IS_PK=0 and IS_DIFF=0 then delete;
* putlog load_ref= libref= dsn= key_hash= tgtvar_nm=;
run; run;
proc append base=&outds_audit data=work.mp_storediffs; proc append base=&outds_audit data=work.mp_storediffs;
run; run;

View File

@ -240,13 +240,17 @@
}, },
{ {
"name": "server", "name": "server",
"serverUrl": "https://sas.4gl.io:5006", "serverUrl": "https://sas9.4gl.io",
"serverType": "SASJS", "serverType": "SASJS",
"httpsAgentOptions": { "httpsAgentOptions": {
"rejectUnauthorized": false, "rejectUnauthorized": false,
"allowInsecureRequests": true "allowInsecureRequests": true
}, },
"appLoc": "/Public/app/dc", "appLoc": "/Public/app/dc",
"deployConfig": {
"deployServicePack": true,
"deployScripts": []
},
"macroFolders": [ "macroFolders": [
"sasjs/targets/server/macros_server" "sasjs/targets/server/macros_server"
], ],
@ -265,8 +269,8 @@
"streamConfig": { "streamConfig": {
"streamWeb": true, "streamWeb": true,
"streamWebFolder": "web", "streamWebFolder": "web",
"streamServiceName": "DataController",
"webSourcePath": "../client/dist", "webSourcePath": "../client/dist",
"streamServiceName": "DataController",
"streamLogo": "favicon.ico", "streamLogo": "favicon.ico",
"assetPaths": [] "assetPaths": []
} }

View File

@ -299,7 +299,7 @@ run;
%dc_assignlib(WRITE,&libref) %dc_assignlib(WRITE,&libref)
/* run pre-approve hook if approving */ /* run pre-approve hook - occurs both BEFORE _and_ AFTER the diff */
%mpe_runhook(PRE_APPROVE_HOOK) %mpe_runhook(PRE_APPROVE_HOOK)
%mp_abort(iftrue= (&syscc ne 0) %mp_abort(iftrue= (&syscc ne 0)

View File

@ -3,7 +3,7 @@
@brief testing postdata with mpe_x_test @brief testing postdata with mpe_x_test
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mp_testservice.sas @li mx_testservice.sas
@li mp_assert.sas @li mp_assert.sas
@ -44,10 +44,11 @@ data work.jsdata;
else stop; else stop;
run; run;
%mp_testservice(&appLoc/services/editors/stagedata, %mx_testservice(&appLoc/services/editors/stagedata,
viyacontext=&defaultcontext, viyacontext=&defaultcontext,
inputdatasets=work.jsdata work.sascontroltable, inputdatasets=work.jsdata work.sascontroltable,
outlib=web1 outlib=web1,
mdebug=&sasjs_mdebug
) )
%let status=0; %let status=0;
@ -73,11 +74,12 @@ data work.sascontroltable;
output; output;
stop; stop;
run; run;
%mp_testservice(&appLoc/services/auditors/postdata, %mx_testservice(&appLoc/services/auditors/postdata,
viyacontext=&defaultcontext, viyacontext=&defaultcontext,
inputdatasets=work.sascontroltable, inputdatasets=work.sascontroltable,
outlib=web2, outlib=web2,
outref=wbout outref=wbout,
mdebug=&sasjs_mdebug
) )
%let leadcheck=0; %let leadcheck=0;
@ -86,7 +88,7 @@ data _null_;
infile wbout; infile wbout;
input; input;
putlog _infile_; putlog _infile_;
/* the JSON libname option removes leading blanks!!!! */ /* the JSON libname engine removes leading blanks!!!! */
if index(_infile_,' leadingblanks') then call symputx('leadcheck',1); if index(_infile_,' leadingblanks') then call symputx('leadcheck',1);
/* there is no clean way to send a special missing - so is sent as string */ /* there is no clean way to send a special missing - so is sent as string */
if index(_infile_,',"SOME_NUM":"_"') then call symputx('speshcheck',1); if index(_infile_,',"SOME_NUM":"_"') then call symputx('speshcheck',1);

View File

@ -7,7 +7,9 @@
@li mp_assertcolvals.sas @li mp_assertcolvals.sas
@li mp_assertdsobs.sas @li mp_assertdsobs.sas
@li mf_getuniquefileref.sas @li mf_getuniquefileref.sas
@li mf_getuniquename.sas
@li mp_filterstore.sas @li mp_filterstore.sas
@li mx_testservice.sas
**/ **/
@ -23,10 +25,11 @@ data _null_;
put 'LIBDS:$43.'; put 'LIBDS:$43.';
put "&dclib..MPE_TABLES"; put "&dclib..MPE_TABLES";
run; run;
%mp_testservice(&_program, %mx_testservice(&_program,
viyacontext=&defaultcontext, viyacontext=&defaultcontext,
inputfiles=&f1:sascontroltable, inputfiles=&f1:sascontroltable,
outlib=web1 outlib=web1,
mdebug=&sasjs_mdebug
) )
data _null_; data _null_;
infile "%sysfunc(pathname(WEB1))" lrecl=32767; infile "%sysfunc(pathname(WEB1))" lrecl=32767;
@ -44,8 +47,6 @@ data dqrules;
set web1.dqrules; set web1.dqrules;
data dqdata; data dqdata;
set web1.dqdata; set web1.dqdata;
data spec;
set web1.spec;
data cols; data cols;
set web1.cols; set web1.cols;
data maxvarlengths; data maxvarlengths;
@ -105,7 +106,7 @@ data _null_;
put "&dclib..MPE_TABLES,&filter_rk"; put "&dclib..MPE_TABLES,&filter_rk";
run; run;
%mp_testservice(&_program, %mx_testservice(&_program,
viyacontext=&defaultcontext, viyacontext=&defaultcontext,
inputfiles=&f2:sascontroltable, inputfiles=&f2:sascontroltable,
outlib=web2 outlib=web2
@ -133,13 +134,31 @@ run;
* Test 3 - format catalog * Test 3 - format catalog
*/ */
/* first, make filter */ /* first, make sure format data exists */
%let fmtname=%upcase(%substr(%mf_getuniquename(prefix=FMT),1,15))NAME;
/* add formats */
data work.fmts;
length fmtname $32;
fmtname="&fmtname";
do start=1 to 10;
label= cats("&fmtname",start);
output;
end;
run;
proc sort data=work.fmts nodupkey;
by fmtname;
run;
proc format cntlin=work.fmts library=dctest.dcfmts;
run;
/* now, make filter */
data work.inquery3; data work.inquery3;
infile datalines4 dsd; infile datalines4 dsd;
input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
OPERATOR_NM:$10. RAW_VALUE:$4000.; OPERATOR_NM:$10. RAW_VALUE:$4000.;
datalines4; datalines4;
AND,AND,1,FMTNAME,CONTAINS,"'MOR'" AND,AND,1,FMTNAME,CONTAINS,"'&fmtname'"
;;;; ;;;;
run; run;
%mp_filterstore( %mp_filterstore(
@ -164,14 +183,16 @@ data _null_;
put "DCTEST.DCFMTS-FC,&filter_rk3"; put "DCTEST.DCFMTS-FC,&filter_rk3";
run; run;
%mp_testservice(&_program, %mx_testservice(&_program,
viyacontext=&defaultcontext, viyacontext=&defaultcontext,
inputfiles=&f3:sascontroltable, inputfiles=&f3:sascontroltable,
outlib=web3 outlib=web3,
mdebug=&sasjs_mdebug
) )
data sasdata3; data sasdata3;
set web3.sasdata; set web3.sasdata;
putlog (_all_)(=);
data query3; data query3;
set web3.query; set web3.query;
run; run;
@ -183,6 +204,6 @@ run;
) )
%mp_assertdsobs(work.sasdata3, %mp_assertdsobs(work.sasdata3,
desc=Test3 - format data has rows, desc=Test3 - format data has rows,
test=ATLEAST 5, test=EQUALS 10,
outds=work.test_results outds=work.test_results
) )

View File

@ -3,7 +3,7 @@
@brief testing stagedata with format table @brief testing stagedata with format table
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mp_testservice.sas @li mx_testservice.sas
@li mp_assert.sas @li mp_assert.sas
@ -29,10 +29,11 @@ data work.jsdata;
if _n_>20 then stop; if _n_>20 then stop;
run; run;
%mp_testservice(&_program, %mx_testservice(&_program,
viyacontext=&defaultcontext, viyacontext=&defaultcontext,
inputdatasets=work.sascontroltable work.jsdata, inputdatasets=work.sascontroltable work.jsdata,
outlib=web1 outlib=web1,
mdebug=&sasjs_mdebug
) )
%let status=0; %let status=0;

View File

@ -51,7 +51,7 @@ data work.params2;
name='filter';value='0';output; name='filter';value='0';output;
run; run;
%mp_testservice(&_program, %mx_testservice(&_program,
viyacontext=&defaultcontext, viyacontext=&defaultcontext,
inputparams=work.params2, inputparams=work.params2,
outref=web2, outref=web2,

View File

@ -28,7 +28,7 @@ run;
data work.libinfo; data work.libinfo;
set web1.libinfo; set web1.libinfo;
putlog (_all_)(=); putlog (_all_)(=);
call symputx('libref',libref); call symputx('libref',libname);
run; run;
%mp_assert( %mp_assert(

View File

@ -14,10 +14,23 @@
**/ **/
/* first, validate the filter and get the RK */ /* first, ensure the table has the 3 records we need */
proc sql;
delete from &dclib..mpe_x_test where PRIMARY_KEY_FIELD in (1,2,3);
data work.append;
set &dclib..mpe_x_test;
do PRIMARY_KEY_FIELD=1,2,3;
output;
end;
stop;
run;
proc append base=&dclib..mpe_x_test data=work.append;
run;
/* now, validate the filter and get the RK */
%let _program=&appLoc/services/public/validatefilter; %let _program=&appLoc/services/public/validatefilter;
/* filter for one record */ /* create filter */
%let f1=%mf_getuniquefileref(); %let f1=%mf_getuniquefileref();
data _null_; data _null_;
file &f1 termstr=crlf; file &f1 termstr=crlf;

View File

@ -75,7 +75,7 @@ data work.fmts;
end; end;
run; run;
proc sort data=work.fmts nodupkey; proc sort data=work.fmts nodupkey;
by type fmtname fmtrow; by fmtname;
run; run;
proc format cntlin=work.fmts library=dctest.dcfmts; proc format cntlin=work.fmts library=dctest.dcfmts;
run; run;