Compare commits

...

28 Commits

Author SHA1 Message Date
semantic-release-bot bba2a6cc9d chore(release): 6.1.0 [skip ci]
Release / release (push) Successful in 11m2s Details
# [6.1.0](https://git.datacontroller.io/dc/dc/compare/v6.0.0...v6.1.0) (2023-07-25)

### Bug Fixes

* missing mf_existds dependency in bitemporal_dataloader ([5ce1701](5ce1701657))
* reducing audit data volumes. Closes [#4](#4) ([54fe701](54fe7013b1))
* release script, excel upload duplicate primary keys, cypress fix ([2f79487](2f79487aea))

### Features

* full format deletion, closes [#2](#2) ([8dc40bd](8dc40bdd4e))
2023-07-25 15:14:52 +00:00
allan b036cc2abe Merge pull request 'adding PDF of non commercial licence to repo' (#10) from development into main
Release / release (push) Successful in 10m14s Details
Reviewed-on: #10
2023-07-25 14:41:56 +00:00
allan 218b15918d Merge branch 'main' into development
Test / Build-and-test-development-latest-adapter (push) Has been cancelled Details
Test / Build-and-test-development (push) Has been cancelled Details
Build / Build-and-ng-test (pull_request) Has been cancelled Details
2023-07-25 14:41:44 +00:00
Allan 19489a68a7 chore: merge commit
Test / Build-and-test-development-latest-adapter (push) Has been cancelled Details
Test / Build-and-test-development (push) Has been cancelled Details
Build / Build-and-ng-test (pull_request) Has been cancelled Details
2023-07-25 15:39:55 +01:00
Allan 7d7061a78a chore: adding pdf version of the non commercial licence 2023-07-25 15:38:47 +01:00
allan 1aceb297ba Merge pull request 'Merging Development' (#5) from development into main
Release / release (push) Failing after 1m53s Details
Reviewed-on: #5
2023-07-25 14:19:46 +00:00
allan cdee7480c2 Merge pull request 'CI Integration and Cypress test fixing' (#9) from ci into development
Test / Build-and-test-development (push) Has been cancelled Details
Test / Build-and-test-development-latest-adapter (push) Has been cancelled Details
Build / Build-and-ng-test (pull_request) Successful in 7m38s Details
Reviewed-on: #9
Reviewed-by: allan <allan@4gl.io>
2023-07-25 13:54:21 +00:00
Mihajlo Medjedovic bb2fb235aa chore(git): Merge branch 'ci' of ssh://git.datacontroller.io:29419/dc/dc into ci
Build / Build-and-ng-test (pull_request) Has been cancelled Details
2023-07-25 15:51:47 +02:00
Mihajlo Medjedovic b2f88e203a chore: ci and repo links 2023-07-25 15:45:11 +02:00
allan c2f1af99ef Merge branch 'development' into ci
Build / Build-and-ng-test (pull_request) Successful in 7m32s Details
2023-07-25 13:36:03 +00:00
Mihajlo Medjedovic 00e9dff1d9 chore: removed woodpecker ci
Build / Build-and-ng-test (pull_request) Failing after 1m2s Details
2023-07-25 14:21:32 +02:00
allan 82d74e6fee Merge pull request 'fix: missing mf_existds dependency in bitemporal_dataloader' (#7) from issue4 into development
Test / Build-and-test-development (push) Failing after 4m54s Details
Test / Build-and-test-development-latest-adapter (push) Failing after 4m52s Details
Build / Build-and-ng-test (pull_request) Successful in 7m30s Details
Reviewed-on: #7
2023-07-25 10:06:41 +00:00
allan f1e2770574 Merge branch 'development' into issue4
Build / Build-and-ng-test (pull_request) Successful in 7m32s Details
2023-07-25 10:06:15 +00:00
Allan 5ce1701657 fix: missing mf_existds dependency in bitemporal_dataloader
Build / Build-and-ng-test (pull_request) Successful in 7m28s Details
2023-07-25 11:05:01 +01:00
Mihajlo Medjedovic 2f79487aea fix: release script, excel upload duplicate primary keys, cypress fix 2023-07-25 11:34:58 +02:00
allan fefdfaf17b chore: readme tidyup
Test / Build-and-test-development (push) Has been cancelled Details
Test / Build-and-test-development-latest-adapter (push) Has been cancelled Details
Build / Build-and-ng-test (pull_request) Successful in 7m32s Details
2023-07-25 08:56:07 +00:00
allan c1e6f71430 chore: line spacing
Test / Build-and-test-development (push) Has been cancelled Details
Test / Build-and-test-development-latest-adapter (push) Has been cancelled Details
Build / Build-and-ng-test (pull_request) Has been cancelled Details
2023-07-25 08:52:27 +00:00
allan a8310fa63f Merge pull request 'fix: reducing audit data volumes. Closes #4' (#6) from issue4 into development
Test / Build-and-test-development (push) Failing after 4m55s Details
Test / Build-and-test-development-latest-adapter (push) Failing after 4m48s Details
Build / Build-and-ng-test (pull_request) Successful in 7m34s Details
Reviewed-on: #6
2023-07-25 08:50:32 +00:00
Allan e28ac814b8 chore: readme update
Build / Build-and-ng-test (pull_request) Successful in 7m34s Details
2023-07-25 09:48:49 +01:00
Allan 45fed59a65 chore: readme
Build / Build-and-ng-test (pull_request) Successful in 7m34s Details
2023-07-25 09:47:27 +01:00
Allan 310399112b chore: licence updates 2023-07-25 09:09:57 +01:00
Allan 01238fc8a9 chore: newline in _eula.ts
Build / Build-and-ng-test (pull_request) Successful in 7m27s Details
2023-07-25 09:05:57 +01:00
Allan 365e46c568 chore: postdata comment 2023-07-25 09:05:28 +01:00
Allan 8d336e5b46 chore: updating tests
Build / Build-and-ng-test (pull_request) Failing after 1m0s Details
2023-07-21 14:23:22 +01:00
Allan 54fe7013b1 fix: reducing audit data volumes. Closes #4
Build / Build-and-ng-test (pull_request) Failing after 1m12s Details
2023-07-20 21:29:34 +01:00
Allan 8dc40bdd4e feat: full format deletion, closes #2
Test / Build-and-test-development (push) Failing after 6m16s Details
Test / Build-and-test-development-latest-adapter (push) Failing after 6m22s Details
Build / Build-and-ng-test (pull_request) Failing after 1m12s Details
2023-07-20 16:12:59 +01:00
Mihajlo Medjedovic d88ab8bf58 ci: removed legacy-peer-deps flag
Test / Build-and-test-development (push) Failing after 6m27s Details
Test / Build-and-test-development-latest-adapter (push) Failing after 6m25s Details
2023-07-14 12:27:33 +02:00
Mihajlo Medjedovic 67030ab033 chore: contributing
Test / Build-and-test-development (push) Has started running Details
Test / Build-and-test-development-latest-adapter (push) Has been cancelled Details
2023-07-13 14:11:50 +02:00
45 changed files with 9889 additions and 4228 deletions

View File

@ -24,7 +24,7 @@ jobs:
- run: export CHROME_BIN=/usr/bin/google-chrome
- run: npm run lint:check
# Install dependencies~
- run: npm ci --legacy-peer-deps
- run: npm ci
# Audit should fail and stop the CI if critical vulnerability found
- run: npm audit --audit-level=critical
- run: |

View File

@ -16,10 +16,9 @@ jobs:
node-version: 18
- name: Write .npmrc file
run: echo "$NPMRC" > client/.npmrc
shell: bash
env:
NPMRC: ${{ secrets.NPMRC}}
run: |
touch client/.npmrc
echo '${{ secrets.NPMRC}}' > client/.npmrc
- run: apt-get update
- run: wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
@ -28,18 +27,14 @@ jobs:
- 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 -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
run: echo "$CYPRESS_CREDS" > ./client/cypress.env.json
shell: bash
env:
CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }}
# - run: mkdir -p ~/.ssh
# - run: chmod 700 ~/.ssh
- run: npm ci --legacy-peer-deps
- run: npm ci
# Install pm2 and prepare SASJS server
- run: npm i -g pm2
- run: curl -L https://github.com/sasjs/server/releases/latest/download/linux.zip > linux.zip
@ -49,44 +44,38 @@ jobs:
- run: echo NODE_PATH=node >> .env
- run: echo CORS=enable >> .env
- run: echo WHITELIST=http://localhost:4200 >> .env
# - run: echo "SERVER_URL=$server_url" >> .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: cat .env
- run: pm2 start api-linux --wait-ready
- name: Deploy mocked services
run: |
cd ./sas/mocks/sasjs
npm install --legacy-peer-deps -g @sasjs/cli
npm install --legacy-peer-deps -g replace-in-files-cli
npm install -g @sasjs/cli
npm install -g replace-in-files-cli
sasjs cbd -t server-ci
# sasjs request services/admin/makedata -t server-ci -d ./deploy/makeData4GL.json -c ./deploy/requestConfig.json -o ./output.json
- name: Prepare frontend
- name: Install ZIP
run: |
apt-get update
apt-get install zip
- name: Prepare and run frontend and cypress
run: |
cd ./client
# 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='"password".*' --replacement='"password":"'$cypress_pwd_sasjs'" ' ./cypress.env.json
mv ./cypress.env.example.json ./cypress.env.json
replace-in-files --regex='"username".*' --replacement='"username":"'${{ secrets.CYPRESS_USERNAME_SASJS }}'",' ./cypress.env.json
replace-in-files --regex='"password".*' --replacement='"password":"'${{ secrets.CYPRESS_PWD_SASJS }}'" ' ./cypress.env.json
cat ./cypress.env.json
npm run postinstall
# Prepare index.html to SASJS local
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='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='"appLocation".*' --replacement='appLocation:"/dcviya/development/newadapter",' ./cypress.config.ts
cat ./cypress.config.ts
# 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"
Build-and-test-development-latest-adapter:
runs-on: ubuntu-latest
@ -110,18 +99,14 @@ jobs:
- 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 -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
run: echo "$CYPRESS_CREDS" > ./client/cypress.env.json
shell: bash
env:
CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }}
# - run: mkdir -p ~/.ssh
# - run: chmod 700 ~/.ssh
- run: npm ci --legacy-peer-deps
- run: npm ci
# Install pm2 and prepare SASJS server
- run: npm i -g pm2
- run: curl -L https://github.com/sasjs/server/releases/latest/download/linux.zip > linux.zip
@ -131,42 +116,36 @@ jobs:
- run: echo NODE_PATH=node >> .env
- run: echo CORS=enable >> .env
- run: echo WHITELIST=http://localhost:4200 >> .env
# - run: echo "SERVER_URL=$server_url" >> .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: cat .env
- run: pm2 start api-linux --wait-ready
- name: Deploy mocked services
run: |
cd ./sas/mocks/sasjs
npm install --legacy-peer-deps -g @sasjs/cli
npm install --legacy-peer-deps -g replace-in-files-cli
npm install -g @sasjs/cli
npm install -g replace-in-files-cli
sasjs cbd -t server-ci
# sasjs request services/admin/makedata -t server-ci -d ./deploy/makeData4GL.json -c ./deploy/requestConfig.json -o ./output.json
- name: Prepare frontend
- name: Install ZIP
run: |
apt-get update
apt-get install zip
- name: Prepare and run frontend and cypress
run: |
cd ./client
# 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='"password".*' --replacement='"password":"'$cypress_pwd_sasjs'" ' ./cypress.env.json
mv ./cypress.env.example.json ./cypress.env.json
replace-in-files --regex='"username".*' --replacement='"username":"'${{ secrets.CYPRESS_USERNAME_SASJS }}'",' ./cypress.env.json
replace-in-files --regex='"password".*' --replacement='"password":"'${{ secrets.CYPRESS_PWD_SASJS }}'" ' ./cypress.env.json
cat ./cypress.env.json
npm run postinstall
npm install --legacy-peer-deps @sasjs/adapter@latest
npm install @sasjs/adapter@latest
# Prepare index.html to SASJS local
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='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='"appLocation".*' --replacement='appLocation:"/dcviya/development/newadapter",' ./cypress.config.ts
cat ./cypress.config.ts
# 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"

View File

@ -22,29 +22,92 @@ jobs:
env:
NPMRC: ${{ secrets.NPMRC}}
- name: Install ZIP
- name: Install ZIP and SASjs CLI
run: |
apt-get update
apt-get install zip
apt-get install zip -y
npm i -g @sasjs/cli
# test
- name: release-build
- name: Install JQ for parsing JSON
run: |
apt-get update
apt-get install jq -y
- name: Install Semantic Release and plugins and create Release
run: |
npm i
npm i -g semantic-release
GITEA_TOKEN=${{ secrets.RELEASE_TOKEN }} GITEA_URL=https://git.datacontroller.io semantic-release
- name: Frontend Build
run: |
cd client
npm ci
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: |
npm i
npm i -g semantic-release
cd sas
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: |
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
run: |
cd client
npm run typedoc
# deploy docs
# deploy docs
- 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'`
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": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
[
"@semantic-release/git",
{
"assets": [
"CHANGELOG.md"
]
}
"@semantic-release/git",
{
"assets": [
"CHANGELOG.md"
]
}
],
["@saithodev/semantic-release-gitea", {
"giteaUrl": "https://git.datacontroller.io",
"assets": [
{"path": "client/dist.zip"}
]
}]
[
"@saithodev/semantic-release-gitea",
{
"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,3 +1,17 @@
# [6.1.0](https://git.datacontroller.io/dc/dc/compare/v6.0.0...v6.1.0) (2023-07-25)
### Bug Fixes
* missing mf_existds dependency in bitemporal_dataloader ([5ce1701](https://git.datacontroller.io/dc/dc/commit/5ce1701657136f2cf792441412230513ff52e7e8))
* reducing audit data volumes. Closes [#4](https://git.datacontroller.io/dc/dc/issues/4) ([54fe701](https://git.datacontroller.io/dc/dc/commit/54fe7013b1a25be228eeb2aba3553f6219952de6))
* release script, excel upload duplicate primary keys, cypress fix ([2f79487](https://git.datacontroller.io/dc/dc/commit/2f79487aeaf6268b027a8fa52fcdaae2b449de3d))
### Features
* full format deletion, closes [#2](https://git.datacontroller.io/dc/dc/issues/2) ([8dc40bd](https://git.datacontroller.io/dc/dc/commit/8dc40bdd4e3a7ad5c1e6582b4130f24bc445eb77))
# Changelog
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.

View File

@ -1,6 +1,9 @@
# Data Controller
# Contributing
## Workflow guidelines
[Wiki Page](https://git.datacontroller.io/dc/dc/wiki/Git-Workflow)
## Dependencies that requires licences
[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
Data Controller is a software distributed by 4GL Apps, a brand owned by Bowe IO Ltd, a UK Limited Company headquarted in 29 Oldfield Rd, Cumbria, registered by companies house under number 08777171, VAT number: 203914240
Data Controller software is distributed by 4GL Apps, a brand owned by Bowe IO Ltd, a UK Limited Company headquarted in 29 Oldfield Rd, Cumbria, registered at Companies House with company number 08777171, VAT number: 203914240
This software is protected by applicable copyright laws, including international treaties, and dual-
licensed depending on whether your use for commercial purposes, meaning intended for or
resulting in commercial advantage or monetary compensation, or not.
This software is protected by applicable copyright laws, including international treaties, and dual-licensed depending on whether your use for commercial purposes, meaning intended for or resulting in commercial advantage or monetary compensation, or not.
If your use is strictly personal or solely for evaluation purposes, meaning for the purposes of testing
the suitability, performance, and usefulness of this software outside the production environment,
you agree to be bound by the terms included in the "licence-non-commercial-datacontroller.md" file.
If your use is strictly personal or solely for evaluation purposes, meaning for the purposes of testing the suitability, performance, and usefulness of this software outside the production environment, you agree to be bound by the terms included in the "licence-non-commercial-datacontroller.md" file available here: https://git.datacontroller.io/dc/dc/src/branch/main/licence-non-commercial-datacontroller.md
Your use of this software for commercial purposes is subject to the terms included in an applicable
license agreement.
Your use of this software for commercial purposes is subject to the terms included in an applicable license agreement.
In any case, you must not make any such use of this software as to develop software which may be
considered competitive with this software.
In any case, you must not make any such use of this software as to develop software which may be considered competitive with this software.
UNLESS EXPRESSLY AGREED OTHERWISE, 4GL APPS PROVIDES THIS SOFTWARE ON AN "AS IS"
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, AND IN NO EVENT AND UNDER NO
LEGAL THEORY, SHALL 4GL APPS BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT,
INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER ARISING FROM
USE OR INABILITY TO USE THIS SOFTWARE.
UNLESS EXPRESSLY AGREED OTHERWISE, 4GL APPS PROVIDES THIS SOFTWARE ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, AND IN NO EVENT AND UNDER NO LEGAL THEORY, SHALL 4GL APPS BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER ARISING FROM USE OR INABILITY TO USE THIS SOFTWARE.

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({
reporter: 'mochawesome',
reporter: "mochawesome",
reporterOptions: {
reportDir: 'cypress/results',
reportDir: "cypress/results",
overwrite: false,
html: true,
json: false,
},
chromeWebSecurity: false,
defaultCommandTimeout: 30000,
env: {
hosturl:"http://localhost:4200",
hosturl: "http://localhost:4200",
appLocation: "",
site_id_SAS9: "70221618",
site_id_SASVIYA: "70253615",
@ -23,6 +26,12 @@ export default defineConfig({
debug: false,
screenshotOnRunFailure: false,
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"

1468
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -88,7 +88,7 @@
"@angular-eslint/template-parser": "16.0.3",
"@angular/cli": "^16.1.0",
"@angular/compiler-cli": "^16.1.2",
"@cypress/webpack-preprocessor": "^5.11.1",
"@cypress/webpack-preprocessor": "^5.17.1",
"@types/core-js": "^2.5.5",
"@types/crypto-js": "^4.0.1",
"@types/es6-shim": "^0.31.39",
@ -99,10 +99,10 @@
"@typescript-eslint/eslint-plugin": "^5.29.0",
"@typescript-eslint/parser": "^5.29.0",
"core-js": "^2.5.4",
"cypress": "^9.5.3",
"cypress": "12.17.1",
"cypress-file-upload": "^5.0.8",
"cypress-plugin-tab": "^1.0.5",
"cypress-real-events": "^1.7.6",
"cypress-real-events": "^1.8.1",
"es6-shim": "^0.35.5",
"eslint": "^8.33.0",
"git-describe": "^4.0.4",

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

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

View File

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

View File

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

View File

@ -28,4 +28,4 @@ INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER ARISING
USE OR INABILITY TO USE THIS SOFTWARE.
`
`

View File

@ -2,13 +2,13 @@
# Data Controller
_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
## 2. License terms and conditions
### 2.1. Grant of license.
## 2. License Terms and Conditions
### 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:
(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.
## 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.
### 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.
## 4. Disclaimer and limitation
### 4.1. Disclaimer of warranty.
## 4. Disclaimer and Limitation
### 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.
### 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.

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 [(\(“Licensor”\) 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 \(“Software”\) 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 \(“Derivative Works”\); 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, “Deliverables”\).)] 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 \(“Commercial Purposes”\) 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 \(“Competitive Purposes”\); 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 “notice” 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 “AS IS” 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": [
{
"ADMIN": "dcgroup",
"ADMIN": "AllUsers",
"DCPATH": "/opt/data/DataController"
}
]
}
}

View File

@ -1,4 +1,4 @@
{
"licenceKey": "CFG2LNF1SYGgRPTwegsgRolmXw0xQmDpbagjRTniIarWXFE+/eClT4m+64U2kL5BBj0p3UwS80+wEer+jaw5w4OX27mDfXAUvkv90k54ER6GP5C5Z/t4VYucQo3Kn8wfhfFOymjRVtKZaHyavl9hBaWGwohX7W+97gsj5pnI9a/63w55OKNJvgeZrCxR04qhRiuf9K1J5EQtKP+wfW/ERoX3R4dPWKANjyFZkURs2GL/UDCGS6nPWTS8grlcinFUhh4V3hIZmzlWc5FG97lrbxnmq4FsdEaey4z4l70H4Yf/52ZZqJzaOyRGeQXmUtpS",
"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"
"licenceKey": "invalid",
"activationKey": "invalid"
}

View File

@ -1,16 +1,6 @@
const path = require('path')
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('/'))
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_AUDIT"
},
{
"MEMNAME": "MPE_ALERTS"
},
{
"MEMNAME": "MPE_VALIDATIONS"
}
]
, "libinfo":

474
sas/package-lock.json generated
View File

@ -6,8 +6,8 @@
"": {
"name": "dc-sas",
"dependencies": {
"@sasjs/cli": "4.3.0",
"@sasjs/core": "^4.46.3"
"@sasjs/cli": "^4.4.2",
"@sasjs/core": "^4.46.4"
}
},
"node_modules/@coolaj86/urequest": {
@ -29,9 +29,9 @@
}
},
"node_modules/@sasjs/adapter": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-4.3.4.tgz",
"integrity": "sha512-hPLNPXqunbIXZY+rsRxwfiLehk8x9a0IG2/96geJqrgP4Xi705X2WqkTaCpJ+Lr9gFGk4PttdprokCl1yoHVNw==",
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-4.5.1.tgz",
"integrity": "sha512-WeKDMfCivBywxDZ6t0jng78ZBPoMk8RIHKFTNDDmvuvmXq5Mr5oqZ0r5lRPB863XkGOeVi6UIEI1+JawZ2TlWQ==",
"hasInstallScript": true,
"dependencies": {
"@sasjs/utils": "2.52.0",
@ -39,7 +39,7 @@
"axios-cookiejar-support": "1.0.1",
"form-data": "4.0.0",
"https": "1.0.0",
"tough-cookie": "4.0.0"
"tough-cookie": "4.1.3"
}
},
"node_modules/@sasjs/adapter/node_modules/@sasjs/utils": {
@ -78,22 +78,22 @@
}
},
"node_modules/@sasjs/cli": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-4.3.0.tgz",
"integrity": "sha512-LDObBUigusiBvq7SzkL49W5zel2YfjcmetdrftEnEdpWeVkVYYby77gJiW9iXdhrdVT+Hgrdubs6tA+RT0LskA==",
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-4.4.2.tgz",
"integrity": "sha512-o1Qp+L7vJOH9dbsEPJK6GaQR7yWW+W0BAI1rrD55+Ij3USMCcdWcRJAOvFxwS8Gflq5BuNrVqa39rg4RK0ZVEQ==",
"hasInstallScript": true,
"dependencies": {
"@sasjs/adapter": "4.3.4",
"@sasjs/core": "4.45.4",
"@sasjs/lint": "2.3.0",
"@sasjs/adapter": "4.5.1",
"@sasjs/core": "4.46.3",
"@sasjs/lint": "2.3.1",
"@sasjs/utils": "3.3.0",
"adm-zip": "0.5.9",
"chalk": "4.1.2",
"dotenv": "10.0.0",
"dotenv": "16.0.3",
"esm": "3.2.25",
"find": "0.3.0",
"js-base64": "3.7.2",
"jsdom": "21.1.0",
"jsdom": "22.1.0",
"jwt-decode": "3.1.2",
"lodash.groupby": "4.6.0",
"lodash.uniqby": "4.7.0",
@ -111,19 +111,19 @@
}
},
"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",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.46.3.tgz",
"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": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@sasjs/lint/-/lint-2.3.0.tgz",
"integrity": "sha512-ac3l4RUf3+87tmi2VCebibxQNIdiyzJuRGErka/BKXu+F4pBhUG1Y8t0lNISLDk6fm5uJXHpYrR5zck8J5tCtQ==",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@sasjs/lint/-/lint-2.3.1.tgz",
"integrity": "sha512-vvoMXAicuewxyiBmF9VkMziQsAA6+MSKMnmcmAfnbR5cvBVSDYDh6X7sAeWXyjNfWxuBweyP9nv6//FRiWaRMQ==",
"hasInstallScript": true,
"dependencies": {
"@sasjs/utils": "2.52.0",
@ -254,34 +254,6 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"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": {
"version": "0.5.9",
"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",
"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": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
"integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz",
"integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==",
"dependencies": {
"cssom": "~0.3.6"
"rrweb-cssom": "^0.6.0"
},
"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": {
"version": "5.6.5",
"resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.6.5.tgz",
"integrity": "sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A=="
},
"node_modules/data-urls": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz",
"integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz",
"integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==",
"dependencies": {
"abab": "^2.0.6",
"whatwg-mimetype": "^3.0.0",
"whatwg-url": "^11.0.0"
"whatwg-url": "^12.0.0"
},
"engines": {
"node": ">=12"
"node": ">=14"
}
},
"node_modules/debug": {
@ -647,11 +609,11 @@
}
},
"node_modules/dotenv": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
"integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==",
"version": "16.0.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
"integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==",
"engines": {
"node": ">=10"
"node": ">=12"
}
},
"node_modules/emoji-regex": {
@ -678,26 +640,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": {
"version": "3.2.25",
"resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
@ -706,34 +648,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": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
@ -1036,26 +950,23 @@
"integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ=="
},
"node_modules/jsdom": {
"version": "21.1.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.1.0.tgz",
"integrity": "sha512-m0lzlP7qOtthD918nenK3hdItSd2I+V3W9IrBcB36sqDwG+KnUs66IF5GY7laGWUnlM9vTsD0W1QwSEBYWWcJg==",
"version": "22.1.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz",
"integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==",
"dependencies": {
"abab": "^2.0.6",
"acorn": "^8.8.1",
"acorn-globals": "^7.0.0",
"cssom": "^0.5.0",
"cssstyle": "^2.3.0",
"data-urls": "^3.0.2",
"decimal.js": "^10.4.2",
"cssstyle": "^3.0.0",
"data-urls": "^4.0.0",
"decimal.js": "^10.4.3",
"domexception": "^4.0.0",
"escodegen": "^2.0.0",
"form-data": "^4.0.0",
"html-encoding-sniffer": "^3.0.0",
"http-proxy-agent": "^5.0.0",
"https-proxy-agent": "^5.0.1",
"is-potential-custom-element-name": "^1.0.1",
"nwsapi": "^2.2.2",
"parse5": "^7.1.1",
"nwsapi": "^2.2.4",
"parse5": "^7.1.2",
"rrweb-cssom": "^0.6.0",
"saxes": "^6.0.0",
"symbol-tree": "^3.2.4",
"tough-cookie": "^4.1.2",
@ -1063,12 +974,12 @@
"webidl-conversions": "^7.0.0",
"whatwg-encoding": "^2.0.0",
"whatwg-mimetype": "^3.0.0",
"whatwg-url": "^11.0.0",
"ws": "^8.11.0",
"whatwg-url": "^12.0.1",
"ws": "^8.13.0",
"xml-name-validator": "^4.0.0"
},
"engines": {
"node": ">=14"
"node": ">=16"
},
"peerDependencies": {
"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": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
@ -1258,9 +1147,9 @@
}
},
"node_modules/nwsapi": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.6.tgz",
"integrity": "sha512-vSZ4miHQ4FojLjmz2+ux4B0/XA16jfwt/LBzIUftDpRd8tujHFkXjMyLwjS08fIZCzesj2z7gJukOKJwqebJAQ=="
"version": "2.2.7",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz",
"integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ=="
},
"node_modules/once": {
"version": "1.4.0",
@ -1492,6 +1381,11 @@
"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": {
"version": "5.2.1",
"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",
"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": {
"version": "1.3.1",
"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=="
},
"node_modules/tough-cookie": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz",
"integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==",
"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.1.2"
"universalify": "^0.2.0",
"url-parse": "^1.5.3"
},
"engines": {
"node": ">=6"
}
},
"node_modules/tough-cookie/node_modules/universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"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/tr46": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
"integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz",
"integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==",
"dependencies": {
"punycode": "^2.1.1"
"punycode": "^2.3.0"
},
"engines": {
"node": ">=12"
"node": ">=14"
}
},
"node_modules/traverse-chain": {
@ -1745,15 +1631,15 @@
}
},
"node_modules/whatwg-url": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
"integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
"version": "12.0.1",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz",
"integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==",
"dependencies": {
"tr46": "^3.0.0",
"tr46": "^4.1.1",
"webidl-conversions": "^7.0.0"
},
"engines": {
"node": ">=12"
"node": ">=14"
}
},
"node_modules/wrap-ansi": {
@ -1869,16 +1755,16 @@
}
},
"@sasjs/adapter": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-4.3.4.tgz",
"integrity": "sha512-hPLNPXqunbIXZY+rsRxwfiLehk8x9a0IG2/96geJqrgP4Xi705X2WqkTaCpJ+Lr9gFGk4PttdprokCl1yoHVNw==",
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-4.5.1.tgz",
"integrity": "sha512-WeKDMfCivBywxDZ6t0jng78ZBPoMk8RIHKFTNDDmvuvmXq5Mr5oqZ0r5lRPB863XkGOeVi6UIEI1+JawZ2TlWQ==",
"requires": {
"@sasjs/utils": "2.52.0",
"axios": "0.27.2",
"axios-cookiejar-support": "1.0.1",
"form-data": "4.0.0",
"https": "1.0.0",
"tough-cookie": "4.0.0"
"tough-cookie": "4.1.3"
},
"dependencies": {
"@sasjs/utils": {
@ -1912,21 +1798,21 @@
}
},
"@sasjs/cli": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-4.3.0.tgz",
"integrity": "sha512-LDObBUigusiBvq7SzkL49W5zel2YfjcmetdrftEnEdpWeVkVYYby77gJiW9iXdhrdVT+Hgrdubs6tA+RT0LskA==",
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-4.4.2.tgz",
"integrity": "sha512-o1Qp+L7vJOH9dbsEPJK6GaQR7yWW+W0BAI1rrD55+Ij3USMCcdWcRJAOvFxwS8Gflq5BuNrVqa39rg4RK0ZVEQ==",
"requires": {
"@sasjs/adapter": "4.3.4",
"@sasjs/core": "4.45.4",
"@sasjs/lint": "2.3.0",
"@sasjs/adapter": "4.5.1",
"@sasjs/core": "4.46.3",
"@sasjs/lint": "2.3.1",
"@sasjs/utils": "3.3.0",
"adm-zip": "0.5.9",
"chalk": "4.1.2",
"dotenv": "10.0.0",
"dotenv": "16.0.3",
"esm": "3.2.25",
"find": "0.3.0",
"js-base64": "3.7.2",
"jsdom": "21.1.0",
"jsdom": "22.1.0",
"jwt-decode": "3.1.2",
"lodash.groupby": "4.6.0",
"lodash.uniqby": "4.7.0",
@ -1941,21 +1827,21 @@
},
"dependencies": {
"@sasjs/core": {
"version": "4.45.4",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.45.4.tgz",
"integrity": "sha512-zf0foZHXNUb7hTHQAbGcQbHhiwX7qNK9g3PTT5XkjR8aDgcYeBpSxu1bEj7GTVvOmv49YF/3xrVZNh5J+38+DA=="
"version": "4.46.3",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.46.3.tgz",
"integrity": "sha512-Grwydm5GxBsYk238PZw41XPjXVVQ9vWcvfZ06L2P0bQbvK0sGn7l69JA7H5MGr3QcaLpiD4Kg70cAh7PgE+JOw=="
}
}
},
"@sasjs/core": {
"version": "4.46.3",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.46.3.tgz",
"integrity": "sha512-Grwydm5GxBsYk238PZw41XPjXVVQ9vWcvfZ06L2P0bQbvK0sGn7l69JA7H5MGr3QcaLpiD4Kg70cAh7PgE+JOw=="
"version": "4.46.4",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.46.4.tgz",
"integrity": "sha512-Q4UiOEYEHWCYn4ak+2BaKnrusLauyvKK/Hq4Y4RwJOfwA2MSjOzJSV8fDpbhnY1Dyubbk4SChA6yAL8lc0hn1Q=="
},
"@sasjs/lint": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@sasjs/lint/-/lint-2.3.0.tgz",
"integrity": "sha512-ac3l4RUf3+87tmi2VCebibxQNIdiyzJuRGErka/BKXu+F4pBhUG1Y8t0lNISLDk6fm5uJXHpYrR5zck8J5tCtQ==",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@sasjs/lint/-/lint-2.3.1.tgz",
"integrity": "sha512-vvoMXAicuewxyiBmF9VkMziQsAA6+MSKMnmcmAfnbR5cvBVSDYDh6X7sAeWXyjNfWxuBweyP9nv6//FRiWaRMQ==",
"requires": {
"@sasjs/utils": "2.52.0",
"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": {
"version": "0.5.9",
"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",
"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": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
"integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz",
"integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==",
"requires": {
"cssom": "~0.3.6"
},
"dependencies": {
"cssom": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="
}
"rrweb-cssom": "^0.6.0"
}
},
"csv-stringify": {
@ -2306,13 +2161,13 @@
"integrity": "sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A=="
},
"data-urls": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz",
"integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz",
"integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==",
"requires": {
"abab": "^2.0.6",
"whatwg-mimetype": "^3.0.0",
"whatwg-url": "^11.0.0"
"whatwg-url": "^12.0.0"
}
},
"debug": {
@ -2350,9 +2205,9 @@
}
},
"dotenv": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
"integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q=="
"version": "16.0.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
"integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ=="
},
"emoji-regex": {
"version": "8.0.0",
@ -2369,37 +2224,11 @@
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"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": {
"version": "3.2.25",
"resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
"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": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
@ -2605,26 +2434,23 @@
"integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ=="
},
"jsdom": {
"version": "21.1.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.1.0.tgz",
"integrity": "sha512-m0lzlP7qOtthD918nenK3hdItSd2I+V3W9IrBcB36sqDwG+KnUs66IF5GY7laGWUnlM9vTsD0W1QwSEBYWWcJg==",
"version": "22.1.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz",
"integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==",
"requires": {
"abab": "^2.0.6",
"acorn": "^8.8.1",
"acorn-globals": "^7.0.0",
"cssom": "^0.5.0",
"cssstyle": "^2.3.0",
"data-urls": "^3.0.2",
"decimal.js": "^10.4.2",
"cssstyle": "^3.0.0",
"data-urls": "^4.0.0",
"decimal.js": "^10.4.3",
"domexception": "^4.0.0",
"escodegen": "^2.0.0",
"form-data": "^4.0.0",
"html-encoding-sniffer": "^3.0.0",
"http-proxy-agent": "^5.0.0",
"https-proxy-agent": "^5.0.1",
"is-potential-custom-element-name": "^1.0.1",
"nwsapi": "^2.2.2",
"parse5": "^7.1.1",
"nwsapi": "^2.2.4",
"parse5": "^7.1.2",
"rrweb-cssom": "^0.6.0",
"saxes": "^6.0.0",
"symbol-tree": "^3.2.4",
"tough-cookie": "^4.1.2",
@ -2632,27 +2458,9 @@
"webidl-conversions": "^7.0.0",
"whatwg-encoding": "^2.0.0",
"whatwg-mimetype": "^3.0.0",
"whatwg-url": "^11.0.0",
"ws": "^8.11.0",
"whatwg-url": "^12.0.1",
"ws": "^8.13.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": {
@ -2774,9 +2582,9 @@
}
},
"nwsapi": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.6.tgz",
"integrity": "sha512-vSZ4miHQ4FojLjmz2+ux4B0/XA16jfwt/LBzIUftDpRd8tujHFkXjMyLwjS08fIZCzesj2z7gJukOKJwqebJAQ=="
"version": "2.2.7",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz",
"integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ=="
},
"once": {
"version": "1.4.0",
@ -2941,6 +2749,11 @@
"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": {
"version": "5.2.1",
"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",
"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": {
"version": "1.3.1",
"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=="
},
"tough-cookie": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz",
"integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==",
"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.1.2"
"universalify": "^0.2.0",
"url-parse": "^1.5.3"
},
"dependencies": {
"universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="
}
}
},
"tr46": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
"integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz",
"integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==",
"requires": {
"punycode": "^2.1.1"
"punycode": "^2.3.0"
}
},
"traverse-chain": {
@ -3131,11 +2939,11 @@
"integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="
},
"whatwg-url": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
"integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
"version": "12.0.1",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz",
"integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==",
"requires": {
"tr46": "^3.0.0",
"tr46": "^4.1.1",
"webidl-conversions": "^7.0.0"
}
},

View File

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

View File

@ -94,6 +94,7 @@ Areas for optimisation
<h4> SAS Macros </h4>
@li bitemporal_closeouts.sas
@li dc_assignlib.sas
@li mf_existds.sas
@li mf_existvar.sas
@li mf_fmtdttm.sas
@li mf_getattrn.sas
@ -1390,9 +1391,11 @@ run;
,processed_dttm=&now
,loadref=%superq(etlsource)
)
data _null_;
/* exclude unchanged values in modified rows */
data 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;
proc append base=&outds_audit data=work.mp_storediffs;
run;

View File

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

View File

@ -299,7 +299,7 @@ run;
%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)
%mp_abort(iftrue= (&syscc ne 0)

View File

@ -3,7 +3,7 @@
@brief testing postdata with mpe_x_test
<h4> SAS Macros </h4>
@li mp_testservice.sas
@li mx_testservice.sas
@li mp_assert.sas
@ -44,10 +44,11 @@ data work.jsdata;
else stop;
run;
%mp_testservice(&appLoc/services/editors/stagedata,
%mx_testservice(&appLoc/services/editors/stagedata,
viyacontext=&defaultcontext,
inputdatasets=work.jsdata work.sascontroltable,
outlib=web1
outlib=web1,
mdebug=&sasjs_mdebug
)
%let status=0;
@ -73,11 +74,12 @@ data work.sascontroltable;
output;
stop;
run;
%mp_testservice(&appLoc/services/auditors/postdata,
%mx_testservice(&appLoc/services/auditors/postdata,
viyacontext=&defaultcontext,
inputdatasets=work.sascontroltable,
outlib=web2,
outref=wbout
outref=wbout,
mdebug=&sasjs_mdebug
)
%let leadcheck=0;
@ -86,7 +88,7 @@ data _null_;
infile wbout;
input;
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);
/* 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);

View File

@ -7,7 +7,9 @@
@li mp_assertcolvals.sas
@li mp_assertdsobs.sas
@li mf_getuniquefileref.sas
@li mf_getuniquename.sas
@li mp_filterstore.sas
@li mx_testservice.sas
**/
@ -23,10 +25,11 @@ data _null_;
put 'LIBDS:$43.';
put "&dclib..MPE_TABLES";
run;
%mp_testservice(&_program,
%mx_testservice(&_program,
viyacontext=&defaultcontext,
inputfiles=&f1:sascontroltable,
outlib=web1
outlib=web1,
mdebug=&sasjs_mdebug
)
data _null_;
infile "%sysfunc(pathname(WEB1))" lrecl=32767;
@ -44,8 +47,6 @@ data dqrules;
set web1.dqrules;
data dqdata;
set web1.dqdata;
data spec;
set web1.spec;
data cols;
set web1.cols;
data maxvarlengths;
@ -105,7 +106,7 @@ data _null_;
put "&dclib..MPE_TABLES,&filter_rk";
run;
%mp_testservice(&_program,
%mx_testservice(&_program,
viyacontext=&defaultcontext,
inputfiles=&f2:sascontroltable,
outlib=web2
@ -133,13 +134,31 @@ run;
* 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;
infile datalines4 dsd;
input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
OPERATOR_NM:$10. RAW_VALUE:$4000.;
datalines4;
AND,AND,1,FMTNAME,CONTAINS,"'MOR'"
AND,AND,1,FMTNAME,CONTAINS,"'&fmtname'"
;;;;
run;
%mp_filterstore(
@ -164,14 +183,16 @@ data _null_;
put "DCTEST.DCFMTS-FC,&filter_rk3";
run;
%mp_testservice(&_program,
%mx_testservice(&_program,
viyacontext=&defaultcontext,
inputfiles=&f3:sascontroltable,
outlib=web3
outlib=web3,
mdebug=&sasjs_mdebug
)
data sasdata3;
set web3.sasdata;
putlog (_all_)(=);
data query3;
set web3.query;
run;
@ -183,6 +204,6 @@ run;
)
%mp_assertdsobs(work.sasdata3,
desc=Test3 - format data has rows,
test=ATLEAST 5,
test=EQUALS 10,
outds=work.test_results
)

View File

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

View File

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

View File

@ -28,7 +28,7 @@ run;
data work.libinfo;
set web1.libinfo;
putlog (_all_)(=);
call symputx('libref',libref);
call symputx('libref',libname);
run;
%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;
/* filter for one record */
/* create filter */
%let f1=%mf_getuniquefileref();
data _null_;
file &f1 termstr=crlf;

View File

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