Compare commits
1 Commits
v7.2.1
...
fix-nldatm
Author | SHA1 | Date | |
---|---|---|---|
f246e75eab |
@@ -1,23 +1,11 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
# Using `--silent` helps for showing any errs in the first line of the response
|
# Avoid commits to the master branch
|
||||||
# The first line is picked up by the VS Code GIT UI popup when rc is not 0
|
BRANCH=`git rev-parse --abbrev-ref HEAD`
|
||||||
|
REGEX="^(master|development)$"
|
||||||
|
|
||||||
if npm run --silent lint:check:silent ; then
|
if [[ "$BRANCH" =~ $REGEX ]]; then
|
||||||
exit 0
|
echo "You are on branch $BRANCH. Are you sure you want to commit to this branch?"
|
||||||
else
|
echo "If so, commit with -n to bypass the pre-commit hook."
|
||||||
npm run --silent lint:fix:silent
|
|
||||||
echo "❌ Prettier check failed! We ran lint:fix for you. Please add & commit again."
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
## Avoid large commits
|
|
||||||
# https://www.backblaze.com/blog/how-many-bytes-are-in-a-megabyte-really/
|
|
||||||
size_limit=$((2 * 2**20)) # 2mbs
|
|
||||||
# https://git-scm.com/docs/git-rev-list#Documentation/git-rev-list.txt---disk-usage
|
|
||||||
commit_size=$(git rev-list --disk-usage HEAD^..HEAD)
|
|
||||||
test "$commit_size" -lt "$size_limit" || (
|
|
||||||
echo "Commit size is too large: $commit_size > $size_limit"
|
|
||||||
echo "Force commit using --no-verify"
|
|
||||||
exit 1
|
|
||||||
)
|
|
@@ -10,7 +10,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20.15.1
|
node-version: 20.14.0
|
||||||
|
|
||||||
- name: Install Google Chrome
|
- name: Install Google Chrome
|
||||||
run: |
|
run: |
|
||||||
@@ -58,7 +58,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20.15.1
|
node-version: 20.14.0
|
||||||
|
|
||||||
- name: Write .npmrc file
|
- name: Write .npmrc file
|
||||||
run: |
|
run: |
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
- run: apt install -y ./google-chrome*.deb;
|
- run: apt install -y ./google-chrome*.deb;
|
||||||
- run: export CHROME_BIN=/usr/bin/google-chrome
|
- run: export CHROME_BIN=/usr/bin/google-chrome
|
||||||
- run: apt-get update -y
|
- run: apt-get update -y
|
||||||
- run: apt-get -y install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2t64 libxtst6 xauth xvfb
|
- run: apt-get -y install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb
|
||||||
- run: apt -y install jq
|
- run: apt -y install jq
|
||||||
|
|
||||||
- name: Write cypress credentials
|
- name: Write cypress credentials
|
||||||
@@ -126,7 +126,7 @@ jobs:
|
|||||||
replace-in-files --regex='"hosturl".*' --replacement='hosturl:"http://localhost:4200",' ./cypress.config.ts
|
replace-in-files --regex='"hosturl".*' --replacement='hosturl:"http://localhost:4200",' ./cypress.config.ts
|
||||||
cat ./cypress.config.ts
|
cat ./cypress.config.ts
|
||||||
# Start frontend and run cypress
|
# Start frontend and run cypress
|
||||||
npx ng serve --host 0.0.0.0 --port 4200 & 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-multi-load.cy.ts,cypress/e2e/excel.cy.ts,cypress/e2e/csv.cy.ts,cypress/e2e/filtering.cy.ts,cypress/e2e/licensing.cy.ts"
|
npm start & npx wait-on http://localhost:4200 && npx cypress run --browser chrome --spec "cypress/e2e/liveness.cy.ts,cypress/e2e/editor.cy.ts,cypress/e2e/excel-multi-load.cy.ts,cypress/e2e/excel.cy.ts,cypress/e2e/csv.cy.ts,cypress/e2e/filtering.cy.ts,cypress/e2e/licensing.cy.ts"
|
||||||
|
|
||||||
- name: Zip Cypress videos
|
- name: Zip Cypress videos
|
||||||
if: always()
|
if: always()
|
||||||
|
@@ -1,101 +0,0 @@
|
|||||||
name: Lighthouse Checks
|
|
||||||
run-name: Running Lighthouse Performance and Accessibility Checks on Pull Request
|
|
||||||
on: [pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lighthouse:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [20.15.1]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
|
|
||||||
- name: Install Google Chrome
|
|
||||||
run: |
|
|
||||||
wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
|
|
||||||
echo "deb http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y google-chrome-stable xvfb
|
|
||||||
|
|
||||||
- name: Install pm2 for process management
|
|
||||||
run: npm i -g pm2
|
|
||||||
|
|
||||||
- name: Install @sasjs/cli
|
|
||||||
run: npm i -g @sasjs/cli
|
|
||||||
|
|
||||||
- name: Install wait-on globally
|
|
||||||
run: npm install -g wait-on
|
|
||||||
|
|
||||||
- name: Create .env file for sasjs/server
|
|
||||||
run: |
|
|
||||||
touch .env
|
|
||||||
echo RUN_TIMES=js >> .env
|
|
||||||
echo NODE_PATH=node >> .env
|
|
||||||
echo CORS=enable >> .env
|
|
||||||
echo WHITELIST=http://localhost:4200 >> .env
|
|
||||||
cat .env
|
|
||||||
|
|
||||||
- name: Download sasjs/server package from github using curl
|
|
||||||
run: curl -L https://github.com/sasjs/server/releases/latest/download/linux.zip > linux.zip
|
|
||||||
|
|
||||||
- name: Unzip downloaded package
|
|
||||||
run: unzip linux.zip
|
|
||||||
|
|
||||||
- name: Run sasjs server
|
|
||||||
run: pm2 start api-linux --wait-ready
|
|
||||||
|
|
||||||
- name: Write .npmrc file
|
|
||||||
run: echo "$NPMRC" > client/.npmrc
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
NPMRC: ${{ secrets.NPMRC}}
|
|
||||||
|
|
||||||
- name: Install npm dependencies
|
|
||||||
run: |
|
|
||||||
cd client
|
|
||||||
# Decrypt and Install sheet
|
|
||||||
echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg
|
|
||||||
npm ci
|
|
||||||
npm install -g replace-in-files-cli
|
|
||||||
|
|
||||||
- name: Update appLoc in index.html
|
|
||||||
run: |
|
|
||||||
cd client
|
|
||||||
replace-in-files --regex='appLoc=".*?"' --replacement='appLoc="/proj/sasjs/genesis-mocks"' ./src/index.html
|
|
||||||
|
|
||||||
- name: Build Frontend
|
|
||||||
run: |
|
|
||||||
cd client
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
- name: Deploy JS mocked services and frontend to the local SASjs Server instance
|
|
||||||
run: |
|
|
||||||
cd sas/mocks
|
|
||||||
npm ci
|
|
||||||
sasjs cbd -t server-ci
|
|
||||||
|
|
||||||
- name: Start frontend server
|
|
||||||
run: |
|
|
||||||
cd client
|
|
||||||
npx ng serve --host 0.0.0.0 --port 4200 &
|
|
||||||
wait-on http://localhost:4200
|
|
||||||
|
|
||||||
- name: Run Lighthouse CI
|
|
||||||
run: |
|
|
||||||
cd client
|
|
||||||
npx lhci autorun
|
|
||||||
|
|
||||||
- name: Lighthouse Result Artifacts
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: Lighthouse results
|
|
||||||
path: client/lighthouse-reports
|
|
||||||
include-hidden-files: true
|
|
@@ -80,7 +80,7 @@ jobs:
|
|||||||
- run: apt install -y ./google-chrome*.deb;
|
- run: apt install -y ./google-chrome*.deb;
|
||||||
- run: export CHROME_BIN=/usr/bin/google-chrome
|
- run: export CHROME_BIN=/usr/bin/google-chrome
|
||||||
- run: apt-get update -y
|
- run: apt-get update -y
|
||||||
- run: apt-get -y install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2t64 libxtst6 xauth xvfb
|
- run: apt-get -y install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb
|
||||||
- run: apt -y install jq
|
- run: apt -y install jq
|
||||||
|
|
||||||
- name: Write cypress credentials
|
- name: Write cypress credentials
|
||||||
@@ -136,7 +136,7 @@ jobs:
|
|||||||
replace-in-files --regex='"hosturl".*' --replacement='hosturl:"http://localhost:4200",' ./cypress.config.ts
|
replace-in-files --regex='"hosturl".*' --replacement='hosturl:"http://localhost:4200",' ./cypress.config.ts
|
||||||
cat ./cypress.config.ts
|
cat ./cypress.config.ts
|
||||||
# Start frontend and run cypress
|
# Start frontend and run cypress
|
||||||
npx ng serve --host 0.0.0.0 --port 4200 & 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-multi-load.cy.ts,cypress/e2e/excel.cy.ts,cypress/e2e/csv.cy.ts,cypress/e2e/filtering.cy.ts,cypress/e2e/licensing.cy.ts"
|
npm start & npx wait-on http://localhost:4200 && npx cypress run --browser chrome --spec "cypress/e2e/liveness.cy.ts,cypress/e2e/editor.cy.ts,cypress/e2e/excel-multi-load.cy.ts,cypress/e2e/excel.cy.ts,cypress/e2e/csv.cy.ts,cypress/e2e/filtering.cy.ts,cypress/e2e/licensing.cy.ts"
|
||||||
|
|
||||||
- name: Zip Cypress videos
|
- name: Zip Cypress videos
|
||||||
if: always()
|
if: always()
|
||||||
@@ -237,20 +237,20 @@ jobs:
|
|||||||
cd sas
|
cd sas
|
||||||
sasjs c -t viya
|
sasjs c -t viya
|
||||||
rm -rf sasjsbuild/tests
|
rm -rf sasjsbuild/tests
|
||||||
sed -i -e 's/servertype="SASJS"/servertype="SASVIYA"/g' sasjsbuild/services/DC.html
|
sed -i -e 's/servertype="SASJS"/servertype="SASVIYA"/g' sasjsbuild/services/clickme.html
|
||||||
sasjs b -t viya
|
sasjs b -t viya
|
||||||
cp sasjsbuild/viya.sas ./viya.sas
|
cp sasjsbuild/viya.sas ./demostream_viya.sas
|
||||||
# compile Viya Full deploy (without web)
|
# compile Viya Full deploy (without web)
|
||||||
rm -rf sasjsbuild/services/web
|
rm -rf sasjsbuild/services/web
|
||||||
rm sasjsbuild/services/DC.html
|
rm sasjsbuild/services/clickme.html
|
||||||
sasjs b -t viya
|
sasjs b -t viya
|
||||||
cp sasjsbuild/viya.sas ./viya_noweb.sas
|
cp sasjsbuild/viya.sas ./viya.sas
|
||||||
cp sasjsbuild/viya.json ./viya_noweb.json
|
cp sasjsbuild/viya.json ./viya.json
|
||||||
|
|
||||||
- name: Zip Frontend (including viya.json for full viya deploy)
|
- name: Zip Frontend (including viya.json for full viya deploy)
|
||||||
run: |
|
run: |
|
||||||
cd sas
|
cd sas
|
||||||
cp sasjsbuild/viya.json ../client/dist/viya.json
|
cp sasjsbuild/viya.json ../client/dist
|
||||||
cd ..
|
cd ..
|
||||||
zip -r frontend.zip ./client/dist
|
zip -r frontend.zip ./client/dist
|
||||||
|
|
||||||
@@ -277,8 +277,8 @@ jobs:
|
|||||||
URL="https://git.datacontroller.io/api/v1/repos/dc/dc/releases/$RELEASE_ID/assets?access_token=${{ secrets.RELEASE_TOKEN }}"
|
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=@frontend.zip
|
||||||
curl -k $URL -F attachment=@sas/demostream_sas9.sas
|
curl -k $URL -F attachment=@sas/demostream_sas9.sas
|
||||||
curl -k $URL -F attachment=@sas/viya.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/sasjs_server.json.zip
|
||||||
curl -k $URL -F attachment=@sas/sas9.sas
|
curl -k $URL -F attachment=@sas/sas9.sas
|
||||||
curl -k $URL -F attachment=@sas/viya_noweb.sas
|
curl -k $URL -F attachment=@sas/viya.sas
|
||||||
curl -k $URL -F attachment=@sas/viya_noweb.json
|
curl -k $URL -F attachment=@sas/viya.json
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -21,4 +21,3 @@ sasjsresults
|
|||||||
.sasjsrc
|
.sasjsrc
|
||||||
client/.npmrc
|
client/.npmrc
|
||||||
*~
|
*~
|
||||||
.lighthouseci
|
|
225
CHANGELOG.md
225
CHANGELOG.md
@@ -1,228 +1,3 @@
|
|||||||
## [7.2.1](https://git.datacontroller.io/dc/dc/compare/v7.2.0...v7.2.1) (2025-08-08)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* removing localhost from index.html ([225e693](https://git.datacontroller.io/dc/dc/commit/225e693d1fd4381f2b8ce42fecb508f0a9e9dad8))
|
|
||||||
|
|
||||||
# [7.2.0](https://git.datacontroller.io/dc/dc/compare/v7.1.1...v7.2.0) (2025-08-08)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **ci:** cypress dependency package not available anymore ([26cdd73](https://git.datacontroller.io/dc/dc/commit/26cdd733315ef8babe9498ce93f6eb29c587dabd))
|
|
||||||
* **hot v16 migration:** multi dataset fixed issues, and cypress tests adapted ([712b384](https://git.datacontroller.io/dc/dc/commit/712b3848480a8769d149e00b0d2de91396022b66))
|
|
||||||
* obsolete cypress deps ([2ba4b53](https://git.datacontroller.io/dc/dc/commit/2ba4b5383e23bff8dfeb82b0ef473e5871c94709))
|
|
||||||
* remaining hot migrations - handsontable/angular-wrapper ([b419cd5](https://git.datacontroller.io/dc/dc/commit/b419cd507837e846e9dfcc6b729254d56cc196e6))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* lighthouse accessibility check pipeline ([670ec2c](https://git.datacontroller.io/dc/dc/commit/670ec2c71cb2d24e9d79e297a8cbc6136aa315c8))
|
|
||||||
|
|
||||||
## [7.1.1](https://git.datacontroller.io/dc/dc/compare/v7.1.0...v7.1.1) (2025-07-24)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **viewboxes:** hot v16 fails to load because of relative height `100%` ([672dd6d](https://git.datacontroller.io/dc/dc/commit/672dd6d4f1fda27e3706dd7caa42b45922319497))
|
|
||||||
|
|
||||||
# [7.1.0](https://git.datacontroller.io/dc/dc/compare/v7.0.3...v7.1.0) (2025-07-23)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* adapter bump ([b495c41](https://git.datacontroller.io/dc/dc/commit/b495c41626c85b7c4141d9361e4d3a826efd6c05))
|
|
||||||
* bumping CLI to 4.12.10 ([a08a717](https://git.datacontroller.io/dc/dc/commit/a08a717ca8d49e8a7d63f3fd91c6a7d42a1d6d8b))
|
|
||||||
* bumping sasjs/core and sasjs/cli ([63e9af4](https://git.datacontroller.io/dc/dc/commit/63e9af402ed65f6be4426e76ee1376a40e6ed097))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* improving accessibility score up to 100, hot update to v16.0.1 ([71c308d](https://git.datacontroller.io/dc/dc/commit/71c308d052400ecedc03f8020a5a69471ac6b116))
|
|
||||||
|
|
||||||
## [7.0.3](https://git.datacontroller.io/dc/dc/compare/v7.0.2...v7.0.3) (2025-06-26)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* makedata vars ([e7cb471](https://git.datacontroller.io/dc/dc/commit/e7cb471c0b60058b03fe8cbed5e3e2e70dd72e26))
|
|
||||||
* viya deploy makedata missing params ([7a82316](https://git.datacontroller.io/dc/dc/commit/7a8231615cb56710351fae5868e8fdeed54d180c))
|
|
||||||
|
|
||||||
## [7.0.2](https://git.datacontroller.io/dc/dc/compare/v7.0.1...v7.0.2) (2025-06-21)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **viya deploy:** run makedata in new window to ensure logs are available for the user ([0b4042a](https://git.datacontroller.io/dc/dc/commit/0b4042af6011fdc65cfaaa5d4b1d8f48cd67f3b3))
|
|
||||||
|
|
||||||
## [7.0.1](https://git.datacontroller.io/dc/dc/compare/v7.0.0...v7.0.1) (2025-06-11)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* refresh process ([4ecd186](https://git.datacontroller.io/dc/dc/commit/4ecd186e5cb22dd436f2d7f1200956f4e3f27425))
|
|
||||||
|
|
||||||
# [7.0.0](https://git.datacontroller.io/dc/dc/compare/v6.16.2...v7.0.0) (2025-06-11)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* bumping adapter to re-enable JES API method ([e874143](https://git.datacontroller.io/dc/dc/commit/e874143a95d0ac2e56c0793e04b979c27f96d74b))
|
|
||||||
* commit git hooks checking lint ([69f687a](https://git.datacontroller.io/dc/dc/commit/69f687a85f1cc562346b6167813d617cb9bd3404))
|
|
||||||
* ensuring apploc is not case sensitive. Closes [#171](https://git.datacontroller.io/dc/dc/issues/171) ([24545f2](https://git.datacontroller.io/dc/dc/commit/24545f2acdd5bd73cbe062526f2bd043269cc6a3))
|
|
||||||
* export unregistered formats ([f6d7d6f](https://git.datacontroller.io/dc/dc/commit/f6d7d6f90c978ac8c071471dfb67a60834424de5)), closes [#158](https://git.datacontroller.io/dc/dc/issues/158)
|
|
||||||
* reload startupservice after user approves the MPE_TABLES page ([e5f8e50](https://git.datacontroller.io/dc/dc/commit/e5f8e500c125ee233c6f7af5ad0077c0ed6abfcb))
|
|
||||||
* showing catalog_cnt in libinfo ([e44a25d](https://git.datacontroller.io/dc/dc/commit/e44a25dcc39ba4b9714257c60da84c2dfa613a85)), closes [#160](https://git.datacontroller.io/dc/dc/issues/160)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* adding 4 new tables for catalogs ([e4dbab8](https://git.datacontroller.io/dc/dc/commit/e4dbab8b1654b24e610e4b0603d1cf2b02a451e2))
|
|
||||||
* capturing catalog specific information, closes [#159](https://git.datacontroller.io/dc/dc/issues/159) ([b4c586a](https://git.datacontroller.io/dc/dc/commit/b4c586a859929e0122cd46449e43d4ca597b8b2b))
|
|
||||||
* viewer added catalog_cnt ([2aa19d1](https://git.datacontroller.io/dc/dc/commit/2aa19d1dca747f41274a032cde78d8ba73d66224))
|
|
||||||
|
|
||||||
|
|
||||||
### BREAKING CHANGES
|
|
||||||
|
|
||||||
* Introduction of 4 new tables for capturing information related to catalogs and their objects. Migration script prepared and available in the DB folder (usual place)
|
|
||||||
|
|
||||||
## [6.16.2](https://git.datacontroller.io/dc/dc/compare/v6.16.1...v6.16.2) (2025-06-06)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* streaming viya deploy `isStreaming` function stability fix ([4830c6d](https://git.datacontroller.io/dc/dc/commit/4830c6d2191cb47abcc7919bc1d49e55595e6121))
|
|
||||||
|
|
||||||
## [6.16.1](https://git.datacontroller.io/dc/dc/compare/v6.16.0...v6.16.1) (2025-06-06)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* viya deploy updating index html based on URL ([86134f4](https://git.datacontroller.io/dc/dc/commit/86134f478ae0b9426e01bfcc9ca4ee597ca733f7))
|
|
||||||
* viya streamed app deploy page flow fix ([89ab296](https://git.datacontroller.io/dc/dc/commit/89ab2961513b245eeea48d1867c6496d3261761e))
|
|
||||||
|
|
||||||
# [6.16.0](https://git.datacontroller.io/dc/dc/compare/v6.15.2...v6.16.0) (2025-06-05)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* adapter bump ([ca7caa2](https://git.datacontroller.io/dc/dc/commit/ca7caa25b6eea1bd4579fb8b67ec9b211a893079))
|
|
||||||
* automatic viya deploy timing issue ([037a97b](https://git.datacontroller.io/dc/dc/commit/037a97b6ffa27b40891531ae6812ebe5b5e71e34))
|
|
||||||
* bump core to ensure ff works on viya streaming deploy ([cbd69df](https://git.datacontroller.io/dc/dc/commit/cbd69df708edf3a8446115ca7315fac3557dcf97)), closes [#156](https://git.datacontroller.io/dc/dc/issues/156)
|
|
||||||
* viya deploy load data timing ([abdbb67](https://git.datacontroller.io/dc/dc/commit/abdbb674713796e5308eb4272197a5c253868a85))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* viya deploy, update the index.html contextname ([7223955](https://git.datacontroller.io/dc/dc/commit/72239558af2ee50cdfc71b7e185e6661ab568ba1))
|
|
||||||
|
|
||||||
## [6.15.2](https://git.datacontroller.io/dc/dc/compare/v6.15.1...v6.15.2) (2025-06-04)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* pipeline updates for DC.html ([624a7a8](https://git.datacontroller.io/dc/dc/commit/624a7a8f37f0265cf576da310ac330c75aa417cf))
|
|
||||||
|
|
||||||
## [6.15.1](https://git.datacontroller.io/dc/dc/compare/v6.15.0...v6.15.1) (2025-06-04)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* updating pipeline to default to streaming on viya ([4b55894](https://git.datacontroller.io/dc/dc/commit/4b558948d997f456ff25a12a58827fe0d2075493))
|
|
||||||
|
|
||||||
# [6.15.0](https://git.datacontroller.io/dc/dc/compare/v6.14.10...v6.15.0) (2025-06-04)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* makedata with context name ([da4d0b2](https://git.datacontroller.io/dc/dc/commit/da4d0b28c7109afd6f96455e1e0e80a40d25a942))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* viya deploy context ([6c96ef7](https://git.datacontroller.io/dc/dc/commit/6c96ef7fb0a55754a84ff0a8bbab838b78c1acaf))
|
|
||||||
|
|
||||||
## [6.14.10](https://git.datacontroller.io/dc/dc/compare/v6.14.9...v6.14.10) (2025-06-02)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* bump core ([0e8503e](https://git.datacontroller.io/dc/dc/commit/0e8503ed2bb22a0fc3924ac929e7f19626772e0a))
|
|
||||||
* default to home directory for SAS Drive in Viya ([9682b54](https://git.datacontroller.io/dc/dc/commit/9682b548e6106d99d97dcc023a35d93addfd5170))
|
|
||||||
|
|
||||||
## [6.14.9](https://git.datacontroller.io/dc/dc/compare/v6.14.8...v6.14.9) (2025-06-02)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* default DC path for viya ([f3125ff](https://git.datacontroller.io/dc/dc/commit/f3125ff4641e47e33cb203228f5b1014ea3343bc))
|
|
||||||
|
|
||||||
## [6.14.8](https://git.datacontroller.io/dc/dc/compare/v6.14.7...v6.14.8) (2025-05-28)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* CSP issues, clarity local library build, fixed some style issues ([841201a](https://git.datacontroller.io/dc/dc/commit/841201adab582149b1cca3a42e75f7cac75167f9))
|
|
||||||
* deploy page, makedata error handling, added local build of clarity, to address clr-stack-view CSP issues (inline styles) ([7b5e7ae](https://git.datacontroller.io/dc/dc/commit/7b5e7ae18414152f9b9d8f2d94fc94de43152003))
|
|
||||||
* improved deploy flow for Viya ([9604661](https://git.datacontroller.io/dc/dc/commit/9604661f3b76111387bc9474cc26348d73ab112e))
|
|
||||||
* requests modal causing VIYA CSP errors ([1dc6934](https://git.datacontroller.io/dc/dc/commit/1dc69341cadb837e1f11624d5cf35788bbb98d96))
|
|
||||||
* sas viya service init timing issue ([9de04e9](https://git.datacontroller.io/dc/dc/commit/9de04e9a0ce016e1a9fb8b19c656077079ddcf2f))
|
|
||||||
* scss of components transferred to the global styles.scss so we do not cause CSP (inline styles) issues when streaming to Viya ([6c171a6](https://git.datacontroller.io/dc/dc/commit/6c171a6394aba8104fe0f50aa8a4e6b9fa8023a2))
|
|
||||||
* viya deploy page improved flow ([4bd2154](https://git.datacontroller.io/dc/dc/commit/4bd215491f8cdc68f78bade68e7cb98e07edc81e))
|
|
||||||
|
|
||||||
## [6.14.7](https://git.datacontroller.io/dc/dc/compare/v6.14.6...v6.14.7) (2025-05-08)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* updated hot, clarity and improved accessibility score. ([2844c70](https://git.datacontroller.io/dc/dc/commit/2844c70f9507036216b8b621900c2bb9010c1d34))
|
|
||||||
|
|
||||||
## [6.14.6](https://git.datacontroller.io/dc/dc/compare/v6.14.5...v6.14.6) (2025-04-03)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* history table modal links styling ([c63fcdd](https://git.datacontroller.io/dc/dc/commit/c63fcdd465950ada439d7d69622a3886e8f3a783))
|
|
||||||
|
|
||||||
## [6.14.5](https://git.datacontroller.io/dc/dc/compare/v6.14.4...v6.14.5) (2025-03-24)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* improving accessibility lighthouse score ([7f3577c](https://git.datacontroller.io/dc/dc/commit/7f3577c3ef9f44e55a58bc64fbf89a3a64006dd4))
|
|
||||||
* prevent errors when using sqlrc in a DI job in a HOOK ([d1f0879](https://git.datacontroller.io/dc/dc/commit/d1f0879f0acf7e816c80f7635fd02f4f284214ed))
|
|
||||||
* user profile style fix, new select library and table icons ([69f8830](https://git.datacontroller.io/dc/dc/commit/69f883034fabbed31aa5d832e20561c4ae3042db))
|
|
||||||
|
|
||||||
## [6.14.4](https://git.datacontroller.io/dc/dc/compare/v6.14.3...v6.14.4) (2025-03-18)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* removing cli dependency warnings2 ([43c0f73](https://git.datacontroller.io/dc/dc/commit/43c0f73c2189ff762986a964caae6b0b108164fc))
|
|
||||||
|
|
||||||
## [6.14.3](https://git.datacontroller.io/dc/dc/compare/v6.14.2...v6.14.3) (2025-03-15)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* NLDAT & NLDATM formats are now being staged ([3f5cb1e](https://git.datacontroller.io/dc/dc/commit/3f5cb1e2defe390220e904e4bf04a165cb31fec4))
|
|
||||||
|
|
||||||
## [6.14.2](https://git.datacontroller.io/dc/dc/compare/v6.14.1...v6.14.2) (2025-03-10)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* improving instructions for setup ([83b3d77](https://git.datacontroller.io/dc/dc/commit/83b3d775b6e33653b087ca9f4eb3ad5b0dbbd479))
|
|
||||||
|
|
||||||
## [6.14.1](https://git.datacontroller.io/dc/dc/compare/v6.14.0...v6.14.1) (2025-03-05)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* handle national language datetime formats ([149e318](https://git.datacontroller.io/dc/dc/commit/149e318a8787be0109f25aeec3a1270ea75a97b2))
|
|
||||||
* updating logic to use NLDAT formats ([95289aa](https://git.datacontroller.io/dc/dc/commit/95289aa9524d3cb2b1c248cfb84f6b0d0a490c32))
|
|
||||||
|
|
||||||
# [6.14.0](https://git.datacontroller.io/dc/dc/compare/v6.13.2...v6.14.0) (2025-02-26)
|
# [6.14.0](https://git.datacontroller.io/dc/dc/compare/v6.13.2...v6.14.0) (2025-02-26)
|
||||||
|
|
||||||
|
|
||||||
|
34
README.md
34
README.md
@@ -23,42 +23,10 @@ _Problems with the above include:_
|
|||||||
|
|
||||||
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).
|
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).
|
||||||
|
|
||||||
An individual Viya deploy can be done in just 2 lines of #SAS code!
|
For more information:
|
||||||
|
|
||||||
```sas
|
|
||||||
filename dc url "https://git.datacontroller.io/dc/dc/releases/download/latest/viya.sas";
|
|
||||||
%inc dc;
|
|
||||||
```
|
|
||||||
|
|
||||||
For a multi-user deploy, using a shared system account, please see [deploy docs](https://docs.datacontroller.io/deploy-viya/).
|
|
||||||
|
|
||||||
For further information:
|
|
||||||
|
|
||||||
* Main site: https://datacontroller.io
|
* Main site: https://datacontroller.io
|
||||||
* Docs: https://docs.datacontroller.io
|
* Docs: https://docs.datacontroller.io
|
||||||
* Code: https://code.datacontroller.io
|
* Code: https://code.datacontroller.io
|
||||||
|
|
||||||
For support, contact support@4gl.io or reach out on [Matrix](https://matrix.to/#/#dc:4gl.io)!
|
For support, contact support@4gl.io or reach out on [Matrix](https://matrix.to/#/#dc:4gl.io)!
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
### Lighthouse CI
|
|
||||||
|
|
||||||
This project includes automated Lighthouse performance and accessibility checks that run on pull requests. The checks ensure:
|
|
||||||
|
|
||||||
- **Accessibility Score**: Minimum 1.0 (100%) median score across all tested pages
|
|
||||||
|
|
||||||
The Lighthouse CI workflow:
|
|
||||||
1. Sets up the development environment with SASjs server and mocked services
|
|
||||||
2. Builds and serves the Angular frontend
|
|
||||||
3. Runs Lighthouse CI against key application pages
|
|
||||||
4. Uploads results as artifacts for review
|
|
||||||
|
|
||||||
To run Lighthouse checks locally:
|
|
||||||
```bash
|
|
||||||
cd client
|
|
||||||
npm install
|
|
||||||
npm run lighthouse
|
|
||||||
```
|
|
||||||
|
|
||||||
Configuration is in `client/lighthouserc.js`.
|
|
@@ -32,7 +32,8 @@ context('excel tests: ', function () {
|
|||||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||||
|
|
||||||
attachExcelFile('regular.csv', () => {
|
attachExcelFile('regular.csv', () => {
|
||||||
cy.get('#approval-btn', { timeout: 60000 }).should('be.visible')
|
cy.get('#approval-btn', { timeout: 60000 })
|
||||||
|
.should('be.visible')
|
||||||
// .then(() => {
|
// .then(() => {
|
||||||
// cy.get('#hotInstance', { timeout: 30000 })
|
// cy.get('#hotInstance', { timeout: 30000 })
|
||||||
// .find('div.ht_master.handsontable')
|
// .find('div.ht_master.handsontable')
|
||||||
|
@@ -217,7 +217,11 @@ const rejectExcel = (callback?: any) => {
|
|||||||
.should('contain', 'Approve')
|
.should('contain', 'Approve')
|
||||||
.then((allButtons: any) => {
|
.then((allButtons: any) => {
|
||||||
for (let approvalButton of allButtons) {
|
for (let approvalButton of allButtons) {
|
||||||
if (approvalButton.innerText.toLowerCase().includes('approve')) {
|
if (
|
||||||
|
approvalButton.innerText
|
||||||
|
.toLowerCase()
|
||||||
|
.includes('approve')
|
||||||
|
) {
|
||||||
approvalButton.click()
|
approvalButton.click()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@@ -34,162 +34,93 @@ context('excel multi load tests: ', function () {
|
|||||||
|
|
||||||
it('1 | Uploads Excel file with multiple sheets, 3 sheets including data, 2 sheets matched with dataset', (done) => {
|
it('1 | Uploads Excel file with multiple sheets, 3 sheets including data, 2 sheets matched with dataset', (done) => {
|
||||||
attachExcelFile('multi_load_test_2.xlsx', () => {
|
attachExcelFile('multi_load_test_2.xlsx', () => {
|
||||||
checkHotUserDatasetTable(
|
checkHotUserDatasetTable('hotTableUserDataset', [
|
||||||
'hotTableUserDataset',
|
|
||||||
[
|
|
||||||
[library, mpeXTestTable],
|
[library, mpeXTestTable],
|
||||||
[library, mpeTablesTable]
|
[library, mpeTablesTable]
|
||||||
],
|
], () => {
|
||||||
() => {
|
cy.get('#continue-btn').trigger('click').then(() => {
|
||||||
cy.get('#continue-btn')
|
checkIfTreeHasTables([`${library}.${mpeXTestTable}`, `${library}.${mpeTablesTable}`], undefined, (includes: boolean) => {
|
||||||
.trigger('click')
|
|
||||||
.then(() => {
|
|
||||||
checkIfTreeHasTables(
|
|
||||||
[`${library}.${mpeXTestTable}`, `${library}.${mpeTablesTable}`],
|
|
||||||
undefined,
|
|
||||||
(includes: boolean) => {
|
|
||||||
if (includes) {
|
if (includes) {
|
||||||
// MPE_TABLES sheet does not have data so 1 error image must be shown
|
// MPE_TABLES sheet does not have data so 1 error image must be shown
|
||||||
hasErrorTables(1, (valid: boolean) => {
|
hasErrorTables(1, (valid: boolean) => {
|
||||||
if (valid) done()
|
if (valid) done()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
)
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('2 | Uploads Excel file with multiple sheets, 2 sheets matched with dataset, 1 matched sheet does not have data', (done) => {
|
it('2 | Uploads Excel file with multiple sheets, 2 sheets matched with dataset, 1 matched sheet does not have data', (done) => {
|
||||||
attachExcelFile('multi_load_test_1.xlsx', () => {
|
attachExcelFile('multi_load_test_1.xlsx', () => {
|
||||||
checkHotUserDatasetTable(
|
checkHotUserDatasetTable('hotTableUserDataset', [
|
||||||
'hotTableUserDataset',
|
|
||||||
[
|
|
||||||
[library, mpeXTestTable],
|
[library, mpeXTestTable],
|
||||||
[library, mpeTablesTable]
|
[library, mpeTablesTable]
|
||||||
],
|
], () => {
|
||||||
() => {
|
cy.get('#continue-btn').trigger('click').then(() => {
|
||||||
cy.get('#continue-btn')
|
checkIfTreeHasTables([`${library}.${mpeXTestTable}`, `${library}.${mpeTablesTable}`], `${library}.${mpeXTestTable}`, (includes: boolean) => {
|
||||||
.trigger('click')
|
|
||||||
.then(() => {
|
|
||||||
checkIfTreeHasTables(
|
|
||||||
[`${library}.${mpeXTestTable}`, `${library}.${mpeTablesTable}`],
|
|
||||||
`${library}.${mpeXTestTable}`,
|
|
||||||
(includes: boolean) => {
|
|
||||||
if (includes) {
|
if (includes) {
|
||||||
cy.get('#hotTable')
|
cy.get('#hotTable').should('be.visible').then(() => {
|
||||||
.should('be.visible')
|
checkHotUserDatasetTable('hotTable', [
|
||||||
.then(() => {
|
|
||||||
checkHotUserDatasetTable(
|
|
||||||
'hotTable',
|
|
||||||
[
|
|
||||||
['No', '1', 'more dummy data'],
|
['No', '1', 'more dummy data'],
|
||||||
[
|
['No', '1', 'It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told:'],
|
||||||
'No',
|
['No', '1', 'if you can fill the unforgiving minute']
|
||||||
'1',
|
], () => {
|
||||||
'It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told:'
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'No',
|
|
||||||
'1',
|
|
||||||
'if you can fill the unforgiving minute'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
() => {
|
|
||||||
submitTables()
|
submitTables()
|
||||||
|
|
||||||
hasSuccessSubmits(2, (valid: boolean) => {
|
hasSuccessSubmits(2, (valid: boolean) => {
|
||||||
if (valid) done()
|
if (valid) done()
|
||||||
})
|
})
|
||||||
}
|
|
||||||
)
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
)
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('3 | Uploads Excel file with multiple sheets, 1 sheets has 2 tables', (done) => {
|
it('3 | Uploads Excel file with multiple sheets, 1 sheets has 2 tables', (done) => {
|
||||||
attachExcelFile('multi_load_test_1.xlsx', () => {
|
attachExcelFile('multi_load_test_1.xlsx', () => {
|
||||||
checkHotUserDatasetTable(
|
checkHotUserDatasetTable('hotTableUserDataset', [
|
||||||
'hotTableUserDataset',
|
|
||||||
[
|
|
||||||
[library, mpeXTestTable],
|
[library, mpeXTestTable],
|
||||||
[library, mpeTablesTable]
|
[library, mpeTablesTable]
|
||||||
],
|
], () => {
|
||||||
() => {
|
cy.get('#continue-btn').trigger('click').then(() => {
|
||||||
cy.get('#continue-btn')
|
checkIfTreeHasTables([`${library}.${mpeXTestTable}`, `${library}.${mpeTablesTable}`], `${library}.${mpeXTestTable}`, (includes: boolean) => {
|
||||||
.trigger('click')
|
|
||||||
.then(() => {
|
|
||||||
checkIfTreeHasTables(
|
|
||||||
[`${library}.${mpeXTestTable}`, `${library}.${mpeTablesTable}`],
|
|
||||||
`${library}.${mpeXTestTable}`,
|
|
||||||
(includes: boolean) => {
|
|
||||||
if (includes) {
|
if (includes) {
|
||||||
cy.get('#hotTable')
|
cy.get('#hotTable').should('be.visible').then(() => {
|
||||||
.should('be.visible')
|
checkHotUserDatasetTable('hotTable', [
|
||||||
.then(() => {
|
|
||||||
checkHotUserDatasetTable(
|
|
||||||
'hotTable',
|
|
||||||
[
|
|
||||||
['No', '1', 'more dummy data'],
|
['No', '1', 'more dummy data'],
|
||||||
[
|
['No', '1', 'It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told:'],
|
||||||
'No',
|
['No', '1', 'if you can fill the unforgiving minute']
|
||||||
'1',
|
], () => {
|
||||||
'It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told:'
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'No',
|
|
||||||
'1',
|
|
||||||
'if you can fill the unforgiving minute'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
() => {
|
|
||||||
clickOnTreeNode('DC996664.MPE_TABLES', () => {
|
clickOnTreeNode('DC996664.MPE_TABLES', () => {
|
||||||
cy.wait(1000).then(() => {
|
cy.wait(1000).then(() => {
|
||||||
cy.get('#hotTable')
|
cy.get('#hotTable').should('be.visible').then(() => {
|
||||||
.should('be.visible')
|
checkHotUserDatasetTable('hotTable', [
|
||||||
.then(() => {
|
['No', 'DC914286', 'MPE_COLUMN_LEVEL_SECURITY'],
|
||||||
checkHotUserDatasetTable(
|
|
||||||
'hotTable',
|
|
||||||
[
|
|
||||||
[
|
|
||||||
'No',
|
|
||||||
'DC914286',
|
|
||||||
'MPE_COLUMN_LEVEL_SECURITY'
|
|
||||||
],
|
|
||||||
['No', 'DC914286', 'MPE_XLMAP_INFO'],
|
['No', 'DC914286', 'MPE_XLMAP_INFO'],
|
||||||
['No', 'DC914286', 'MPE_XLMAP_RULES']
|
['No', 'DC914286', 'MPE_XLMAP_RULES']
|
||||||
],
|
], () => {
|
||||||
() => {
|
|
||||||
submitTables()
|
submitTables()
|
||||||
|
|
||||||
hasSuccessSubmits(
|
hasSuccessSubmits(2, (valid: boolean) => {
|
||||||
2,
|
|
||||||
(valid: boolean) => {
|
|
||||||
if (valid) done()
|
if (valid) done()
|
||||||
}
|
})
|
||||||
)
|
|
||||||
}
|
})
|
||||||
)
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -211,11 +142,7 @@ const attachExcelFile = (excelFilename: string, callback?: any) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkHotUserDatasetTable = (
|
const checkHotUserDatasetTable = (hotId: string, dataToContain: any[][], callback?: () => void) => {
|
||||||
hotId: string,
|
|
||||||
dataToContain: any[][],
|
|
||||||
callback?: () => void
|
|
||||||
) => {
|
|
||||||
cy.get(`#${hotId}`, { timeout: longerCommandTimeout })
|
cy.get(`#${hotId}`, { timeout: longerCommandTimeout })
|
||||||
.find('div.ht_master.handsontable')
|
.find('div.ht_master.handsontable')
|
||||||
.find('div.wtHolder')
|
.find('div.wtHolder')
|
||||||
@@ -227,9 +154,7 @@ const checkHotUserDatasetTable = (
|
|||||||
cy.wait(2000).then(() => {
|
cy.wait(2000).then(() => {
|
||||||
for (let rowI = 0; rowI < dataToContain.length; rowI++) {
|
for (let rowI = 0; rowI < dataToContain.length; rowI++) {
|
||||||
for (let colI = 0; colI < dataToContain[rowI].length; colI++) {
|
for (let colI = 0; colI < dataToContain[rowI].length; colI++) {
|
||||||
expect(data[0].children[rowI].children[colI]).to.contain(
|
expect(data[0].children[rowI].children[colI]).to.contain(dataToContain[rowI][colI])
|
||||||
dataToContain[rowI][colI]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,11 +174,7 @@ const clickOnTreeNode = (clickOnNode: string, callback?: () => void) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkIfTreeHasTables = (
|
const checkIfTreeHasTables = (tables: string[], clickOnNode?: string, callback?: (includes: boolean) => void) => {
|
||||||
tables: string[],
|
|
||||||
clickOnNode?: string,
|
|
||||||
callback?: (includes: boolean) => void
|
|
||||||
) => {
|
|
||||||
cy.get('.nav-tree clr-tree > clr-tree-node').then((treeNodes: any) => {
|
cy.get('.nav-tree clr-tree > clr-tree-node').then((treeNodes: any) => {
|
||||||
let datasets = tables
|
let datasets = tables
|
||||||
let nodesCorrect = true
|
let nodesCorrect = true
|
||||||
@@ -286,24 +207,14 @@ const submitTables = () => {
|
|||||||
cy.wait(1000)
|
cy.wait(1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasSuccessSubmits = (
|
const hasSuccessSubmits = (expectedNoOfSubmits: number, callback: (valid: boolean) => void) => {
|
||||||
expectedNoOfSubmits: number,
|
cy.get('.nav-tree clr-tree > clr-tree-node cds-icon[status="success"]').should('be.visible').then(($nodes) => {
|
||||||
callback: (valid: boolean) => void
|
|
||||||
) => {
|
|
||||||
cy.get('.nav-tree clr-tree > clr-tree-node cds-icon[status="success"]')
|
|
||||||
.should('be.visible')
|
|
||||||
.then(($nodes) => {
|
|
||||||
callback(expectedNoOfSubmits === $nodes.length)
|
callback(expectedNoOfSubmits === $nodes.length)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasErrorTables = (
|
const hasErrorTables = (expectedNoOfErrors: number, callback: (valid: boolean) => void) => {
|
||||||
expectedNoOfErrors: number,
|
cy.get('.nav-tree clr-tree > clr-tree-node cds-icon[status="danger"]').should('be.visible').then(($nodes) => {
|
||||||
callback: (valid: boolean) => void
|
|
||||||
) => {
|
|
||||||
cy.get('.nav-tree clr-tree > clr-tree-node cds-icon[status="danger"]')
|
|
||||||
.should('be.visible')
|
|
||||||
.then(($nodes) => {
|
|
||||||
callback(expectedNoOfErrors === $nodes.length)
|
callback(expectedNoOfErrors === $nodes.length)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -234,7 +234,7 @@ context('excel tests: ', function () {
|
|||||||
cy.get('.btn-upload-preview', { timeout: 60000 })
|
cy.get('.btn-upload-preview', { timeout: 60000 })
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.then(() => {
|
.then(() => {
|
||||||
cy.get('#hotTable', { timeout: 30000 })
|
cy.get('#hotInstance', { timeout: 30000 })
|
||||||
.find('div.ht_master.handsontable')
|
.find('div.ht_master.handsontable')
|
||||||
.find('div.wtHolder')
|
.find('div.wtHolder')
|
||||||
.find('div.wtHider')
|
.find('div.wtHider')
|
||||||
@@ -283,7 +283,7 @@ context('excel tests: ', function () {
|
|||||||
cy.get('.btn-upload-preview', { timeout: 60000 })
|
cy.get('.btn-upload-preview', { timeout: 60000 })
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.then(() => {
|
.then(() => {
|
||||||
cy.get('#hotTable', { timeout: 30000 })
|
cy.get('#hotInstance', { timeout: 30000 })
|
||||||
.find('div.ht_master.handsontable')
|
.find('div.ht_master.handsontable')
|
||||||
.find('div.wtHolder')
|
.find('div.wtHolder')
|
||||||
.find('div.wtHider')
|
.find('div.wtHider')
|
||||||
@@ -399,7 +399,11 @@ const rejectExcel = (callback?: any) => {
|
|||||||
.should('contain', 'Approve')
|
.should('contain', 'Approve')
|
||||||
.then((allButtons: any) => {
|
.then((allButtons: any) => {
|
||||||
for (let approvalButton of allButtons) {
|
for (let approvalButton of allButtons) {
|
||||||
if (approvalButton.innerText.toLowerCase().includes('approve')) {
|
if (
|
||||||
|
approvalButton.innerText
|
||||||
|
.toLowerCase()
|
||||||
|
.includes('approve')
|
||||||
|
) {
|
||||||
approvalButton.click()
|
approvalButton.click()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -428,7 +432,11 @@ const acceptExcel = (callback?: any) => {
|
|||||||
.should('contain', 'Approve')
|
.should('contain', 'Approve')
|
||||||
.then((allButtons: any) => {
|
.then((allButtons: any) => {
|
||||||
for (let approvalButton of allButtons) {
|
for (let approvalButton of allButtons) {
|
||||||
if (approvalButton.innerText.toLowerCase().includes('approve')) {
|
if (
|
||||||
|
approvalButton.innerText
|
||||||
|
.toLowerCase()
|
||||||
|
.includes('approve')
|
||||||
|
) {
|
||||||
approvalButton.click()
|
approvalButton.click()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -447,7 +455,7 @@ const acceptExcel = (callback?: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const checkResultOfFormulaUpload = (callback?: any) => {
|
const checkResultOfFormulaUpload = (callback?: any) => {
|
||||||
cy.get('#hotTable', { timeout: longerCommandTimeout })
|
cy.get('#hotInstance', { timeout: longerCommandTimeout })
|
||||||
.find('div.ht_master.handsontable')
|
.find('div.ht_master.handsontable')
|
||||||
.find('div.wtHolder')
|
.find('div.wtHolder')
|
||||||
.find('div.wtHider')
|
.find('div.wtHider')
|
||||||
@@ -463,7 +471,7 @@ const checkResultOfFormulaUpload = (callback?: any) => {
|
|||||||
|
|
||||||
const checkResultOfXLSUpload = (callback?: any) => {
|
const checkResultOfXLSUpload = (callback?: any) => {
|
||||||
cy.viewport(1280, 720)
|
cy.viewport(1280, 720)
|
||||||
cy.get('#hotTable', { timeout: 30000 })
|
cy.get('#hotInstance', { timeout: 30000 })
|
||||||
.find('div.ht_master.handsontable')
|
.find('div.ht_master.handsontable')
|
||||||
.find('div.wtHolder')
|
.find('div.wtHolder')
|
||||||
.find('div.wtHider')
|
.find('div.wtHider')
|
||||||
@@ -492,7 +500,7 @@ const checkResultOfXLSUpload = (callback?: any) => {
|
|||||||
if (callback) callback()
|
if (callback) callback()
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.get('#hotTable', { timeout: 30000 })
|
cy.get('#hotInstance', { timeout: 30000 })
|
||||||
.find('div.ht_master.handsontable')
|
.find('div.ht_master.handsontable')
|
||||||
.find('div.wtHolder')
|
.find('div.wtHolder')
|
||||||
.scrollTo('right')
|
.scrollTo('right')
|
||||||
|
@@ -16,6 +16,7 @@ context('filtering tests: ', function () {
|
|||||||
this.beforeEach(() => {
|
this.beforeEach(() => {
|
||||||
cy.visit(hostUrl + appLocation, { timeout: longerCommandTimeout })
|
cy.visit(hostUrl + appLocation, { timeout: longerCommandTimeout })
|
||||||
|
|
||||||
|
|
||||||
visitPage('home')
|
visitPage('home')
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -298,16 +299,14 @@ const setFilterWithValue = (
|
|||||||
cy.get('.no-values')
|
cy.get('.no-values')
|
||||||
.should('not.exist')
|
.should('not.exist')
|
||||||
.then(() => {
|
.then(() => {
|
||||||
cy.get('.in-values-modal clr-checkbox-wrapper input').then(
|
cy.get('.in-values-modal clr-checkbox-wrapper input').then((inputs: any) => {
|
||||||
(inputs: any) => {
|
|
||||||
inputs[0].click()
|
inputs[0].click()
|
||||||
cy.get('.in-values-modal .modal-footer button').click()
|
cy.get('.in-values-modal .modal-footer button').click()
|
||||||
|
|
||||||
cy.get('.modal-footer .btn-success-outline').click()
|
cy.get('.modal-footer .btn-success-outline').click()
|
||||||
|
|
||||||
if (callback) callback()
|
if (callback) callback()
|
||||||
}
|
})
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@@ -23,6 +23,7 @@ interface EditConfigTableCells {
|
|||||||
|
|
||||||
context('licensing tests: ', function () {
|
context('licensing tests: ', function () {
|
||||||
this.beforeAll(() => {
|
this.beforeAll(() => {
|
||||||
|
|
||||||
cy.loginAndUpdateValidKey()
|
cy.loginAndUpdateValidKey()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -370,6 +371,8 @@ context('licensing tests: ', function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const logout = (callback?: any) => {
|
const logout = (callback?: any) => {
|
||||||
@@ -694,7 +697,11 @@ const approveTable = (callback?: any) => {
|
|||||||
.should('contain', 'Approve')
|
.should('contain', 'Approve')
|
||||||
.then((allButtons: any) => {
|
.then((allButtons: any) => {
|
||||||
for (let approvalButton of allButtons) {
|
for (let approvalButton of allButtons) {
|
||||||
if (approvalButton.innerText.toLowerCase().includes('approve')) {
|
if (
|
||||||
|
approvalButton.innerText
|
||||||
|
.toLowerCase()
|
||||||
|
.includes('approve')
|
||||||
|
) {
|
||||||
approvalButton.click()
|
approvalButton.click()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,7 @@ context('liveness tests: ', function () {
|
|||||||
this.beforeEach(() => {
|
this.beforeEach(() => {
|
||||||
cy.visit(hostUrl + appLocation)
|
cy.visit(hostUrl + appLocation)
|
||||||
|
|
||||||
|
|
||||||
visitPage('home')
|
visitPage('home')
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -124,7 +125,11 @@ const rejectExcel = (callback?: any) => {
|
|||||||
.should('contain', 'Approve')
|
.should('contain', 'Approve')
|
||||||
.then((allButtons: any) => {
|
.then((allButtons: any) => {
|
||||||
for (let approvalButton of allButtons) {
|
for (let approvalButton of allButtons) {
|
||||||
if (approvalButton.innerText.toLowerCase().includes('approve')) {
|
if (
|
||||||
|
approvalButton.innerText
|
||||||
|
.toLowerCase()
|
||||||
|
.includes('approve')
|
||||||
|
) {
|
||||||
approvalButton.click()
|
approvalButton.click()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@@ -76,8 +76,7 @@ context('editor tests: ', function () {
|
|||||||
cy.get('.viewbox-open').click()
|
cy.get('.viewbox-open').click()
|
||||||
openTableFromViewboxTree(
|
openTableFromViewboxTree(
|
||||||
libraryToOpenIncludes,
|
libraryToOpenIncludes,
|
||||||
viewboxes.map((viewbox) => viewbox.viewbox_table)
|
viewboxes.map((viewbox) => viewbox.viewbox_table))
|
||||||
)
|
|
||||||
cy.get('.open-viewbox').then((viewboxNodes: any) => {
|
cy.get('.open-viewbox').then((viewboxNodes: any) => {
|
||||||
let found = 0
|
let found = 0
|
||||||
|
|
||||||
@@ -92,8 +91,7 @@ context('editor tests: ', function () {
|
|||||||
|
|
||||||
if (found < viewboxes.length) return
|
if (found < viewboxes.length) return
|
||||||
|
|
||||||
cy.get('.viewboxes-container .viewbox', { withinSubject: null }).then(
|
cy.get('.viewboxes-container .viewbox', { withinSubject: null }).then((viewboxNodes: any) => {
|
||||||
(viewboxNodes: any) => {
|
|
||||||
for (let viewboxNode of viewboxNodes) {
|
for (let viewboxNode of viewboxNodes) {
|
||||||
cy.get(viewboxNode).within(() => {
|
cy.get(viewboxNode).within(() => {
|
||||||
cy.get('.table-title').then((tableTitle) => {
|
cy.get('.table-title').then((tableTitle) => {
|
||||||
@@ -118,8 +116,7 @@ context('editor tests: ', function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -398,13 +395,11 @@ context('editor tests: ', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const removeAllColumns = () => {
|
const removeAllColumns = () => {
|
||||||
cy.get('.configuration-wrapper clr-icon[shape="trash"]').then(
|
cy.get('.configuration-wrapper clr-icon[shape="trash"]').then(removeNodes => {
|
||||||
(removeNodes) => {
|
|
||||||
for (let removeNode of removeNodes) {
|
for (let removeNode of removeNodes) {
|
||||||
removeNode.click()
|
removeNode.click()
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkColumns = (columns: string[], callback: () => void) => {
|
const checkColumns = (columns: string[], callback: () => void) => {
|
||||||
|
255
client/cypress/integration/download.tests.ts
Normal file
255
client/cypress/integration/download.tests.ts
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
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/'
|
||||||
|
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`)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.afterAll(() => {
|
||||||
|
cy.visit(`https://sas.4gl.io/mihmed/cypress_finish`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
246
client/cypress/integration/editor.tests.ts
Normal file
246
client/cypress/integration/editor.tests.ts
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
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')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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', 'Approve')
|
||||||
|
.then((allButtons: any) => {
|
||||||
|
for (let approvalButton of allButtons) {
|
||||||
|
if (approvalButton.innerText.toLowerCase().includes('approve')) {
|
||||||
|
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}`)
|
||||||
|
}
|
527
client/cypress/integration/excel.tests.ts
Normal file
527
client/cypress/integration/excel.tests.ts
Normal file
@@ -0,0 +1,527 @@
|
|||||||
|
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/'
|
||||||
|
|
||||||
|
// TODO: 4 and 9 failing
|
||||||
|
|
||||||
|
context('excel tests: ', function () {
|
||||||
|
this.beforeAll(() => {
|
||||||
|
cy.visit(`${hostUrl}/SASLogon/logout`)
|
||||||
|
cy.loginAndUpdateValidKey()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.beforeEach(() => {
|
||||||
|
cy.visit(hostUrl + appLocation)
|
||||||
|
|
||||||
|
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('.abortMsg', { 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')
|
||||||
|
.should((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')
|
||||||
|
.should((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')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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', 'Approve')
|
||||||
|
.then((allButtons: any) => {
|
||||||
|
for (let approvalButton of allButtons) {
|
||||||
|
if (approvalButton.innerText.toLowerCase().includes('approve')) {
|
||||||
|
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', 'Approve')
|
||||||
|
.then((allButtons: any) => {
|
||||||
|
for (let approvalButton of allButtons) {
|
||||||
|
if (approvalButton.innerText.toLowerCase().includes('approve')) {
|
||||||
|
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')
|
||||||
|
.should((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')
|
||||||
|
.should((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')
|
||||||
|
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')
|
||||||
|
cell = data[0].children[0].children[9]
|
||||||
|
expect(cell.innerText).to.equal('3')
|
||||||
|
|
||||||
|
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')
|
||||||
|
.should((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;')
|
||||||
|
}
|
376
client/cypress/integration/filtering.tests.ts
Normal file
376
client/cypress/integration/filtering.tests.ts
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
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 })
|
||||||
|
|
||||||
|
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.only('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()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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('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}`)
|
||||||
|
}
|
719
client/cypress/integration/licensing.tests.ts
Normal file
719
client/cypress/integration/licensing.tests.ts
Normal file
@@ -0,0 +1,719 @@
|
|||||||
|
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.loginAndUpdateValidKey()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.beforeEach(() => {
|
||||||
|
cy.visit(hostUrl + appLocation)
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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', 'Approve')
|
||||||
|
.then((allButtons: any) => {
|
||||||
|
for (let approvalButton of allButtons) {
|
||||||
|
if (approvalButton.innerText.toLowerCase().includes('approve')) {
|
||||||
|
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}`)
|
||||||
|
}
|
149
client/cypress/integration/liveness.tests.ts
Normal file
149
client/cypress/integration/liveness.tests.ts
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
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', 'Approve')
|
||||||
|
.then((allButtons: any) => {
|
||||||
|
for (let approvalButton of allButtons) {
|
||||||
|
if (approvalButton.innerText.toLowerCase().includes('approve')) {
|
||||||
|
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()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
61
client/cypress/integration/metanav.tests.ts
Normal file
61
client/cypress/integration/metanav.tests.ts
Normal 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}`)
|
||||||
|
}
|
624
client/cypress/integration/viewbox.tests.ts
Normal file
624
client/cypress/integration/viewbox.tests.ts
Normal file
@@ -0,0 +1,624 @@
|
|||||||
|
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}`)
|
||||||
|
const fixturePath = 'excels_general/'
|
||||||
|
|
||||||
|
context('editor tests: ', function () {
|
||||||
|
this.beforeAll(() => {
|
||||||
|
cy.visit(`${hostUrl}/SASLogon/logout`)
|
||||||
|
cy.loginAndUpdateValidKey()
|
||||||
|
})
|
||||||
|
|
||||||
|
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').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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
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((innerNodes: any) => {
|
||||||
|
for (let innerNode of innerNodes) {
|
||||||
|
for (let tablename of tablenames) {
|
||||||
|
if (innerNode.innerText.toLowerCase().includes(tablename)) {
|
||||||
|
innerNode.click()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const visitPage = (url: string) => {
|
||||||
|
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
@@ -10,7 +10,7 @@ const check = (cwd) => {
|
|||||||
onlyAllow:
|
onlyAllow:
|
||||||
'AFLv2.1;Apache 2.0;Apache-2.0;Apache*;Artistic-2.0;0BSD;BSD*;BSD-2-Clause;BSD-3-Clause;CC0-1.0;CC-BY-3.0;CC-BY-4.0;ISC;MIT;MPL-2.0;ODC-By-1.0;Python-2.0;Unlicense;',
|
'AFLv2.1;Apache 2.0;Apache-2.0;Apache*;Artistic-2.0;0BSD;BSD*;BSD-2-Clause;BSD-3-Clause;CC0-1.0;CC-BY-3.0;CC-BY-4.0;ISC;MIT;MPL-2.0;ODC-By-1.0;Python-2.0;Unlicense;',
|
||||||
excludePackages:
|
excludePackages:
|
||||||
'@cds/city@1.1.0;@handsontable/angular-wrapper@16.0.1;handsontable@16.0.1;hyperformula@2.7.1;hyperformula@3.0.0;jackspeak@3.4.3;path-scurry@1.11.1;package-json-from-dist@1.0.1'
|
'@cds/city@1.1.0;@handsontable/angular@14.6.2;handsontable@14.6.2;hyperformula@2.7.1;jackspeak@3.4.3;path-scurry@1.11.1;package-json-from-dist@1.0.1'
|
||||||
},
|
},
|
||||||
(error, json) => {
|
(error, json) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
|
@@ -1,42 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
ci: {
|
|
||||||
collect: {
|
|
||||||
settings: {
|
|
||||||
preset: "desktop",
|
|
||||||
chromeFlags: "--no-sandbox --disable-dev-shm-usage"
|
|
||||||
},
|
|
||||||
url: [
|
|
||||||
'http://localhost:5000/AppStream/clickme/#/home/tables',
|
|
||||||
'http://localhost:5000/AppStream/clickme/#/editor/DC996664.MPE_X_TEST',
|
|
||||||
'http://localhost:5000/AppStream/clickme/#/view/data',
|
|
||||||
'http://localhost:5000/AppStream/clickme/#/view/data/DC996664',
|
|
||||||
'http://localhost:5000/AppStream/clickme/#/view/data/DC996664.MPE_X_TEST',
|
|
||||||
'http://localhost:5000/AppStream/clickme/#/view/usernav/groups',
|
|
||||||
'http://localhost:5000/AppStream/clickme/#/view/usernav/groups',
|
|
||||||
'http://localhost:5000/AppStream/clickme/#/view/usernav/groups/1',
|
|
||||||
'http://localhost:5000/AppStream/clickme/#/view/usernav/users/1',
|
|
||||||
'http://localhost:5000/AppStream/clickme/#/home/excel-maps',
|
|
||||||
'http://localhost:5000/AppStream/clickme/#/home/excel-maps/BASEL-CR2',
|
|
||||||
'http://localhost:5000/AppStream/clickme/#/home/multi-load',
|
|
||||||
'http://localhost:5000/AppStream/clickme/#/review/submitted',
|
|
||||||
'http://localhost:5000/AppStream/clickme/#/review/approve',
|
|
||||||
'http://localhost:5000/AppStream/clickme/#/review/history',
|
|
||||||
'http://localhost:5000/AppStream/clickme/#/stage/DC20221006T142649516_059582_7169',
|
|
||||||
'http://localhost:5000/AppStream/clickme/#/review/submitted/DC20221006T142649516_059582_7169',
|
|
||||||
'http://localhost:5000/AppStream/clickme/#/system'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
assert: {
|
|
||||||
assertions: {
|
|
||||||
'categories:accessibility': [
|
|
||||||
'error',
|
|
||||||
{ minScore: 1, aggregationMethod: 'median' }
|
|
||||||
],
|
|
||||||
'categories:performance': [
|
|
||||||
'error',
|
|
||||||
{ minScore: 0.4, aggregationMethod: 'median' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
5686
client/package-lock.json
generated
5686
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -31,8 +31,7 @@
|
|||||||
"sasdocs": "sasjs doc && ./sasjs/utils/deploydocs.sh",
|
"sasdocs": "sasjs doc && ./sasjs/utils/deploydocs.sh",
|
||||||
"compodoc:build": "compodoc -p tsconfig.doc.json --name 'Data Controller Client'",
|
"compodoc:build": "compodoc -p tsconfig.doc.json --name 'Data Controller Client'",
|
||||||
"compodoc:build-and-serve": "compodoc -p tsconfig.doc.json -s --name 'Data Controller Client'",
|
"compodoc:build-and-serve": "compodoc -p tsconfig.doc.json -s --name 'Data Controller Client'",
|
||||||
"compodoc:serve": "compodoc -s --name 'Data Controller Client'",
|
"compodoc:serve": "compodoc -s --name 'Data Controller Client'"
|
||||||
"lighthouse": "lhci autorun"
|
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -45,12 +44,12 @@
|
|||||||
"@angular/platform-browser": "^17.3.3",
|
"@angular/platform-browser": "^17.3.3",
|
||||||
"@angular/platform-browser-dynamic": "^17.3.3",
|
"@angular/platform-browser-dynamic": "^17.3.3",
|
||||||
"@angular/router": "^17.3.3",
|
"@angular/router": "^17.3.3",
|
||||||
"@cds/core": "^6.15.1",
|
"@cds/core": "^6.10.0",
|
||||||
"@clr/angular": "file:libraries/clr-angular-17.9.0.tgz",
|
"@clr/angular": "^17.0.1",
|
||||||
"@clr/icons": "^13.0.2",
|
"@clr/icons": "^13.0.2",
|
||||||
"@clr/ui": "file:libraries/clr-ui-17.9.0.tgz",
|
"@clr/ui": "^17.0.1",
|
||||||
"@handsontable/angular-wrapper": "16.0.1",
|
"@handsontable/angular": "^14.3.0",
|
||||||
"@sasjs/adapter": "^4.12.2",
|
"@sasjs/adapter": "^4.11.0",
|
||||||
"@sasjs/utils": "^3.4.0",
|
"@sasjs/utils": "^3.4.0",
|
||||||
"@sheet/crypto": "file:libraries/sheet-crypto.tgz",
|
"@sheet/crypto": "file:libraries/sheet-crypto.tgz",
|
||||||
"@types/d3-graphviz": "^2.6.7",
|
"@types/d3-graphviz": "^2.6.7",
|
||||||
@@ -61,7 +60,7 @@
|
|||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"d3-graphviz": "^5.0.2",
|
"d3-graphviz": "^5.0.2",
|
||||||
"fs-extra": "^7.0.1",
|
"fs-extra": "^7.0.1",
|
||||||
"handsontable": "^16.0.1",
|
"handsontable": "^14.3.0",
|
||||||
"https-browserify": "1.0.0",
|
"https-browserify": "1.0.0",
|
||||||
"hyperformula": "^2.5.0",
|
"hyperformula": "^2.5.0",
|
||||||
"iconv-lite": "^0.5.0",
|
"iconv-lite": "^0.5.0",
|
||||||
@@ -96,7 +95,6 @@
|
|||||||
"@babel/plugin-proposal-private-methods": "^7.18.6",
|
"@babel/plugin-proposal-private-methods": "^7.18.6",
|
||||||
"@compodoc/compodoc": "^1.1.21",
|
"@compodoc/compodoc": "^1.1.21",
|
||||||
"@cypress/webpack-preprocessor": "^5.17.1",
|
"@cypress/webpack-preprocessor": "^5.17.1",
|
||||||
"@lhci/cli": "^0.12.0",
|
|
||||||
"@types/core-js": "^2.5.5",
|
"@types/core-js": "^2.5.5",
|
||||||
"@types/crypto-js": "^4.2.1",
|
"@types/crypto-js": "^4.2.1",
|
||||||
"@types/es6-shim": "^0.31.39",
|
"@types/es6-shim": "^0.31.39",
|
||||||
|
@@ -148,8 +148,5 @@ export const globals: {
|
|||||||
},
|
},
|
||||||
handsontable: {
|
handsontable: {
|
||||||
darkTableHeaderClass: 'darkTH'
|
darkTableHeaderClass: 'darkTH'
|
||||||
},
|
|
||||||
userDropdownConfig: {
|
|
||||||
closeOnDebugClick: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -139,15 +139,10 @@
|
|||||||
[routerLink]="['/']"
|
[routerLink]="['/']"
|
||||||
class="nav-link"
|
class="nav-link"
|
||||||
>
|
>
|
||||||
<img
|
<img class="without-text d-block d-md-none" src="images/dc-logo.svg" />
|
||||||
class="without-text d-block d-md-none"
|
|
||||||
src="images/dc-logo.svg"
|
|
||||||
alt="datacontroller logo without text"
|
|
||||||
/>
|
|
||||||
<img
|
<img
|
||||||
class="with-text d-none d-md-block"
|
class="with-text d-none d-md-block"
|
||||||
src="images/datacontroller.svg"
|
src="images/datacontroller.svg"
|
||||||
alt="datacontroller logo"
|
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@@ -288,11 +283,7 @@
|
|||||||
|
|
||||||
<!-- App Loading Page -->
|
<!-- App Loading Page -->
|
||||||
<div *ngIf="!startupDataLoaded" class="app-loading">
|
<div *ngIf="!startupDataLoaded" class="app-loading">
|
||||||
<img
|
<img class="loading-logo" src="images/datacontroller.svg" />
|
||||||
class="loading-logo"
|
|
||||||
src="images/datacontroller.svg"
|
|
||||||
alt="datacontroller logo"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div *ngIf="appActive === null" class="slider">
|
<div *ngIf="appActive === null" class="slider">
|
||||||
<div class="line"></div>
|
<div class="line"></div>
|
||||||
|
@@ -0,0 +1,447 @@
|
|||||||
|
@import '../colors.scss';
|
||||||
|
|
||||||
|
// Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
// This software is released under MIT license.
|
||||||
|
// The full license information can be found in LICENSE in the root directory of this project.
|
||||||
|
app-requests-modal {
|
||||||
|
z-index: 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
header.app-header {
|
||||||
|
background: $headerBackground !important;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo img.without-text {
|
||||||
|
width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo img.with-text {
|
||||||
|
width: 210px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-hamburger-trigger {
|
||||||
|
display: block;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-expired-notice {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100vh !important;
|
||||||
|
width: 100vw !important;
|
||||||
|
z-index: 105;
|
||||||
|
background: rgba(33, 33, 33, .5);
|
||||||
|
|
||||||
|
.expired-details {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 30px;
|
||||||
|
z-index: 110;
|
||||||
|
background: $headerBackground;
|
||||||
|
|
||||||
|
.expired-notice {
|
||||||
|
color: #e0e0e0;
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
.mailto {
|
||||||
|
color: #8dc53e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-container .update-key {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: white;
|
||||||
|
padding: 0px 10px;
|
||||||
|
background: #00000026;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-wrapper {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-text {
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-toggle {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 10px;
|
||||||
|
|
||||||
|
clr-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
.header-actions {
|
||||||
|
.dropdown {
|
||||||
|
position: unset; //without it, when opening user dropdown scrollbar was displaying without reason
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link:hover {
|
||||||
|
color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link.active {
|
||||||
|
background: #61717D;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notf {
|
||||||
|
background: #16a57a;
|
||||||
|
color: #fffcfc;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.toggle-switch input[type=checkbox]:checked+label:before {
|
||||||
|
border-color: #61717D;
|
||||||
|
background-color: #61717D;
|
||||||
|
transition: .15s ease-in;
|
||||||
|
transition-property: border-color,background-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
min-height: 100vh !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-container .content-container .content-area {
|
||||||
|
padding: 0rem 1rem 1rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-container {
|
||||||
|
z-index: 0!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navBarResp {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
background: #495A67;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep {
|
||||||
|
.htInvalid {
|
||||||
|
background: black!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width:480px) {
|
||||||
|
h2 {
|
||||||
|
font-size: .7rem!important;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: .7rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link {
|
||||||
|
padding: 0rem 1rem 0rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[cds-theme="light"] {
|
||||||
|
.btn-primary .btn, .btn.btn-primary {
|
||||||
|
border-color: $headerBackground;
|
||||||
|
background-color: $headerBackground;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body[cds-theme="dark"] {
|
||||||
|
.btn-primary .btn, .btn.btn-primary {
|
||||||
|
border-color: #5e7382;
|
||||||
|
background-color: #5e7382;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
clr-icon, cds-icon {
|
||||||
|
color: #fff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary .btn, .btn.btn-primary {
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
-webkit-appearance: none!important;
|
||||||
|
border-radius: .125rem;
|
||||||
|
border: 1px solid;
|
||||||
|
min-width: 3rem;
|
||||||
|
max-width: 15rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
letter-spacing: .12em;
|
||||||
|
font-size: .5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
height: 1.5rem;
|
||||||
|
padding: 0 .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.btn-outline:hover {
|
||||||
|
border-color: $headerBackground;
|
||||||
|
background-color: #495A67;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
body[cds-theme="dark"] {
|
||||||
|
.btn.btn-icon.btn-dimmed {
|
||||||
|
color: #7295ae;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body[cds-theme="light"] {
|
||||||
|
.btn.btn-icon.btn-dimmed {
|
||||||
|
color: $headerBackground;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.btn-outline {
|
||||||
|
border-color: $headerBackground;
|
||||||
|
background-color: transparent;
|
||||||
|
color: $headerBackground;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.htMobileEditorContainer .inputs textarea {
|
||||||
|
font-size: 13pt;
|
||||||
|
border: 2px solid #485967;
|
||||||
|
border-radius: 4px;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
box-shadow: none;
|
||||||
|
position: absolute;
|
||||||
|
left: 14px;
|
||||||
|
right: 0px;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
padding: 7pt;
|
||||||
|
width: 290px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.htMobileEditorContainer .positionControls {
|
||||||
|
width: 333px;
|
||||||
|
position: absolute;
|
||||||
|
right: 5pt;
|
||||||
|
top: 50px;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.htMobileEditorContainer.active {
|
||||||
|
display: block;
|
||||||
|
height: 120px;
|
||||||
|
width: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Left and right */
|
||||||
|
|
||||||
|
/* Column headers */
|
||||||
|
|
||||||
|
body[cds-theme="light"] {
|
||||||
|
.wtBorder {
|
||||||
|
background-color: #495A67!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ht_master tr:nth-of-type(odd) > td {
|
||||||
|
filter: brightness(0.95);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$darkBorderColor: #697c85;
|
||||||
|
|
||||||
|
body[cds-theme="dark"] {
|
||||||
|
.ht_master tr:nth-of-type(odd) > td {
|
||||||
|
filter: brightness(1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ht_master:not(.emptyColumns) ~ .handsontable tbody tr th, .ht_master:not(.emptyColumns) ~ .handsontable:not(.ht_clone_top) thead tr th:first-child {
|
||||||
|
background-color: #2d4048;
|
||||||
|
border-color: $darkBorderColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handsontable td {
|
||||||
|
// border-right: 1px solid #697c85;
|
||||||
|
// border-bottom: 1px solid #697c85;
|
||||||
|
border-color: $darkBorderColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handsontable tr:first-child th, .handsontable tr:first-child td {
|
||||||
|
border-color: $darkBorderColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handsontable .handsontable.ht_clone_top .wtHider {
|
||||||
|
border-color: $darkBorderColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handsontable .changeType {
|
||||||
|
background-color: #3c5662;
|
||||||
|
border-color: $darkBorderColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handsontableInput {
|
||||||
|
background-color: #708b98;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.handsontable .handsontable.ht_clone_top .wtHider {
|
||||||
|
padding: 0 0 0px 0!important;
|
||||||
|
margin: 0px;
|
||||||
|
border-bottom: 3px solid #d6d3d3;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[cds-theme="light"] {
|
||||||
|
.content-container {
|
||||||
|
// background: red;
|
||||||
|
background: #F5F6FF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.datagrid-compact, .datagrid-history{
|
||||||
|
.datagrid {
|
||||||
|
border-collapse: separate;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: .125rem;
|
||||||
|
margin: 0;
|
||||||
|
margin-top: 1rem;
|
||||||
|
max-width: 100%;
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px 15px 50px 15px;
|
||||||
|
}
|
||||||
|
.datagrid-foot {
|
||||||
|
-webkit-box-pack: end;
|
||||||
|
-ms-flex-pack: end;
|
||||||
|
justify-content: flex-end;
|
||||||
|
height: 1.5rem;
|
||||||
|
padding: 0 .5rem;
|
||||||
|
line-height: calc(1.5rem - 3px);
|
||||||
|
font-size: .45833rem;
|
||||||
|
background-color: #fff;
|
||||||
|
border-top: 1px solid #ccc;
|
||||||
|
border-radius: 0px;
|
||||||
|
// border-radius: 0 0 .125rem .125rem;
|
||||||
|
}
|
||||||
|
.datagrid-footer {
|
||||||
|
position: absolute;
|
||||||
|
right: 30px;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
.datagrid .datagrid-head {
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
margin-top: .083333rem;
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-box-direction: normal;
|
||||||
|
-ms-flex-direction: column;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: .5rem 0;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
box-shadow: 0 1px 0.125rem hsla(0,0%,45%,.25);
|
||||||
|
min-width: 5rem;
|
||||||
|
max-width: 15rem;
|
||||||
|
border-radius: .125rem;
|
||||||
|
visibility: hidden;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
border-collapse: separate;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 0px;
|
||||||
|
margin: 0;
|
||||||
|
margin-top: 1rem;
|
||||||
|
max-width: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table th {
|
||||||
|
font-size: .45833rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: .03em;
|
||||||
|
vertical-align: bottom;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
border-bottom: 2px solid #e4e4e4;
|
||||||
|
padding: 0 0 .5rem 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-container .content-container {
|
||||||
|
min-height: 0px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-loading {
|
||||||
|
.loading-logo {
|
||||||
|
max-width: 400px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.navBarResp {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
background: #495A67;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-container .sub-nav.clr-nav-level-1 .nav .nav-link, .main-container .sub-nav.clr-nav-level-2 .nav .nav-link, .main-container .subnav.clr-nav-level-1 .nav .nav-link, .main-container .subnav.clr-nav-level-2 .nav .nav-link {
|
||||||
|
padding: 0 .5rem 0 1rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
border-radius: .125rem 0 0 .125rem;
|
||||||
|
color: #95c84b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.card-block, .card-footer {
|
||||||
|
padding: 10px 0px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-container[_ngcontent-c0] .content-container[_ngcontent-c0] .content-area[_ngcontent-c0] {
|
||||||
|
padding: 0rem 0rem 0rem 0rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,9 +1,4 @@
|
|||||||
import {
|
import { ChangeDetectorRef, Component, ElementRef } from '@angular/core'
|
||||||
ChangeDetectorRef,
|
|
||||||
Component,
|
|
||||||
ElementRef,
|
|
||||||
ViewEncapsulation
|
|
||||||
} from '@angular/core'
|
|
||||||
import { Router } from '@angular/router'
|
import { Router } from '@angular/router'
|
||||||
import { VERSION } from '../environments/version'
|
import { VERSION } from '../environments/version'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
@@ -41,8 +36,7 @@ ClarityIcons.addIcons(
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'my-app',
|
selector: 'my-app',
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrls: ['./app.component.scss'],
|
styleUrls: ['./app.component.scss']
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
private dcAdapterSettings: DcAdapterSettings | undefined
|
private dcAdapterSettings: DcAdapterSettings | undefined
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
<div class="card-header">Terms and Conditions</div>
|
<div class="card-header">Terms and Conditions</div>
|
||||||
<div class="card-block">
|
<div class="card-block">
|
||||||
<div class="card-text">
|
<div class="card-text">
|
||||||
<p class="mt-0">
|
<p>
|
||||||
The Demo version of Data Controller is free for EVALUATION purposes
|
The Demo version of Data Controller is free for EVALUATION purposes
|
||||||
only. Before proceeding with configuration, please confirm that you
|
only. Before proceeding with configuration, please confirm that you
|
||||||
have read, understood, and agreed to the
|
have read, understood, and agreed to the
|
||||||
|
@@ -0,0 +1,50 @@
|
|||||||
|
.card {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
background: #f0f0f0;
|
||||||
|
border: 1px solid #c9c9c9;
|
||||||
|
padding: 10px;
|
||||||
|
overflow: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
#contexts-btn {
|
||||||
|
padding: 0;
|
||||||
|
min-width: 30px;
|
||||||
|
margin-left: 10px;
|
||||||
|
height: 30px;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.validation-bar {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 20px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
clr-icon {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.autodeploy-section {
|
||||||
|
padding: 0px 15px;
|
||||||
|
|
||||||
|
.clr-checkbox-wrapper {
|
||||||
|
margin: 20px 0 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-autodeploy {
|
||||||
|
display: block;
|
||||||
|
margin: 15px 0 15px 0;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core'
|
import { Component, OnInit } from '@angular/core'
|
||||||
import { SasService } from '../services/sas.service'
|
import { SasService } from '../services/sas.service'
|
||||||
import { SASjsConfig } from '@sasjs/adapter'
|
import { SASjsConfig } from '@sasjs/adapter'
|
||||||
import { Router } from '@angular/router'
|
import { Router } from '@angular/router'
|
||||||
@@ -13,8 +13,7 @@ import { DcAdapterSettings } from '../models/DcAdapterSettings'
|
|||||||
styleUrls: ['./deploy.component.scss'],
|
styleUrls: ['./deploy.component.scss'],
|
||||||
host: {
|
host: {
|
||||||
class: 'content-container'
|
class: 'content-container'
|
||||||
},
|
}
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class DeployComponent implements OnInit {
|
export class DeployComponent implements OnInit {
|
||||||
public step: number = 0
|
public step: number = 0
|
||||||
@@ -57,6 +56,25 @@ export class DeployComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
if (this.sasJsConfig.serverType === ServerType.SasViya) {
|
||||||
|
fetch('sasbuild/viya.json')
|
||||||
|
.then((res) => res.text())
|
||||||
|
.then((res) => {
|
||||||
|
let initJsonFile: any = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
initJsonFile = JSON.parse(res)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initJsonFile) {
|
||||||
|
this.jsonFile = initJsonFile
|
||||||
|
this.loggerService.log(this.jsonFile)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
this.setDeployDefaults()
|
this.setDeployDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,17 +9,14 @@
|
|||||||
<p class="m-0 align-self-start">Done</p>
|
<p class="m-0 align-self-start">Done</p>
|
||||||
<hr class="w-100" />
|
<hr class="w-100" />
|
||||||
|
|
||||||
<div
|
<div class="deploy-status-row">
|
||||||
*ngIf="autoDeployStatus.deployServicePack !== null"
|
|
||||||
class="deploy-status-row"
|
|
||||||
>
|
|
||||||
<clr-icon
|
<clr-icon
|
||||||
*ngIf="autoDeployStatus.deployServicePack === true"
|
*ngIf="autoDeployStatus.deployServicePack"
|
||||||
class="deploy-success"
|
class="deploy-success"
|
||||||
shape="success-standard"
|
shape="success-standard"
|
||||||
></clr-icon>
|
></clr-icon>
|
||||||
<clr-icon
|
<clr-icon
|
||||||
*ngIf="!autoDeployStatus.deployServicePack === false"
|
*ngIf="!autoDeployStatus.deployServicePack"
|
||||||
class="deploy-error"
|
class="deploy-error"
|
||||||
shape="times-circle"
|
shape="times-circle"
|
||||||
></clr-icon>
|
></clr-icon>
|
||||||
@@ -55,7 +52,7 @@
|
|||||||
class="deploy-error"
|
class="deploy-error"
|
||||||
shape="times-circle"
|
shape="times-circle"
|
||||||
></clr-icon>
|
></clr-icon>
|
||||||
LAUNCH
|
LAUNCH / CONFIGURE
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -97,72 +94,20 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label for="dcloc" class="mt-20 clr-control-label">DC Loc</label>
|
<label for="dcloc" class="mt-20 clr-control-label">DC Loc</label>
|
||||||
<div class="mb-10 clr-control-container dc-loc-input-wrapper">
|
<div class="mb-10 clr-control-container">
|
||||||
<div class="clr-input-wrapper small-mt">
|
<div class="clr-input-wrapper">
|
||||||
<input clrInput name="dcloc" [(ngModel)]="dcPath" />
|
<p class="mt-0">{{ dcPath }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label for="dcloc" class="mt-20 clr-control-label">SAS Admin group</label>
|
<label for="dcloc" class="mt-20 clr-control-label">SAS Admin group</label>
|
||||||
<div class="mb-10 clr-control-container">
|
|
||||||
<div class="clr-input-wrapper small-mt">
|
|
||||||
<select
|
|
||||||
*ngIf="!adminGroupsLoading"
|
|
||||||
clrSelect
|
|
||||||
name="options"
|
|
||||||
[(ngModel)]="selectedAdminGroup"
|
|
||||||
>
|
|
||||||
<option *ngFor="let adminGroup of adminGroups" [value]="adminGroup.id">
|
|
||||||
{{ adminGroup.name }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<clr-spinner
|
|
||||||
clrInline
|
|
||||||
class="spinner-sm"
|
|
||||||
*ngIf="adminGroupsLoading"
|
|
||||||
></clr-spinner>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label for="computeContext" class="mt-20 clr-control-label"
|
|
||||||
>Compute Context</label
|
|
||||||
>
|
|
||||||
<div class="mb-10 clr-control-container">
|
|
||||||
<div class="clr-input-wrapper small-mt">
|
|
||||||
<select
|
|
||||||
*ngIf="!computeContextsLoading"
|
|
||||||
clrSelect
|
|
||||||
name="options"
|
|
||||||
(ngModelChange)="onComputeContextChange($event)"
|
|
||||||
[(ngModel)]="selectedComputeContext"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
*ngFor="let computeContext of computeContexts"
|
|
||||||
[value]="computeContext.id"
|
|
||||||
>
|
|
||||||
{{ computeContext.name }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<clr-spinner
|
|
||||||
clrInline
|
|
||||||
class="spinner-sm"
|
|
||||||
*ngIf="computeContextsLoading"
|
|
||||||
></clr-spinner>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ng-container *ngIf="runningAsUser">
|
|
||||||
<label for="dcloc" class="mt-20 clr-control-label">Running as user:</label>
|
|
||||||
<div class="mb-10 clr-control-container">
|
<div class="mb-10 clr-control-container">
|
||||||
<div class="clr-input-wrapper">
|
<div class="clr-input-wrapper">
|
||||||
<p class="mt-0">{{ runningAsUser }}</p>
|
<p class="mt-0">{{ selectedAdminGroup }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<!-- Keeping this for a reference in case future VIYA changes and starts allowing separate backend and frontend) -->
|
<clr-checkbox-wrapper>
|
||||||
|
|
||||||
<!-- <clr-checkbox-wrapper>
|
|
||||||
<input
|
<input
|
||||||
clrCheckbox
|
clrCheckbox
|
||||||
[(ngModel)]="recreateDatabase"
|
[(ngModel)]="recreateDatabase"
|
||||||
@@ -171,28 +116,19 @@
|
|||||||
checked
|
checked
|
||||||
/>
|
/>
|
||||||
<label>Recreate database</label>
|
<label>Recreate database</label>
|
||||||
</clr-checkbox-wrapper> -->
|
</clr-checkbox-wrapper>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<button
|
<button
|
||||||
(click)="runAutoDeploy()"
|
|
||||||
class="btn-autodeploy btn btn-primary d-inline-block mr-10"
|
|
||||||
>
|
|
||||||
Deploy
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Keeping this for a reference in case future VIYA changes and starts allowing separate backend and frontend) -->
|
|
||||||
|
|
||||||
<!-- <button
|
|
||||||
(click)="executeJson()"
|
(click)="executeJson()"
|
||||||
class="btn-autodeploy btn btn-primary d-inline-block mr-10"
|
class="btn-autodeploy btn btn-primary d-inline-block mr-10"
|
||||||
[disabled]="!jsonFile"
|
[disabled]="!jsonFile"
|
||||||
>
|
>
|
||||||
Deploy {{ !jsonFile ? '(json file is not available)' : '' }}
|
Deploy {{ !jsonFile ? '(json file is not available)' : '' }}
|
||||||
</button> -->
|
</button>
|
||||||
|
|
||||||
<!-- <button
|
<button
|
||||||
(click)="uploadJsonAuto.click()"
|
(click)="uploadJsonAuto.click()"
|
||||||
class="btn-autodeploy btn btn-primary d-inline-block mr-10"
|
class="btn-autodeploy btn btn-primary d-inline-block mr-10"
|
||||||
>
|
>
|
||||||
@@ -204,7 +140,7 @@
|
|||||||
hidden
|
hidden
|
||||||
(click)="clearUploadInput($event)"
|
(click)="clearUploadInput($event)"
|
||||||
(change)="onJsonFileChange($event)"
|
(change)="onJsonFileChange($event)"
|
||||||
/> -->
|
/>
|
||||||
|
|
||||||
<clr-modal [(clrModalOpen)]="recreateDatabaseModal" [clrModalClosable]="false">
|
<clr-modal [(clrModalOpen)]="recreateDatabaseModal" [clrModalClosable]="false">
|
||||||
<h3 class="modal-title">Warning</h3>
|
<h3 class="modal-title">Warning</h3>
|
||||||
|
@@ -0,0 +1,61 @@
|
|||||||
|
.auto-deploy {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-box {
|
||||||
|
width: 400px;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #fff;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
box-shadow: 1px 1px 8px 0px #00000082;
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.deploy-status-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
align-self: flex-start;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0 0 0 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.deploy-success {
|
||||||
|
color: #6ECF44;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deploy-error {
|
||||||
|
color: #E74C3C;
|
||||||
|
// width: 20px;
|
||||||
|
// height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deploy-undeterminated {
|
||||||
|
color: #cacaca;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 1px solid #00000045;
|
||||||
|
}
|
@@ -1,35 +1,15 @@
|
|||||||
import {
|
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
|
||||||
Component,
|
|
||||||
EventEmitter,
|
|
||||||
Input,
|
|
||||||
OnInit,
|
|
||||||
Output,
|
|
||||||
ViewEncapsulation
|
|
||||||
} from '@angular/core'
|
|
||||||
import SASjs, { SASjsConfig } from '@sasjs/adapter'
|
import SASjs, { SASjsConfig } from '@sasjs/adapter'
|
||||||
import { DcAdapterSettings } from 'src/app/models/DcAdapterSettings'
|
import { DcAdapterSettings } from 'src/app/models/DcAdapterSettings'
|
||||||
import { HelperService } from 'src/app/services'
|
|
||||||
import { DeployService } from 'src/app/services/deploy.service'
|
import { DeployService } from 'src/app/services/deploy.service'
|
||||||
import { EventService } from 'src/app/services/event.service'
|
import { EventService } from 'src/app/services/event.service'
|
||||||
import { LoggerService } from 'src/app/services/logger.service'
|
import { LoggerService } from 'src/app/services/logger.service'
|
||||||
import { SasViyaService } from 'src/app/services/sas-viya.service'
|
|
||||||
import { SasService } from 'src/app/services/sas.service'
|
import { SasService } from 'src/app/services/sas.service'
|
||||||
import { ViyaApiCurrentUser } from 'src/app/viya-api-explorer/models/viya-api-current-user.model'
|
|
||||||
import {
|
|
||||||
Item,
|
|
||||||
ViyaApiIdentities
|
|
||||||
} from 'src/app/viya-api-explorer/models/viya-api-identities.model'
|
|
||||||
import { ComputeContextDetails } from 'src/app/viya-api-explorer/models/viya-compute-context-details.model'
|
|
||||||
import {
|
|
||||||
ViyaComputeContexts,
|
|
||||||
Item as ComputeContextItem
|
|
||||||
} from 'src/app/viya-api-explorer/models/viya-compute-contexts.model'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-automatic-deploy',
|
selector: 'app-automatic-deploy',
|
||||||
templateUrl: './automatic.component.html',
|
templateUrl: './automatic.component.html',
|
||||||
styleUrls: ['./automatic.component.scss'],
|
styleUrls: ['./automatic.component.scss']
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class AutomaticComponent implements OnInit {
|
export class AutomaticComponent implements OnInit {
|
||||||
@Input() sasJs!: SASjs
|
@Input() sasJs!: SASjs
|
||||||
@@ -41,7 +21,6 @@ export class AutomaticComponent implements OnInit {
|
|||||||
|
|
||||||
@Output() onNavigateToHome: EventEmitter<any> = new EventEmitter<any>()
|
@Output() onNavigateToHome: EventEmitter<any> = new EventEmitter<any>()
|
||||||
|
|
||||||
public selectedComputeContext: string = ''
|
|
||||||
public makeDataResponse: string = ''
|
public makeDataResponse: string = ''
|
||||||
public jsonFile: any = null
|
public jsonFile: any = null
|
||||||
public autodeploying: boolean = false
|
public autodeploying: boolean = false
|
||||||
@@ -49,19 +28,8 @@ export class AutomaticComponent implements OnInit {
|
|||||||
public recreateDatabaseModal: boolean = false
|
public recreateDatabaseModal: boolean = false
|
||||||
public isSubmittingJson: boolean = false
|
public isSubmittingJson: boolean = false
|
||||||
public isJsonSubmitted: boolean = false
|
public isJsonSubmitted: boolean = false
|
||||||
/**
|
public recreateDatabase: boolean = false
|
||||||
* Default was `false` when deploy was done with frontend and backend separately.
|
|
||||||
* Now we are using only streaming app, so we always want to recreate database (makedata)
|
|
||||||
*/
|
|
||||||
public recreateDatabase: boolean = true
|
|
||||||
public createDatabaseLoading: boolean = false
|
public createDatabaseLoading: boolean = false
|
||||||
public adminGroupsLoading: boolean = false
|
|
||||||
public currentUserInfoLoading: boolean = false
|
|
||||||
public computeContextsLoading: boolean = false
|
|
||||||
public adminGroups: { id: string; name: string }[] = []
|
|
||||||
public runningAsUser: string | undefined
|
|
||||||
public currentUserInfo: ViyaApiCurrentUser | null = null
|
|
||||||
public computeContexts: ComputeContextItem[] = []
|
|
||||||
|
|
||||||
/** autoDeployStatus
|
/** autoDeployStatus
|
||||||
* This object presents the status for two steps that we have for deploy.
|
* This object presents the status for two steps that we have for deploy.
|
||||||
@@ -78,138 +46,14 @@ export class AutomaticComponent implements OnInit {
|
|||||||
runMakeData: null
|
runMakeData: null
|
||||||
}
|
}
|
||||||
|
|
||||||
public sasjsConfig = this.sasService.getSasjsConfig()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* makedata service will be run in a new window
|
|
||||||
* This is needed to ensure that the user can see the logs
|
|
||||||
* and the progress of the service execution.
|
|
||||||
* If this is set to `false`, the service will be run in the same window
|
|
||||||
* using the adapter request method.
|
|
||||||
*/
|
|
||||||
public deployInNewWindow: boolean = true
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private eventService: EventService,
|
private eventService: EventService,
|
||||||
private deployService: DeployService,
|
private deployService: DeployService,
|
||||||
private sasService: SasService,
|
private sasService: SasService,
|
||||||
private sasViyaService: SasViyaService,
|
private loggerService: LoggerService
|
||||||
private loggerService: LoggerService,
|
|
||||||
private helperService: HelperService
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {}
|
||||||
this.loadData()
|
|
||||||
}
|
|
||||||
|
|
||||||
public async loadData() {
|
|
||||||
await this.getAdminGroups()
|
|
||||||
await this.getComputeContexts()
|
|
||||||
await this.getCurrentUser()
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
if (this.selectedComputeContext) {
|
|
||||||
this.onComputeContextChange(this.selectedComputeContext)
|
|
||||||
}
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getComputeContexts() {
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
|
||||||
this.computeContextsLoading = true
|
|
||||||
|
|
||||||
this.sasViyaService.getComputeContexts().subscribe(
|
|
||||||
(res: ViyaComputeContexts) => {
|
|
||||||
this.computeContextsLoading = false
|
|
||||||
|
|
||||||
const defaultContext = res.items.find(
|
|
||||||
(item: ComputeContextItem) =>
|
|
||||||
item.name === 'SAS Job Execution compute context'
|
|
||||||
)
|
|
||||||
|
|
||||||
if (defaultContext) {
|
|
||||||
this.selectedComputeContext = defaultContext.id
|
|
||||||
}
|
|
||||||
|
|
||||||
this.computeContexts = res.items
|
|
||||||
|
|
||||||
resolve()
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getCurrentUser() {
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
|
||||||
this.currentUserInfoLoading = true
|
|
||||||
|
|
||||||
this.sasViyaService.getCurrentUser().subscribe(
|
|
||||||
(res: ViyaApiCurrentUser) => {
|
|
||||||
this.currentUserInfoLoading = false
|
|
||||||
|
|
||||||
this.currentUserInfo = res
|
|
||||||
|
|
||||||
this.dcPath = `/export/viya/homes/${res.id}`
|
|
||||||
|
|
||||||
resolve()
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
console.error('Error while getting current user', err)
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getAdminGroups() {
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
|
||||||
this.adminGroupsLoading = true
|
|
||||||
;(this.sasViyaService
|
|
||||||
.getAdminGroups()
|
|
||||||
.subscribe((res: ViyaApiIdentities) => {
|
|
||||||
this.adminGroupsLoading = false
|
|
||||||
// Map admin groups with only needed fields
|
|
||||||
this.adminGroups = res.items.map((item: Item) => {
|
|
||||||
return {
|
|
||||||
id: item.id,
|
|
||||||
name: item.name
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
resolve()
|
|
||||||
}),
|
|
||||||
(err: any) => {
|
|
||||||
this.adminGroupsLoading = false
|
|
||||||
this.loggerService.error('Error while getting admin groups', err)
|
|
||||||
this.eventService.showAbortModal('admin groups', err)
|
|
||||||
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public async onComputeContextChange(computeContextId: string) {
|
|
||||||
this.sasViyaService
|
|
||||||
.getComputeContextById(computeContextId)
|
|
||||||
.subscribe((res: ComputeContextDetails) => {
|
|
||||||
if (res.attributes && res.attributes.runServerAs) {
|
|
||||||
this.runningAsUser = res.attributes.runServerAs
|
|
||||||
} else {
|
|
||||||
this.runningAsUser = this.currentUserInfo?.id || 'unknown'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public getComputeContextName(id: string): string | undefined {
|
|
||||||
return (
|
|
||||||
this.computeContexts.find(
|
|
||||||
(context: ComputeContextItem) => context.id === id
|
|
||||||
)?.name || undefined
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes sas.json file to deploy the backend
|
* Executes sas.json file to deploy the backend
|
||||||
@@ -219,6 +63,7 @@ export class AutomaticComponent implements OnInit {
|
|||||||
* to create database if checkbox is toggled on
|
* to create database if checkbox is toggled on
|
||||||
*/
|
*/
|
||||||
public async executeJson() {
|
public async executeJson() {
|
||||||
|
this.autodeploying = true
|
||||||
this.isSubmittingJson = true
|
this.isSubmittingJson = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -253,19 +98,11 @@ export class AutomaticComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.isSubmittingJson = false
|
this.isSubmittingJson = false
|
||||||
}
|
|
||||||
|
|
||||||
public async runAutoDeploy(executeJson: boolean = false) {
|
|
||||||
if (!this.deployInNewWindow) this.autodeploying = true
|
|
||||||
|
|
||||||
if (executeJson) {
|
|
||||||
this.executeJson()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.recreateDatabase) {
|
if (this.recreateDatabase) {
|
||||||
this.createDatabase()
|
this.createDatabase()
|
||||||
} else {
|
} else {
|
||||||
if (!this.deployInNewWindow) this.autodeployDone = true
|
this.autodeployDone = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,37 +119,17 @@ export class AutomaticComponent implements OnInit {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get and run service using the selected context name
|
|
||||||
let selectedComputeContextName = this.sasJsConfig.contextName
|
|
||||||
|
|
||||||
if (this.selectedComputeContext.length && this.computeContexts.length) {
|
|
||||||
const computeContextName = this.getComputeContextName(
|
|
||||||
this.selectedComputeContext
|
|
||||||
)
|
|
||||||
|
|
||||||
if (computeContextName) {
|
|
||||||
selectedComputeContextName = computeContextName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We are overriding default `sasjsConfig` object fields with this object fields.
|
* We are overriding default `sasjsConfig` object fields with this object fields.
|
||||||
* Here we want to run this request using original WEB method.
|
* Here we want to run this request using original WEB method.
|
||||||
* contextName: null is the MUST field for it.
|
* contextName: null is the MUST field for it.
|
||||||
*/
|
*/
|
||||||
let overrideConfig = {
|
let overrideConfig = {
|
||||||
useComputeApi: null,
|
useComputeApi: false,
|
||||||
contextName: selectedComputeContextName,
|
contextName: this.sasJsConfig.contextName,
|
||||||
debug: true
|
debug: true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.deployInNewWindow) {
|
|
||||||
this.runMakedataInNewWindow({
|
|
||||||
contextName: selectedComputeContextName,
|
|
||||||
admin: this.selectedAdminGroup,
|
|
||||||
dcPath: this.dcPath
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.sasJs
|
this.sasJs
|
||||||
.request(`services/admin/makedata`, data, overrideConfig, () => {
|
.request(`services/admin/makedata`, data, overrideConfig, () => {
|
||||||
this.sasService.shouldLogin.next(true)
|
this.sasService.shouldLogin.next(true)
|
||||||
@@ -331,24 +148,8 @@ export class AutomaticComponent implements OnInit {
|
|||||||
} else {
|
} else {
|
||||||
this.autoDeployStatus.runMakeData = false
|
this.autoDeployStatus.runMakeData = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof res.sasjsAbort !== 'undefined') {
|
|
||||||
const abortRes = res
|
|
||||||
const abortMsg = abortRes.sasjsAbort[0].MSG
|
|
||||||
const macMsg = abortRes.sasjsAbort[0].MAC
|
|
||||||
|
|
||||||
this.eventService.showAbortModal('makedata', abortMsg, {
|
|
||||||
SYSWARNINGTEXT: abortRes.SYSWARNINGTEXT,
|
|
||||||
SYSERRORTEXT: abortRes.SYSERRORTEXT,
|
|
||||||
MAC: macMsg
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.helperService.isStreamingViya())
|
|
||||||
this.updateIndexHtmlComputeContext()
|
|
||||||
})
|
})
|
||||||
.catch((err: any) => {
|
.catch((err: any) => {
|
||||||
this.eventService.showAbortModal('makedata', JSON.stringify(err))
|
|
||||||
this.autoDeployStatus.runMakeData = false
|
this.autoDeployStatus.runMakeData = false
|
||||||
this.autodeployDone = true
|
this.autodeployDone = true
|
||||||
|
|
||||||
@@ -359,85 +160,6 @@ export class AutomaticComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public runMakedataInNewWindow(params: {
|
|
||||||
contextName: string
|
|
||||||
admin: string
|
|
||||||
dcPath: string
|
|
||||||
}) {
|
|
||||||
let serverUrl = this.sasjsConfig.serverUrl
|
|
||||||
let appLoc = this.sasjsConfig.appLoc
|
|
||||||
const execPath = this.sasService.getExecutionPath()
|
|
||||||
let contextname = `&_contextname=${params.contextName}`
|
|
||||||
let admin = `&admin=${params.admin}`
|
|
||||||
let dcPath = `&dcpath=${params.dcPath}`
|
|
||||||
let debug = `&_debug=131`
|
|
||||||
|
|
||||||
let programUrl =
|
|
||||||
serverUrl +
|
|
||||||
execPath +
|
|
||||||
'/?_program=' +
|
|
||||||
appLoc +
|
|
||||||
'/services/admin/makedata' +
|
|
||||||
contextname +
|
|
||||||
admin +
|
|
||||||
dcPath +
|
|
||||||
debug
|
|
||||||
|
|
||||||
window.open(programUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Only when on Viya streamed app, this method will update the `contextname` in the `index.html` on the SAS drive
|
|
||||||
* This is needed to ensure that the DC will use the same compute context `makedata` service used to run against.
|
|
||||||
*/
|
|
||||||
public async updateIndexHtmlComputeContext() {
|
|
||||||
const filenamePath = location.search.split('/').pop()
|
|
||||||
const filename = filenamePath?.includes('.') ? filenamePath : undefined
|
|
||||||
|
|
||||||
if (!filename) {
|
|
||||||
this.eventService.showAbortModal(
|
|
||||||
null,
|
|
||||||
'We could not figure out the file name of `index.html` based on the url.'
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const indexHtmlContent = await this.sasService.getFileContent(
|
|
||||||
`${this.appLoc}/services`,
|
|
||||||
filename
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!indexHtmlContent) {
|
|
||||||
this.loggerService.error(
|
|
||||||
`Failed to get ${filename} at ${this.appLoc}/services`
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const computeContextName = this.getComputeContextName(
|
|
||||||
this.selectedComputeContext
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!computeContextName) {
|
|
||||||
this.loggerService.error(
|
|
||||||
`Compute context name not found for ID: ${this.selectedComputeContext} | List: ${JSON.stringify(this.computeContexts)}`
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedContent = indexHtmlContent.replace(
|
|
||||||
/contextname="[^"]*"/g,
|
|
||||||
`contextname="${computeContextName}"`
|
|
||||||
)
|
|
||||||
|
|
||||||
await this.sasService
|
|
||||||
.updateFileContent(`${this.appLoc}/services`, filename, updatedContent)
|
|
||||||
.catch((err: any) => {
|
|
||||||
this.loggerService.error(`Failed to update DataController.html: ${err}`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public downloadFile(
|
public downloadFile(
|
||||||
content: any,
|
content: any,
|
||||||
|
@@ -0,0 +1,4 @@
|
|||||||
|
.clear-memory-button {
|
||||||
|
right: 10px;
|
||||||
|
top: 2px;
|
||||||
|
}
|
@@ -1,11 +1,4 @@
|
|||||||
import {
|
import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core'
|
||||||
Component,
|
|
||||||
Input,
|
|
||||||
OnInit,
|
|
||||||
Output,
|
|
||||||
EventEmitter,
|
|
||||||
ViewEncapsulation
|
|
||||||
} from '@angular/core'
|
|
||||||
import SASjs, { SASjsConfig } from '@sasjs/adapter'
|
import SASjs, { SASjsConfig } from '@sasjs/adapter'
|
||||||
import { DcAdapterSettings } from 'src/app/models/DcAdapterSettings'
|
import { DcAdapterSettings } from 'src/app/models/DcAdapterSettings'
|
||||||
import { RequestWrapperResponse } from 'src/app/models/request-wrapper/RequestWrapperResponse'
|
import { RequestWrapperResponse } from 'src/app/models/request-wrapper/RequestWrapperResponse'
|
||||||
@@ -17,8 +10,7 @@ import { SasService } from 'src/app/services/sas.service'
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-manual-deploy',
|
selector: 'app-manual-deploy',
|
||||||
templateUrl: './manual.component.html',
|
templateUrl: './manual.component.html',
|
||||||
styleUrls: ['./manual.component.scss'],
|
styleUrls: ['./manual.component.scss']
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class ManualComponent implements OnInit {
|
export class ManualComponent implements OnInit {
|
||||||
@Input() sasJs!: SASjs
|
@Input() sasJs!: SASjs
|
||||||
@@ -274,7 +266,7 @@ export class ManualComponent implements OnInit {
|
|||||||
* contextName: null is the MUST field for it.
|
* contextName: null is the MUST field for it.
|
||||||
*/
|
*/
|
||||||
let overrideConfig = {
|
let overrideConfig = {
|
||||||
useComputeApi: null,
|
useComputeApi: false,
|
||||||
contextName: this.sasJsConfig.contextName,
|
contextName: this.sasJsConfig.contextName,
|
||||||
debug: true
|
debug: true
|
||||||
}
|
}
|
||||||
|
@@ -10,13 +10,11 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="m-0 mt-10">
|
<p class="m-0 mt-10">
|
||||||
Please specify a physical directory (on the
|
Please specify a physical directory below, to which user
|
||||||
<strong> {{ SYSHOSTNAME }}</strong>
|
<strong>{{ SYSUSERID }}</strong> can write, on behalf of Data Controller:
|
||||||
compute server) below, to which user
|
|
||||||
<strong>{{ SYSUSERID }}</strong> can write, on behalf of Data Controller.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<label class="mt-20 clr-control-label">DC Staging Directory</label>
|
<label class="mt-20 clr-control-label">DC Directory</label>
|
||||||
<div class="mb-10 clr-control-container">
|
<div class="mb-10 clr-control-container">
|
||||||
<div class="clr-input-wrapper">
|
<div class="clr-input-wrapper">
|
||||||
<input
|
<input
|
||||||
|
@@ -0,0 +1,23 @@
|
|||||||
|
.clr-control-container {
|
||||||
|
width: 50vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clr-input-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.thinProgress {
|
||||||
|
left: 0px;
|
||||||
|
right: 0;
|
||||||
|
width: unset;
|
||||||
|
height: 1px;
|
||||||
|
margin-top: 0 !important;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,12 +1,5 @@
|
|||||||
import { Location } from '@angular/common'
|
import { Location } from '@angular/common'
|
||||||
import {
|
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
|
||||||
Component,
|
|
||||||
EventEmitter,
|
|
||||||
Input,
|
|
||||||
OnInit,
|
|
||||||
Output,
|
|
||||||
ViewEncapsulation
|
|
||||||
} from '@angular/core'
|
|
||||||
import SASjs, { SASjsConfig } from '@sasjs/adapter'
|
import SASjs, { SASjsConfig } from '@sasjs/adapter'
|
||||||
import { ServerType } from '@sasjs/utils/types/serverType'
|
import { ServerType } from '@sasjs/utils/types/serverType'
|
||||||
import { DcAdapterSettings } from 'src/app/models/DcAdapterSettings'
|
import { DcAdapterSettings } from 'src/app/models/DcAdapterSettings'
|
||||||
@@ -19,8 +12,7 @@ import { SasjsService } from 'src/app/services/sasjs.service'
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-sasjs-configurator',
|
selector: 'app-sasjs-configurator',
|
||||||
templateUrl: './sasjs-configurator.component.html',
|
templateUrl: './sasjs-configurator.component.html',
|
||||||
styleUrls: ['./sasjs-configurator.component.scss'],
|
styleUrls: ['./sasjs-configurator.component.scss']
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class SasjsConfiguratorComponent implements OnInit {
|
export class SasjsConfiguratorComponent implements OnInit {
|
||||||
@Input() sasJs!: SASjs
|
@Input() sasJs!: SASjs
|
||||||
|
@@ -0,0 +1,238 @@
|
|||||||
|
.record-edit-modal {
|
||||||
|
.column-entry {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
.name-input-row {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 260px;
|
||||||
|
|
||||||
|
.cell-desc {
|
||||||
|
margin-right: 30px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputs-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
::ng-deep >*:not(.date-field):not(clr-select-container) {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep {
|
||||||
|
.clr-textarea-wrapper {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clr-form-control {
|
||||||
|
margin-top: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
app-soft-select {
|
||||||
|
display: block;
|
||||||
|
width: 224px;
|
||||||
|
border: 1px solid #999;
|
||||||
|
color: #000;
|
||||||
|
padding: calc(.25rem + 2px) .5rem;
|
||||||
|
border-radius: .125rem;
|
||||||
|
font-size: .541667rem;
|
||||||
|
margin-right: 6px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
border: 0;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background: none;
|
||||||
|
border: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-outer-spin-button,
|
||||||
|
&::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child p:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-field {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
width: 230px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 4px;
|
||||||
|
|
||||||
|
::ng-deep {
|
||||||
|
// clr-datepicker-view-manager {
|
||||||
|
// transform: unset !important;
|
||||||
|
// left: unset !important;
|
||||||
|
// right: 70px !important;
|
||||||
|
// }
|
||||||
|
.clr-input-group {
|
||||||
|
border: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep {
|
||||||
|
clr-select-container {
|
||||||
|
border: 1px solid #999;
|
||||||
|
color: #000;
|
||||||
|
border-radius: .125rem;
|
||||||
|
margin-right: 5px;
|
||||||
|
|
||||||
|
.clr-select-wrapper {
|
||||||
|
max-height: unset;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
top: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
height: auto;
|
||||||
|
padding: 10px;
|
||||||
|
padding-right: 20px;
|
||||||
|
border: 0 !important;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background: 0 0 !important;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clr-input-container {
|
||||||
|
width: 224px;
|
||||||
|
border: 1px solid #999;
|
||||||
|
color: #000;
|
||||||
|
padding: calc(.25rem + 2px) .5rem;
|
||||||
|
border-radius: .125rem;
|
||||||
|
font-size: .541667rem;
|
||||||
|
margin-right: 6px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
border: 0;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background: none;
|
||||||
|
border: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-outer-spin-button,
|
||||||
|
&::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.invalid-data {
|
||||||
|
border-color: red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-dialog {
|
||||||
|
width: 80vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clr-control-container {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
resize: none;
|
||||||
|
border-color: #999;
|
||||||
|
|
||||||
|
&.invalid-data {
|
||||||
|
border-color: red;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.not-char {
|
||||||
|
font-family: "Lucida Console", Monaco, monospace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.generate-record-url {
|
||||||
|
right: 40px;
|
||||||
|
top: 40px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.generate-record-url-button {
|
||||||
|
right: 25px;
|
||||||
|
top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
padding: 0 0 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
// height: 65px;
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.prev-next {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 0px 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.focusable {
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0 0 3px 0px #5aa220;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-input-left-offset {
|
||||||
|
left: -30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.validation-info-alert {
|
||||||
|
width: 310px
|
||||||
|
}
|
@@ -1,12 +1,5 @@
|
|||||||
import { KeyValue } from '@angular/common'
|
import { KeyValue } from '@angular/common'
|
||||||
import {
|
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
|
||||||
Component,
|
|
||||||
EventEmitter,
|
|
||||||
Input,
|
|
||||||
OnInit,
|
|
||||||
Output,
|
|
||||||
ViewEncapsulation
|
|
||||||
} from '@angular/core'
|
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { ValidateFilterSASResponse } from 'src/app/models/sas/validate-filter.model'
|
import { ValidateFilterSASResponse } from 'src/app/models/sas/validate-filter.model'
|
||||||
import { QueryClause } from 'src/app/models/TableData'
|
import { QueryClause } from 'src/app/models/TableData'
|
||||||
@@ -23,8 +16,7 @@ import { EditRecordModal } from '../../models/EditRecordModal'
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-edit-record',
|
selector: 'app-edit-record',
|
||||||
templateUrl: './edit-record.component.html',
|
templateUrl: './edit-record.component.html',
|
||||||
styleUrls: ['./edit-record.component.scss'],
|
styleUrls: ['./edit-record.component.scss']
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class EditRecordComponent implements OnInit {
|
export class EditRecordComponent implements OnInit {
|
||||||
@Input() currentRecord!: EditRecordModal
|
@Input() currentRecord!: EditRecordModal
|
||||||
|
@@ -0,0 +1,8 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core'
|
import { Component, OnInit } from '@angular/core'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Goal of this component is to recieve array of strings where every element is one state
|
* Goal of this component is to recieve array of strings where every element is one state
|
||||||
@@ -10,8 +10,7 @@ import { Component, OnInit, ViewEncapsulation } from '@angular/core'
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-upload-stater',
|
selector: 'app-upload-stater',
|
||||||
templateUrl: './upload-stater.component.html',
|
templateUrl: './upload-stater.component.html',
|
||||||
styleUrls: ['./upload-stater.component.scss'],
|
styleUrls: ['./upload-stater.component.scss']
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class UploadStaterComponent implements OnInit {
|
export class UploadStaterComponent implements OnInit {
|
||||||
public statesList: string[] = [] //States appended to be displayed
|
public statesList: string[] = [] //States appended to be displayed
|
||||||
|
@@ -36,7 +36,7 @@
|
|||||||
<div class="clr-row card-block mt-15 d-flex justify-content-between">
|
<div class="clr-row card-block mt-15 d-flex justify-content-between">
|
||||||
<div class="clr-col-md-auto">
|
<div class="clr-col-md-auto">
|
||||||
<div class="encoding-block">
|
<div class="encoding-block">
|
||||||
<clr-radio-container class="mt-0" clrInline>
|
<clr-radio-container class="mt-0-i" clrInline>
|
||||||
<clr-radio-wrapper>
|
<clr-radio-wrapper>
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
@@ -193,14 +193,13 @@
|
|||||||
libName: (libds?.split('.'))![0],
|
libName: (libds?.split('.'))![0],
|
||||||
tableName: (libds?.split('.'))![1]
|
tableName: (libds?.split('.'))![1]
|
||||||
} as libdsParsed"
|
} as libdsParsed"
|
||||||
class="editor-title text-center mt-0"
|
class="editor-title text-center mt-0-i"
|
||||||
>
|
>
|
||||||
<clr-tooltip>
|
<clr-tooltip>
|
||||||
<clr-icon
|
<clr-icon
|
||||||
clrTooltipTrigger
|
clrTooltipTrigger
|
||||||
(click)="datasetInfo = true"
|
(click)="datasetInfo = true"
|
||||||
shape="info-circle"
|
shape="info-circle"
|
||||||
aria-label="View dataset meta info"
|
|
||||||
class="is-highlight cursor-pointer"
|
class="is-highlight cursor-pointer"
|
||||||
size="24"
|
size="24"
|
||||||
></clr-icon>
|
></clr-icon>
|
||||||
@@ -408,11 +407,12 @@
|
|||||||
<div class="hot-wrapper clr-flex-1">
|
<div class="hot-wrapper clr-flex-1">
|
||||||
<hot-table
|
<hot-table
|
||||||
#hotInstance
|
#hotInstance
|
||||||
|
hotId="hotInstance"
|
||||||
id="hotTable"
|
id="hotTable"
|
||||||
class="edit-hot"
|
class="edit-hot"
|
||||||
|
className="htDark"
|
||||||
[class.hidden]="hotTable.hidden"
|
[class.hidden]="hotTable.hidden"
|
||||||
[data]="hotTable.data"
|
[licenseKey]="hotTable.licenseKey"
|
||||||
[settings]="hotTableSettings"
|
|
||||||
>
|
>
|
||||||
</hot-table>
|
</hot-table>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -0,0 +1,246 @@
|
|||||||
|
.card {
|
||||||
|
margin-top: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonBar {
|
||||||
|
padding: 2px 10px 2px 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.testRed {
|
||||||
|
color: white;
|
||||||
|
background: rgba(255,0,0, 0.8) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
hot-table {
|
||||||
|
::ng-deep {
|
||||||
|
.firstColumnHeaderStyle button.changeType {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handsontable tbody th.ht__highlight, .handsontable thead th.ht__highlight {
|
||||||
|
&.primaryKeyHeaderStyle {
|
||||||
|
background-color: #306b00b0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.primaryKeyHeaderStyle {
|
||||||
|
background-color: #306b006e !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
th.readonlyCell {
|
||||||
|
div {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
td.readonlyCell {
|
||||||
|
opacity: 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-reason {
|
||||||
|
min-height: 120px;
|
||||||
|
max-height: 120px;
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoBar {
|
||||||
|
margin-top:14px;
|
||||||
|
background: #495967;
|
||||||
|
color: white;
|
||||||
|
text-align:center;
|
||||||
|
padding: 3px;
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
height: 30px;
|
||||||
|
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
span {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
height: unset;
|
||||||
|
white-space: normal;
|
||||||
|
|
||||||
|
span {
|
||||||
|
width: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pkHeader {
|
||||||
|
background: #687682;
|
||||||
|
color: #fff;
|
||||||
|
margin: -1px -1px -1px -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerBar {
|
||||||
|
// padding: 13px 0px 14px 0px;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
-ms-flex-align: center;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--clr-vertical-nav-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-icon {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnCtrl {
|
||||||
|
display:flex;
|
||||||
|
justify-content:flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
border-bottom: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-drop-zone {
|
||||||
|
border: solid 1px lightgray;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: whitesmoke;
|
||||||
|
box-shadow: inset 0px 0px 4px 2px #a7a5a52b;
|
||||||
|
height: 50vh;
|
||||||
|
}
|
||||||
|
.nv-file-over {
|
||||||
|
border: solid 2px green;
|
||||||
|
} /* Default class applied to drop zones on over */
|
||||||
|
|
||||||
|
.file-drop-text{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-file-over {
|
||||||
|
border: solid 2px green;
|
||||||
|
} /* Default class applied to drop zones on over */
|
||||||
|
|
||||||
|
.file-drop-text{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
|
||||||
|
.progresStatic {
|
||||||
|
margin-top:9px!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress, .progress-static {
|
||||||
|
width: calc(100% - 14px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hotEditor {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.excel-parsing {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.details {
|
||||||
|
margin: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: -45px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-record-spinner {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: rgba(255, 255, 255, 0.6);
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 480px) {
|
||||||
|
|
||||||
|
.progresStatic {
|
||||||
|
margin-top:32px!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-block, .card-footer {
|
||||||
|
padding: 10px 0px 0px 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-area {
|
||||||
|
padding: 0 0.8rem 0.8rem 0.8rem !important;
|
||||||
|
padding-top: 0;
|
||||||
|
|
||||||
|
// .card {
|
||||||
|
// min-height: calc(100vh - 160px);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-area {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
margin: 1px;
|
||||||
|
|
||||||
|
border: 2px dashed #fff;
|
||||||
|
|
||||||
|
z-index: -1;
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 10px;
|
||||||
|
background: #dbdbdb;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#submitBtn, #cancelSubmitBtn {
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-table {
|
||||||
|
font-size: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When width is smaller remove the text from the buttons
|
||||||
|
// keep only the icons
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.icon-collapse {
|
||||||
|
.text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME
|
||||||
|
// Let's leave it here for a reference if there
|
||||||
|
// is an issue with viewboxes/filter modal overlaying
|
||||||
|
// we will remove it if no issues found
|
||||||
|
// .filter-modal {
|
||||||
|
// z-index: 1210;
|
||||||
|
// }
|
@@ -3,7 +3,6 @@ import {
|
|||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
Component,
|
Component,
|
||||||
ElementRef,
|
ElementRef,
|
||||||
OnDestroy,
|
|
||||||
OnInit,
|
OnInit,
|
||||||
QueryList,
|
QueryList,
|
||||||
ViewChild,
|
ViewChild,
|
||||||
@@ -17,7 +16,7 @@ import { SasStoreService } from '../services/sas-store.service'
|
|||||||
|
|
||||||
type AOA = any[][]
|
type AOA = any[][]
|
||||||
|
|
||||||
import { HotTableComponent } from '@handsontable/angular-wrapper'
|
import { HotTableRegisterer } from '@handsontable/angular'
|
||||||
import { UploadFile } from '@sasjs/adapter'
|
import { UploadFile } from '@sasjs/adapter'
|
||||||
import { isSpecialMissing } from '@sasjs/utils/input/validators'
|
import { isSpecialMissing } from '@sasjs/utils/input/validators'
|
||||||
import CellRange from 'handsontable/3rdparty/walkontable/src/cell/range'
|
import CellRange from 'handsontable/3rdparty/walkontable/src/cell/range'
|
||||||
@@ -70,15 +69,15 @@ import { ParseResult } from '../models/ParseResult.interface'
|
|||||||
host: {
|
host: {
|
||||||
class: 'content-container'
|
class: 'content-container'
|
||||||
},
|
},
|
||||||
encapsulation: ViewEncapsulation.None
|
encapsulation: ViewEncapsulation.Emulated
|
||||||
})
|
})
|
||||||
export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
export class EditorComponent implements OnInit, AfterViewInit {
|
||||||
@ViewChildren('uploadStater')
|
@ViewChildren('uploadStater')
|
||||||
uploadStaterCompList: QueryList<UploadStaterComponent> = new QueryList()
|
uploadStaterCompList: QueryList<UploadStaterComponent> = new QueryList()
|
||||||
@ViewChildren('queryFilter')
|
@ViewChildren('queryFilter')
|
||||||
queryFilterCompList: QueryList<QueryComponent> = new QueryList()
|
queryFilterCompList: QueryList<QueryComponent> = new QueryList()
|
||||||
@ViewChild(HotTableComponent, { static: false })
|
@ViewChildren('hotInstance')
|
||||||
hotTableComponent!: HotTableComponent
|
hotInstanceCompList: QueryList<Handsontable> = new QueryList()
|
||||||
@ViewChildren('fileUploadInput')
|
@ViewChildren('fileUploadInput')
|
||||||
fileUploadInputCompList: QueryList<ElementRef> = new QueryList()
|
fileUploadInputCompList: QueryList<ElementRef> = new QueryList()
|
||||||
|
|
||||||
@@ -120,26 +119,13 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
public hotInstance!: Handsontable
|
public hotInstance!: Handsontable
|
||||||
public dcValidator: DcValidator | undefined
|
public dcValidator: DcValidator | undefined
|
||||||
|
|
||||||
public hotTableSettings: Handsontable.GridSettings = {}
|
|
||||||
|
|
||||||
private updateHotTableSettings(): void {
|
|
||||||
this.hotTableSettings = {
|
|
||||||
colHeaders: this.hotTable.colHeaders,
|
|
||||||
columns: this.hotTable.columns,
|
|
||||||
height: this.hotTable.height,
|
|
||||||
licenseKey: this.hotTable.licenseKey,
|
|
||||||
readOnly: this.hotTable.readOnly,
|
|
||||||
copyPaste: this.hotTable.copyPaste,
|
|
||||||
contextMenu: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public hotTable: HotTableInterface = {
|
public hotTable: HotTableInterface = {
|
||||||
data: [],
|
data: [],
|
||||||
colHeaders: [],
|
colHeaders: [],
|
||||||
hidden: true,
|
hidden: true,
|
||||||
columns: [],
|
columns: [],
|
||||||
height: 'calc(100vh - 160px)',
|
height: '100%',
|
||||||
|
minSpareRows: 1,
|
||||||
licenseKey: undefined,
|
licenseKey: undefined,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
copyPaste: {
|
copyPaste: {
|
||||||
@@ -176,30 +162,10 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
row_above: {
|
row_above: {
|
||||||
name: 'Insert Row above',
|
name: 'Insert Row above'
|
||||||
callback: (
|
|
||||||
key: string,
|
|
||||||
selection: any[],
|
|
||||||
clickEvent: MouseEvent
|
|
||||||
) => {
|
|
||||||
const firstSelection = selection[0]
|
|
||||||
const targetRow = firstSelection.start.row
|
|
||||||
|
|
||||||
this.insertRowAtPosition(targetRow)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
row_below: {
|
row_below: {
|
||||||
name: 'Insert Row below',
|
name: 'Insert Row below'
|
||||||
callback: (
|
|
||||||
key: string,
|
|
||||||
selection: any[],
|
|
||||||
clickEvent: MouseEvent
|
|
||||||
) => {
|
|
||||||
const firstSelection = selection[0]
|
|
||||||
const targetRow = firstSelection.start.row + 1
|
|
||||||
|
|
||||||
this.insertRowAtPosition(targetRow)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
remove_row: {
|
remove_row: {
|
||||||
name: 'Ignore row'
|
name: 'Ignore row'
|
||||||
@@ -384,9 +350,6 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
|
|
||||||
public licenceState = this.licenceService.licenceState
|
public licenceState = this.licenceService.licenceState
|
||||||
|
|
||||||
private ariaObserver: MutationObserver | undefined
|
|
||||||
private ariaCheckInterval: any | undefined
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private licenceService: LicenceService,
|
private licenceService: LicenceService,
|
||||||
private eventService: EventService,
|
private eventService: EventService,
|
||||||
@@ -397,12 +360,15 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private sasService: SasService,
|
private sasService: SasService,
|
||||||
private cdf: ChangeDetectorRef,
|
private cdf: ChangeDetectorRef,
|
||||||
|
private hotRegisterer: HotTableRegisterer,
|
||||||
private spreadsheetService: SpreadsheetService
|
private spreadsheetService: SpreadsheetService
|
||||||
) {
|
) {
|
||||||
const lang = languages[window.navigator.language]
|
const lang = languages[window.navigator.language]
|
||||||
if (lang)
|
if (lang)
|
||||||
numbro.default.registerLanguage(languages[window.navigator.language])
|
numbro.default.registerLanguage(languages[window.navigator.language])
|
||||||
|
|
||||||
|
this.hotRegisterer = new HotTableRegisterer()
|
||||||
|
|
||||||
this.parseRestrictions()
|
this.parseRestrictions()
|
||||||
this.setRestrictions()
|
this.setRestrictions()
|
||||||
}
|
}
|
||||||
@@ -930,11 +896,6 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.reSetCellValidationValues()
|
this.reSetCellValidationValues()
|
||||||
|
|
||||||
// Fix ARIA accessibility issues after table edit
|
|
||||||
setTimeout(() => {
|
|
||||||
this.fixAriaAccessibility()
|
|
||||||
}, 100)
|
|
||||||
}, 0)
|
}, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -961,9 +922,6 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
|
|
||||||
this.cellValidationSource = []
|
this.cellValidationSource = []
|
||||||
|
|
||||||
// Clear custom validation styling
|
|
||||||
this.clearDuplicateValidation()
|
|
||||||
|
|
||||||
const hot = this.hotInstance
|
const hot = this.hotInstance
|
||||||
const columnSorting = hot.getPlugin('multiColumnSorting')
|
const columnSorting = hot.getPlugin('multiColumnSorting')
|
||||||
const columnSortConfig = columnSorting.getSortConfig()
|
const columnSortConfig = columnSorting.getSortConfig()
|
||||||
@@ -1024,54 +982,22 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const hot = this.hotInstance
|
const hot = this.hotInstance
|
||||||
|
|
||||||
// Create a new empty row object with proper structure
|
const dsInsertIndex = this.dataSource.length
|
||||||
const newRow = this.createEmptyRow()
|
hot.alter('insert_row_below', dsInsertIndex, 1)
|
||||||
|
|
||||||
// Add the new row to the data source
|
|
||||||
this.dataSource.push(newRow)
|
|
||||||
|
|
||||||
// Update the hot table with the new data
|
|
||||||
hot.updateSettings({ data: this.dataSource }, false)
|
hot.updateSettings({ data: this.dataSource }, false)
|
||||||
|
|
||||||
// Select the newly added row
|
|
||||||
hot.selectCell(this.dataSource.length - 1, 0)
|
hot.selectCell(this.dataSource.length - 1, 0)
|
||||||
hot.render()
|
hot.render()
|
||||||
|
|
||||||
|
if (this.dataSource[dsInsertIndex]) {
|
||||||
|
this.dataSource[dsInsertIndex]['noLinkOption'] = true
|
||||||
|
}
|
||||||
|
|
||||||
this.addingNewRow = false
|
this.addingNewRow = false
|
||||||
|
|
||||||
this.reSetCellValidationValues()
|
this.reSetCellValidationValues()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new empty row object with proper structure
|
|
||||||
*/
|
|
||||||
private createEmptyRow(): any {
|
|
||||||
const newRow: any = {}
|
|
||||||
this.headerColumns.forEach((col: string) => {
|
|
||||||
newRow[col] = ''
|
|
||||||
})
|
|
||||||
newRow['noLinkOption'] = true
|
|
||||||
return newRow
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts a new row at the specified position and updates the table
|
|
||||||
*/
|
|
||||||
private insertRowAtPosition(targetRow: number): void {
|
|
||||||
const newRow = this.createEmptyRow()
|
|
||||||
|
|
||||||
// Insert the new row at the target position
|
|
||||||
this.dataSource.splice(targetRow, 0, newRow)
|
|
||||||
|
|
||||||
// Update the hot table
|
|
||||||
const hot = this.hotInstance
|
|
||||||
hot.updateSettings({ data: this.dataSource }, false)
|
|
||||||
hot.render()
|
|
||||||
|
|
||||||
this.reSetCellValidationValues()
|
|
||||||
}
|
|
||||||
|
|
||||||
public cancelSubmit() {
|
public cancelSubmit() {
|
||||||
this.dataSource = this.helperService.deepClone(this.dataSourceBeforeSubmit)
|
this.dataSource = this.helperService.deepClone(this.dataSourceBeforeSubmit)
|
||||||
this.dataSourceBeforeSubmit = []
|
this.dataSourceBeforeSubmit = []
|
||||||
@@ -1151,96 +1077,51 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private clearDuplicateValidation() {
|
|
||||||
const hot = this.hotInstance
|
|
||||||
|
|
||||||
// Clear previous duplicate validation styling
|
|
||||||
for (const rowIndex of this.duplicatePkIndexes) {
|
|
||||||
for (let col = 1; col <= this.readOnlyFields; col++) {
|
|
||||||
hot.removeCellMeta(rowIndex, col, 'valid')
|
|
||||||
hot.removeCellMeta(rowIndex, col, 'dupKey')
|
|
||||||
// Remove our custom class from cell metadata
|
|
||||||
const cellMeta = hot.getCellMeta(rowIndex, col)
|
|
||||||
if (cellMeta.className) {
|
|
||||||
let cleanedClassName: string
|
|
||||||
if (Array.isArray(cellMeta.className)) {
|
|
||||||
cleanedClassName = cellMeta.className
|
|
||||||
.filter((c) => c !== 'dc-invalid-cell')
|
|
||||||
.join(' ')
|
|
||||||
} else {
|
|
||||||
cleanedClassName = cellMeta.className
|
|
||||||
.replace('dc-invalid-cell', '')
|
|
||||||
.trim()
|
|
||||||
}
|
|
||||||
hot.setCellMeta(rowIndex, col, 'className', cleanedClassName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.duplicatePkIndexes = []
|
|
||||||
hot.render()
|
|
||||||
}
|
|
||||||
|
|
||||||
public validatePrimaryKeys() {
|
public validatePrimaryKeys() {
|
||||||
const hot = this.hotInstance
|
const hot = this.hotInstance
|
||||||
|
|
||||||
// Clear previous validation before applying new ones
|
const myTable = hot.getData()
|
||||||
this.clearDuplicateValidation()
|
|
||||||
|
|
||||||
// Get data from the data source instead of hot.getData() to ensure consistency
|
|
||||||
const myTable = this.dataSource
|
|
||||||
this.pkFields = []
|
this.pkFields = []
|
||||||
|
|
||||||
for (let index = 0; index < myTable.length; index++) {
|
for (let index = 0; index < myTable.length; index++) {
|
||||||
let pkRow = ''
|
let pkRow = ''
|
||||||
for (let ind = 1; ind < this.readOnlyFields + 1; ind++) {
|
for (let ind = 1; ind < this.readOnlyFields + 1; ind++) {
|
||||||
const colName = this.headerColumns[ind]
|
pkRow = pkRow + '|' + myTable[index][ind]
|
||||||
const value = myTable[index][colName] || ''
|
|
||||||
pkRow = pkRow + '|' + value
|
|
||||||
}
|
}
|
||||||
this.pkFields.push(pkRow)
|
this.pkFields.push(pkRow)
|
||||||
}
|
}
|
||||||
|
|
||||||
const results: any = []
|
const results = []
|
||||||
const rows = this.dataSource.length
|
const rows = this.dataSource.length
|
||||||
|
|
||||||
// Only check for duplicates if we have data
|
|
||||||
if (this.pkFields.length > 0) {
|
|
||||||
for (let j = 0; j < this.pkFields.length; j++) {
|
for (let j = 0; j < this.pkFields.length; j++) {
|
||||||
for (let i = 0; i < this.pkFields.length; i++) {
|
for (let i = 0; i < this.pkFields.length; i++) {
|
||||||
if (
|
if (this.pkFields[j] === this.pkFields[i] && i !== j) {
|
||||||
this.pkFields[j] === this.pkFields[i] &&
|
|
||||||
i !== j &&
|
|
||||||
this.pkFields[j] !== '|'
|
|
||||||
) {
|
|
||||||
results.push(i)
|
results.push(i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Clear any existing validation marks for all cells
|
if (this.pkFields.length > rows) {
|
||||||
for (let row = 0; row < myTable.length; row++) {
|
for (let n = rows; n < this.pkFields.length; n++) {
|
||||||
for (let col = 0; col < this.headerColumns.length; col++) {
|
for (let p = rows; p < this.pkFields.length; p++) {
|
||||||
const cellMeta = hot.getCellMeta(row, col)
|
if (n < p && this.pkFields[n] === this.pkFields[p]) {
|
||||||
if (cellMeta) {
|
results.push(p)
|
||||||
cellMeta.valid = true
|
}
|
||||||
cellMeta.dupKey = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark duplicate cells as invalid
|
let cellMeta
|
||||||
for (let k = 0; k < results.length; k++) {
|
for (let k = 0; k < results.length; k++) {
|
||||||
for (let index = 1; index < this.readOnlyFields + 1; index++) {
|
for (let index = 1; index < this.readOnlyFields + 1; index++) {
|
||||||
hot.setCellMeta(results[k], index, 'valid', false)
|
cellMeta = hot.getCellMeta(results[k], index)
|
||||||
hot.setCellMeta(results[k], index, 'dupKey', true)
|
cellMeta.valid = false
|
||||||
hot.setCellMeta(results[k], index, 'className', 'dc-invalid-cell')
|
cellMeta.dupKey = true
|
||||||
|
hot.render()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.duplicatePkIndexes = [...new Set(results.sort())]
|
this.duplicatePkIndexes = [...new Set(results.sort())]
|
||||||
hot.render()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1535,26 +1416,10 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
|
|
||||||
this.dataSourceBeforeSubmit = this.helperService.deepClone(this.dataSource)
|
this.dataSourceBeforeSubmit = this.helperService.deepClone(this.dataSource)
|
||||||
|
|
||||||
// Clean up the data source by removing noLinkOption property
|
|
||||||
for (let i = 0; i < this.dataSource.length; i++) {
|
for (let i = 0; i < this.dataSource.length; i++) {
|
||||||
delete this.dataSource[i].noLinkOption
|
delete this.dataSource[i].noLinkOption
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove any completely empty rows from the end
|
|
||||||
while (this.dataSource.length > 0) {
|
|
||||||
const lastRow = this.dataSource[this.dataSource.length - 1]
|
|
||||||
const isEmpty = Object.keys(lastRow).every((key) => {
|
|
||||||
if (key === '_____DELETE__THIS__RECORD_____') return true
|
|
||||||
return !lastRow[key] || lastRow[key] === ''
|
|
||||||
})
|
|
||||||
|
|
||||||
if (isEmpty) {
|
|
||||||
this.dataSource.pop()
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hot.updateSettings(
|
hot.updateSettings(
|
||||||
{
|
{
|
||||||
data: this.dataSource,
|
data: this.dataSource,
|
||||||
@@ -1572,6 +1437,17 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
|
|
||||||
EditorComponent.cnt = 0
|
EditorComponent.cnt = 0
|
||||||
EditorComponent.nonPkCnt = 0
|
EditorComponent.nonPkCnt = 0
|
||||||
|
// this.saveLoading = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Below code should be analized, not sure what is the purpose of exceedCells
|
||||||
|
*/
|
||||||
|
const myTableData = hot.getData()
|
||||||
|
|
||||||
|
// If the last row is empty, remove it before validation
|
||||||
|
if (myTableData.length > 1 && hot.isEmptyRow(myTableData.length - 1)) {
|
||||||
|
hot.alter('remove_row', myTableData.length - 1)
|
||||||
|
}
|
||||||
|
|
||||||
this.validatePrimaryKeys()
|
this.validatePrimaryKeys()
|
||||||
|
|
||||||
@@ -1601,6 +1477,15 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
if (txt) txt.focus()
|
if (txt) txt.focus()
|
||||||
}, 200)
|
}, 200)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// let cnt = 0;
|
||||||
|
// hot.addHook("afterValidate", () => {
|
||||||
|
// this.updateSoftSelectColumns(true);
|
||||||
|
// cnt++;
|
||||||
|
// if (cnt === long) {
|
||||||
|
// this.validationDone = 1;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async saveTable(data: any) {
|
public async saveTable(data: any) {
|
||||||
@@ -1754,20 +1639,11 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public checkInvalid() {
|
public checkInvalid() {
|
||||||
// Use Angular wrapper to access Handsontable element instead of DOM queries
|
const hotElement = (this.hotInstanceCompList.first.container as any)
|
||||||
if (!this.hotTableComponent || !this.hotTableComponent.hotInstance)
|
.nativeElement
|
||||||
return false
|
const invalidCells = hotElement.querySelectorAll('.htInvalid')
|
||||||
|
|
||||||
const hotElement = this.hotTableComponent.hotInstance.rootElement
|
return invalidCells.length > 0
|
||||||
if (!hotElement) return false
|
|
||||||
|
|
||||||
// Check for standard Handsontable validation failures
|
|
||||||
const standardInvalidCells = hotElement.querySelectorAll('.htInvalid')
|
|
||||||
|
|
||||||
// Check for our custom duplicate primary key validation failures
|
|
||||||
const customInvalidCells = hotElement.querySelectorAll('.dc-invalid-cell')
|
|
||||||
|
|
||||||
return standardInvalidCells.length > 0 || customInvalidCells.length > 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public goToEditor() {
|
public goToEditor() {
|
||||||
@@ -2307,7 +2183,6 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
private setCellFilter(filter: boolean) {
|
private setCellFilter(filter: boolean) {
|
||||||
const hotSelected = this.hotInstance.getSelected()
|
const hotSelected = this.hotInstance.getSelected()
|
||||||
if (!hotSelected) return
|
|
||||||
const selection = hotSelected ? hotSelected[0] : hotSelected
|
const selection = hotSelected ? hotSelected[0] : hotSelected
|
||||||
|
|
||||||
// When we open a dropdown we want filter disabled so value in cell
|
// When we open a dropdown we want filter disabled so value in cell
|
||||||
@@ -2332,13 +2207,9 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
// Initialize hot table settings
|
|
||||||
this.updateHotTableSettings()
|
|
||||||
|
|
||||||
this.licenceService.hot_license_key.subscribe(
|
this.licenceService.hot_license_key.subscribe(
|
||||||
(hot_license_key: string | undefined) => {
|
(hot_license_key: string | undefined) => {
|
||||||
this.hotTable.licenseKey = hot_license_key
|
this.hotTable.licenseKey = hot_license_key
|
||||||
this.updateHotTableSettings() // Update settings when license key changes
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -2391,202 +2262,14 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {}
|
||||||
// Fix ARIA accessibility issues after table initialization
|
|
||||||
setTimeout(() => {
|
|
||||||
this.fixAriaAccessibility()
|
|
||||||
}, 1000)
|
|
||||||
|
|
||||||
// Set up event listener for hot table element
|
|
||||||
// Double click to edit
|
|
||||||
setTimeout(() => {
|
|
||||||
if (this.hotTableComponent && this.hotTableComponent.hotInstance) {
|
|
||||||
const hotElement = this.hotTableComponent.hotInstance.rootElement
|
|
||||||
if (hotElement) {
|
|
||||||
hotElement.addEventListener('mousedown', (event: MouseEvent) => {
|
|
||||||
if (!this.uploadPreview) {
|
|
||||||
this.hotClicked()
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
const menuDebugItem: any =
|
|
||||||
document.querySelector('.debug-switch-item') || undefined
|
|
||||||
if (menuDebugItem) menuDebugItem.click()
|
|
||||||
}, 100)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy() {
|
|
||||||
// Clean up the MutationObserver
|
|
||||||
if (this.ariaObserver) {
|
|
||||||
this.ariaObserver.disconnect()
|
|
||||||
this.ariaObserver = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up the interval
|
|
||||||
if (this.ariaCheckInterval) {
|
|
||||||
clearInterval(this.ariaCheckInterval)
|
|
||||||
this.ariaCheckInterval = undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fixes ARIA accessibility issues in the Handsontable component
|
|
||||||
* This addresses the accessibility report issues with treegrid and presentation roles
|
|
||||||
*/
|
|
||||||
private fixAriaAccessibility() {
|
|
||||||
// Use a more aggressive approach to find and fix all ARIA issues
|
|
||||||
const fixAriaIssues = () => {
|
|
||||||
// Specifically target Handsontable wrapper elements that are causing issues
|
|
||||||
const hotWrappers = document.querySelectorAll(
|
|
||||||
'.ht-wrapper, .wtHolder, [id^="ht_"]'
|
|
||||||
)
|
|
||||||
|
|
||||||
hotWrappers.forEach((wrapper) => {
|
|
||||||
// Remove problematic ARIA attributes from Handsontable wrappers
|
|
||||||
wrapper.removeAttribute('role')
|
|
||||||
wrapper.removeAttribute('aria-rowcount')
|
|
||||||
wrapper.removeAttribute('aria-colcount')
|
|
||||||
wrapper.removeAttribute('aria-multiselectable')
|
|
||||||
})
|
|
||||||
|
|
||||||
// Find all elements with problematic ARIA roles in the entire document
|
|
||||||
const allTreegridElements = document.querySelectorAll('[role="treegrid"]')
|
|
||||||
const allPresentationElements = document.querySelectorAll(
|
|
||||||
'[role="presentation"]'
|
|
||||||
)
|
|
||||||
|
|
||||||
// Fix treegrid role issues - remove them completely as they're causing problems
|
|
||||||
allTreegridElements.forEach((element) => {
|
|
||||||
element.removeAttribute('role')
|
|
||||||
element.removeAttribute('aria-rowcount')
|
|
||||||
element.removeAttribute('aria-colcount')
|
|
||||||
element.removeAttribute('aria-multiselectable')
|
|
||||||
})
|
|
||||||
|
|
||||||
// Fix presentation role issues - remove them if they contain interactive elements
|
|
||||||
allPresentationElements.forEach((element) => {
|
|
||||||
const hasInteractiveChildren =
|
|
||||||
element.querySelectorAll(
|
|
||||||
'button, input, select, textarea, [tabindex], [onclick], [contenteditable]'
|
|
||||||
).length > 0
|
|
||||||
if (hasInteractiveChildren) {
|
|
||||||
element.removeAttribute('role')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Also fix any elements with aria-rowcount="-1" which is problematic
|
|
||||||
const negativeRowCountElements = document.querySelectorAll(
|
|
||||||
'[aria-rowcount="-1"]'
|
|
||||||
)
|
|
||||||
negativeRowCountElements.forEach((element) => {
|
|
||||||
element.removeAttribute('aria-rowcount')
|
|
||||||
})
|
|
||||||
|
|
||||||
// Ensure proper table structure
|
|
||||||
const tableElements = document.querySelectorAll('table')
|
|
||||||
tableElements.forEach((table) => {
|
|
||||||
if (!table.getAttribute('role')) {
|
|
||||||
table.setAttribute('role', 'table')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure table headers have proper scope
|
|
||||||
const headerCells = table.querySelectorAll('th')
|
|
||||||
headerCells.forEach((th) => {
|
|
||||||
if (!th.getAttribute('scope')) {
|
|
||||||
th.setAttribute('scope', 'col')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Add proper ARIA labels to interactive elements
|
|
||||||
const interactiveElements = document.querySelectorAll(
|
|
||||||
'button, input, select, textarea, [contenteditable]'
|
|
||||||
)
|
|
||||||
interactiveElements.forEach((element) => {
|
|
||||||
if (
|
|
||||||
!element.getAttribute('aria-label') &&
|
|
||||||
!element.getAttribute('aria-labelledby')
|
|
||||||
) {
|
|
||||||
const textContent = element.textContent?.trim()
|
|
||||||
if (textContent) {
|
|
||||||
element.setAttribute('aria-label', textContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the fix immediately
|
|
||||||
fixAriaIssues()
|
|
||||||
|
|
||||||
// Run it again after a short delay to catch any dynamically created elements
|
|
||||||
setTimeout(fixAriaIssues, 100)
|
|
||||||
setTimeout(fixAriaIssues, 500)
|
|
||||||
setTimeout(fixAriaIssues, 1000)
|
|
||||||
setTimeout(fixAriaIssues, 2000)
|
|
||||||
|
|
||||||
// Set up a periodic check to ensure accessibility fixes are maintained
|
|
||||||
if (!this.ariaCheckInterval) {
|
|
||||||
this.ariaCheckInterval = setInterval(fixAriaIssues, 3000)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up a MutationObserver to continuously monitor for new problematic elements
|
|
||||||
if (!this.ariaObserver) {
|
|
||||||
this.ariaObserver = new MutationObserver((mutations) => {
|
|
||||||
let shouldFix = false
|
|
||||||
mutations.forEach((mutation) => {
|
|
||||||
if (
|
|
||||||
mutation.type === 'attributes' &&
|
|
||||||
(mutation.attributeName === 'role' ||
|
|
||||||
mutation.attributeName === 'aria-rowcount')
|
|
||||||
) {
|
|
||||||
shouldFix = true
|
|
||||||
}
|
|
||||||
if (mutation.type === 'childList') {
|
|
||||||
mutation.addedNodes.forEach((node) => {
|
|
||||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
||||||
const element = node as Element
|
|
||||||
if (
|
|
||||||
element.hasAttribute('role') ||
|
|
||||||
element.hasAttribute('aria-rowcount')
|
|
||||||
) {
|
|
||||||
shouldFix = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (shouldFix) {
|
|
||||||
setTimeout(fixAriaIssues, 50)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Start observing the entire document for changes
|
|
||||||
this.ariaObserver.observe(document.body, {
|
|
||||||
childList: true,
|
|
||||||
subtree: true,
|
|
||||||
attributes: true,
|
|
||||||
attributeFilter: [
|
|
||||||
'role',
|
|
||||||
'aria-rowcount',
|
|
||||||
'aria-colcount',
|
|
||||||
'aria-multiselectable'
|
|
||||||
]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
initSetup(response: EditorsGetDataServiceResponse) {
|
initSetup(response: EditorsGetDataServiceResponse) {
|
||||||
this.hotInstance = this.hotTableComponent!.hotInstance!
|
this.hotInstance = this.hotRegisterer.getInstance('hotInstance')
|
||||||
|
|
||||||
if (this.getdataError) return
|
if (this.getdataError) return
|
||||||
if (!response) return
|
if (!response) return
|
||||||
if (!response.data) return
|
if (!response.data) return
|
||||||
if (!this.hotInstance) return
|
|
||||||
|
|
||||||
this.cols = response.data.cols
|
this.cols = response.data.cols
|
||||||
this.dsmeta = response.data.dsmeta
|
this.dsmeta = response.data.dsmeta
|
||||||
@@ -2606,7 +2289,7 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.dsNote = ''
|
this.dsNote = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const hot = this.hotInstance
|
const hot: Handsontable = this.hotInstance
|
||||||
|
|
||||||
const approvers: Approver[] = response.data.approvers
|
const approvers: Approver[] = response.data.approvers
|
||||||
|
|
||||||
@@ -2727,11 +2410,6 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
rowHeights: 24,
|
rowHeights: 24,
|
||||||
maxRows: this.licenceState.value.editor_rows_allowed || Infinity,
|
maxRows: this.licenceState.value.editor_rows_allowed || Infinity,
|
||||||
invalidCellClassName: 'htInvalid',
|
invalidCellClassName: 'htInvalid',
|
||||||
// Prevent automatic row creation
|
|
||||||
autoWrapRow: false,
|
|
||||||
autoWrapCol: false,
|
|
||||||
// Ensure proper data binding
|
|
||||||
bindRowsWithHeaders: false,
|
|
||||||
dropdownMenu: {
|
dropdownMenu: {
|
||||||
items: {
|
items: {
|
||||||
make_read_only: {
|
make_read_only: {
|
||||||
@@ -2806,51 +2484,7 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
cellProperties: Handsontable.CellProperties
|
cellProperties: Handsontable.CellProperties
|
||||||
) => {
|
) => {
|
||||||
const isReadonlyCol = col && this.isReadonlyCol(col)
|
const isReadonlyCol = col && this.isReadonlyCol(col)
|
||||||
|
if (isReadonlyCol) cellProperties.className = 'readonlyCell'
|
||||||
// Check if this cell should be marked as invalid due to duplicate primary key values
|
|
||||||
// Only applies to primary key columns (col 1 through readOnlyFields)
|
|
||||||
const isDuplicateCell =
|
|
||||||
this.duplicatePkIndexes.includes(row) &&
|
|
||||||
col >= 1 &&
|
|
||||||
col <= this.readOnlyFields
|
|
||||||
|
|
||||||
// Handle existing CSS classes - Handsontable can provide className as string or array
|
|
||||||
const existingClasses = cellProperties.className || ''
|
|
||||||
let classes: string[]
|
|
||||||
|
|
||||||
if (Array.isArray(existingClasses)) {
|
|
||||||
// If already an array, create a copy
|
|
||||||
classes = [...existingClasses]
|
|
||||||
} else {
|
|
||||||
// If string, split by spaces and filter out empty strings
|
|
||||||
classes = existingClasses
|
|
||||||
.split(' ')
|
|
||||||
.filter((c: string) => c.length > 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add readonlyCell class for readonly columns to maintain original styling
|
|
||||||
if (isReadonlyCol && !classes.includes('readonlyCell')) {
|
|
||||||
classes.push('readonlyCell')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply custom validation styling for duplicate primary key cells
|
|
||||||
// Note: Uses 'dc-invalid-cell' instead of Handsontable's 'htInvalid' class
|
|
||||||
// because Handsontable's internal validation system was removing 'htInvalid'
|
|
||||||
// causing flickering. Our custom class persists reliably.
|
|
||||||
if (isDuplicateCell) {
|
|
||||||
if (!classes.includes('dc-invalid-cell')) {
|
|
||||||
classes.push('dc-invalid-cell')
|
|
||||||
}
|
|
||||||
// Mark cell as invalid to prevent form submission
|
|
||||||
cellProperties.valid = false
|
|
||||||
// Custom flag to identify this as a duplicate key cell for cleanup
|
|
||||||
cellProperties.dupKey = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the combined CSS classes back to the cell
|
|
||||||
if (classes.length > 0) {
|
|
||||||
cellProperties.className = classes.join(' ')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
@@ -2869,6 +2503,22 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.columnHeader[0] = 'Delete?'
|
this.columnHeader[0] = 'Delete?'
|
||||||
this.readOnlyFields = response.data.sasparams[0].PKCNT
|
this.readOnlyFields = response.data.sasparams[0].PKCNT
|
||||||
|
|
||||||
|
const hotInstaceEl = document.getElementById('hotInstance')
|
||||||
|
|
||||||
|
if (hotInstaceEl) {
|
||||||
|
hotInstaceEl.addEventListener('mousedown', (event) => {
|
||||||
|
if (!this.uploadPreview) {
|
||||||
|
this.hotClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const menuDebugItem: any =
|
||||||
|
document.querySelector('.debug-switch-item') || undefined
|
||||||
|
if (menuDebugItem) menuDebugItem.click()
|
||||||
|
}, 100)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
hot.addHook(
|
hot.addHook(
|
||||||
'afterSelection',
|
'afterSelection',
|
||||||
(
|
(
|
||||||
@@ -2947,17 +2597,6 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
|
|
||||||
hot.addHook('afterRender', (isForced: boolean) => {
|
hot.addHook('afterRender', (isForced: boolean) => {
|
||||||
this.eventService.dispatchEvent('resize')
|
this.eventService.dispatchEvent('resize')
|
||||||
|
|
||||||
// Fix ARIA accessibility issues after each render
|
|
||||||
this.fixAriaAccessibility()
|
|
||||||
})
|
|
||||||
|
|
||||||
// Add a more frequent accessibility fix hook
|
|
||||||
hot.addHook('afterChange', () => {
|
|
||||||
// Fix ARIA accessibility issues after any data change
|
|
||||||
setTimeout(() => {
|
|
||||||
this.fixAriaAccessibility()
|
|
||||||
}, 50)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
hot.addHook('afterCreateRow', (source: any, change: any) => {
|
hot.addHook('afterCreateRow', (source: any, change: any) => {
|
||||||
@@ -2971,21 +2610,6 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add hook to prevent unwanted row creation
|
|
||||||
hot.addHook(
|
|
||||||
'beforeCreateRow',
|
|
||||||
(index: number, amount: number, source?: any) => {
|
|
||||||
// Only allow row creation through the Add Row button or context menu
|
|
||||||
if (
|
|
||||||
!this.addingNewRow &&
|
|
||||||
source !== 'ContextMenu.insert_row_above' &&
|
|
||||||
source !== 'ContextMenu.insert_row_below'
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
hot.addHook('beforePaste', (data: any, cords: any) => {
|
hot.addHook('beforePaste', (data: any, cords: any) => {
|
||||||
const startCol = cords[0].startCol
|
const startCol = cords[0].startCol
|
||||||
|
|
||||||
@@ -3036,10 +2660,5 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hot.render()
|
hot.render()
|
||||||
|
|
||||||
// Fix ARIA accessibility issues after table initialization
|
|
||||||
setTimeout(() => {
|
|
||||||
this.fixAriaAccessibility()
|
|
||||||
}, 500)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common'
|
|||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { FormsModule } from '@angular/forms'
|
import { FormsModule } from '@angular/forms'
|
||||||
import { ClarityModule } from '@clr/angular'
|
import { ClarityModule } from '@clr/angular'
|
||||||
import { HotTableModule } from '@handsontable/angular-wrapper'
|
import { HotTableModule } from '@handsontable/angular'
|
||||||
import { registerAllModules } from 'handsontable/registry'
|
import { registerAllModules } from 'handsontable/registry'
|
||||||
import { AppSharedModule } from '../app-shared.module'
|
import { AppSharedModule } from '../app-shared.module'
|
||||||
import { DirectivesModule } from '../directives/directives.module'
|
import { DirectivesModule } from '../directives/directives.module'
|
||||||
@@ -28,7 +28,7 @@ registerAllModules()
|
|||||||
FormsModule,
|
FormsModule,
|
||||||
EditorRoutingModule,
|
EditorRoutingModule,
|
||||||
ClarityModule,
|
ClarityModule,
|
||||||
HotTableModule,
|
HotTableModule.forRoot(),
|
||||||
AppSharedModule,
|
AppSharedModule,
|
||||||
DirectivesModule,
|
DirectivesModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
|
@@ -0,0 +1,85 @@
|
|||||||
|
@import '../../colors.scss';
|
||||||
|
|
||||||
|
::ng-deep body[cds-theme="dark"] {
|
||||||
|
.group-info {
|
||||||
|
background-color: $headerBackground;
|
||||||
|
border-color: $headerBackground;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-data {
|
||||||
|
background-color: $headerBackground;
|
||||||
|
border-color: $headerBackground;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-table tbody{
|
||||||
|
tr:hover{
|
||||||
|
background-color: #29404b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep body[cds-theme="light"] {
|
||||||
|
.group-info{
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-color: #a7a7a7;
|
||||||
|
box-shadow: 0px 2px 5px #dad7d7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-data {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-color: #a7a7a7;
|
||||||
|
box-shadow: 0px 2px 5px #dad7d7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-table tbody{
|
||||||
|
tr:hover{
|
||||||
|
background-color: #e6e6e6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-height{
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.group-info-text{
|
||||||
|
display: inline;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
.group-info{
|
||||||
|
border: 1px solid;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.group-info td{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-data{
|
||||||
|
border: 1px solid;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-data{
|
||||||
|
min-height: auto;
|
||||||
|
h3, h5{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-table{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-table tbody{
|
||||||
|
tr:hover{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.table-container{
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px){
|
||||||
|
.group-data{
|
||||||
|
min-height: unset !important;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,5 +1,5 @@
|
|||||||
import { Location } from '@angular/common'
|
import { Location } from '@angular/common'
|
||||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core'
|
import { Component, OnInit } from '@angular/core'
|
||||||
import { ActivatedRoute, Router } from '@angular/router'
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
import { SASjsConfig } from '@sasjs/adapter'
|
import { SASjsConfig } from '@sasjs/adapter'
|
||||||
import { ServerType } from '@sasjs/utils/types/serverType'
|
import { ServerType } from '@sasjs/utils/types/serverType'
|
||||||
@@ -14,8 +14,7 @@ import { RequestWrapperResponse } from '../models/request-wrapper/RequestWrapper
|
|||||||
styleUrls: ['./group.component.scss'],
|
styleUrls: ['./group.component.scss'],
|
||||||
host: {
|
host: {
|
||||||
class: 'content-container'
|
class: 'content-container'
|
||||||
},
|
}
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class GroupComponent implements OnInit {
|
export class GroupComponent implements OnInit {
|
||||||
public groups: Array<any> | undefined
|
public groups: Array<any> | undefined
|
||||||
|
@@ -123,11 +123,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="!loading" class="no-table-selected">
|
<div *ngIf="!loading" class="no-table-selected">
|
||||||
<img
|
<clr-icon
|
||||||
src="images/select-table.png"
|
shape="warning-standard"
|
||||||
class="select-table-icon"
|
size="60"
|
||||||
alt="select table icon"
|
class="is-info icon-dc-fill"
|
||||||
/>
|
></clr-icon>
|
||||||
<p
|
<p
|
||||||
*ngIf="treeNodeLibraries?.length! > 0"
|
*ngIf="treeNodeLibraries?.length! > 0"
|
||||||
class="text-center color-gray mt-10"
|
class="text-center color-gray mt-10"
|
||||||
|
@@ -0,0 +1,32 @@
|
|||||||
|
clr-tree-node button {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-block {
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-table-selected {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep {
|
||||||
|
// .badge.badge-info {
|
||||||
|
// background: #6a9235!important;
|
||||||
|
// color: #fff;
|
||||||
|
// }
|
||||||
|
clr-icon.is-blue, clr-icon.is-info {
|
||||||
|
fill: #6a9235;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-wrapper-fullpage {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
@@ -3,7 +3,7 @@
|
|||||||
* This software is released under MIT license.
|
* This software is released under MIT license.
|
||||||
* The full license information can be found in LICENSE in the root directory of this project.
|
* The full license information can be found in LICENSE in the root directory of this project.
|
||||||
*/
|
*/
|
||||||
import { Component, AfterContentInit, ViewEncapsulation } from '@angular/core'
|
import { Component, AfterContentInit } from '@angular/core'
|
||||||
import { Router } from '@angular/router'
|
import { Router } from '@angular/router'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
import { globals } from '../_globals'
|
import { globals } from '../_globals'
|
||||||
@@ -18,8 +18,7 @@ import { LicenceService } from '../services/licence.service'
|
|||||||
templateUrl: './home.component.html',
|
templateUrl: './home.component.html',
|
||||||
host: {
|
host: {
|
||||||
class: 'content-container'
|
class: 'content-container'
|
||||||
},
|
}
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class HomeComponent implements AfterContentInit {
|
export class HomeComponent implements AfterContentInit {
|
||||||
public treeNodeLibraries: Array<any> | null = null
|
public treeNodeLibraries: Array<any> | null = null
|
||||||
|
@@ -0,0 +1,51 @@
|
|||||||
|
:host {
|
||||||
|
height: calc(100% - 96px);
|
||||||
|
padding: 20px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-error {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.misskey {
|
||||||
|
color: #E74C3C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.license-key-form, .activation-key-form {
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.clr-control-container {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 170px;
|
||||||
|
max-height: 170px;
|
||||||
|
min-height: 170px;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.apply-keys {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-area {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 15px;
|
||||||
|
border: 2px dashed #b2b2b2;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
clr-tabs button {
|
||||||
|
box-shadow: none !important
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core'
|
import { Component, OnInit } from '@angular/core'
|
||||||
import { ActivatedRoute, Router } from '@angular/router'
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
import { AppService, LicenceService, SasService } from '../services'
|
import { AppService, LicenceService, SasService } from '../services'
|
||||||
import { LicenseKeyData } from '../models/LicenseKeyData'
|
import { LicenseKeyData } from '../models/LicenseKeyData'
|
||||||
@@ -14,8 +14,7 @@ enum LicenseActions {
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-licensing',
|
selector: 'app-licensing',
|
||||||
templateUrl: './licensing.component.html',
|
templateUrl: './licensing.component.html',
|
||||||
styleUrls: ['./licensing.component.scss'],
|
styleUrls: ['./licensing.component.scss']
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class LicensingComponent implements OnInit {
|
export class LicensingComponent implements OnInit {
|
||||||
public action: LicenseActions | null = null
|
public action: LicenseActions | null = null
|
||||||
|
@@ -0,0 +1,85 @@
|
|||||||
|
@import '../../colors.scss';
|
||||||
|
|
||||||
|
.toggle-switch input[type=checkbox]:checked+label:before {
|
||||||
|
border-color: $headerBackground;
|
||||||
|
background-color: $headerBackground !important;
|
||||||
|
transition: .15s ease-in;
|
||||||
|
transition-property: border-color,background-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
#graph{
|
||||||
|
height: calc(100vh - 195px);
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid #e4e4e4;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 670px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-active {
|
||||||
|
background: #d8e3e9;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-area {
|
||||||
|
padding: 0.5rem !important;
|
||||||
|
|
||||||
|
.card {
|
||||||
|
min-height: calc(100vh - 120px);
|
||||||
|
|
||||||
|
.card-block {
|
||||||
|
padding: 0.5rem 0.35rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clr-tree-node button {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group.direction {
|
||||||
|
margin-left: var(--cds-global-space-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.graph-render-spinner {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.biglineage-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lineage-title-wrapper {
|
||||||
|
left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.max-depth-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.toggle-switch-container {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, ViewEncapsulation } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { Location } from '@angular/common'
|
import { Location } from '@angular/common'
|
||||||
import { globals } from '../_globals'
|
import { globals } from '../_globals'
|
||||||
import * as d3Viz from 'd3-graphviz'
|
import * as d3Viz from 'd3-graphviz'
|
||||||
@@ -18,8 +18,7 @@ const moment = require('moment')
|
|||||||
templateUrl: './lineage.component.html',
|
templateUrl: './lineage.component.html',
|
||||||
host: {
|
host: {
|
||||||
class: 'content-container'
|
class: 'content-container'
|
||||||
},
|
}
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class LineageComponent {
|
export class LineageComponent {
|
||||||
public switchFlag: boolean = false
|
public switchFlag: boolean = false
|
||||||
|
@@ -0,0 +1,82 @@
|
|||||||
|
::ng-deep body[cds-theme="dark"] {
|
||||||
|
.object-header:hover {
|
||||||
|
background-color: #405560;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep body[cds-theme="light"] {
|
||||||
|
.objects-col {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.object-header:hover {
|
||||||
|
background-color: #d8e3e9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.objects-col{
|
||||||
|
height: 75vh;
|
||||||
|
overflow: scroll;
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cols-head {
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.object-text {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-left: 10px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.repo-dropdown{
|
||||||
|
margin-right: 15px;
|
||||||
|
margin-left: 15px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.clr-accordion-title{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.float-right{
|
||||||
|
margin: 0px;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
.full-width{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.object-uri{
|
||||||
|
margin: 0px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
.object-header{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-left: 3px;
|
||||||
|
padding-right: 3px;
|
||||||
|
}
|
||||||
|
.object-header:hover{
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.datagrid-host{
|
||||||
|
display: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin-top: 0;
|
||||||
|
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-area {
|
||||||
|
padding: 0.5rem !important;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
@@ -1,5 +1,5 @@
|
|||||||
import { Location } from '@angular/common'
|
import { Location } from '@angular/common'
|
||||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core'
|
import { Component, OnInit } from '@angular/core'
|
||||||
import { ActivatedRoute, Router } from '@angular/router'
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
import { ClrDatagridStringFilterInterface } from '@clr/angular'
|
import { ClrDatagridStringFilterInterface } from '@clr/angular'
|
||||||
import { Observable, of } from 'rxjs'
|
import { Observable, of } from 'rxjs'
|
||||||
@@ -50,8 +50,7 @@ class ValueFilter implements ClrDatagridStringFilterInterface<MetaData> {
|
|||||||
styleUrls: ['./metadata.component.scss'],
|
styleUrls: ['./metadata.component.scss'],
|
||||||
host: {
|
host: {
|
||||||
class: 'content-container'
|
class: 'content-container'
|
||||||
},
|
}
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class MetadataComponent implements OnInit {
|
export class MetadataComponent implements OnInit {
|
||||||
metaDataList: Array<any> | undefined
|
metaDataList: Array<any> | undefined
|
||||||
@@ -99,11 +98,6 @@ export class MetadataComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
this.pageSize = 5
|
this.pageSize = 5
|
||||||
|
|
||||||
// Initialize filters for accessibility
|
|
||||||
this.typeFilter = new TypeFilter()
|
|
||||||
this.nameFilter = new NameFilter()
|
|
||||||
this.valueFilter = new ValueFilter()
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
globals.metadata.metaDataList &&
|
globals.metadata.metaDataList &&
|
||||||
globals.metadata.metaRepositories &&
|
globals.metadata.metaRepositories &&
|
||||||
|
@@ -8,5 +8,4 @@ export interface Libinfo {
|
|||||||
LIBID: string
|
LIBID: string
|
||||||
LIBSIZE: number
|
LIBSIZE: number
|
||||||
TABLE_CNT: number
|
TABLE_CNT: number
|
||||||
CATALOG_CNT: number
|
|
||||||
}
|
}
|
||||||
|
@@ -1,14 +1,30 @@
|
|||||||
import { BaseSASResponse } from './common/BaseSASResponse'
|
import { BaseSASResponse } from './common/BaseSASResponse'
|
||||||
|
|
||||||
export interface EditorsStageDataSASResponse extends BaseSASResponse {
|
export interface EditorsStageDataSASResponse extends BaseSASResponse {
|
||||||
|
SYSDATE: string
|
||||||
|
SYSTIME: string
|
||||||
sasparams: Sasparam[]
|
sasparams: Sasparam[]
|
||||||
|
_DEBUG: string
|
||||||
|
_PROGRAM: string
|
||||||
AUTOEXEC: string
|
AUTOEXEC: string
|
||||||
|
MF_GETUSER: string
|
||||||
|
SYSCC: string
|
||||||
SYSENCODING: string
|
SYSENCODING: string
|
||||||
|
SYSERRORTEXT: string
|
||||||
SYSHOSTINFOLONG: string
|
SYSHOSTINFOLONG: string
|
||||||
|
SYSHOSTNAME: string
|
||||||
SYSPROCESSID: string
|
SYSPROCESSID: string
|
||||||
SYSPROCESSMODE: string
|
SYSPROCESSMODE: string
|
||||||
SYSPROCESSNAME: string
|
SYSPROCESSNAME: string
|
||||||
|
SYSJOBID: string
|
||||||
|
SYSSCPL: string
|
||||||
|
SYSSITE: string
|
||||||
SYSTCPIPHOSTNAME: string
|
SYSTCPIPHOSTNAME: string
|
||||||
|
SYSUSERID: string
|
||||||
|
SYSVLONG: string
|
||||||
|
SYSWARNINGTEXT: string
|
||||||
|
END_DTTM: string
|
||||||
|
MEMSIZE: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Sasparam {
|
export interface Sasparam {
|
||||||
|
@@ -1,28 +0,0 @@
|
|||||||
import { BaseSASResponse } from './common/BaseSASResponse'
|
|
||||||
|
|
||||||
export interface PublicGetChangeinfo extends BaseSASResponse {
|
|
||||||
jsparams: Jsparam[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Jsparam {
|
|
||||||
TABLE_ID: string
|
|
||||||
SUBMIT_STATUS_CD: string
|
|
||||||
BASE_LIB: string
|
|
||||||
BASE_DS: string
|
|
||||||
SUBMITTED_BY_NM: string
|
|
||||||
SUBMITTED_ON: number
|
|
||||||
SUBMITTED_REASON_TXT: string
|
|
||||||
INPUT_OBS: number
|
|
||||||
INPUT_VARS: number
|
|
||||||
NUM_OF_APPROVALS_REQUIRED: number
|
|
||||||
NUM_OF_APPROVALS_REMAINING: number
|
|
||||||
REVIEWED_BY_NM: string
|
|
||||||
REVIEWED_ON?: any
|
|
||||||
TABLE_NM: string
|
|
||||||
BASE_TABLE: string
|
|
||||||
REVIEWED_ON_DTTM: string
|
|
||||||
SUBMITTED_ON_DTTM: string
|
|
||||||
LIB_ENGINE: string
|
|
||||||
ALLOW_RESTORE: string
|
|
||||||
REASON: string
|
|
||||||
}
|
|
@@ -127,7 +127,7 @@
|
|||||||
class="no-table-selected pointer-events-none"
|
class="no-table-selected pointer-events-none"
|
||||||
>
|
>
|
||||||
<clr-icon
|
<clr-icon
|
||||||
shape="upload-cloud"
|
shape="warning-standard"
|
||||||
size="40"
|
size="40"
|
||||||
class="is-info icon-dc-fill"
|
class="is-info icon-dc-fill"
|
||||||
></clr-icon>
|
></clr-icon>
|
||||||
@@ -166,10 +166,13 @@
|
|||||||
>
|
>
|
||||||
|
|
||||||
<hot-table
|
<hot-table
|
||||||
#hotInstanceUserDataset
|
hotId="hotInstanceUserDataset"
|
||||||
id="hotTableUserDataset"
|
id="hotTableUserDataset"
|
||||||
class="mt-15"
|
class="mt-15"
|
||||||
[settings]="hotUserDatasetsSettings"
|
[afterGetColHeader]="afterGetColHeader"
|
||||||
|
[settings]="hotUserDatasets"
|
||||||
|
[licenseKey]="hotTableLicenseKey"
|
||||||
|
stretchH="all"
|
||||||
>
|
>
|
||||||
</hot-table>
|
</hot-table>
|
||||||
|
|
||||||
@@ -357,10 +360,17 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hot-table
|
<hot-table
|
||||||
#hotInstanceMain
|
hotId="hotInstance"
|
||||||
id="hotTable"
|
id="hotTable"
|
||||||
class="mt-15"
|
class="mt-15"
|
||||||
[settings]="hotMainTableSettings"
|
[afterGetColHeader]="afterGetColHeader"
|
||||||
|
[className]="['htDark', 'htCustomHidden']"
|
||||||
|
[licenseKey]="hotTableLicenseKey"
|
||||||
|
[multiColumnSorting]="true"
|
||||||
|
[viewportRowRenderingOffset]="50"
|
||||||
|
[manualColumnResize]="true"
|
||||||
|
[filters]="true"
|
||||||
|
stretchH="all"
|
||||||
>
|
>
|
||||||
</hot-table>
|
</hot-table>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@@ -0,0 +1,54 @@
|
|||||||
|
.no-table-selected {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
position: absolute;
|
||||||
|
background: var(--clr-vertical-nav-bg-color);
|
||||||
|
z-index: 10;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-row {
|
||||||
|
padding: 15px 0;
|
||||||
|
border-bottom: 1px solid #d3d3d3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataset-input-wrapper {
|
||||||
|
max-width: 500px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
min-height: 200px;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-reason {
|
||||||
|
min-height: 70px;
|
||||||
|
max-height: 70px;
|
||||||
|
height: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-wrapper {
|
||||||
|
margin: 0 10px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep td.not-matched {
|
||||||
|
background-color: #ff000054;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataset-selection-actions {
|
||||||
|
border-top: 1px solid #d3d3d3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.licence-limit-notice {
|
||||||
|
color: var(--cds-alias-status-warning-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.submission-results {
|
||||||
|
border-bottom: 1px solid #d3d3d3;
|
||||||
|
}
|
@@ -4,9 +4,7 @@ import {
|
|||||||
ElementRef,
|
ElementRef,
|
||||||
HostBinding,
|
HostBinding,
|
||||||
OnInit,
|
OnInit,
|
||||||
AfterViewInit,
|
ViewChild
|
||||||
ViewChild,
|
|
||||||
ViewEncapsulation
|
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import {
|
import {
|
||||||
EventService,
|
EventService,
|
||||||
@@ -23,7 +21,7 @@ import { HotTableInterface } from '../models/HotTable.interface'
|
|||||||
import { Col } from '../shared/dc-validator/models/col.model'
|
import { Col } from '../shared/dc-validator/models/col.model'
|
||||||
import { SpreadsheetService } from '../services/spreadsheet.service'
|
import { SpreadsheetService } from '../services/spreadsheet.service'
|
||||||
import Handsontable from 'handsontable'
|
import Handsontable from 'handsontable'
|
||||||
import { HotTableComponent } from '@handsontable/angular-wrapper'
|
import { HotTableRegisterer } from '@handsontable/angular'
|
||||||
import { EditorsStageDataSASResponse } from '../models/sas/editors-stagedata.model'
|
import { EditorsStageDataSASResponse } from '../models/sas/editors-stagedata.model'
|
||||||
import { CellChange, ChangeSource } from 'handsontable/common'
|
import { CellChange, ChangeSource } from 'handsontable/common'
|
||||||
import { baseAfterGetColHeader } from '../shared/utils/hot.utils'
|
import { baseAfterGetColHeader } from '../shared/utils/hot.utils'
|
||||||
@@ -47,10 +45,9 @@ enum FileLoadingState {
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-multi-dataset',
|
selector: 'app-multi-dataset',
|
||||||
templateUrl: './multi-dataset.component.html',
|
templateUrl: './multi-dataset.component.html',
|
||||||
styleUrls: ['./multi-dataset.component.scss'],
|
styleUrls: ['./multi-dataset.component.scss']
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class MultiDatasetComponent implements OnInit, AfterViewInit {
|
export class MultiDatasetComponent implements OnInit {
|
||||||
@HostBinding('class.content-container') contentContainerClass = true
|
@HostBinding('class.content-container') contentContainerClass = true
|
||||||
@ViewChild('contentArea', { static: true }) contentAreaRef!: ElementRef
|
@ViewChild('contentArea', { static: true }) contentAreaRef!: ElementRef
|
||||||
|
|
||||||
@@ -90,13 +87,7 @@ export class MultiDatasetComponent implements OnInit, AfterViewInit {
|
|||||||
|
|
||||||
public hotInstance!: Handsontable
|
public hotInstance!: Handsontable
|
||||||
public hotInstanceUserDataset!: Handsontable
|
public hotInstanceUserDataset!: Handsontable
|
||||||
@ViewChild('hotInstanceMain', { static: false })
|
private hotRegisterer: HotTableRegisterer
|
||||||
hotTableMainComponent!: HotTableComponent
|
|
||||||
@ViewChild('hotInstanceUserDataset', { static: false })
|
|
||||||
hotTableUserDatasetComponent!: HotTableComponent
|
|
||||||
|
|
||||||
public hotMainTableSettings: Handsontable.GridSettings = {}
|
|
||||||
public hotUserDatasetsSettings: Handsontable.GridSettings = {}
|
|
||||||
|
|
||||||
public showSubmitReasonModal: boolean = false
|
public showSubmitReasonModal: boolean = false
|
||||||
public submitReasonMessage: string = ''
|
public submitReasonMessage: string = ''
|
||||||
@@ -143,36 +134,7 @@ export class MultiDatasetComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
manualRowMove: true,
|
manualRowMove: true,
|
||||||
columnSorting: true,
|
columnSorting: true
|
||||||
afterGetColHeader: baseAfterGetColHeader,
|
|
||||||
stretchH: 'all'
|
|
||||||
}
|
|
||||||
|
|
||||||
private initializeHotSettings() {
|
|
||||||
this.hotMainTableSettings = {
|
|
||||||
className: ['htDark'],
|
|
||||||
licenseKey: this.hotTableLicenseKey,
|
|
||||||
multiColumnSorting: true,
|
|
||||||
viewportRowRenderingOffset: 50,
|
|
||||||
manualColumnResize: true,
|
|
||||||
autoColumnSize: true,
|
|
||||||
filters: true,
|
|
||||||
stretchH: 'all',
|
|
||||||
afterGetColHeader: baseAfterGetColHeader,
|
|
||||||
modifyColWidth: this.maxWidthCheker
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exclude data from settings for HOT v16 - it will be loaded manually
|
|
||||||
const { data, ...settingsWithoutData } = this.hotUserDatasets
|
|
||||||
this.hotUserDatasetsSettings = {
|
|
||||||
...settingsWithoutData,
|
|
||||||
licenseKey: this.hotTableLicenseKey
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public maxWidthCheker(width: any, col: any) {
|
|
||||||
if (width > 200) return 200
|
|
||||||
else return width
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public afterGetColHeader = baseAfterGetColHeader
|
public afterGetColHeader = baseAfterGetColHeader
|
||||||
@@ -185,28 +147,16 @@ export class MultiDatasetComponent implements OnInit, AfterViewInit {
|
|||||||
private spreadsheetService: SpreadsheetService,
|
private spreadsheetService: SpreadsheetService,
|
||||||
private sasService: SasService,
|
private sasService: SasService,
|
||||||
private cdr: ChangeDetectorRef
|
private cdr: ChangeDetectorRef
|
||||||
) {}
|
) {
|
||||||
|
this.hotRegisterer = new HotTableRegisterer()
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.licenceService.hot_license_key.subscribe(
|
this.licenceService.hot_license_key.subscribe(
|
||||||
(hot_license_key: string | undefined) => {
|
(hot_license_key: string | undefined) => {
|
||||||
this.hotTableLicenseKey = hot_license_key
|
this.hotTableLicenseKey = hot_license_key
|
||||||
this.initializeHotSettings()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
this.initializeHotSettings()
|
|
||||||
}
|
|
||||||
|
|
||||||
ngAfterViewInit() {
|
|
||||||
// Ensure HOT instances are properly initialized after view is ready
|
|
||||||
setTimeout(() => {
|
|
||||||
if (this.hotTableUserDatasetComponent && !this.hotInstanceUserDataset) {
|
|
||||||
this.initUserInputHot()
|
|
||||||
}
|
|
||||||
if (this.hotTableMainComponent && !this.hotInstance) {
|
|
||||||
this.initHot()
|
|
||||||
}
|
|
||||||
}, 50)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterContentInit(): void {
|
ngAfterContentInit(): void {
|
||||||
@@ -281,10 +231,7 @@ export class MultiDatasetComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.initUserInputHot()
|
this.initUserInputHot()
|
||||||
// Call onAutoDetectColumns after HOT is initialized
|
|
||||||
setTimeout(() => {
|
|
||||||
this.onAutoDetectColumns()
|
this.onAutoDetectColumns()
|
||||||
}, 100)
|
|
||||||
} else if (matchedExtension === 'csv') {
|
} else if (matchedExtension === 'csv') {
|
||||||
this.onMultiCsvFiles(event.target.files)
|
this.onMultiCsvFiles(event.target.files)
|
||||||
} else {
|
} else {
|
||||||
@@ -443,55 +390,30 @@ export class MultiDatasetComponent implements OnInit, AfterViewInit {
|
|||||||
|
|
||||||
initHot() {
|
initHot() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.hotTableMainComponent?.hotInstance) {
|
this.hotInstance = this.hotRegisterer.getInstance('hotInstance')
|
||||||
this.hotInstance = this.hotTableMainComponent.hotInstance
|
|
||||||
|
|
||||||
// Set height of parsed data to full height of the page content area
|
// Set height of parsed data to full height of the page content area
|
||||||
const contentAreaHeight = this.contentAreaRef.nativeElement.clientHeight
|
const contentAreaHeight = this.contentAreaRef.nativeElement.clientHeight
|
||||||
const hotHeight = `${contentAreaHeight - 160}px`
|
const hotHeight = `${contentAreaHeight - 160}px`
|
||||||
|
|
||||||
if (this.activeParsedDataset) {
|
if (this.activeParsedDataset) {
|
||||||
// Update settings without data - data will be loaded manually
|
|
||||||
this.hotInstance.updateSettings({
|
this.hotInstance.updateSettings({
|
||||||
|
data: this.activeParsedDataset.datasource || [],
|
||||||
colHeaders: this.activeParsedDataset.datasetInfo.headerColumns,
|
colHeaders: this.activeParsedDataset.datasetInfo.headerColumns,
|
||||||
columns:
|
columns: this.activeParsedDataset.datasetInfo.dcValidator?.getRules(),
|
||||||
this.activeParsedDataset.datasetInfo.dcValidator?.getRules(),
|
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
height: hotHeight || '300px',
|
height: hotHeight || '300px',
|
||||||
className: ['htDark']
|
className: 'htDark'
|
||||||
})
|
})
|
||||||
|
|
||||||
// Trigger change detection to avoid ExpressionChangedAfterItHasBeenCheckedError
|
|
||||||
this.cdr.detectChanges()
|
|
||||||
|
|
||||||
// Load data manually - this is required for HOT v16 Angular wrapper
|
|
||||||
setTimeout(() => {
|
|
||||||
if (
|
|
||||||
this.activeParsedDataset &&
|
|
||||||
this.activeParsedDataset.datasource
|
|
||||||
) {
|
|
||||||
this.hotInstance.loadData(this.activeParsedDataset.datasource)
|
|
||||||
this.hotInstance.render()
|
|
||||||
}
|
}
|
||||||
}, 100)
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 100)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initUserInputHot() {
|
initUserInputHot() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.hotTableUserDatasetComponent?.hotInstance) {
|
this.hotInstanceUserDataset = this.hotRegisterer.getInstance(
|
||||||
this.hotInstanceUserDataset =
|
'hotInstanceUserDataset'
|
||||||
this.hotTableUserDatasetComponent.hotInstance
|
)
|
||||||
|
|
||||||
// Load initial data manually after instance is ready
|
|
||||||
setTimeout(() => {
|
|
||||||
if (this.hotUserDatasets.data) {
|
|
||||||
this.hotInstanceUserDataset.loadData(this.hotUserDatasets.data)
|
|
||||||
this.hotInstanceUserDataset.render()
|
|
||||||
}
|
|
||||||
}, 50)
|
|
||||||
|
|
||||||
this.hotInstanceUserDataset.addHook(
|
this.hotInstanceUserDataset.addHook(
|
||||||
'beforeChange',
|
'beforeChange',
|
||||||
@@ -542,13 +464,10 @@ export class MultiDatasetComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
}, 100)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dynamicCellValidations() {
|
dynamicCellValidations() {
|
||||||
if (!this.hotInstanceUserDataset) return
|
|
||||||
|
|
||||||
const hotData = this.hotInstanceUserDataset.getData()
|
const hotData = this.hotInstanceUserDataset.getData()
|
||||||
|
|
||||||
hotData.forEach((row, rowIndex) => {
|
hotData.forEach((row, rowIndex) => {
|
||||||
@@ -562,8 +481,6 @@ export class MultiDatasetComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
markUnmatchedRows(row: number) {
|
markUnmatchedRows(row: number) {
|
||||||
if (!this.hotInstanceUserDataset) return
|
|
||||||
|
|
||||||
const dataAtRow = this.hotInstanceUserDataset.getDataAtRow(row) as number[]
|
const dataAtRow = this.hotInstanceUserDataset.getDataAtRow(row) as number[]
|
||||||
const dataset = `${dataAtRow[0]}.${dataAtRow[1]}`
|
const dataset = `${dataAtRow[0]}.${dataAtRow[1]}`
|
||||||
const cellMetaAtRow = this.hotInstanceUserDataset.getCellMetaAtRow(row)
|
const cellMetaAtRow = this.hotInstanceUserDataset.getCellMetaAtRow(row)
|
||||||
@@ -637,20 +554,6 @@ export class MultiDatasetComponent implements OnInit, AfterViewInit {
|
|||||||
* convention. {@link isValidDatasetFormat}
|
* convention. {@link isValidDatasetFormat}
|
||||||
*/
|
*/
|
||||||
async onAutoDetectColumns() {
|
async onAutoDetectColumns() {
|
||||||
// Wait for hotInstanceUserDataset to be ready
|
|
||||||
if (!this.hotInstanceUserDataset) {
|
|
||||||
let attempts = 0
|
|
||||||
const maxAttempts = 20
|
|
||||||
while (!this.hotInstanceUserDataset && attempts < maxAttempts) {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
||||||
attempts++
|
|
||||||
}
|
|
||||||
if (!this.hotInstanceUserDataset) {
|
|
||||||
console.warn('hotInstanceUserDataset not ready after waiting')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let passwordError = false
|
let passwordError = false
|
||||||
|
|
||||||
await this.parseExcelSheetNames()
|
await this.parseExcelSheetNames()
|
||||||
@@ -711,13 +614,7 @@ export class MultiDatasetComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hotInstanceUserDataset) {
|
this.hotInstanceUserDataset.updateData(hotReadyData)
|
||||||
// Load data manually - this is required for HOT v16 Angular wrapper
|
|
||||||
setTimeout(() => {
|
|
||||||
this.hotInstanceUserDataset.loadData(hotReadyData)
|
|
||||||
this.hotInstanceUserDataset.render()
|
|
||||||
}, 100)
|
|
||||||
}
|
|
||||||
this.dynamicCellValidations()
|
this.dynamicCellValidations()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common'
|
|||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { FormsModule } from '@angular/forms'
|
import { FormsModule } from '@angular/forms'
|
||||||
import { ClarityModule } from '@clr/angular'
|
import { ClarityModule } from '@clr/angular'
|
||||||
import { HotTableModule } from '@handsontable/angular-wrapper'
|
import { HotTableModule } from '@handsontable/angular'
|
||||||
import { registerAllModules } from 'handsontable/registry'
|
import { registerAllModules } from 'handsontable/registry'
|
||||||
import { AppSharedModule } from '../app-shared.module'
|
import { AppSharedModule } from '../app-shared.module'
|
||||||
import { DirectivesModule } from '../directives/directives.module'
|
import { DirectivesModule } from '../directives/directives.module'
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core'
|
import { Component, OnInit } from '@angular/core'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-not-found',
|
selector: 'app-not-found',
|
||||||
@@ -6,8 +6,7 @@ import { Component, OnInit, ViewEncapsulation } from '@angular/core'
|
|||||||
styleUrls: ['./not-found.component.scss'],
|
styleUrls: ['./not-found.component.scss'],
|
||||||
host: {
|
host: {
|
||||||
class: 'content-container'
|
class: 'content-container'
|
||||||
},
|
}
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class NotFoundComponent implements OnInit {
|
export class NotFoundComponent implements OnInit {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
@@ -0,0 +1,307 @@
|
|||||||
|
::ng-deep {
|
||||||
|
body[cds-theme="dark"] {
|
||||||
|
.clause-logic {
|
||||||
|
background: #192a30;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clause-query {
|
||||||
|
background: #263e48;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body[cds-theme="light"] {
|
||||||
|
.clause-logic {
|
||||||
|
background: #e9e9e9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clause-query {
|
||||||
|
background: #fbf8f8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.clauses-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.clause-logic {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clause-query {
|
||||||
|
padding: 30px 0px 20px 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
& > .clr-row {
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
padding-bottom: 15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-group-clause-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
right: 10px;
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variable-col {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding-bottom: 1px;
|
||||||
|
|
||||||
|
.datalist-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator-col {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
clr-select-container {
|
||||||
|
height: 45px;
|
||||||
|
margin-top: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-col {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding-bottom: 1px;
|
||||||
|
|
||||||
|
.checkbox-vals {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0px 5px;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.3);
|
||||||
|
|
||||||
|
clr-checkbox-container {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
max-height: 120px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-field-vals {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
::ng-deep {
|
||||||
|
.clr-control-container {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.clr-input-wrapper {
|
||||||
|
max-width: none;
|
||||||
|
|
||||||
|
.clr-input-group {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=time] {
|
||||||
|
width: 100%;
|
||||||
|
padding-right: 17px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-vals {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
::ng-deep {
|
||||||
|
.clr-control-container {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.clr-input-wrapper {
|
||||||
|
max-width: none;
|
||||||
|
|
||||||
|
.clr-input-group {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.from {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
& > input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=time] {
|
||||||
|
width: 100%;
|
||||||
|
padding-right: 17px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.from, .to {
|
||||||
|
min-width: 100px;
|
||||||
|
|
||||||
|
& > input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=time] {
|
||||||
|
width: 100%;
|
||||||
|
padding-right: 17px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.contains-vals {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
::ng-deep {
|
||||||
|
.clr-control-container {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.clr-input-wrapper {
|
||||||
|
max-width: none;
|
||||||
|
|
||||||
|
.clr-input-group {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=time] {
|
||||||
|
width: 100%;
|
||||||
|
padding-right: 17px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.clause-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
button {
|
||||||
|
min-width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.invalid-clause {
|
||||||
|
border-left: 2px solid #d94b31;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clause-row {
|
||||||
|
clr-icon {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.clause-row:after {
|
||||||
|
position: relative;
|
||||||
|
content: "";
|
||||||
|
height: .41667rem;
|
||||||
|
width: .41667rem;
|
||||||
|
top: .29167rem;
|
||||||
|
right: .25rem;
|
||||||
|
background-image: url(data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org…%2C9.84%2C3.24a0.68%2C0.68%2C0%2C1%2C1%2C1%2C1Z%22%2F%3E%0A%3C%2Fsvg%3E%0A);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep body[cds-theme="dark"] {
|
||||||
|
.line-numbers {
|
||||||
|
border-color: #989797 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class*="language-"] {
|
||||||
|
padding: 8px;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 1px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 66px;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
span.spinner {
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-val {
|
||||||
|
border: 0px;
|
||||||
|
background: #fbf8f8;
|
||||||
|
border-bottom: 1px solid #999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
clr-date-container {
|
||||||
|
margin-top: 2px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="time"] {
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
border-bottom: 1px solid #b3b3b3;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.in-values-modal {
|
||||||
|
.modal-footer {
|
||||||
|
border-top: 1px solid #d8d8d8;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress, .progress-static {
|
||||||
|
background-color: transparent;
|
||||||
|
width: 100%;
|
||||||
|
height: 4px;
|
||||||
|
top: 3px;
|
||||||
|
}
|
@@ -6,8 +6,7 @@ import {
|
|||||||
OnDestroy,
|
OnDestroy,
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
LOCALE_ID,
|
LOCALE_ID,
|
||||||
Input,
|
Input
|
||||||
ViewEncapsulation
|
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { SasStoreService } from '../services/sas-store.service'
|
import { SasStoreService } from '../services/sas-store.service'
|
||||||
import { globals } from '../_globals'
|
import { globals } from '../_globals'
|
||||||
@@ -28,8 +27,7 @@ registerLocaleData(localeEnGB)
|
|||||||
selector: 'app-query',
|
selector: 'app-query',
|
||||||
templateUrl: './query.component.html',
|
templateUrl: './query.component.html',
|
||||||
styleUrls: ['./query.component.scss'],
|
styleUrls: ['./query.component.scss'],
|
||||||
providers: [{ provide: LOCALE_ID, useValue: 'en-GB' }],
|
providers: [{ provide: LOCALE_ID, useValue: 'en-GB' }]
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class QueryComponent
|
export class QueryComponent
|
||||||
implements AfterViewInit, AfterContentInit, OnDestroy
|
implements AfterViewInit, AfterContentInit, OnDestroy
|
||||||
|
@@ -0,0 +1,210 @@
|
|||||||
|
@import '../../../colors.scss';
|
||||||
|
|
||||||
|
.loader {
|
||||||
|
display:flex;
|
||||||
|
justify-content: center;
|
||||||
|
height:75vh;
|
||||||
|
align-items:center;
|
||||||
|
flex-direction:column
|
||||||
|
}
|
||||||
|
.modalLarge {
|
||||||
|
width: 50rem!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addedRow {
|
||||||
|
border: 1px solid rgba(9, 77, 117, 0.2);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deletedRow {
|
||||||
|
border: 1px solid rgba(70, 71, 70, 0.2);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep body[cds-theme="dark"] {
|
||||||
|
table {
|
||||||
|
.updatedRow {
|
||||||
|
background: #93971e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addedRow {
|
||||||
|
background: rgb(86 153 95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.deletedRow {
|
||||||
|
background: rgb(138 90 90);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep body[cds-theme="light"] {
|
||||||
|
table {
|
||||||
|
.updatedRow {
|
||||||
|
background: #fafda8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addedRow {
|
||||||
|
background: rgb(146, 208, 154);
|
||||||
|
}
|
||||||
|
|
||||||
|
.deletedRow {
|
||||||
|
background: rgb(230, 179, 179);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.updatedRow {
|
||||||
|
border: 1px solid rgba(9, 117, 9, 0.2);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
border: 0px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ch {
|
||||||
|
background: rgba(0,0,0,.1);
|
||||||
|
border: 1px solid rgba(104, 100, 0, 0.4);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ch:hover {
|
||||||
|
background: rgba(252, 135, 120, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip .tooltip-content.tooltip-top-right, .tooltip.tooltip-top-right>.tooltip-content, .tooltip>.tooltip-content {
|
||||||
|
font-size: .54167rem;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: normal;
|
||||||
|
background: $headerBackground;
|
||||||
|
border-radius: .125rem;
|
||||||
|
color: #f0f1ec;;
|
||||||
|
line-height: .75rem;
|
||||||
|
margin: 0;
|
||||||
|
padding: .375rem .5rem;
|
||||||
|
width: 235px;
|
||||||
|
position: absolute;
|
||||||
|
top: auto;
|
||||||
|
bottom: 100%;
|
||||||
|
left: 12px;
|
||||||
|
right: auto;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
margin-bottom: .66667rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip .tooltip-content.tooltip-top-right:before, .tooltip.tooltip-top-right>.tooltip-content:before, .tooltip>.tooltip-content:before {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -.375rem;
|
||||||
|
left: 0;
|
||||||
|
top: auto;
|
||||||
|
right: auto;
|
||||||
|
content: "";
|
||||||
|
border-left: .25rem solid $headerBackground;
|
||||||
|
border-top: .20833rem solid $headerBackground;
|
||||||
|
border-right: .25rem solid transparent;
|
||||||
|
border-bottom: .20833rem solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
border: 0px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch input[type=checkbox]:checked+label:before {
|
||||||
|
border-color: $headerBackground;
|
||||||
|
background-color: $headerBackground !important;
|
||||||
|
transition: .15s ease-in;
|
||||||
|
transition-property: border-color,background-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableCont {
|
||||||
|
overflow:auto;
|
||||||
|
margin: 15px 10px 10px 10px;
|
||||||
|
|
||||||
|
td {
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.approvalInfo {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end
|
||||||
|
}
|
||||||
|
|
||||||
|
.approvalBack {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width:768px) {
|
||||||
|
.approvalInfo {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 15px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.approvalBack {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin-top:0rem!important;
|
||||||
|
min-height: calc(100vh - 0px)!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table td.left, .table th.left {
|
||||||
|
text-align: left;
|
||||||
|
width: 150px!important;
|
||||||
|
flex: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table td.left, .table th.left {
|
||||||
|
text-align: left;
|
||||||
|
flex: 1;
|
||||||
|
width: 300px!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.tooll {
|
||||||
|
position: absolute;
|
||||||
|
background: #e6b3b3;
|
||||||
|
color: $headerBackground;
|
||||||
|
top: 0px;
|
||||||
|
height: 36px;
|
||||||
|
width: 100%;
|
||||||
|
left: 0px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
#acceptBtn, #rejectBtn {
|
||||||
|
width: 175px
|
||||||
|
}
|
||||||
|
|
||||||
|
.formatted-values-toggle {
|
||||||
|
min-width: 75px
|
||||||
|
}
|
||||||
|
|
||||||
|
clr-modal {
|
||||||
|
::ng-deep {
|
||||||
|
.modal-body-wrapper {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.rows-notice {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 10px;
|
||||||
|
color: #6a6a6a;
|
||||||
|
font-size: 15px;
|
||||||
|
|
||||||
|
clr-icon {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,11 +1,6 @@
|
|||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
import { SasStoreService } from '../../services/sas-store.service'
|
import { SasStoreService } from '../../services/sas-store.service'
|
||||||
import {
|
import { Component, AfterViewInit, OnDestroy } from '@angular/core'
|
||||||
Component,
|
|
||||||
AfterViewInit,
|
|
||||||
OnDestroy,
|
|
||||||
ViewEncapsulation
|
|
||||||
} from '@angular/core'
|
|
||||||
import { Subscription } from 'rxjs'
|
import { Subscription } from 'rxjs'
|
||||||
import { Router } from '@angular/router'
|
import { Router } from '@angular/router'
|
||||||
import { EventService } from '../../services/event.service'
|
import { EventService } from '../../services/event.service'
|
||||||
@@ -13,8 +8,6 @@ import {
|
|||||||
AuditorsPostdataSASResponse,
|
AuditorsPostdataSASResponse,
|
||||||
Param
|
Param
|
||||||
} from '../../models/sas/auditors-postdata.model'
|
} from '../../models/sas/auditors-postdata.model'
|
||||||
import { PublicGetChangeinfo } from 'src/app/models/sas/public-getchangeinfo.model'
|
|
||||||
import { SasService } from 'src/app/services'
|
|
||||||
|
|
||||||
interface ChangesObj {
|
interface ChangesObj {
|
||||||
ind: any
|
ind: any
|
||||||
@@ -29,8 +22,7 @@ interface ChangesObj {
|
|||||||
styleUrls: ['./approve-details.component.scss'],
|
styleUrls: ['./approve-details.component.scss'],
|
||||||
host: {
|
host: {
|
||||||
class: 'content-container'
|
class: 'content-container'
|
||||||
},
|
}
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class ApproveDetailsComponent implements AfterViewInit, OnDestroy {
|
export class ApproveDetailsComponent implements AfterViewInit, OnDestroy {
|
||||||
private _detailsSub: Subscription | undefined
|
private _detailsSub: Subscription | undefined
|
||||||
@@ -78,11 +70,9 @@ export class ApproveDetailsComponent implements AfterViewInit, OnDestroy {
|
|||||||
|
|
||||||
public diffsLimit: boolean = false
|
public diffsLimit: boolean = false
|
||||||
public recordsLimit: number = 100
|
public recordsLimit: number = 100
|
||||||
public refreshStartupserviceAfterApprove: boolean = false
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private sasStoreService: SasStoreService,
|
private sasStoreService: SasStoreService,
|
||||||
private sasService: SasService,
|
|
||||||
private eventService: EventService,
|
private eventService: EventService,
|
||||||
private router: ActivatedRoute,
|
private router: ActivatedRoute,
|
||||||
private route: Router
|
private route: Router
|
||||||
@@ -166,9 +156,6 @@ export class ApproveDetailsComponent implements AfterViewInit, OnDestroy {
|
|||||||
await this.sasStoreService
|
await this.sasStoreService
|
||||||
.approveTable(approveParams, 'SASControlTable', 'auditors/postdata')
|
.approveTable(approveParams, 'SASControlTable', 'auditors/postdata')
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
// If we are approving MPE_TABLES we will arm the trigger for the reload of startup data to se the updated tables
|
|
||||||
if (this.refreshStartupserviceAfterApprove)
|
|
||||||
this.sasService.reloadStartupData()
|
|
||||||
this.route.navigateByUrl('/review/history')
|
this.route.navigateByUrl('/review/history')
|
||||||
})
|
})
|
||||||
.catch((err: any) => {
|
.catch((err: any) => {
|
||||||
@@ -183,7 +170,7 @@ export class ApproveDetailsComponent implements AfterViewInit, OnDestroy {
|
|||||||
public async callChangesInfo(tableId: any) {
|
public async callChangesInfo(tableId: any) {
|
||||||
await this.sasStoreService
|
await this.sasStoreService
|
||||||
.getChangeInfo(tableId)
|
.getChangeInfo(tableId)
|
||||||
.then((res: PublicGetChangeinfo) => {
|
.then((res: any) => {
|
||||||
this.tableDetails = res.jsparams[0]
|
this.tableDetails = res.jsparams[0]
|
||||||
this.jsParams = res.jsparams[0]
|
this.jsParams = res.jsparams[0]
|
||||||
|
|
||||||
@@ -196,11 +183,6 @@ export class ApproveDetailsComponent implements AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.keysArray = keysArray
|
this.keysArray = keysArray
|
||||||
|
|
||||||
// If we are approving MPE_TABLES we will arm the trigger for the reload of startup data to se the updated tables
|
|
||||||
// After user approved if armed, reload will be triggered
|
|
||||||
if (res.jsparams[0].BASE_DS === 'MPE_TABLES')
|
|
||||||
this.refreshStartupserviceAfterApprove = true
|
|
||||||
})
|
})
|
||||||
.catch((err: any) => {
|
.catch((err: any) => {
|
||||||
this.acceptLoading = false
|
this.acceptLoading = false
|
||||||
@@ -367,12 +349,13 @@ export class ApproveDetailsComponent implements AfterViewInit, OnDestroy {
|
|||||||
this.params = param
|
this.params = param
|
||||||
this.response = res
|
this.response = res
|
||||||
this.calcDiff()
|
this.calcDiff()
|
||||||
this.callChangesInfo(this.tableId)
|
|
||||||
})
|
})
|
||||||
.catch((err: any) => err)
|
.catch((err: any) => err)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.loadingTable = true
|
this.loadingTable = true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.callChangesInfo(this.tableId)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if (typeof this.router.snapshot.params['tableId'] === 'undefined') {
|
if (typeof this.router.snapshot.params['tableId'] === 'undefined') {
|
||||||
@@ -395,7 +378,6 @@ export class ApproveDetailsComponent implements AfterViewInit, OnDestroy {
|
|||||||
this.params = param
|
this.params = param
|
||||||
this.response = res
|
this.response = res
|
||||||
this.calcDiff()
|
this.calcDiff()
|
||||||
this.callChangesInfo(this.tableId)
|
|
||||||
})
|
})
|
||||||
.catch((err: any) => {
|
.catch((err: any) => {
|
||||||
this.acceptLoading = false
|
this.acceptLoading = false
|
||||||
@@ -404,6 +386,8 @@ export class ApproveDetailsComponent implements AfterViewInit, OnDestroy {
|
|||||||
this.loadingTable = true
|
this.loadingTable = true
|
||||||
this.setFocus()
|
this.setFocus()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.callChangesInfo(this.tableId)
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
|
@@ -33,34 +33,12 @@
|
|||||||
<div class="clr-col-md-12" ng-if="loaded">
|
<div class="clr-col-md-12" ng-if="loaded">
|
||||||
<div *ngIf="approveList && remained !== 0">
|
<div *ngIf="approveList && remained !== 0">
|
||||||
<clr-datagrid class="datagrid-compact datagrid-custom-footer">
|
<clr-datagrid class="datagrid-compact datagrid-custom-footer">
|
||||||
<clr-dg-column [clrDgField]="'submitter'">
|
<clr-dg-column [clrDgField]="'submitter'">SUBMITTER</clr-dg-column>
|
||||||
SUBMITTER
|
<clr-dg-column [clrDgField]="'baseTable'">BASE TABLE</clr-dg-column>
|
||||||
<clr-dg-string-filter
|
<clr-dg-column [clrDgField]="'submitted'">SUBMITTED</clr-dg-column>
|
||||||
[clrDgStringFilter]="submitterFilter"
|
<clr-dg-column [clrDgField]="'submitReason'"
|
||||||
aria-label="Filter submitter"
|
>SUBMIT REASON</clr-dg-column
|
||||||
></clr-dg-string-filter>
|
>
|
||||||
</clr-dg-column>
|
|
||||||
<clr-dg-column [clrDgField]="'baseTable'">
|
|
||||||
BASE TABLE
|
|
||||||
<clr-dg-string-filter
|
|
||||||
[clrDgStringFilter]="baseTableFilter"
|
|
||||||
aria-label="Filter base table"
|
|
||||||
></clr-dg-string-filter>
|
|
||||||
</clr-dg-column>
|
|
||||||
<clr-dg-column [clrDgField]="'submitted'">
|
|
||||||
SUBMITTED
|
|
||||||
<clr-dg-string-filter
|
|
||||||
[clrDgStringFilter]="submittedFilter"
|
|
||||||
aria-label="Filter submitted date"
|
|
||||||
></clr-dg-string-filter>
|
|
||||||
</clr-dg-column>
|
|
||||||
<clr-dg-column [clrDgField]="'submitReason'">
|
|
||||||
SUBMIT REASON
|
|
||||||
<clr-dg-string-filter
|
|
||||||
[clrDgStringFilter]="submitReasonFilter"
|
|
||||||
aria-label="Filter submit reason"
|
|
||||||
></clr-dg-string-filter>
|
|
||||||
</clr-dg-column>
|
|
||||||
<clr-dg-column>ACTION</clr-dg-column>
|
<clr-dg-column>ACTION</clr-dg-column>
|
||||||
<clr-dg-column>DOWNLOAD</clr-dg-column>
|
<clr-dg-column>DOWNLOAD</clr-dg-column>
|
||||||
|
|
||||||
@@ -73,19 +51,15 @@
|
|||||||
<clr-dg-cell>{{ approveItem.submitReason }}</clr-dg-cell>
|
<clr-dg-cell>{{ approveItem.submitReason }}</clr-dg-cell>
|
||||||
<clr-dg-cell>
|
<clr-dg-cell>
|
||||||
<div
|
<div
|
||||||
class="clr-row d-flex justify-content-around"
|
class="clr-row"
|
||||||
role="toolbar"
|
role="tooltip"
|
||||||
aria-label="Table actions"
|
class="d-flex justify-content-around"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="column-center links tooltip tooltip-md tooltip-bottom-left color-green"
|
class="column-center links tooltip tooltip-md tooltip-bottom-left color-green"
|
||||||
(click)="getClicked(i)"
|
(click)="getClicked(i)"
|
||||||
>
|
>
|
||||||
<clr-icon
|
<clr-icon shape="check" size="24"></clr-icon>
|
||||||
shape="check"
|
|
||||||
size="24"
|
|
||||||
aria-hidden="true"
|
|
||||||
></clr-icon>
|
|
||||||
<span class="tooltip-content">Go to review page screen</span>
|
<span class="tooltip-content">Go to review page screen</span>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
@@ -96,12 +70,10 @@
|
|||||||
*ngIf="!approveItem.rejectLoading"
|
*ngIf="!approveItem.rejectLoading"
|
||||||
shape="ban"
|
shape="ban"
|
||||||
size="22"
|
size="22"
|
||||||
aria-hidden="true"
|
|
||||||
></clr-icon>
|
></clr-icon>
|
||||||
<clr-spinner
|
<clr-spinner
|
||||||
*ngIf="approveItem.rejectLoading"
|
*ngIf="approveItem.rejectLoading"
|
||||||
[clrSmall]="true"
|
[clrSmall]="true"
|
||||||
aria-hidden="true"
|
|
||||||
></clr-spinner>
|
></clr-spinner>
|
||||||
<span class="tooltip-content">Reject</span>
|
<span class="tooltip-content">Reject</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -109,11 +81,7 @@
|
|||||||
class="column-center links tooltip tooltip-md tooltip-bottom-left color-blue"
|
class="column-center links tooltip tooltip-md tooltip-bottom-left color-blue"
|
||||||
(click)="getTable(approveItem.tableId)"
|
(click)="getTable(approveItem.tableId)"
|
||||||
>
|
>
|
||||||
<clr-icon
|
<clr-icon shape="code" size="28"></clr-icon>
|
||||||
shape="code"
|
|
||||||
size="28"
|
|
||||||
aria-hidden="true"
|
|
||||||
></clr-icon>
|
|
||||||
<span class="tooltip-content">Go to staged data screen</span>
|
<span class="tooltip-content">Go to staged data screen</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -121,7 +89,6 @@
|
|||||||
<clr-dg-cell class="p-0 d-flex justify-content-center">
|
<clr-dg-cell class="p-0 d-flex justify-content-center">
|
||||||
<button
|
<button
|
||||||
class="btn btn-success"
|
class="btn btn-success"
|
||||||
aria-label="Download audit file"
|
|
||||||
[id]="approveItem.tableId"
|
[id]="approveItem.tableId"
|
||||||
(click)="
|
(click)="
|
||||||
download(approveItem.tableId); $event.stopPropagation()
|
download(approveItem.tableId); $event.stopPropagation()
|
||||||
|
@@ -0,0 +1,43 @@
|
|||||||
|
@import '../../../colors.scss';
|
||||||
|
|
||||||
|
.column-center {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.datagrid .datagrid-column .datagrid-column-title{
|
||||||
|
outline: none!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links {
|
||||||
|
font-weight: 700;cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip.tooltip-bottom-left>.tooltip-content, .tooltip .tooltip-content.tooltip-bottom-left {
|
||||||
|
background: $headerBackground !important;
|
||||||
|
}
|
||||||
|
.tooltip.tooltip-bottom-left>.tooltip-content:before, .tooltip .tooltip-content.tooltip-bottom-left:before {
|
||||||
|
border-right: .25rem solid $headerBackground;
|
||||||
|
border-bottom: .20833rem solid $headerBackground;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noBorder {
|
||||||
|
border-bottom: 1px solid transparent!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.approvals-list-wrapper {
|
||||||
|
height: 70vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noapprovals-info-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
height: calc(100vh - 200px);
|
||||||
|
}
|
@@ -1,14 +1,8 @@
|
|||||||
import {
|
import { Component, OnInit, ChangeDetectorRef } from '@angular/core'
|
||||||
Component,
|
|
||||||
OnInit,
|
|
||||||
ChangeDetectorRef,
|
|
||||||
ViewEncapsulation
|
|
||||||
} from '@angular/core'
|
|
||||||
import { SasStoreService } from '../../services/sas-store.service'
|
import { SasStoreService } from '../../services/sas-store.service'
|
||||||
import { Router } from '@angular/router'
|
import { Router } from '@angular/router'
|
||||||
import { SasService } from '../../services/sas.service'
|
import { SasService } from '../../services/sas.service'
|
||||||
import { EventService } from '../../services/event.service'
|
import { EventService } from '../../services/event.service'
|
||||||
import { ClrDatagridStringFilterInterface } from '@clr/angular'
|
|
||||||
|
|
||||||
interface ApproveData {
|
interface ApproveData {
|
||||||
tableId: string
|
tableId: string
|
||||||
@@ -20,40 +14,13 @@ interface ApproveData {
|
|||||||
rejectLoading?: boolean
|
rejectLoading?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
class SubmitterFilter implements ClrDatagridStringFilterInterface<ApproveData> {
|
|
||||||
accepts(data: ApproveData, search: string): boolean {
|
|
||||||
return data.submitter.toLowerCase().indexOf(search.toLowerCase()) >= 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BaseTableFilter implements ClrDatagridStringFilterInterface<ApproveData> {
|
|
||||||
accepts(data: ApproveData, search: string): boolean {
|
|
||||||
return data.baseTable.toLowerCase().indexOf(search.toLowerCase()) >= 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SubmittedFilter implements ClrDatagridStringFilterInterface<ApproveData> {
|
|
||||||
accepts(data: ApproveData, search: string): boolean {
|
|
||||||
return data.submitted.toLowerCase().indexOf(search.toLowerCase()) >= 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SubmitReasonFilter
|
|
||||||
implements ClrDatagridStringFilterInterface<ApproveData>
|
|
||||||
{
|
|
||||||
accepts(data: ApproveData, search: string): boolean {
|
|
||||||
return data.submitReason.toLowerCase().indexOf(search.toLowerCase()) >= 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-approve',
|
selector: 'app-approve',
|
||||||
templateUrl: './approve.component.html',
|
templateUrl: './approve.component.html',
|
||||||
styleUrls: ['./approve.component.scss'],
|
styleUrls: ['./approve.component.scss'],
|
||||||
host: {
|
host: {
|
||||||
class: 'content-container'
|
class: 'content-container'
|
||||||
},
|
}
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class ApproveComponent implements OnInit {
|
export class ApproveComponent implements OnInit {
|
||||||
public approveList: Array<ApproveData> | undefined
|
public approveList: Array<ApproveData> | undefined
|
||||||
@@ -62,12 +29,6 @@ export class ApproveComponent implements OnInit {
|
|||||||
public loaded: boolean = false
|
public loaded: boolean = false
|
||||||
public itemsNum: number = 10
|
public itemsNum: number = 10
|
||||||
|
|
||||||
// Filter instances for datagrid accessibility
|
|
||||||
public submitterFilter = new SubmitterFilter()
|
|
||||||
public baseTableFilter = new BaseTableFilter()
|
|
||||||
public submittedFilter = new SubmittedFilter()
|
|
||||||
public submitReasonFilter = new SubmitReasonFilter()
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private sasStoreService: SasStoreService,
|
private sasStoreService: SasStoreService,
|
||||||
private eventService: EventService,
|
private eventService: EventService,
|
||||||
|
@@ -24,19 +24,19 @@
|
|||||||
<a
|
<a
|
||||||
*ngIf="ind < 1"
|
*ngIf="ind < 1"
|
||||||
(click)="getTable(approveData[col])"
|
(click)="getTable(approveData[col])"
|
||||||
class="cursor-pointer table-link"
|
class="cursor-pointer"
|
||||||
>{{ approveData[col] }}</a
|
>{{ approveData[col] }}</a
|
||||||
>
|
>
|
||||||
<div *ngIf="ind < 2 && ind >= 1">
|
<div *ngIf="ind < 2 && ind >= 1">
|
||||||
<a
|
<a
|
||||||
(click)="getBaseTable(approveData[col])"
|
(click)="getBaseTable(approveData[col])"
|
||||||
class="cursor-pointer table-link"
|
class="cursor-pointer"
|
||||||
>VIEW</a
|
>VIEW</a
|
||||||
>
|
>
|
||||||
<span> / </span>
|
<span> / </span>
|
||||||
<a
|
<a
|
||||||
(click)="getEditTable(approveData[col])"
|
(click)="getEditTable(approveData[col])"
|
||||||
class="cursor-pointer table-link"
|
class="cursor-pointer"
|
||||||
>EDIT</a
|
>EDIT</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@@ -47,12 +47,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button
|
<button type="button" class="btn btn-outline" (click)="openModal = false">
|
||||||
type="button"
|
|
||||||
aria-label="Close modal"
|
|
||||||
class="btn btn-outline"
|
|
||||||
(click)="openModal = false"
|
|
||||||
>
|
|
||||||
OK
|
OK
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -85,48 +80,16 @@
|
|||||||
class="datagrid-history datagrid-custom-footer"
|
class="datagrid-history datagrid-custom-footer"
|
||||||
*ngIf="loaded"
|
*ngIf="loaded"
|
||||||
>
|
>
|
||||||
<clr-dg-column [clrDgField]="'basetable'">
|
<clr-dg-column [clrDgField]="'basetable'">BASE_TABLE</clr-dg-column>
|
||||||
BASE_TABLE
|
<clr-dg-column [clrDgField]="'status'">STATUS</clr-dg-column>
|
||||||
<clr-dg-string-filter
|
<clr-dg-column [clrDgField]="'submitter'">SUBMITTER</clr-dg-column>
|
||||||
[clrDgStringFilter]="baseTableFilter"
|
<clr-dg-column [clrDgField]="'submittedReason'"
|
||||||
aria-label="Filter base table"
|
>SUBMIT REASON</clr-dg-column
|
||||||
></clr-dg-string-filter>
|
>
|
||||||
</clr-dg-column>
|
<clr-dg-column [clrDgField]="'submitted'">SUBMITTED</clr-dg-column>
|
||||||
<clr-dg-column [clrDgField]="'status'">
|
<clr-dg-column [clrDgField]="'reviewed'"
|
||||||
STATUS
|
>APPROVED / REJECTED</clr-dg-column
|
||||||
<clr-dg-string-filter
|
>
|
||||||
[clrDgStringFilter]="statusFilter"
|
|
||||||
aria-label="Filter status"
|
|
||||||
></clr-dg-string-filter>
|
|
||||||
</clr-dg-column>
|
|
||||||
<clr-dg-column [clrDgField]="'submitter'">
|
|
||||||
SUBMITTER
|
|
||||||
<clr-dg-string-filter
|
|
||||||
[clrDgStringFilter]="submitterFilter"
|
|
||||||
aria-label="Filter submitter"
|
|
||||||
></clr-dg-string-filter>
|
|
||||||
</clr-dg-column>
|
|
||||||
<clr-dg-column [clrDgField]="'submittedReason'">
|
|
||||||
SUBMIT REASON
|
|
||||||
<clr-dg-string-filter
|
|
||||||
[clrDgStringFilter]="submitReasonFilter"
|
|
||||||
aria-label="Filter submit reason"
|
|
||||||
></clr-dg-string-filter>
|
|
||||||
</clr-dg-column>
|
|
||||||
<clr-dg-column [clrDgField]="'submitted'">
|
|
||||||
SUBMITTED
|
|
||||||
<clr-dg-string-filter
|
|
||||||
[clrDgStringFilter]="submittedFilter"
|
|
||||||
aria-label="Filter submitted date"
|
|
||||||
></clr-dg-string-filter>
|
|
||||||
</clr-dg-column>
|
|
||||||
<clr-dg-column [clrDgField]="'reviewed'">
|
|
||||||
APPROVED / REJECTED
|
|
||||||
<clr-dg-string-filter
|
|
||||||
[clrDgStringFilter]="reviewedFilter"
|
|
||||||
aria-label="Filter reviewed date"
|
|
||||||
></clr-dg-string-filter>
|
|
||||||
</clr-dg-column>
|
|
||||||
<clr-dg-column>DOWNLOAD</clr-dg-column>
|
<clr-dg-column>DOWNLOAD</clr-dg-column>
|
||||||
|
|
||||||
<clr-dg-row
|
<clr-dg-row
|
||||||
@@ -156,7 +119,6 @@
|
|||||||
<clr-dg-cell class="verCenter">{{ historyItem.reviewed }}</clr-dg-cell>
|
<clr-dg-cell class="verCenter">{{ historyItem.reviewed }}</clr-dg-cell>
|
||||||
<clr-dg-cell class="verCenter p-0 d-flex justify-content-center">
|
<clr-dg-cell class="verCenter p-0 d-flex justify-content-center">
|
||||||
<button
|
<button
|
||||||
aria-label="Download audit file"
|
|
||||||
class="btn btn-success"
|
class="btn btn-success"
|
||||||
(click)="download(historyItem.tableId); $event.stopPropagation()"
|
(click)="download(historyItem.tableId); $event.stopPropagation()"
|
||||||
>
|
>
|
||||||
|
@@ -0,0 +1,37 @@
|
|||||||
|
.rejected {
|
||||||
|
color: #f83126;
|
||||||
|
font-weight: bold
|
||||||
|
}
|
||||||
|
|
||||||
|
.accepted {
|
||||||
|
color: #3fc424;
|
||||||
|
font-weight: bold
|
||||||
|
}
|
||||||
|
|
||||||
|
.hsCell {
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
align-items: center !important;
|
||||||
|
padding: 7px;
|
||||||
|
}
|
||||||
|
.btCell {
|
||||||
|
display: flex !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verCenter {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-more {
|
||||||
|
input {
|
||||||
|
width: 90px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#noDataContainer {
|
||||||
|
height: calc(100vh - 200px);
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core'
|
import { Component, OnInit } from '@angular/core'
|
||||||
|
|
||||||
import { Router } from '@angular/router'
|
import { Router } from '@angular/router'
|
||||||
import { SASjsConfig } from '@sasjs/adapter'
|
import { SASjsConfig } from '@sasjs/adapter'
|
||||||
@@ -8,55 +8,6 @@ import {
|
|||||||
EventService,
|
EventService,
|
||||||
SasService
|
SasService
|
||||||
} from 'src/app/services'
|
} from 'src/app/services'
|
||||||
import { ClrDatagridStringFilterInterface } from '@clr/angular'
|
|
||||||
|
|
||||||
interface HistoryData {
|
|
||||||
tableId: string
|
|
||||||
basetable: string
|
|
||||||
status: string
|
|
||||||
submitter: string
|
|
||||||
submittedReason: string
|
|
||||||
submitted: string
|
|
||||||
reviewed: string
|
|
||||||
}
|
|
||||||
|
|
||||||
class BaseTableFilter implements ClrDatagridStringFilterInterface<HistoryData> {
|
|
||||||
accepts(data: HistoryData, search: string): boolean {
|
|
||||||
return data.basetable.toLowerCase().indexOf(search.toLowerCase()) >= 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class StatusFilter implements ClrDatagridStringFilterInterface<HistoryData> {
|
|
||||||
accepts(data: HistoryData, search: string): boolean {
|
|
||||||
return data.status.toLowerCase().indexOf(search.toLowerCase()) >= 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SubmitterFilter implements ClrDatagridStringFilterInterface<HistoryData> {
|
|
||||||
accepts(data: HistoryData, search: string): boolean {
|
|
||||||
return data.submitter.toLowerCase().indexOf(search.toLowerCase()) >= 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SubmitReasonFilter
|
|
||||||
implements ClrDatagridStringFilterInterface<HistoryData>
|
|
||||||
{
|
|
||||||
accepts(data: HistoryData, search: string): boolean {
|
|
||||||
return data.submittedReason.toLowerCase().indexOf(search.toLowerCase()) >= 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SubmittedFilter implements ClrDatagridStringFilterInterface<HistoryData> {
|
|
||||||
accepts(data: HistoryData, search: string): boolean {
|
|
||||||
return data.submitted.toLowerCase().indexOf(search.toLowerCase()) >= 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ReviewedFilter implements ClrDatagridStringFilterInterface<HistoryData> {
|
|
||||||
accepts(data: HistoryData, search: string): boolean {
|
|
||||||
return data.reviewed.toLowerCase().indexOf(search.toLowerCase()) >= 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-history',
|
selector: 'app-history',
|
||||||
@@ -64,8 +15,7 @@ class ReviewedFilter implements ClrDatagridStringFilterInterface<HistoryData> {
|
|||||||
styleUrls: ['./history.component.scss'],
|
styleUrls: ['./history.component.scss'],
|
||||||
host: {
|
host: {
|
||||||
class: 'content-container'
|
class: 'content-container'
|
||||||
},
|
}
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class HistoryComponent implements OnInit {
|
export class HistoryComponent implements OnInit {
|
||||||
public history: Array<any> = []
|
public history: Array<any> = []
|
||||||
@@ -78,14 +28,6 @@ export class HistoryComponent implements OnInit {
|
|||||||
public approveData: any = {}
|
public approveData: any = {}
|
||||||
public sasjsConfig: SASjsConfig = new SASjsConfig()
|
public sasjsConfig: SASjsConfig = new SASjsConfig()
|
||||||
|
|
||||||
// Filter instances for datagrid accessibility
|
|
||||||
public baseTableFilter = new BaseTableFilter()
|
|
||||||
public statusFilter = new StatusFilter()
|
|
||||||
public submitterFilter = new SubmitterFilter()
|
|
||||||
public submitReasonFilter = new SubmitReasonFilter()
|
|
||||||
public submittedFilter = new SubmittedFilter()
|
|
||||||
public reviewedFilter = new ReviewedFilter()
|
|
||||||
|
|
||||||
public histParams: { HIST: number; STARTROW: number; NOBS: number } = {
|
public histParams: { HIST: number; STARTROW: number; NOBS: number } = {
|
||||||
HIST: 0,
|
HIST: 0,
|
||||||
STARTROW: 1,
|
STARTROW: 1,
|
||||||
|
@@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common'
|
|||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { FormsModule } from '@angular/forms'
|
import { FormsModule } from '@angular/forms'
|
||||||
import { ClarityModule } from '@clr/angular'
|
import { ClarityModule } from '@clr/angular'
|
||||||
import { HotTableModule } from '@handsontable/angular-wrapper'
|
import { HotTableModule } from '@handsontable/angular'
|
||||||
import { DirectivesModule } from '../directives/directives.module'
|
import { DirectivesModule } from '../directives/directives.module'
|
||||||
import { SharedModule } from '../shared/shared.module'
|
import { SharedModule } from '../shared/shared.module'
|
||||||
import { ApproveDetailsComponent } from './approve-details/approve-details.component'
|
import { ApproveDetailsComponent } from './approve-details/approve-details.component'
|
||||||
@@ -23,7 +23,7 @@ import { HistoryComponent } from './history/history.component'
|
|||||||
FormsModule,
|
FormsModule,
|
||||||
ReviewRoutingModule,
|
ReviewRoutingModule,
|
||||||
ClarityModule,
|
ClarityModule,
|
||||||
HotTableModule,
|
HotTableModule.forRoot(),
|
||||||
DirectivesModule,
|
DirectivesModule,
|
||||||
SharedModule
|
SharedModule
|
||||||
]
|
]
|
||||||
|
@@ -44,20 +44,10 @@
|
|||||||
<div *ngIf="submitterList && remained !== 0">
|
<div *ngIf="submitterList && remained !== 0">
|
||||||
<clr-datagrid class="datagrid-compact datagrid-custom-footer">
|
<clr-datagrid class="datagrid-compact datagrid-custom-footer">
|
||||||
<clr-dg-column>BASE TABLE</clr-dg-column>
|
<clr-dg-column>BASE TABLE</clr-dg-column>
|
||||||
<clr-dg-column [clrDgField]="'submitted'">
|
<clr-dg-column [clrDgField]="'submitted'">SUBMITTED</clr-dg-column>
|
||||||
SUBMITTED
|
<clr-dg-column [clrDgField]="'submitReason'"
|
||||||
<clr-dg-string-filter
|
>SUBMIT REASON</clr-dg-column
|
||||||
[clrDgStringFilter]="submittedFilter"
|
>
|
||||||
aria-label="Filter submitted date"
|
|
||||||
></clr-dg-string-filter>
|
|
||||||
</clr-dg-column>
|
|
||||||
<clr-dg-column [clrDgField]="'submitReason'">
|
|
||||||
SUBMIT REASON
|
|
||||||
<clr-dg-string-filter
|
|
||||||
[clrDgStringFilter]="submitReasonFilter"
|
|
||||||
aria-label="Filter submit reason"
|
|
||||||
></clr-dg-string-filter>
|
|
||||||
</clr-dg-column>
|
|
||||||
<clr-dg-column class="d-flex justify-content-center"
|
<clr-dg-column class="d-flex justify-content-center"
|
||||||
>ACTION</clr-dg-column
|
>ACTION</clr-dg-column
|
||||||
>
|
>
|
||||||
@@ -74,11 +64,7 @@
|
|||||||
<!-- <clr-dg-cell>{{sub.approver}}</clr-dg-cell> -->
|
<!-- <clr-dg-cell>{{sub.approver}}</clr-dg-cell> -->
|
||||||
<clr-dg-cell>{{ sub.submitReason }}</clr-dg-cell>
|
<clr-dg-cell>{{ sub.submitReason }}</clr-dg-cell>
|
||||||
<clr-dg-cell>
|
<clr-dg-cell>
|
||||||
<div
|
<div class="row justify-content-around" role="tooltip">
|
||||||
class="row justify-content-around"
|
|
||||||
role="tooltip"
|
|
||||||
aria-label="Go to staged data screen"
|
|
||||||
>
|
|
||||||
<a
|
<a
|
||||||
class="column-center links tooltip tooltip-md tooltip-bottom-left color-blue"
|
class="column-center links tooltip tooltip-md tooltip-bottom-left color-blue"
|
||||||
(click)="goToStage(sub.tableId)"
|
(click)="goToStage(sub.tableId)"
|
||||||
@@ -93,7 +79,6 @@
|
|||||||
<clr-dg-cell class="p-0 d-flex justify-content-center">
|
<clr-dg-cell class="p-0 d-flex justify-content-center">
|
||||||
<button
|
<button
|
||||||
class="btn btn-success"
|
class="btn btn-success"
|
||||||
aria-label="Download audit file for table record"
|
|
||||||
(click)="download(sub.tableId); $event.stopPropagation()"
|
(click)="download(sub.tableId); $event.stopPropagation()"
|
||||||
>
|
>
|
||||||
<clr-icon shape="download"></clr-icon>
|
<clr-icon shape="download"></clr-icon>
|
||||||
|
@@ -0,0 +1,17 @@
|
|||||||
|
@import '../../../colors.scss';
|
||||||
|
|
||||||
|
.noBorder {
|
||||||
|
border-bottom: 1px solid transparent!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip.tooltip-bottom-left>.tooltip-content, .tooltip .tooltip-content.tooltip-bottom-left {
|
||||||
|
background: $headerBackground !important;
|
||||||
|
}
|
||||||
|
.tooltip.tooltip-bottom-left>.tooltip-content:before, .tooltip .tooltip-content.tooltip-bottom-left:before {
|
||||||
|
border-right: .25rem solid $headerBackground;
|
||||||
|
border-bottom: .20833rem solid $headerBackground;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-submitted-tables {
|
||||||
|
height: calc(100vh - 200px);
|
||||||
|
}
|
@@ -1,13 +1,7 @@
|
|||||||
import {
|
import { Component, AfterViewInit, OnInit } from '@angular/core'
|
||||||
Component,
|
|
||||||
AfterViewInit,
|
|
||||||
OnInit,
|
|
||||||
ViewEncapsulation
|
|
||||||
} from '@angular/core'
|
|
||||||
import { Subscription } from 'rxjs'
|
import { Subscription } from 'rxjs'
|
||||||
import { ActivatedRoute, Router } from '@angular/router'
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
import { SasStoreService, EventService, SasService } from '../../services'
|
import { SasStoreService, EventService, SasService } from '../../services'
|
||||||
import { ClrDatagridStringFilterInterface } from '@clr/angular'
|
|
||||||
|
|
||||||
interface SubmitterData {
|
interface SubmitterData {
|
||||||
tableId: string
|
tableId: string
|
||||||
@@ -17,30 +11,13 @@ interface SubmitterData {
|
|||||||
approver: string
|
approver: string
|
||||||
}
|
}
|
||||||
|
|
||||||
class SubmittedFilter
|
|
||||||
implements ClrDatagridStringFilterInterface<SubmitterData>
|
|
||||||
{
|
|
||||||
accepts(data: SubmitterData, search: string): boolean {
|
|
||||||
return data.submitted.toLowerCase().indexOf(search.toLowerCase()) >= 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SubmitReasonFilter
|
|
||||||
implements ClrDatagridStringFilterInterface<SubmitterData>
|
|
||||||
{
|
|
||||||
accepts(data: SubmitterData, search: string): boolean {
|
|
||||||
return data.submitReason.toLowerCase().indexOf(search.toLowerCase()) >= 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-submitter',
|
selector: 'app-submitter',
|
||||||
templateUrl: './submitter.component.html',
|
templateUrl: './submitter.component.html',
|
||||||
styleUrls: ['./submitter.component.scss'],
|
styleUrls: ['./submitter.component.scss'],
|
||||||
host: {
|
host: {
|
||||||
class: 'content-container'
|
class: 'content-container'
|
||||||
},
|
}
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class SubmitterComponent implements OnInit, AfterViewInit {
|
export class SubmitterComponent implements OnInit, AfterViewInit {
|
||||||
public remained: number = 0
|
public remained: number = 0
|
||||||
@@ -54,10 +31,6 @@ export class SubmitterComponent implements OnInit, AfterViewInit {
|
|||||||
private _readySub!: Subscription
|
private _readySub!: Subscription
|
||||||
private _backToSub!: Subscription
|
private _backToSub!: Subscription
|
||||||
|
|
||||||
// Filter instances for datagrid accessibility
|
|
||||||
public submittedFilter = new SubmittedFilter()
|
|
||||||
public submitReasonFilter = new SubmitReasonFilter()
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private sasStoreService: SasStoreService,
|
private sasStoreService: SasStoreService,
|
||||||
private eventService: EventService,
|
private eventService: EventService,
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core'
|
import { Component, OnInit } from '@angular/core'
|
||||||
import { globals } from '../_globals'
|
import { globals } from '../_globals'
|
||||||
import { HelperService } from '../services/helper.service'
|
import { HelperService } from '../services/helper.service'
|
||||||
import { Location } from '@angular/common'
|
import { Location } from '@angular/common'
|
||||||
@@ -12,8 +12,7 @@ import { RequestWrapperResponse } from '../models/request-wrapper/RequestWrapper
|
|||||||
styleUrls: ['./role.component.scss'],
|
styleUrls: ['./role.component.scss'],
|
||||||
host: {
|
host: {
|
||||||
class: 'content-container'
|
class: 'content-container'
|
||||||
},
|
}
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class RoleComponent implements OnInit {
|
export class RoleComponent implements OnInit {
|
||||||
public roles: Array<any> | undefined
|
public roles: Array<any> | undefined
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core'
|
import { Component, OnInit, OnDestroy } from '@angular/core'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-home-route',
|
selector: 'app-home-route',
|
||||||
@@ -6,8 +6,7 @@ import { Component, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core'
|
|||||||
styleUrls: ['./home-route.component.scss'],
|
styleUrls: ['./home-route.component.scss'],
|
||||||
host: {
|
host: {
|
||||||
class: 'content-container'
|
class: 'content-container'
|
||||||
},
|
}
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class HomeRouteComponent implements OnInit, OnDestroy {
|
export class HomeRouteComponent implements OnInit, OnDestroy {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core'
|
import { Component, OnInit } from '@angular/core'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-review-route',
|
selector: 'app-review-route',
|
||||||
@@ -6,8 +6,7 @@ import { Component, OnInit, ViewEncapsulation } from '@angular/core'
|
|||||||
styleUrls: ['./review-route.component.scss'],
|
styleUrls: ['./review-route.component.scss'],
|
||||||
host: {
|
host: {
|
||||||
class: 'content-container'
|
class: 'content-container'
|
||||||
},
|
}
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class ReviewRouteComponent implements OnInit {
|
export class ReviewRouteComponent implements OnInit {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core'
|
import { Component, OnInit } from '@angular/core'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-usernav-route',
|
selector: 'app-usernav-route',
|
||||||
@@ -6,8 +6,7 @@ import { Component, OnInit, ViewEncapsulation } from '@angular/core'
|
|||||||
styleUrls: ['./usernav-route.component.scss'],
|
styleUrls: ['./usernav-route.component.scss'],
|
||||||
host: {
|
host: {
|
||||||
class: 'content-container'
|
class: 'content-container'
|
||||||
},
|
}
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class UsernavRouteComponent implements OnInit {
|
export class UsernavRouteComponent implements OnInit {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core'
|
import { Component, OnInit, OnDestroy } from '@angular/core'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-view-route',
|
selector: 'app-view-route',
|
||||||
@@ -6,8 +6,7 @@ import { Component, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core'
|
|||||||
styleUrls: ['./view-route.component.scss'],
|
styleUrls: ['./view-route.component.scss'],
|
||||||
host: {
|
host: {
|
||||||
class: 'content-container'
|
class: 'content-container'
|
||||||
},
|
}
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class ViewRouteComponent implements OnInit, OnDestroy {
|
export class ViewRouteComponent implements OnInit, OnDestroy {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core'
|
import { Component, OnInit, OnDestroy } from '@angular/core'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-xlmap-route',
|
selector: 'app-xlmap-route',
|
||||||
@@ -6,8 +6,7 @@ import { Component, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core'
|
|||||||
styleUrls: ['./xlmap-route.component.scss'],
|
styleUrls: ['./xlmap-route.component.scss'],
|
||||||
host: {
|
host: {
|
||||||
class: 'content-container'
|
class: 'content-container'
|
||||||
},
|
}
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class XLMapRouteComponent implements OnInit, OnDestroy {
|
export class XLMapRouteComponent implements OnInit, OnDestroy {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import cloneDeep from 'lodash-es/cloneDeep'
|
import cloneDeep from 'lodash-es/cloneDeep'
|
||||||
import * as CryptoMD5 from 'crypto-js/md5'
|
import * as CryptoMD5 from 'crypto-js/md5'
|
||||||
import { SasService } from './sas.service'
|
|
||||||
|
|
||||||
const librariesToShow = 50
|
const librariesToShow = 50
|
||||||
|
|
||||||
@@ -13,7 +12,7 @@ export class HelperService {
|
|||||||
public loadMoreCount: number = librariesToShow
|
public loadMoreCount: number = librariesToShow
|
||||||
public isMicrosoft: boolean = false
|
public isMicrosoft: boolean = false
|
||||||
|
|
||||||
constructor(private sasService: SasService) {
|
constructor() {
|
||||||
this.isMicrosoft = this.isIEorEDGE()
|
this.isMicrosoft = this.isIEorEDGE()
|
||||||
console.log('Is IE or Edge?', this.isMicrosoft)
|
console.log('Is IE or Edge?', this.isMicrosoft)
|
||||||
}
|
}
|
||||||
@@ -315,20 +314,6 @@ export class HelperService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public isStreamingViya(): boolean {
|
|
||||||
const serverType = this.sasService.getServerType()
|
|
||||||
|
|
||||||
if (serverType !== 'SASVIYA') return false
|
|
||||||
|
|
||||||
if (
|
|
||||||
location.search.toLowerCase().includes('?_file=') &&
|
|
||||||
location.pathname.toLowerCase().includes('/sasjobexecution')
|
|
||||||
)
|
|
||||||
return true
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required type is NodeJS.Timeout
|
// Required type is NodeJS.Timeout
|
||||||
// But NodeJS is not available in browser so we have to go with any
|
// But NodeJS is not available in browser so we have to go with any
|
||||||
private debounceTimeout: any
|
private debounceTimeout: any
|
||||||
|
@@ -223,7 +223,7 @@ export class SasStoreService {
|
|||||||
tables[tableName] = [tableData]
|
tables[tableName] = [tableData]
|
||||||
return (
|
return (
|
||||||
await this.sasService.request(program, tables, {
|
await this.sasService.request(program, tables, {
|
||||||
useComputeApi: null // Using WEB APPROACH as a temporary workaround until VIYA JES API is fixed. For other server types then VIYA this is not applicable
|
useComputeApi: false
|
||||||
})
|
})
|
||||||
).adapterResponse
|
).adapterResponse
|
||||||
}
|
}
|
||||||
@@ -232,7 +232,7 @@ export class SasStoreService {
|
|||||||
tables[tableName] = [tableData]
|
tables[tableName] = [tableData]
|
||||||
return (
|
return (
|
||||||
await this.sasService.request(program, tables, {
|
await this.sasService.request(program, tables, {
|
||||||
useComputeApi: null // Using WEB APPROACH as a temporary workaround until VIYA JES API is fixed. For other server types then VIYA this is not applicable
|
useComputeApi: false
|
||||||
})
|
})
|
||||||
).adapterResponse
|
).adapterResponse
|
||||||
}
|
}
|
||||||
|
@@ -1,21 +1,9 @@
|
|||||||
import {
|
import { HttpClient } from '@angular/common/http'
|
||||||
HttpClient,
|
|
||||||
HttpContext,
|
|
||||||
HttpErrorResponse,
|
|
||||||
HttpHeaders,
|
|
||||||
HttpParams
|
|
||||||
} from '@angular/common/http'
|
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { catchError, Observable, throwError } from 'rxjs'
|
import { Observable } from 'rxjs'
|
||||||
import { Collection } from '../viya-api-explorer/models/collection.model'
|
import { Collection } from '../viya-api-explorer/models/collection.model'
|
||||||
import { AppStoreService } from './app-store.service'
|
import { AppStoreService } from './app-store.service'
|
||||||
import { ViyaApis } from '../viya-api-explorer/models/viya-apis.models'
|
import { ViyaApis } from '../viya-api-explorer/models/viya-apis.models'
|
||||||
import { ViyaApiFolderMembers } from '../viya-api-explorer/models/viya-api-folder-content.model'
|
|
||||||
import { ViyaApiFolder } from '../viya-api-explorer/models/viya-api-folder.model'
|
|
||||||
import { ViyaApiIdentities } from '../viya-api-explorer/models/viya-api-identities.model'
|
|
||||||
import { ViyaApiCurrentUser } from '../viya-api-explorer/models/viya-api-current-user.model'
|
|
||||||
import { ViyaComputeContexts } from '../viya-api-explorer/models/viya-compute-contexts.model'
|
|
||||||
import { ComputeContextDetails } from '../viya-api-explorer/models/viya-compute-context-details.model'
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@@ -34,8 +22,7 @@ export class SasViyaService {
|
|||||||
},
|
},
|
||||||
Compute: {
|
Compute: {
|
||||||
jobs: '/jobDefinitions',
|
jobs: '/jobDefinitions',
|
||||||
jobExecution: '/jobExecution',
|
jobExecution: '/jobExecution'
|
||||||
contexts: '/compute/contexts'
|
|
||||||
},
|
},
|
||||||
Decision_Management: {
|
Decision_Management: {
|
||||||
modelManagement: '/modelManagement',
|
modelManagement: '/modelManagement',
|
||||||
@@ -70,23 +57,15 @@ export class SasViyaService {
|
|||||||
constructor(
|
constructor(
|
||||||
private http: HttpClient,
|
private http: HttpClient,
|
||||||
private appStoreService: AppStoreService
|
private appStoreService: AppStoreService
|
||||||
) {}
|
) {
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is replacing the constructor.
|
|
||||||
* The reason for this is timing issues, other services eg. sas.service, app-store.service
|
|
||||||
* must be initialized before this bit of code is executed.
|
|
||||||
* This function is being called by `sas.service`
|
|
||||||
*/
|
|
||||||
setup() {
|
|
||||||
const adapterConfig = this.appStoreService.getDcAdapterSettings()
|
const adapterConfig = this.appStoreService.getDcAdapterSettings()
|
||||||
|
|
||||||
this.serverUrl = adapterConfig?.serverUrl || ''
|
this.serverUrl = adapterConfig?.serverUrl || ''
|
||||||
|
|
||||||
// example collection request
|
//example
|
||||||
// this.getByCollection('jobs').subscribe((res) => {
|
this.getByCollection('jobs').subscribe((res) => {
|
||||||
// console.log('res', res)
|
console.log('res', res)
|
||||||
// })
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -103,7 +82,7 @@ export class SasViyaService {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
getByUrl(url: string): Observable<Collection> {
|
getByUrl(url: string): Observable<Collection> {
|
||||||
return this.get<Collection>(`${this.serverUrl}${url}`, {
|
return this.http.get<Collection>(`${this.serverUrl}${url}`, {
|
||||||
withCredentials: true
|
withCredentials: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -114,106 +93,8 @@ export class SasViyaService {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
getByCollection(apiCollection: string): Observable<Collection> {
|
getByCollection(apiCollection: string): Observable<Collection> {
|
||||||
return this.get<Collection>(`${this.serverUrl}${apiCollection}`, {
|
return this.http.get<Collection>(`${this.serverUrl}${apiCollection}`, {
|
||||||
withCredentials: true
|
withCredentials: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
getComputeContexts(): Observable<ViyaComputeContexts> {
|
|
||||||
return this.get<ViyaComputeContexts>(`${this.serverUrl}/compute/contexts`, {
|
|
||||||
withCredentials: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
getComputeContextById(id: string): Observable<ComputeContextDetails> {
|
|
||||||
return this.get<ComputeContextDetails>(
|
|
||||||
`${this.serverUrl}/compute/contexts/${id}`,
|
|
||||||
{
|
|
||||||
withCredentials: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param path Path to the folder
|
|
||||||
* @returns The folder info object
|
|
||||||
*/
|
|
||||||
getFolderByPath(path: string): Observable<ViyaApiFolder> {
|
|
||||||
return this.get<ViyaApiFolder>(
|
|
||||||
`${this.serverUrl}/folders/folders/@item?path=${path}`,
|
|
||||||
{
|
|
||||||
withCredentials: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
getFolderMembers(folderId: string): Observable<ViyaApiFolderMembers> {
|
|
||||||
return this.get<ViyaApiFolderMembers>(
|
|
||||||
`${this.serverUrl}/folders/folders/${folderId}/members`,
|
|
||||||
{
|
|
||||||
withCredentials: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
getAdminGroups(limit: number = 5000): Observable<ViyaApiIdentities> {
|
|
||||||
return this.get<ViyaApiIdentities>(
|
|
||||||
`${this.serverUrl}/identities/groups?sortBy=name&limit=${limit}`,
|
|
||||||
{
|
|
||||||
withCredentials: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
getCurrentUser(): Observable<ViyaApiCurrentUser> {
|
|
||||||
return this.get<ViyaApiCurrentUser>(
|
|
||||||
`${this.serverUrl}/identities/users/@currentUser`,
|
|
||||||
{
|
|
||||||
withCredentials: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
get<T>(
|
|
||||||
url: string,
|
|
||||||
options?: {
|
|
||||||
headers?:
|
|
||||||
| HttpHeaders
|
|
||||||
| {
|
|
||||||
[header: string]: string | string[]
|
|
||||||
}
|
|
||||||
context?: HttpContext
|
|
||||||
observe?: 'body'
|
|
||||||
params?:
|
|
||||||
| HttpParams
|
|
||||||
| {
|
|
||||||
[param: string]:
|
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| boolean
|
|
||||||
| ReadonlyArray<string | number | boolean>
|
|
||||||
}
|
|
||||||
reportProgress?: boolean
|
|
||||||
responseType?: 'json'
|
|
||||||
withCredentials?: boolean
|
|
||||||
transferCache?:
|
|
||||||
| {
|
|
||||||
includeHeaders?: string[]
|
|
||||||
}
|
|
||||||
| boolean
|
|
||||||
}
|
|
||||||
): Observable<T> {
|
|
||||||
return this.http.get<T>(url, options).pipe(
|
|
||||||
catchError((err: HttpErrorResponse) => {
|
|
||||||
console.log('url', url)
|
|
||||||
console.log('err.status', err.status)
|
|
||||||
if (err.status === 449 || err.status === 401) {
|
|
||||||
// Retry once if we got a 449
|
|
||||||
return this.http.get<T>(url, options)
|
|
||||||
}
|
|
||||||
// Otherwise propagate the error
|
|
||||||
return throwError(() => err)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -16,9 +16,6 @@ import { RequestWrapperOptions } from '../models/request-wrapper/RequestWrapperO
|
|||||||
import { ErrorBody } from '../models/ErrorBody'
|
import { ErrorBody } from '../models/ErrorBody'
|
||||||
import { UploadFileResponse } from '../models/UploadFile'
|
import { UploadFileResponse } from '../models/UploadFile'
|
||||||
import { RequestWrapperResponse } from '../models/request-wrapper/RequestWrapperResponse'
|
import { RequestWrapperResponse } from '../models/request-wrapper/RequestWrapperResponse'
|
||||||
import { SasViyaService } from './sas-viya.service'
|
|
||||||
import { ViyaApiFolder } from '../viya-api-explorer/models/viya-api-folder.model'
|
|
||||||
import { ViyaApiFolderMembers } from '../viya-api-explorer/models/viya-api-folder-content.model'
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@@ -41,7 +38,6 @@ export class SasService {
|
|||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
private eventService: EventService,
|
private eventService: EventService,
|
||||||
private sasjsService: SasjsService,
|
private sasjsService: SasjsService,
|
||||||
private sasViyaService: SasViyaService,
|
|
||||||
private loggerService: LoggerService,
|
private loggerService: LoggerService,
|
||||||
private router: Router
|
private router: Router
|
||||||
) {}
|
) {}
|
||||||
@@ -55,7 +51,6 @@ export class SasService {
|
|||||||
this.dcAdapterSettings = this.appStoreService.getDcAdapterSettings()
|
this.dcAdapterSettings = this.appStoreService.getDcAdapterSettings()
|
||||||
|
|
||||||
this.sasjsService.setup()
|
this.sasjsService.setup()
|
||||||
this.sasViyaService.setup()
|
|
||||||
|
|
||||||
if (!this.dcAdapterSettings) {
|
if (!this.dcAdapterSettings) {
|
||||||
this.eventService.showInfoModal(
|
this.eventService.showInfoModal(
|
||||||
@@ -333,10 +328,6 @@ export class SasService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public reloadStartupData() {
|
|
||||||
this.loadStartupServiceEmitter.emit()
|
|
||||||
}
|
|
||||||
|
|
||||||
public getLicenseSiteId(): string[] {
|
public getLicenseSiteId(): string[] {
|
||||||
return this.license_site_id.value || []
|
return this.license_site_id.value || []
|
||||||
}
|
}
|
||||||
@@ -365,18 +356,13 @@ export class SasService {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
(err: any) => {
|
(err: any) => {
|
||||||
const errorMessage =
|
if (err.error.includes('Unauthorized')) {
|
||||||
typeof err.error === 'string'
|
|
||||||
? err.error
|
|
||||||
: JSON.stringify(err.error || err)
|
|
||||||
|
|
||||||
if (errorMessage.includes('Unauthorized')) {
|
|
||||||
this.shouldLogin.next(true)
|
this.shouldLogin.next(true)
|
||||||
|
|
||||||
this.shouldLogin.subscribe((res: boolean) => {
|
this.shouldLogin.subscribe((res: boolean) => {
|
||||||
if (res === false) location.reload()
|
if (res === false) location.reload()
|
||||||
})
|
})
|
||||||
} else if (errorMessage.includes(`Folder doesn't exist.`)) {
|
} else if (err.error.includes(`Folder doesn't exist.`)) {
|
||||||
console.warn(
|
console.warn(
|
||||||
'SASjs SAS services are not present on the current appLoc.'
|
'SASjs SAS services are not present on the current appLoc.'
|
||||||
)
|
)
|
||||||
@@ -424,11 +410,7 @@ export class SasService {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
(err: any) => {
|
(err: any) => {
|
||||||
const errorMessage =
|
if (err.error.includes(`Folder doesn't exist.`)) {
|
||||||
typeof err.error === 'string'
|
|
||||||
? err.error
|
|
||||||
: JSON.stringify(err.error || err)
|
|
||||||
if (errorMessage.includes(`Folder doesn't exist.`)) {
|
|
||||||
reject()
|
reject()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -441,93 +423,21 @@ export class SasService {
|
|||||||
typeof this.sasjsAdapter.getFolder !== 'undefined'
|
typeof this.sasjsAdapter.getFolder !== 'undefined'
|
||||||
|
|
||||||
let appLocExists: boolean = false
|
let appLocExists: boolean = false
|
||||||
let errorMessage: string | undefined = undefined
|
|
||||||
|
|
||||||
if (getFolderExistsInAdapter) {
|
if (getFolderExistsInAdapter) {
|
||||||
const results = await this.appLocCheck(path)
|
appLocExists = await this.appLocCheck(path)
|
||||||
|
|
||||||
appLocExists = results.found
|
|
||||||
errorMessage = results.errorMessage
|
|
||||||
} else {
|
} else {
|
||||||
appLocExists = await this.appLocCheckPreAxiosdAdapter(path)
|
appLocExists = await this.appLocCheckPreAxiosdAdapter(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appLocExists) {
|
if (appLocExists) {
|
||||||
// Check if there is appLoc/services/admin/makedata.sas present
|
|
||||||
// if yes, it needs to be run, so we redirect to /deploy
|
|
||||||
// if not, we load the startup service
|
|
||||||
|
|
||||||
this.viyaMakedataSuccessfull().then(
|
|
||||||
(success: boolean) => {
|
|
||||||
if (success) {
|
|
||||||
this.loadStartupServiceEmitter.emit()
|
this.loadStartupServiceEmitter.emit()
|
||||||
} else {
|
|
||||||
this.eventService.startupDataLoaded()
|
|
||||||
this.router.navigateByUrl('/deploy')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
console.error('Error while looking for the file: makedata.sas', error)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
const errorMessageToShow =
|
|
||||||
(errorMessage ||
|
|
||||||
'Viya services are not present on the current appLoc, or API not reachable. Check the ADAPTER configuration.') +
|
|
||||||
`\nAppLoc: ${path}`
|
|
||||||
|
|
||||||
this.eventService.showInfoModal('Error', errorMessageToShow)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async viyaMakedataSuccessfull(): Promise<boolean> {
|
public appLocCheck(path: string): Promise<boolean> {
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const sasjsConfig = this.getSasjsConfig()
|
|
||||||
const configuratorFolder = `${sasjsConfig.appLoc}/services/admin`
|
|
||||||
|
|
||||||
this.sasViyaService.getFolderByPath(configuratorFolder).subscribe(
|
|
||||||
(folderInfo: ViyaApiFolder) => {
|
|
||||||
const folderId = folderInfo.id
|
|
||||||
|
|
||||||
if (!folderId) {
|
|
||||||
console.error(
|
|
||||||
`Folder ID is not present. ${configuratorFolder}`,
|
|
||||||
sasjsConfig
|
|
||||||
)
|
|
||||||
resolve(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.sasViyaService.getFolderMembers(folderId).subscribe(
|
|
||||||
(members: ViyaApiFolderMembers) => {
|
|
||||||
if (
|
|
||||||
!members.items.some((item: any) => item.name === 'makedata')
|
|
||||||
) {
|
|
||||||
// Makedata.sas is not present, which means it was run
|
|
||||||
resolve(true)
|
|
||||||
} else {
|
|
||||||
// Makedata.sas is present, which means it was not run
|
|
||||||
resolve(false)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(err: any) => {
|
|
||||||
console.error('Error getting folder contents', err)
|
|
||||||
reject()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
(err: any) => {
|
|
||||||
console.warn('Error getting folder info', err)
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public appLocCheck(
|
|
||||||
path: string
|
|
||||||
): Promise<{ found: boolean; errorMessage?: string }> {
|
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
let fetchError: string = ''
|
let statusNotFound: boolean = false
|
||||||
|
|
||||||
let res: any
|
let res: any
|
||||||
|
|
||||||
@@ -538,21 +448,20 @@ export class SasService {
|
|||||||
this.appLocCheckPending = true
|
this.appLocCheckPending = true
|
||||||
this.shouldLogin.next(true)
|
this.shouldLogin.next(true)
|
||||||
|
|
||||||
resolve({ found: false })
|
resolve(false)
|
||||||
} else if (err.name === 'NotFoundeError') {
|
|
||||||
fetchError = err.message
|
|
||||||
} else {
|
} else {
|
||||||
fetchError =
|
statusNotFound = true
|
||||||
'Viya services are not present on the current appLoc, or API not reachable. Check the ADAPTER configuration.'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fetchError.length) {
|
if (statusNotFound) {
|
||||||
console.warn(fetchError)
|
console.warn('Viya services are not present on the current appLoc.')
|
||||||
return resolve({ found: false, errorMessage: fetchError })
|
this.eventService.startupDataLoaded()
|
||||||
|
this.router.navigateByUrl('/deploy')
|
||||||
|
return resolve(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve({ found: true })
|
resolve(true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -670,17 +579,4 @@ export class SasService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Viya specific functions
|
|
||||||
public getFileContent(folderPath: string, fileName: string) {
|
|
||||||
return this.sasjsAdapter.getFileContent(folderPath, fileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
public updateFileContent(
|
|
||||||
folderPath: string,
|
|
||||||
fileName: string,
|
|
||||||
content: string
|
|
||||||
) {
|
|
||||||
return this.sasjsAdapter.updateFileContent(folderPath, fileName, content)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -7,10 +7,7 @@
|
|||||||
>
|
>
|
||||||
<h3 class="modal-title">
|
<h3 class="modal-title">
|
||||||
{{ data.modalTitle }}
|
{{ data.modalTitle }}
|
||||||
<p
|
<p *ngIf="data.sasService && data.sasService.length > 0" class="sasService">
|
||||||
*ngIf="data.sasService && data.sasService.length > 0"
|
|
||||||
class="sasService mt-0"
|
|
||||||
>
|
|
||||||
SAS Service: <strong>{{ data.sasService }}</strong>
|
SAS Service: <strong>{{ data.sasService }}</strong>
|
||||||
</p>
|
</p>
|
||||||
</h3>
|
</h3>
|
||||||
|
@@ -0,0 +1,43 @@
|
|||||||
|
.clr-abort-modal {
|
||||||
|
::ng-deep {
|
||||||
|
.modal {
|
||||||
|
z-index: 2050;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
z-index: 2050;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sasService {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
right: 10px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
position: relative;
|
||||||
|
border-top: 1px solid #dcdcdc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.systext {
|
||||||
|
overflow: auto;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 10px 0;
|
||||||
|
border-top: 1px solid #dcdcdc;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 0;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,11 +1,4 @@
|
|||||||
import {
|
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
|
||||||
Component,
|
|
||||||
EventEmitter,
|
|
||||||
Input,
|
|
||||||
OnInit,
|
|
||||||
Output,
|
|
||||||
ViewEncapsulation
|
|
||||||
} from '@angular/core'
|
|
||||||
import { Router } from '@angular/router'
|
import { Router } from '@angular/router'
|
||||||
import { ServerType } from '@sasjs/utils/types/serverType'
|
import { ServerType } from '@sasjs/utils/types/serverType'
|
||||||
import { EventService } from 'src/app/services/event.service'
|
import { EventService } from 'src/app/services/event.service'
|
||||||
@@ -16,8 +9,7 @@ import { AbortDetails, InfoModal } from '../../models/InfoModal'
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-info-modal',
|
selector: 'app-info-modal',
|
||||||
templateUrl: './info-modal.component.html',
|
templateUrl: './info-modal.component.html',
|
||||||
styleUrls: ['./info-modal.component.scss'],
|
styleUrls: ['./info-modal.component.scss']
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class InfoModalComponent implements OnInit {
|
export class InfoModalComponent implements OnInit {
|
||||||
@Output() onConfirmModalClick: EventEmitter<any> = new EventEmitter()
|
@Output() onConfirmModalClick: EventEmitter<any> = new EventEmitter()
|
||||||
|
@@ -0,0 +1,3 @@
|
|||||||
|
clr-alerts {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core'
|
import { Component, OnInit } from '@angular/core'
|
||||||
import { Subscription } from 'rxjs'
|
import { Subscription } from 'rxjs'
|
||||||
import { Alert } from './alert'
|
import { Alert } from './alert'
|
||||||
import { AlertsService } from './alerts.service'
|
import { AlertsService } from './alerts.service'
|
||||||
@@ -6,8 +6,7 @@ import { AlertsService } from './alerts.service'
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-alerts',
|
selector: 'app-alerts',
|
||||||
templateUrl: './alerts.component.html',
|
templateUrl: './alerts.component.html',
|
||||||
styleUrls: ['./alerts.component.scss'],
|
styleUrls: ['./alerts.component.scss']
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class AlertsComponent implements OnInit {
|
export class AlertsComponent implements OnInit {
|
||||||
public alerts: Array<Alert> = []
|
public alerts: Array<Alert> = []
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user