Compare commits
	
		
			198 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 26ce95f7c1 | |||
|  | 4924df2ef3 | ||
| 2e141a5d52 | |||
|  | cb1978bcaf | ||
|  | 387f5122f1 | ||
|  | db5887de21 | ||
| fe24d9bcbd | |||
|  | 6c6b1cbf46 | ||
|  | 4d65c9c999 | ||
| 4417279275 | |||
|  | 365f12996d | ||
|  | ef1015f33b | ||
| b43dfb5cf4 | |||
|  | 225e693d1f | ||
|  | fda91770be | ||
| d512876e0b | |||
|  | 2ba4b5383e | ||
| ecc3184609 | |||
|  | 712b384848 | ||
|  | 26cdd73331 | ||
|  | 919aa6dcfe | ||
|  | 3bb3093b49 | ||
|  | 9c12250558 | ||
|  | 6c843f64fb | ||
|  | 3fda7dc5b0 | ||
|  | 22ec7f0340 | ||
|  | 378461dcbb | ||
|  | 905c7b9d3c | ||
|  | 670ec2c71c | ||
|  | b419cd5078 | ||
|  | b1db4ea590 | ||
| 822ddb1274 | |||
|  | 672dd6d4f1 | ||
|  | b3ac73d903 | ||
|  | f8554dd5e7 | ||
| 88679c0c9a | |||
|  | a08a717ca8 | ||
| c8b6fdbfdb | |||
|  | b495c41626 | ||
|  | 7f4be474c6 | ||
| 7f6f68fcbb | |||
|  | 03fd7db033 | ||
|  | 9dc5c66f7b | ||
|  | aa1b08632e | ||
|  | 6bbe354c9e | ||
|  | 8ff429793b | ||
|  | 70d010127a | ||
|  | 696717c509 | ||
|  | 71c308d052 | ||
|  | bed5b320ad | ||
| b0e827412e | |||
|  | 63e9af402e | ||
| fd55105f62 | |||
|  | c2e3b362e7 | ||
|  | 5d2d60d040 | ||
| 0e59f5406f | |||
|  | e7cb471c0b | ||
|  | 0465089207 | ||
|  | 4f2f59907c | ||
|  | 7d85328d41 | ||
| f2a9329196 | |||
|  | 7a8231615c | ||
|  | 0db6b25327 | ||
| e91f6f01a6 | |||
|  | 0b4042af60 | ||
|  | 519d8953b5 | ||
| 14a616fc1b | |||
| bfe5a8626f | |||
|  | 4ecd186e5c | ||
|  | 8c60473c15 | ||
| bb126eba5b | |||
|  | d1998422d2 | ||
|  | 69f687a85f | ||
|  | 2aa19d1dca | ||
|  | e44a25dcc3 | ||
|  | efb5ffa906 | ||
|  | b4c586a859 | ||
|  | e874143a95 | ||
|  | e4dbab8b16 | ||
|  | f6d7d6f90c | ||
|  | 063c90caf4 | ||
|  | 2011c2eee7 | ||
|  | 24545f2acd | ||
| a7c81245ff | |||
|  | 4f2c993b2d | ||
|  | e5f8e500c1 | ||
|  | a61e2de140 | ||
| 881d2b060e | |||
|  | 4830c6d219 | ||
|  | 4c3c9ac88c | ||
| 7e1c610a4d | |||
|  | 8139f495ce | ||
|  | 89ab296151 | ||
|  | a0dc92c403 | ||
|  | 86134f478a | ||
|  | 9a2addc18e | ||
| 9264ce2a60 | |||
|  | cbd69df708 | ||
|  | ca7caa25b6 | ||
| c10330627f | |||
| d80c59afce | |||
|  | abdbb67471 | ||
|  | 037a97b6ff | ||
|  | a0a529ad38 | ||
|  | 72239558af | ||
|  | d2097ad6dd | ||
| 1bd542cddb | |||
| fb1c1ee874 | |||
|  | 624a7a8f37 | ||
|  | 381378f532 | ||
| af05486c0e | |||
|  | 4b558948d9 | ||
|  | d9cff42f5c | ||
| 2a3d2b8d0d | |||
|  | d0f453d291 | ||
|  | 8e65dd0eae | ||
|  | da4d0b28c7 | ||
|  | 6c96ef7fb0 | ||
|  | 997f09adde | ||
|  | 0e8503ed2b | ||
| 97dfcd79b1 | |||
| 9a12a2e41f | |||
|  | 5c114e562b | ||
| ae696a0be0 | |||
|  | 22d46a5dcc | ||
|  | f3125ff464 | ||
|  | 9682b548e6 | ||
|  | ec11a74265 | ||
| 4be0614604 | |||
|  | 27cbff2bc5 | ||
|  | c41c8963f2 | ||
|  | 7249d4fa29 | ||
|  | 2e0c60cc0d | ||
| 7c5e47f5e4 | |||
|  | f9decbd366 | ||
|  | 1dc69341ca | ||
|  | 75ae19fa8e | ||
|  | 9de04e9a0c | ||
|  | 983f59cd51 | ||
|  | 7b5e7ae184 | ||
|  | 6e96b1daec | ||
|  | 9604661f3b | ||
|  | 4bd215491f | ||
|  | 6a7dd451b5 | ||
|  | 841201adab | ||
|  | e013e62776 | ||
|  | 6c171a6394 | ||
|  | a377f6e8d6 | ||
| 0337318e0b | |||
|  | 2844c70f95 | ||
|  | 7e11c8f375 | ||
| 23cbbce964 | |||
|  | c63fcdd465 | ||
|  | 82412b2659 | ||
| eef3832e40 | |||
| 63b75a1c61 | |||
| d1f0879f0a | |||
|  | 36416aab2e | ||
|  | 7f3577c3ef | ||
|  | 69f883034f | ||
|  | 1c56af01d0 | ||
|   | 43c0f73c21 | ||
|   | e8cd3d63da | ||
|  | b7f564cb21 | ||
|   | 3f5cb1e2de | ||
|  | 7d8c0472f0 | ||
| b8b516ba77 | |||
|   | 83b3d775b6 | ||
|  | aea252ccc6 | ||
| d8908f9c7f | |||
|   | 95289aa952 | ||
|  | 149e318a87 | ||
|  | 8657826e60 | ||
|   | a45f5bb3b2 | ||
|   | ae9a91a7a1 | ||
|  | 3638bde633 | ||
| 3a2361f42c | |||
|  | 1bd0eef913 | ||
| 85aa3b38b7 | |||
| 678859a68d | |||
|  | e531acee3f | ||
|  | 4a45ebfe3b | ||
| 8fb9a344f2 | |||
|  | 8829b60220 | ||
|  | 4e64f28732 | ||
| a0749de700 | |||
| f9623e046e | |||
|   | bce1fd57ef | ||
|  | c6a1c53b46 | ||
| e36229e4c4 | |||
|  | 853c1bc23e | ||
|  | 3b6f6853bc | ||
|  | b415437662 | ||
|  | 24df878abe | ||
|  | f474673a14 | ||
|  | c53ab85107 | ||
|  | 5c4dd7c9e3 | ||
|  | 217220ffaa | 
| @@ -1,11 +1,23 @@ | |||||||
| #!/bin/sh | #!/bin/sh | ||||||
|  |  | ||||||
| # Avoid commits to the master branch | # Using `--silent` helps for showing any errs in the first line of the response | ||||||
| BRANCH=`git rev-parse --abbrev-ref HEAD` | # The first line is picked up by the VS Code GIT UI popup when rc is not 0 | ||||||
| REGEX="^(master|development)$" |  | ||||||
|  |  | ||||||
| if [[ "$BRANCH" =~ $REGEX ]]; then | if npm run --silent lint:check:silent ; then | ||||||
|   echo "You are on branch $BRANCH. Are you sure you want to commit to this branch?" |     exit 0 | ||||||
|   echo "If so, commit with -n to bypass the pre-commit hook." | else | ||||||
|   exit 1 |     npm run --silent lint:fix:silent | ||||||
|  |     echo "❌ Prettier check failed! We ran lint:fix for you. Please add & commit again." | ||||||
|  |     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.14.0 |           node-version: 20.15.1 | ||||||
|  |  | ||||||
|       - name: Install Google Chrome |       - name: Install Google Chrome | ||||||
|         run: | |         run: | | ||||||
| @@ -49,3 +49,93 @@ jobs: | |||||||
|         run: | |         run: | | ||||||
|           cd client |           cd client | ||||||
|           npm run build |           npm run build | ||||||
|  |  | ||||||
|  |   Build-and-test-development: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     needs: Build-production-and-ng-test | ||||||
|  |  | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v3 | ||||||
|  |       - uses: actions/setup-node@v4 | ||||||
|  |         with: | ||||||
|  |           node-version: 20.15.1 | ||||||
|  |  | ||||||
|  |       - name: Write .npmrc file | ||||||
|  |         run: | | ||||||
|  |           touch client/.npmrc | ||||||
|  |           echo '${{ secrets.NPMRC}}' > client/.npmrc | ||||||
|  |  | ||||||
|  |       - run: apt-get update | ||||||
|  |       - run: wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb | ||||||
|  |       - run: apt install -y ./google-chrome*.deb; | ||||||
|  |       - run: export CHROME_BIN=/usr/bin/google-chrome | ||||||
|  |       - 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 -y install jq | ||||||
|  |  | ||||||
|  |       - name: Write cypress credentials | ||||||
|  |         run: echo "$CYPRESS_CREDS" > ./client/cypress.env.json | ||||||
|  |         shell: bash | ||||||
|  |         env: | ||||||
|  |           CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }} | ||||||
|  |  | ||||||
|  |       - name: Install 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 | ||||||
|  |  | ||||||
|  |       # Install pm2 and prepare SASJS server | ||||||
|  |       - run: npm i -g pm2 | ||||||
|  |       - run: curl -L https://github.com/sasjs/server/releases/latest/download/linux.zip > linux.zip | ||||||
|  |       - run: unzip linux.zip | ||||||
|  |       - run: touch .env | ||||||
|  |       - run: echo RUN_TIMES=js >> .env | ||||||
|  |       - run: echo NODE_PATH=node >> .env | ||||||
|  |       - run: echo CORS=enable >> .env | ||||||
|  |       - run: echo WHITELIST=http://localhost:4200 >> .env | ||||||
|  |       - run: cat .env | ||||||
|  |       - run: pm2 start api-linux --wait-ready | ||||||
|  |  | ||||||
|  |       - name: Deploy mocked services | ||||||
|  |         run: | | ||||||
|  |           cd ./sas/mocks/sasjs | ||||||
|  |           npm install -g @sasjs/cli | ||||||
|  |           npm install -g replace-in-files-cli | ||||||
|  |           sasjs cbd -t server-ci | ||||||
|  |           # sasjs request services/admin/makedata -t server-ci -d ./deploy/makeData4GL.json -c ./deploy/requestConfig.json -o ./output.json | ||||||
|  |  | ||||||
|  |       - name: Install ZIP | ||||||
|  |         run: | | ||||||
|  |           apt-get update | ||||||
|  |           apt-get install zip | ||||||
|  |  | ||||||
|  |       - name: Prepare and run frontend and cypress | ||||||
|  |         run: | | ||||||
|  |           cd ./client | ||||||
|  |           mv ./cypress.env.example.json ./cypress.env.json | ||||||
|  |           replace-in-files --regex='"username".*' --replacement='"username":"'${{ secrets.CYPRESS_USERNAME_SASJS }}'",' ./cypress.env.json | ||||||
|  |           replace-in-files --regex='"password".*' --replacement='"password":"'${{ secrets.CYPRESS_PWD_SASJS }}'" ' ./cypress.env.json | ||||||
|  |           cat ./cypress.env.json | ||||||
|  |           npm run postinstall | ||||||
|  |           # Prepare index.html to SASJS local | ||||||
|  |           replace-in-files --regex='serverUrl=".*?"' --replacement='serverUrl="http://localhost:5000"' ./src/index.html | ||||||
|  |           replace-in-files --regex='appLoc=".*?"' --replacement='appLoc="/Public/app/devtest"' ./src/index.html | ||||||
|  |           replace-in-files --regex='serverType=".*?"' --replacement='serverType="SASJS"' ./src/index.html | ||||||
|  |           replace-in-files --regex='"hosturl".*' --replacement='hosturl:"http://localhost:4200",' ./cypress.config.ts | ||||||
|  |           cat ./cypress.config.ts | ||||||
|  |           # 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" | ||||||
|  |  | ||||||
|  |       - name: Zip Cypress videos | ||||||
|  |         if: always() | ||||||
|  |         run: | | ||||||
|  |           zip -r cypress-videos ./client/cypress/videos | ||||||
|  |  | ||||||
|  |       - name: Add cypress videos artifacts | ||||||
|  |         if: always() | ||||||
|  |         uses: actions/upload-artifact@v3 | ||||||
|  |         with: | ||||||
|  |           name: cypress-videos.zip | ||||||
|  |           path: cypress-videos.zip | ||||||
|   | |||||||
							
								
								
									
										101
									
								
								.gitea/workflows/lighthouse.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								.gitea/workflows/lighthouse.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | |||||||
|  | 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 libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb |       - run: apt-get -y install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2t64 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 | ||||||
|           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/filtering.cy.ts,cypress/e2e/licensing.cy.ts" |           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" | ||||||
|  |  | ||||||
|       - 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/clickme.html |           sed -i -e 's/servertype="SASJS"/servertype="SASVIYA"/g' sasjsbuild/services/DC.html | ||||||
|           sasjs b -t viya |  | ||||||
|           cp sasjsbuild/viya.sas ./demostream_viya.sas |  | ||||||
|           # compile Viya Full deploy (without web) |  | ||||||
|           rm -rf sasjsbuild/services/web |  | ||||||
|           rm sasjsbuild/services/clickme.html |  | ||||||
|           sasjs b -t viya |           sasjs b -t viya | ||||||
|           cp sasjsbuild/viya.sas ./viya.sas |           cp sasjsbuild/viya.sas ./viya.sas | ||||||
|           cp sasjsbuild/viya.json ./viya.json |           # compile Viya Full deploy (without web) | ||||||
|  |           rm -rf sasjsbuild/services/web | ||||||
|  |           rm sasjsbuild/services/DC.html | ||||||
|  |           sasjs b -t viya | ||||||
|  |           cp sasjsbuild/viya.sas ./viya_noweb.sas | ||||||
|  |           cp sasjsbuild/viya.json ./viya_noweb.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 |           cp sasjsbuild/viya.json ../client/dist/viya.json | ||||||
|           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/demostream_viya.sas |           curl -k $URL -F attachment=@sas/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.sas |           curl -k $URL -F attachment=@sas/viya_noweb.sas | ||||||
|           curl -k $URL -F attachment=@sas/viya.json |           curl -k $URL -F attachment=@sas/viya_noweb.json | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -21,3 +21,4 @@ sasjsresults | |||||||
| .sasjsrc | .sasjsrc | ||||||
| client/.npmrc | client/.npmrc | ||||||
| *~ | *~ | ||||||
|  | .lighthouseci | ||||||
							
								
								
									
										281
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										281
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,3 +1,284 @@ | |||||||
|  | ## [7.2.4](https://git.datacontroller.io/dc/dc/compare/v7.2.3...v7.2.4) (2025-10-14) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Bug Fixes | ||||||
|  |  | ||||||
|  | * ensure reload after applying licence key ([cb1978b](https://git.datacontroller.io/dc/dc/commit/cb1978bcaf23b0bf45b5d3b78b9707fd4e48a5f4)) | ||||||
|  | * snyk report security patches ([387f512](https://git.datacontroller.io/dc/dc/commit/387f5122f1ea6dff55d23c9223f17737283a94d3)) | ||||||
|  |  | ||||||
|  | ## [7.2.3](https://git.datacontroller.io/dc/dc/compare/v7.2.2...v7.2.3) (2025-10-02) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Bug Fixes | ||||||
|  |  | ||||||
|  | * opening second table in viewer throws an error ([6c6b1cb](https://git.datacontroller.io/dc/dc/commit/6c6b1cbf460e5291ec746af017e764b894fff8d5)) | ||||||
|  |  | ||||||
|  | ## [7.2.2](https://git.datacontroller.io/dc/dc/compare/v7.2.1...v7.2.2) (2025-09-23) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Bug Fixes | ||||||
|  |  | ||||||
|  | * jsrsasign,  @sasjs/cli bump ([365f129](https://git.datacontroller.io/dc/dc/commit/365f12996db3ef50a4f4f099d5af15696c43bb42)) | ||||||
|  |  | ||||||
|  | ## [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) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Features | ||||||
|  |  | ||||||
|  | * uses SORTSEQ=LINGUISTIC for the services/metanav/metadetails service ([a45f5bb](https://git.datacontroller.io/dc/dc/commit/a45f5bb3b27a86da5f55ff28c9c7669956484ddf)) | ||||||
|  |  | ||||||
|  | ## [6.13.2](https://git.datacontroller.io/dc/dc/compare/v6.13.1...v6.13.2) (2025-02-26) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Bug Fixes | ||||||
|  |  | ||||||
|  | * get metadata email if exists ([1bd0eef](https://git.datacontroller.io/dc/dc/commit/1bd0eef913593579771c647d80010ce628c00819)) | ||||||
|  |  | ||||||
|  | ## [6.13.1](https://git.datacontroller.io/dc/dc/compare/v6.13.0...v6.13.1) (2025-02-18) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Bug Fixes | ||||||
|  |  | ||||||
|  | * Avoiding LATIN1 unprintables in various UI locations ([bce1fd5](https://git.datacontroller.io/dc/dc/commit/bce1fd57ef397cfdd030552c3f424a8407174729)) | ||||||
|  | * updated @sasjs/adapter, crypto-browserify ([4e64f28](https://git.datacontroller.io/dc/dc/commit/4e64f28732868b07ec2ab403912bd384c62c7e1d)) | ||||||
|  |  | ||||||
|  | # [6.13.0](https://git.datacontroller.io/dc/dc/compare/v6.12.3...v6.13.0) (2025-01-31) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Bug Fixes | ||||||
|  |  | ||||||
|  | * editor page csv upload ([217220f](https://git.datacontroller.io/dc/dc/commit/217220ffaaf688133321cc68d770aaf1e50590a9)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Features | ||||||
|  |  | ||||||
|  | * csv test ([c53ab85](https://git.datacontroller.io/dc/dc/commit/c53ab85107f10c023117a099cc06321afc3e1f03)) | ||||||
|  |  | ||||||
| ## [6.12.3](https://git.datacontroller.io/dc/dc/compare/v6.12.2...v6.12.3) (2025-01-27) | ## [6.12.3](https://git.datacontroller.io/dc/dc/compare/v6.12.2...v6.12.3) (2025-01-27) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										34
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								README.md
									
									
									
									
									
								
							| @@ -23,10 +23,42 @@ _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). | ||||||
|  |  | ||||||
| For more information: | An individual Viya deploy can be done in just 2 lines of #SAS code! | ||||||
|  |  | ||||||
|  | ```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`. | ||||||
							
								
								
									
										114
									
								
								client/cypress/e2e/csv.cy.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								client/cypress/e2e/csv.cy.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | |||||||
|  | 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 = 'csvs/' | ||||||
|  |  | ||||||
|  | context('excel tests: ', function () { | ||||||
|  |   this.beforeAll(() => { | ||||||
|  |     cy.visit(`${hostUrl}/SASLogon/logout`) | ||||||
|  |     cy.loginAndUpdateValidKey(true) | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   this.beforeEach(() => { | ||||||
|  |     cy.visit(hostUrl + appLocation) | ||||||
|  |  | ||||||
|  |     visitPage('home') | ||||||
|  |  | ||||||
|  |     colorLog( | ||||||
|  |       `TEST START ---> ${ | ||||||
|  |         Cypress.mocha.getRunner().suite.ctx.currentTest.title | ||||||
|  |       }`, | ||||||
|  |       '#3498DB' | ||||||
|  |     ) | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   it('1 | Uploads regular csv file', () => { | ||||||
|  |     openTableFromTree(libraryToOpenIncludes, 'mpe_x_test') | ||||||
|  |  | ||||||
|  |     attachExcelFile('regular.csv', () => { | ||||||
|  |       cy.get('#approval-btn', { timeout: 60000 }).should('be.visible') | ||||||
|  |       // .then(() => { | ||||||
|  |       //   cy.get('#hotInstance', { timeout: 30000 }) | ||||||
|  |       //     .find('div.ht_master.handsontable') | ||||||
|  |       //     .find('div.wtHolder') | ||||||
|  |       //     .find('div.wtHider') | ||||||
|  |       //     .find('div.wtSpreader') | ||||||
|  |       //     .find('table.htCore') | ||||||
|  |       //     .find('tbody') | ||||||
|  |       //     .then((data) => { | ||||||
|  |       //       let cell: any = data[0].children[0].children[1] | ||||||
|  |       //       expect(cell.innerText).to.equal('0') | ||||||
|  |       //       cell = data[0].children[0].children[2] | ||||||
|  |       //       expect(cell.innerText).to.equal('44') | ||||||
|  |       //       cell = data[0].children[0].children[3] | ||||||
|  |       //       expect(cell.innerText).to.equal('abc') | ||||||
|  |       //       cell = data[0].children[0].children[6] | ||||||
|  |       //       expect(cell.innerText).to.equal('Option abc') | ||||||
|  |       //     }) | ||||||
|  |       // }) | ||||||
|  |     }) | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   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(() => { | ||||||
|  |           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;') | ||||||
|  | } | ||||||
| @@ -217,11 +217,7 @@ 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 ( |         if (approvalButton.innerText.toLowerCase().includes('approve')) { | ||||||
|           approvalButton.innerText |  | ||||||
|             .toLowerCase() |  | ||||||
|             .includes('approve') |  | ||||||
|         ) { |  | ||||||
|           approvalButton.click() |           approvalButton.click() | ||||||
|           break |           break | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -34,93 +34,162 @@ 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('hotTableUserDataset', [ |       checkHotUserDatasetTable( | ||||||
|         [library, mpeXTestTable], |         'hotTableUserDataset', | ||||||
|         [library, mpeTablesTable] |         [ | ||||||
|       ], () => { |           [library, mpeXTestTable], | ||||||
|         cy.get('#continue-btn').trigger('click').then(() => { |           [library, mpeTablesTable] | ||||||
|           checkIfTreeHasTables([`${library}.${mpeXTestTable}`, `${library}.${mpeTablesTable}`], undefined, (includes: boolean) => { |         ], | ||||||
|             if (includes) { |         () => { | ||||||
|               // MPE_TABLES sheet does not have data so 1 error image must be shown |           cy.get('#continue-btn') | ||||||
|               hasErrorTables(1, (valid: boolean) => { |             .trigger('click') | ||||||
|                 if (valid) done() |             .then(() => { | ||||||
|               }) |               checkIfTreeHasTables( | ||||||
|             } |                 [`${library}.${mpeXTestTable}`, `${library}.${mpeTablesTable}`], | ||||||
|           }) |                 undefined, | ||||||
|         }) |                 (includes: boolean) => { | ||||||
|       }) |                   if (includes) { | ||||||
|  |                     // MPE_TABLES sheet does not have data so 1 error image must be shown | ||||||
|  |                     hasErrorTables(1, (valid: boolean) => { | ||||||
|  |                       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('hotTableUserDataset', [ |       checkHotUserDatasetTable( | ||||||
|         [library, mpeXTestTable], |         'hotTableUserDataset', | ||||||
|         [library, mpeTablesTable] |         [ | ||||||
|       ], () => { |           [library, mpeXTestTable], | ||||||
|         cy.get('#continue-btn').trigger('click').then(() => { |           [library, mpeTablesTable] | ||||||
|           checkIfTreeHasTables([`${library}.${mpeXTestTable}`, `${library}.${mpeTablesTable}`], `${library}.${mpeXTestTable}`, (includes: boolean) => { |         ], | ||||||
|             if (includes) { |         () => { | ||||||
|               cy.get('#hotTable').should('be.visible').then(() => { |           cy.get('#continue-btn') | ||||||
|                 checkHotUserDatasetTable('hotTable', [ |             .trigger('click') | ||||||
|                   ['No', '1', 'more dummy data'], |             .then(() => { | ||||||
|                   ['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:'], |               checkIfTreeHasTables( | ||||||
|                   ['No', '1', 'if you can fill the unforgiving minute'] |                 [`${library}.${mpeXTestTable}`, `${library}.${mpeTablesTable}`], | ||||||
|                 ], () => { |                 `${library}.${mpeXTestTable}`, | ||||||
|                   submitTables() |                 (includes: boolean) => { | ||||||
|  |                   if (includes) { | ||||||
|  |                     cy.get('#hotTable') | ||||||
|  |                       .should('be.visible') | ||||||
|  |                       .then(() => { | ||||||
|  |                         checkHotUserDatasetTable( | ||||||
|  |                           'hotTable', | ||||||
|  |                           [ | ||||||
|  |                             ['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', | ||||||
|  |                               '1', | ||||||
|  |                               'if you can fill the unforgiving minute' | ||||||
|  |                             ] | ||||||
|  |                           ], | ||||||
|  |                           () => { | ||||||
|  |                             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('hotTableUserDataset', [ |       checkHotUserDatasetTable( | ||||||
|         [library, mpeXTestTable], |         'hotTableUserDataset', | ||||||
|         [library, mpeTablesTable] |         [ | ||||||
|       ], () => { |           [library, mpeXTestTable], | ||||||
|         cy.get('#continue-btn').trigger('click').then(() => { |           [library, mpeTablesTable] | ||||||
|           checkIfTreeHasTables([`${library}.${mpeXTestTable}`, `${library}.${mpeTablesTable}`], `${library}.${mpeXTestTable}`, (includes: boolean) => { |         ], | ||||||
|             if (includes) { |         () => { | ||||||
|               cy.get('#hotTable').should('be.visible').then(() => { |           cy.get('#continue-btn') | ||||||
|                 checkHotUserDatasetTable('hotTable', [ |             .trigger('click') | ||||||
|                   ['No', '1', 'more dummy data'], |             .then(() => { | ||||||
|                   ['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:'], |               checkIfTreeHasTables( | ||||||
|                   ['No', '1', 'if you can fill the unforgiving minute'] |                 [`${library}.${mpeXTestTable}`, `${library}.${mpeTablesTable}`], | ||||||
|                 ], () => { |                 `${library}.${mpeXTestTable}`, | ||||||
|                   clickOnTreeNode('DC996664.MPE_TABLES', () => { |                 (includes: boolean) => { | ||||||
|                     cy.wait(1000).then(() => { |                   if (includes) { | ||||||
|                       cy.get('#hotTable').should('be.visible').then(() => { |                     cy.get('#hotTable') | ||||||
|                         checkHotUserDatasetTable('hotTable', [ |                       .should('be.visible') | ||||||
|                           ['No', 'DC914286', 'MPE_COLUMN_LEVEL_SECURITY'], |                       .then(() => { | ||||||
|                           ['No', 'DC914286', 'MPE_XLMAP_INFO'], |                         checkHotUserDatasetTable( | ||||||
|                           ['No', 'DC914286', 'MPE_XLMAP_RULES'] |                           'hotTable', | ||||||
|                         ], () => { |                           [ | ||||||
|                           submitTables() |                             ['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', | ||||||
|  |                               '1', | ||||||
|  |                               'if you can fill the unforgiving minute' | ||||||
|  |                             ] | ||||||
|  |                           ], | ||||||
|  |                           () => { | ||||||
|  |                             clickOnTreeNode('DC996664.MPE_TABLES', () => { | ||||||
|  |                               cy.wait(1000).then(() => { | ||||||
|  |                                 cy.get('#hotTable') | ||||||
|  |                                   .should('be.visible') | ||||||
|  |                                   .then(() => { | ||||||
|  |                                     checkHotUserDatasetTable( | ||||||
|  |                                       'hotTable', | ||||||
|  |                                       [ | ||||||
|  |                                         [ | ||||||
|  |                                           'No', | ||||||
|  |                                           'DC914286', | ||||||
|  |                                           'MPE_COLUMN_LEVEL_SECURITY' | ||||||
|  |                                         ], | ||||||
|  |                                         ['No', 'DC914286', 'MPE_XLMAP_INFO'], | ||||||
|  |                                         ['No', 'DC914286', 'MPE_XLMAP_RULES'] | ||||||
|  |                                       ], | ||||||
|  |                                       () => { | ||||||
|  |                                         submitTables() | ||||||
|  |  | ||||||
|                           hasSuccessSubmits(2, (valid: boolean) => { |                                         hasSuccessSubmits( | ||||||
|                             if (valid) done() |                                           2, | ||||||
|                           }) |                                           (valid: boolean) => { | ||||||
|  |                                             if (valid) done() | ||||||
|                         }) |                                           } | ||||||
|  |                                         ) | ||||||
|  |                                       } | ||||||
|  |                                     ) | ||||||
|  |                                   }) | ||||||
|  |                               }) | ||||||
|  |                             }) | ||||||
|  |                           } | ||||||
|  |                         ) | ||||||
|                       }) |                       }) | ||||||
|                     }) |                   } | ||||||
|                   }) |                 } | ||||||
|                 }) |               ) | ||||||
|               }) |             }) | ||||||
|             } |         } | ||||||
|           }) |       ) | ||||||
|         }) |  | ||||||
|       }) |  | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
| @@ -142,25 +211,31 @@ const attachExcelFile = (excelFilename: string, callback?: any) => { | |||||||
|     }) |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
| const checkHotUserDatasetTable = (hotId: string, dataToContain: any[][], callback?: () => void) => { | const checkHotUserDatasetTable = ( | ||||||
|  |   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') | ||||||
|   .find('div.wtHider') |     .find('div.wtHider') | ||||||
|   .find('div.wtSpreader') |     .find('div.wtSpreader') | ||||||
|   .find('table.htCore') |     .find('table.htCore') | ||||||
|   .find('tbody') |     .find('tbody') | ||||||
|   .then((data) => { |     .then((data) => { | ||||||
|     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(dataToContain[rowI][colI]) |             expect(data[0].children[rowI].children[colI]).to.contain( | ||||||
|  |               dataToContain[rowI][colI] | ||||||
|  |             ) | ||||||
|  |           } | ||||||
|         } |         } | ||||||
|       } |  | ||||||
|  |  | ||||||
|       if (callback) callback() |         if (callback) callback() | ||||||
|  |       }) | ||||||
|     }) |     }) | ||||||
|   }) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| const clickOnTreeNode = (clickOnNode: string, callback?: () => void) => { | const clickOnTreeNode = (clickOnNode: string, callback?: () => void) => { | ||||||
| @@ -174,7 +249,11 @@ const clickOnTreeNode = (clickOnNode: string, callback?: () => void) => { | |||||||
|   }) |   }) | ||||||
| } | } | ||||||
|  |  | ||||||
| const checkIfTreeHasTables = (tables: string[], clickOnNode?: string, callback?: (includes: boolean) => void) => { | const checkIfTreeHasTables = ( | ||||||
|  |   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 | ||||||
| @@ -207,16 +286,26 @@ const submitTables = () => { | |||||||
|   cy.wait(1000) |   cy.wait(1000) | ||||||
| } | } | ||||||
|  |  | ||||||
| const hasSuccessSubmits = (expectedNoOfSubmits: number, callback: (valid: boolean) => void) => { | const hasSuccessSubmits = ( | ||||||
|   cy.get('.nav-tree clr-tree > clr-tree-node cds-icon[status="success"]').should('be.visible').then(($nodes) => { |   expectedNoOfSubmits: number, | ||||||
|     callback(expectedNoOfSubmits === $nodes.length) |   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) | ||||||
|  |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
| const hasErrorTables = (expectedNoOfErrors: number, callback: (valid: boolean) => void) => { | const hasErrorTables = ( | ||||||
|   cy.get('.nav-tree clr-tree > clr-tree-node cds-icon[status="danger"]').should('be.visible').then(($nodes) => { |   expectedNoOfErrors: number, | ||||||
|     callback(expectedNoOfErrors === $nodes.length) |   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) | ||||||
|  |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
| const visitPage = (url: string) => { | const visitPage = (url: string) => { | ||||||
|   | |||||||
| @@ -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('#hotInstance', { timeout: 30000 }) |           cy.get('#hotTable', { 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('#hotInstance', { timeout: 30000 }) |           cy.get('#hotTable', { 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,11 +399,7 @@ 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 ( |         if (approvalButton.innerText.toLowerCase().includes('approve')) { | ||||||
|           approvalButton.innerText |  | ||||||
|             .toLowerCase() |  | ||||||
|             .includes('approve') |  | ||||||
|         ) { |  | ||||||
|           approvalButton.click() |           approvalButton.click() | ||||||
|           break |           break | ||||||
|         } |         } | ||||||
| @@ -432,11 +428,7 @@ 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 ( |         if (approvalButton.innerText.toLowerCase().includes('approve')) { | ||||||
|           approvalButton.innerText |  | ||||||
|             .toLowerCase() |  | ||||||
|             .includes('approve') |  | ||||||
|         ) { |  | ||||||
|           approvalButton.click() |           approvalButton.click() | ||||||
|           break |           break | ||||||
|         } |         } | ||||||
| @@ -455,7 +447,7 @@ const acceptExcel = (callback?: any) => { | |||||||
| } | } | ||||||
|  |  | ||||||
| const checkResultOfFormulaUpload = (callback?: any) => { | const checkResultOfFormulaUpload = (callback?: any) => { | ||||||
|   cy.get('#hotInstance', { timeout: longerCommandTimeout }) |   cy.get('#hotTable', { timeout: longerCommandTimeout }) | ||||||
|     .find('div.ht_master.handsontable') |     .find('div.ht_master.handsontable') | ||||||
|     .find('div.wtHolder') |     .find('div.wtHolder') | ||||||
|     .find('div.wtHider') |     .find('div.wtHider') | ||||||
| @@ -471,7 +463,7 @@ const checkResultOfFormulaUpload = (callback?: any) => { | |||||||
|  |  | ||||||
| const checkResultOfXLSUpload = (callback?: any) => { | const checkResultOfXLSUpload = (callback?: any) => { | ||||||
|   cy.viewport(1280, 720) |   cy.viewport(1280, 720) | ||||||
|   cy.get('#hotInstance', { timeout: 30000 }) |   cy.get('#hotTable', { timeout: 30000 }) | ||||||
|     .find('div.ht_master.handsontable') |     .find('div.ht_master.handsontable') | ||||||
|     .find('div.wtHolder') |     .find('div.wtHolder') | ||||||
|     .find('div.wtHider') |     .find('div.wtHider') | ||||||
| @@ -500,7 +492,7 @@ const checkResultOfXLSUpload = (callback?: any) => { | |||||||
|       if (callback) callback() |       if (callback) callback() | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|   cy.get('#hotInstance', { timeout: 30000 }) |   cy.get('#hotTable', { timeout: 30000 }) | ||||||
|     .find('div.ht_master.handsontable') |     .find('div.ht_master.handsontable') | ||||||
|     .find('div.wtHolder') |     .find('div.wtHolder') | ||||||
|     .scrollTo('right') |     .scrollTo('right') | ||||||
|   | |||||||
| @@ -16,7 +16,6 @@ context('filtering tests: ', function () { | |||||||
|   this.beforeEach(() => { |   this.beforeEach(() => { | ||||||
|     cy.visit(hostUrl + appLocation, { timeout: longerCommandTimeout }) |     cy.visit(hostUrl + appLocation, { timeout: longerCommandTimeout }) | ||||||
|  |  | ||||||
|  |  | ||||||
|     visitPage('home') |     visitPage('home') | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
| @@ -299,14 +298,16 @@ 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((inputs: any) => { |             cy.get('.in-values-modal clr-checkbox-wrapper input').then( | ||||||
|               inputs[0].click() |               (inputs: any) => { | ||||||
|               cy.get('.in-values-modal .modal-footer button').click() |                 inputs[0].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,7 +23,6 @@ interface EditConfigTableCells { | |||||||
|  |  | ||||||
| context('licensing tests: ', function () { | context('licensing tests: ', function () { | ||||||
|   this.beforeAll(() => { |   this.beforeAll(() => { | ||||||
|  |  | ||||||
|     cy.loginAndUpdateValidKey() |     cy.loginAndUpdateValidKey() | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
| @@ -371,8 +370,6 @@ context('licensing tests: ', function () { | |||||||
|       }) |       }) | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |  | ||||||
| }) | }) | ||||||
|  |  | ||||||
| const logout = (callback?: any) => { | const logout = (callback?: any) => { | ||||||
| @@ -697,11 +694,7 @@ 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 ( |         if (approvalButton.innerText.toLowerCase().includes('approve')) { | ||||||
|           approvalButton.innerText |  | ||||||
|             .toLowerCase() |  | ||||||
|             .includes('approve') |  | ||||||
|         ) { |  | ||||||
|           approvalButton.click() |           approvalButton.click() | ||||||
|           break |           break | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -18,7 +18,6 @@ context('liveness tests: ', function () { | |||||||
|   this.beforeEach(() => { |   this.beforeEach(() => { | ||||||
|     cy.visit(hostUrl + appLocation) |     cy.visit(hostUrl + appLocation) | ||||||
|  |  | ||||||
|  |  | ||||||
|     visitPage('home') |     visitPage('home') | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
| @@ -125,11 +124,7 @@ 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 ( |         if (approvalButton.innerText.toLowerCase().includes('approve')) { | ||||||
|           approvalButton.innerText |  | ||||||
|             .toLowerCase() |  | ||||||
|             .includes('approve') |  | ||||||
|         ) { |  | ||||||
|           approvalButton.click() |           approvalButton.click() | ||||||
|           break |           break | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -76,7 +76,8 @@ 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 | ||||||
|  |  | ||||||
| @@ -91,32 +92,34 @@ context('editor tests: ', function () { | |||||||
|  |  | ||||||
|       if (found < viewboxes.length) return |       if (found < viewboxes.length) return | ||||||
|  |  | ||||||
|       cy.get('.viewboxes-container .viewbox', { withinSubject: null }).then((viewboxNodes: any) => { |       cy.get('.viewboxes-container .viewbox', { withinSubject: null }).then( | ||||||
|         for (let viewboxNode of viewboxNodes) { |         (viewboxNodes: any) => { | ||||||
|           cy.get(viewboxNode).within(() => { |           for (let viewboxNode of viewboxNodes) { | ||||||
|             cy.get('.table-title').then((tableTitle) => { |             cy.get(viewboxNode).within(() => { | ||||||
|               const title = tableTitle[0].innerText |               cy.get('.table-title').then((tableTitle) => { | ||||||
|               const viewbox = viewboxes.find((vb) => |                 const title = tableTitle[0].innerText | ||||||
|                 title.toLowerCase().includes(vb.viewbox_table) |                 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() |  | ||||||
|                   } |  | ||||||
|                 ) |                 ) | ||||||
|               } |  | ||||||
|  |                 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() | ||||||
|  |                     } | ||||||
|  |                   ) | ||||||
|  |                 } | ||||||
|  |               }) | ||||||
|             }) |             }) | ||||||
|           }) |           } | ||||||
|         } |         } | ||||||
|       }) |       ) | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
| @@ -395,11 +398,13 @@ context('editor tests: ', function () { | |||||||
| }) | }) | ||||||
|  |  | ||||||
| const removeAllColumns = () => { | const removeAllColumns = () => { | ||||||
|   cy.get('.configuration-wrapper clr-icon[shape="trash"]').then(removeNodes => { |   cy.get('.configuration-wrapper clr-icon[shape="trash"]').then( | ||||||
|     for (let removeNode of removeNodes) { |     (removeNodes) => { | ||||||
|       removeNode.click() |       for (let removeNode of removeNodes) { | ||||||
|  |         removeNode.click() | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   }) |   ) | ||||||
| } | } | ||||||
|  |  | ||||||
| const checkColumns = (columns: string[], callback: () => void) => { | const checkColumns = (columns: string[], callback: () => void) => { | ||||||
| @@ -412,7 +417,7 @@ const checkColumns = (columns: string[], callback: () => void) => { | |||||||
|               console.log('viewboxColNode', viewboxColNodes) |               console.log('viewboxColNode', viewboxColNodes) | ||||||
|               console.log('columns', columns) |               console.log('columns', columns) | ||||||
|               for (let i = 0; i < viewboxColNodes.length; i++) { |               for (let i = 0; i < viewboxColNodes.length; i++) { | ||||||
|                 const col = columns[i]|| '' |                 const col = columns[i] || '' | ||||||
|                 const colNode = viewboxColNodes[i] |                 const colNode = viewboxColNodes[i] | ||||||
|  |  | ||||||
|                 if ( |                 if ( | ||||||
|   | |||||||
							
								
								
									
										497
									
								
								client/cypress/fixtures/csvs/regular.csv
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										497
									
								
								client/cypress/fixtures/csvs/regular.csv
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,497 @@ | |||||||
|  | PRIMARY_KEY_FIELD,SOME_CHAR,SOME_DROPDOWN,SOME_NUM,SOME_DATE,SOME_DATETIME,SOME_TIME,SOME_SHORTNUM,SOME_BESTNUM | ||||||
|  | 0,abc,Option abc,42,12FEB1960,01JAN1960:00:00:42,0:00:42,3,44 | ||||||
|  | 1,more dummy data,Option 2,42,12FEB1960,01JAN1960:00:00:42,0:07:02,3,44 | ||||||
|  | 2,even more dummy data,Option 3,42,12FEB1960,01JAN1960:00:00:42,0:02:22,3,44 | ||||||
|  | 3,"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:",Option 2,1613.001,27FEB1961,01JAN1960:00:07:03,0:00:44,3,44 | ||||||
|  | 4,if you can fill the unforgiving minute,Option 1,1613.0011235,02AUG1971,29MAY1973:06:12:03,0:06:52,3,44 | ||||||
|  | 1010,10 bottles of beer on the wall,Option 1,0.9153696885,04MAR1962,01JAN1960:12:47:55,0:01:40,92,76 | ||||||
|  | 1011,11 bottles of beer on the wall,Option 1,0.3531217558,29MAR1960,01JAN1960:03:33:24,0:01:03,80,29 | ||||||
|  | 1012,12 bottles of beer on the wall,Option 1,0.6743748717,02AUG1962,01JAN1960:07:25:59,0:00:10,16,98 | ||||||
|  | 1013,13 bottles of beer on the wall,Option 1,0.1305445992,11SEP1960,01JAN1960:13:51:32,0:00:35,73,15 | ||||||
|  | 1014,14 bottles of beer on the wall,Option 1,0.7409067949,26JUL1960,01JAN1960:05:18:10,0:00:41,30,89 | ||||||
|  | 1015,15 bottles of beer on the wall,Option 1,0.0869016028,28FEB1961,01JAN1960:13:23:45,0:00:44,80,3 | ||||||
|  | 1016,16 bottles of beer on the wall,Option 1,0.0462121419,09AUG1962,01JAN1960:07:42:38,0:01:17,62,2 | ||||||
|  | 1017,17 bottles of beer on the wall,Option 1,0.7501918947,14MAY1962,01JAN1960:04:40:20,0:00:15,53,65 | ||||||
|  | 1018,18 bottles of beer on the wall,Option 1,0.7300173294,03AUG1962,01JAN1960:03:20:41,0:00:41,21,60 | ||||||
|  | 1019,19 bottles of beer on the wall,Option 1,0.6960950437,01JUN1960,01JAN1960:01:58:52,0:01:08,38,5 | ||||||
|  | 1020,20 bottles of beer on the wall,Option 1,0.6190566065,30MAY1961,01JAN1960:09:04:20,0:01:07,92,23 | ||||||
|  | 1021,21 bottles of beer on the wall,Option 1,0.5173368238,07JAN1961,01JAN1960:07:52:34,0:00:52,57,21 | ||||||
|  | 1022,22 bottles of beer on the wall,Option 1,0.4720626452,07NOV1960,01JAN1960:12:12:00,0:00:26,53,32 | ||||||
|  | 1023,23 bottles of beer on the wall,Option 1,0.2856596393,08AUG1960,01JAN1960:06:09:25,0:00:28,40,12 | ||||||
|  | 1024,24 bottles of beer on the wall,Option 1,0.5160869418,02JUN1960,01JAN1960:06:36:06,0:01:10,41,81 | ||||||
|  | 1025,25 bottles of beer on the wall,Option 1,0.1683158517,05JAN1961,01JAN1960:08:14:35,0:00:06,18,53 | ||||||
|  | 1026,26 bottles of beer on the wall,Option 1,0.8951142248,28NOV1961,01JAN1960:03:31:17,0:00:58,79,54 | ||||||
|  | 1027,27 bottles of beer on the wall,Option 1,0.7037817481,01SEP1961,01JAN1960:05:48:34,0:00:29,50,15 | ||||||
|  | 1028,28 bottles of beer on the wall,Option 1,0.6193826714,31MAR1962,01JAN1960:02:49:39,0:00:24,78,87 | ||||||
|  | 1029,29 bottles of beer on the wall,Option 1,0.9339028457,06DEC1961,01JAN1960:02:57:57,0:00:24,73,64 | ||||||
|  | 1030,30 bottles of beer on the wall,Option 1,0.5647351339,10AUG1960,01JAN1960:11:02:59,0:00:55,39,28 | ||||||
|  | 1031,31 bottles of beer on the wall,Option 1,0.1218988607,19JUN1961,01JAN1960:04:19:32,0:00:58,51,32 | ||||||
|  | 1032,32 bottles of beer on the wall,Option 1,0.3459929113,14MAY1962,01JAN1960:05:42:48,0:00:54,96,46 | ||||||
|  | 1033,33 bottles of beer on the wall,Option 1,0.092664999,31AUG1962,01JAN1960:00:08:34,0:00:51,69,90 | ||||||
|  | 1034,34 bottles of beer on the wall,Option 1,0.9793458097,08FEB1960,01JAN1960:01:55:23,0:00:42,45,28 | ||||||
|  | 1035,35 bottles of beer on the wall,Option 1,0.8964386624,18DEC1961,01JAN1960:04:42:45,0:00:07,49,97 | ||||||
|  | 1036,36 bottles of beer on the wall,Option 1,0.0961652911,13NOV1960,01JAN1960:03:44:53,0:01:25,62,59 | ||||||
|  | 1037,37 bottles of beer on the wall,Option 1,0.3475089201,16JAN1962,01JAN1960:01:35:19,0:00:15,23,50 | ||||||
|  | 1038,38 bottles of beer on the wall,Option 1,0.3096271312,21MAY1960,01JAN1960:09:51:33,0:00:15,2,71 | ||||||
|  | 1039,39 bottles of beer on the wall,Option 1,0.9445223114,28AUG1962,01JAN1960:07:09:31,0:00:12,30,31 | ||||||
|  | 1040,40 bottles of beer on the wall,Option 1,0.5626084667,06NOV1960,01JAN1960:01:42:16,0:01:14,18,97 | ||||||
|  | 1041,41 bottles of beer on the wall,Option 1,0.9432962513,01JUN1962,01JAN1960:03:30:04,0:00:11,20,34 | ||||||
|  | 1042,42 bottles of beer on the wall,Option 1,0.5802429382,08JUL1961,01JAN1960:08:12:43,0:01:26,18,5 | ||||||
|  | 1043,43 bottles of beer on the wall,Option 1,0.1970176255,27MAR1961,01JAN1960:00:19:45,0:01:29,13,76 | ||||||
|  | 1044,44 bottles of beer on the wall,Option 1,0.4980671608,05JAN1961,01JAN1960:13:36:08,0:00:56,4,36 | ||||||
|  | 1045,45 bottles of beer on the wall,Option 1,0.2486515531,05MAY1962,01JAN1960:08:47:09,0:00:42,2,23 | ||||||
|  | 1046,46 bottles of beer on the wall,Option 1,0.4097825794,20JUN1960,01JAN1960:03:33:26,0:00:31,98,71 | ||||||
|  | 1047,47 bottles of beer on the wall,Option 1,0.138754441,28JAN1960,01JAN1960:00:57:41,0:00:18,80,32 | ||||||
|  | 1048,48 bottles of beer on the wall,Option 1,0.0249874415,03MAR1960,01JAN1960:11:33:53,0:00:04,96,76 | ||||||
|  | 1049,49 bottles of beer on the wall,Option 1,0.8395310011,06NOV1961,01JAN1960:09:54:04,0:00:52,28,45 | ||||||
|  | 1050,50 bottles of beer on the wall,Option 1,0.0942291618,14APR1962,01JAN1960:08:09:30,0:01:36,37,86 | ||||||
|  | 1051,51 bottles of beer on the wall,Option 1,0.1670458001,13NOV1961,01JAN1960:01:05:55,0:00:25,42,83 | ||||||
|  | 1052,52 bottles of beer on the wall,Option 1,0.3122402715,04JUN1960,01JAN1960:03:47:47,0:01:01,18,78 | ||||||
|  | 1053,53 bottles of beer on the wall,Option 1,0.3854694261,14JUN1960,01JAN1960:02:43:08,0:00:06,22,67 | ||||||
|  | 1054,54 bottles of beer on the wall,Option 1,0.1950434345,14NOV1961,01JAN1960:02:46:34,0:00:55,42,0 | ||||||
|  | 1055,55 bottles of beer on the wall,Option 1,0.4948673586,29MAR1962,01JAN1960:00:48:06,0:01:04,28,4 | ||||||
|  | 1056,56 bottles of beer on the wall,Option 1,0.6464513832,06SEP1962,01JAN1960:10:08:36,0:01:02,43,82 | ||||||
|  | 1057,57 bottles of beer on the wall,Option 1,0.0724864798,20JUN1961,01JAN1960:12:22:51,0:01:27,82,53 | ||||||
|  | 1058,58 bottles of beer on the wall,Option 1,0.8114467793,20MAR1962,01JAN1960:06:11:33,0:01:29,40,89 | ||||||
|  | 1059,59 bottles of beer on the wall,Option 1,0.6348024321,28JUN1962,01JAN1960:05:21:21,0:01:37,55,41 | ||||||
|  | 1060,60 bottles of beer on the wall,Option 1,0.8019492933,08APR1961,01JAN1960:12:37:00,0:01:29,49,88 | ||||||
|  | 1061,61 bottles of beer on the wall,Option 1,0.4695742002,29JAN1962,01JAN1960:08:54:24,0:00:15,40,91 | ||||||
|  | 1062,62 bottles of beer on the wall,Option 1,0.902706475,15JUN1961,01JAN1960:09:46:49,0:00:23,74,70 | ||||||
|  | 1063,63 bottles of beer on the wall,Option 1,0.4557614594,16JUL1961,01JAN1960:02:06:05,0:01:09,7,3 | ||||||
|  | 1064,64 bottles of beer on the wall,Option 1,0.6632444466,20MAY1961,01JAN1960:02:44:44,0:00:20,42,100 | ||||||
|  | 1065,65 bottles of beer on the wall,Option 1,0.3901674,31AUG1961,01JAN1960:07:56:49,0:00:32,98,3 | ||||||
|  | 1066,66 bottles of beer on the wall,Option 1,0.8453234848,30JUN1962,01JAN1960:04:51:54,0:01:02,51,22 | ||||||
|  | 1067,67 bottles of beer on the wall,Option 1,0.9370150906,26APR1960,01JAN1960:04:05:08,0:01:39,28,86 | ||||||
|  | 1068,68 bottles of beer on the wall,Option 1,0.8854161277,22MAR1962,01JAN1960:10:38:49,0:01:07,60,85 | ||||||
|  | 1069,69 bottles of beer on the wall,Option 1,0.1327841906,24MAY1960,01JAN1960:01:18:46,0:01:15,88,85 | ||||||
|  | 1070,70 bottles of beer on the wall,Option 1,0.5846563226,27JUL1962,01JAN1960:03:52:31,0:00:09,20,8 | ||||||
|  | 1071,71 bottles of beer on the wall,Option 1,0.0257193684,18FEB1961,01JAN1960:03:25:01,0:00:29,1,62 | ||||||
|  | 1072,72 bottles of beer on the wall,Option 1,0.9471486034,01JUN1962,01JAN1960:04:05:25,0:01:22,65,20 | ||||||
|  | 1073,73 bottles of beer on the wall,Option 1,0.3037446282,16MAY1962,01JAN1960:05:10:19,0:00:01,14,34 | ||||||
|  | 1074,74 bottles of beer on the wall,Option 1,0.2508690675,01NOV1961,01JAN1960:11:26:03,0:00:15,8,74 | ||||||
|  | 1075,75 bottles of beer on the wall,Option 1,0.814380363,17SEP1960,01JAN1960:09:00:38,0:00:25,95,1 | ||||||
|  | 1076,76 bottles of beer on the wall,Option 1,0.3761493621,16AUG1961,01JAN1960:01:48:17,0:00:52,5,38 | ||||||
|  | 1077,77 bottles of beer on the wall,Option 1,0.3621215761,25JUL1961,01JAN1960:11:48:47,0:01:20,86,90 | ||||||
|  | 1078,78 bottles of beer on the wall,Option 1,0.0268799584,20MAY1961,01JAN1960:12:43:34,0:01:00,70,96 | ||||||
|  | 1079,79 bottles of beer on the wall,Option 1,0.4112483945,27JUL1962,01JAN1960:01:20:24,0:01:26,66,20 | ||||||
|  | 1080,80 bottles of beer on the wall,Option 1,0.9501868011,15APR1961,01JAN1960:09:58:20,0:00:51,93,79 | ||||||
|  | 1081,81 bottles of beer on the wall,Option 1,0.9866548018,13SEP1961,01JAN1960:05:20:04,0:00:14,28,97 | ||||||
|  | 1082,82 bottles of beer on the wall,Option 1,0.9907830073,22FEB1962,01JAN1960:03:29:03,0:00:17,16,91 | ||||||
|  | 1083,83 bottles of beer on the wall,Option 1,0.8927816567,11MAR1960,01JAN1960:05:52:48,0:01:26,54,14 | ||||||
|  | 1084,84 bottles of beer on the wall,Option 1,0.12871663,02FEB1961,01JAN1960:10:34:37,0:00:44,52,90 | ||||||
|  | 1085,85 bottles of beer on the wall,Option 1,0.5490252802,02JAN1960,01JAN1960:06:11:58,0:00:27,4,98 | ||||||
|  | 1086,86 bottles of beer on the wall,Option 1,0.5432773864,11FEB1960,01JAN1960:08:40:00,0:01:15,79,19 | ||||||
|  | 1087,87 bottles of beer on the wall,Option 1,0.8223943137,01OCT1960,01JAN1960:08:11:33,0:01:19,2,86 | ||||||
|  | 1088,88 bottles of beer on the wall,Option 1,0.8496777699,09FEB1962,01JAN1960:03:10:35,0:00:15,95,6 | ||||||
|  | 1089,89 bottles of beer on the wall,Option 1,0.9308730536,27MAY1962,01JAN1960:11:57:53,0:01:18,86,90 | ||||||
|  | 1090,90 bottles of beer on the wall,Option 1,0.3072653344,23FEB1962,01JAN1960:04:52:38,0:00:25,85,17 | ||||||
|  | 1091,91 bottles of beer on the wall,Option 1,0.7687679575,12FEB1960,01JAN1960:08:47:11,0:01:20,8,7 | ||||||
|  | 1092,92 bottles of beer on the wall,Option 1,0.1873595105,29SEP1961,01JAN1960:04:29:58,0:00:29,17,78 | ||||||
|  | 1093,93 bottles of beer on the wall,Option 1,0.0495966631,03OCT1961,01JAN1960:03:18:50,0:00:39,89,56 | ||||||
|  | 1094,94 bottles of beer on the wall,Option 1,0.2607690526,19SEP1960,01JAN1960:03:22:28,0:00:29,81,16 | ||||||
|  | 1095,95 bottles of beer on the wall,Option 1,0.549640266,07JUN1962,01JAN1960:06:15:32,0:00:04,57,70 | ||||||
|  | 1096,96 bottles of beer on the wall,Option 1,0.9993291092,08MAR1961,01JAN1960:13:49:08,0:00:33,37,28 | ||||||
|  | 1097,97 bottles of beer on the wall,Option 1,0.9517237963,02SEP1960,01JAN1960:05:16:03,0:00:40,77,61 | ||||||
|  | 1098,98 bottles of beer on the wall,Option 1,0.5952155588,14FEB1962,01JAN1960:05:05:11,0:01:29,63,83 | ||||||
|  | 1099,99 bottles of beer on the wall,Option 1,0.7526210732,05MAY1961,01JAN1960:06:58:36,0:00:02,95,1 | ||||||
|  | 10100,100 bottles of beer on the wall,Option 1,0.307558153,17MAY1961,01JAN1960:06:13:01,0:01:37,68,7 | ||||||
|  | 10101,101 bottles of beer on the wall,Option 1,0.6596710829,15APR1962,01JAN1960:08:34:02,0:00:43,66,43 | ||||||
|  | 10102,102 bottles of beer on the wall,Option 1,0.0202811998,31AUG1961,01JAN1960:07:22:35,0:01:31,57,35 | ||||||
|  | 10103,103 bottles of beer on the wall,Option 1,0.6699061034,02MAY1962,01JAN1960:05:13:17,0:00:36,30,23 | ||||||
|  | 10104,104 bottles of beer on the wall,Option 1,0.330972748,04JUN1961,01JAN1960:06:47:20,0:01:05,69,82 | ||||||
|  | 10105,105 bottles of beer on the wall,Option 1,0.2274839176,25JAN1961,01JAN1960:05:34:51,0:00:56,63,68 | ||||||
|  | 10106,106 bottles of beer on the wall,Option 1,0.5612243989,27JUN1962,01JAN1960:04:32:03,0:01:15,19,73 | ||||||
|  | 10107,107 bottles of beer on the wall,Option 1,0.7398902111,03SEP1962,01JAN1960:08:34:07,0:00:17,90,6 | ||||||
|  | 10108,108 bottles of beer on the wall,Option 1,0.6124899791,08AUG1960,01JAN1960:04:59:34,0:00:25,56,12 | ||||||
|  | 10109,109 bottles of beer on the wall,Option 1,0.882404773,26JAN1961,01JAN1960:01:29:15,0:01:26,36,4 | ||||||
|  | 10110,110 bottles of beer on the wall,Option 1,0.4427004733,27FEB1961,01JAN1960:06:16:49,0:01:40,97,84 | ||||||
|  | 10111,111 bottles of beer on the wall,Option 1,0.3609524622,10JAN1962,01JAN1960:09:48:37,0:01:11,87,62 | ||||||
|  | 10112,112 bottles of beer on the wall,Option 1,0.9408929562,03AUG1960,01JAN1960:06:54:26,0:00:08,19,33 | ||||||
|  | 10113,113 bottles of beer on the wall,Option 1,0.3149107319,10AUG1962,01JAN1960:13:01:00,0:00:04,75,60 | ||||||
|  | 10114,114 bottles of beer on the wall,Option 1,0.0525069181,17APR1962,01JAN1960:13:00:52,0:00:35,9,23 | ||||||
|  | 10115,115 bottles of beer on the wall,Option 1,0.145448105,14FEB1962,01JAN1960:04:06:08,0:00:26,45,91 | ||||||
|  | 10116,116 bottles of beer on the wall,Option 1,0.2444279959,10OCT1961,01JAN1960:08:03:12,0:01:37,12,41 | ||||||
|  | 10117,117 bottles of beer on the wall,Option 1,0.4619846043,30JUL1960,01JAN1960:09:40:16,0:00:50,2,88 | ||||||
|  | 10118,118 bottles of beer on the wall,Option 1,0.0316203502,13JUL1961,01JAN1960:08:31:39,0:01:05,60,94 | ||||||
|  | 10119,119 bottles of beer on the wall,Option 1,0.4738720574,13AUG1960,01JAN1960:01:31:05,0:01:17,43,79 | ||||||
|  | 10120,120 bottles of beer on the wall,Option 1,0.8058761856,11JUN1960,01JAN1960:12:56:35,0:00:08,92,36 | ||||||
|  | 10121,121 bottles of beer on the wall,Option 1,0.2955600979,08JUL1962,01JAN1960:06:09:22,0:00:03,94,80 | ||||||
|  | 10122,122 bottles of beer on the wall,Option 1,0.0064115427,18SEP1962,01JAN1960:00:06:24,0:00:13,72,75 | ||||||
|  | 10123,123 bottles of beer on the wall,Option 1,0.5678159327,21APR1960,01JAN1960:10:54:21,0:00:16,75,67 | ||||||
|  | 10124,124 bottles of beer on the wall,Option 1,0.1431510994,10JAN1962,01JAN1960:01:57:00,0:00:12,48,31 | ||||||
|  | 10125,125 bottles of beer on the wall,Option 1,0.3805634409,26JAN1962,01JAN1960:03:03:19,0:01:29,83,52 | ||||||
|  | 10126,126 bottles of beer on the wall,Option 1,0.3833517993,26APR1960,01JAN1960:11:27:41,0:00:44,99,36 | ||||||
|  | 10127,127 bottles of beer on the wall,Option 1,0.5669089111,04MAR1961,01JAN1960:05:36:22,0:01:18,43,27 | ||||||
|  | 10128,128 bottles of beer on the wall,Option 1,0.1514211843,01NOV1960,01JAN1960:07:45:50,0:01:02,22,12 | ||||||
|  | 10129,129 bottles of beer on the wall,Option 1,0.0446588583,05JAN1961,01JAN1960:02:13:55,0:00:42,27,46 | ||||||
|  | 10130,130 bottles of beer on the wall,Option 1,0.7892141611,22APR1962,01JAN1960:04:17:54,0:01:05,75,84 | ||||||
|  | 10131,131 bottles of beer on the wall,Option 1,0.5012088001,24DEC1960,01JAN1960:13:03:23,0:01:22,87,82 | ||||||
|  | 10132,132 bottles of beer on the wall,Option 1,0.2327582944,07APR1961,01JAN1960:01:33:15,0:01:14,18,46 | ||||||
|  | 10133,133 bottles of beer on the wall,Option 1,0.2234651173,20MAR1961,01JAN1960:13:52:02,0:01:06,42,58 | ||||||
|  | 10134,134 bottles of beer on the wall,Option 1,0.4954405918,10FEB1961,01JAN1960:13:51:14,0:01:36,35,11 | ||||||
|  | 10135,135 bottles of beer on the wall,Option 1,0.7874922891,15AUG1960,01JAN1960:00:21:57,0:00:52,45,36 | ||||||
|  | 10136,136 bottles of beer on the wall,Option 1,0.3992494891,06SEP1961,01JAN1960:09:51:46,0:01:25,26,9 | ||||||
|  | 10137,137 bottles of beer on the wall,Option 1,0.3964866136,25MAY1960,01JAN1960:03:19:48,0:00:28,44,3 | ||||||
|  | 10138,138 bottles of beer on the wall,Option 1,0.9466173323,06APR1962,01JAN1960:13:19:18,0:01:27,78,51 | ||||||
|  | 10139,139 bottles of beer on the wall,Option 1,0.6525219277,09APR1960,01JAN1960:05:43:49,0:00:21,63,6 | ||||||
|  | 10140,140 bottles of beer on the wall,Option 1,0.4684071925,29MAY1961,01JAN1960:02:53:36,0:00:46,68,4 | ||||||
|  | 10141,141 bottles of beer on the wall,Option 1,0.8581724013,16MAY1960,01JAN1960:01:45:44,0:01:32,31,85 | ||||||
|  | 10142,142 bottles of beer on the wall,Option 1,0.825792401,23APR1961,01JAN1960:12:03:13,0:00:49,36,45 | ||||||
|  | 10143,143 bottles of beer on the wall,Option 1,0.3172852538,20FEB1962,01JAN1960:12:38:31,0:01:34,51,78 | ||||||
|  | 10144,144 bottles of beer on the wall,Option 1,0.670397946,27JAN1962,01JAN1960:04:59:37,0:00:39,38,99 | ||||||
|  | 10145,145 bottles of beer on the wall,Option 1,0.3304372441,04JUN1960,01JAN1960:00:39:12,0:00:29,88,76 | ||||||
|  | 10146,146 bottles of beer on the wall,Option 1,0.845151971,31JUL1962,01JAN1960:05:03:34,0:00:13,2,80 | ||||||
|  | 10147,147 bottles of beer on the wall,Option 1,0.7957223709,02FEB1961,01JAN1960:00:03:07,0:01:11,29,99 | ||||||
|  | 10148,148 bottles of beer on the wall,Option 1,0.323337108,29FEB1960,01JAN1960:01:58:05,0:01:17,23,65 | ||||||
|  | 10149,149 bottles of beer on the wall,Option 1,0.1813316611,29JUN1960,01JAN1960:02:18:08,0:00:40,45,52 | ||||||
|  | 10150,150 bottles of beer on the wall,Option 1,0.7860426655,05MAR1962,01JAN1960:01:57:15,0:00:26,31,91 | ||||||
|  | 10151,151 bottles of beer on the wall,Option 1,0.3305453571,09APR1960,01JAN1960:07:08:32,0:01:30,72,15 | ||||||
|  | 10152,152 bottles of beer on the wall,Option 1,0.9367212513,18AUG1962,01JAN1960:10:36:03,0:01:26,85,81 | ||||||
|  | 10153,153 bottles of beer on the wall,Option 1,0.3385623458,19MAR1962,01JAN1960:04:21:46,0:01:29,11,54 | ||||||
|  | 10154,154 bottles of beer on the wall,Option 1,0.9756794413,17JUN1961,01JAN1960:08:35:40,0:00:34,3,72 | ||||||
|  | 10155,155 bottles of beer on the wall,Option 1,0.6385958868,21OCT1961,01JAN1960:08:51:00,0:00:50,1,6 | ||||||
|  | 10156,156 bottles of beer on the wall,Option 1,0.3569769959,14AUG1960,01JAN1960:10:57:16,0:00:05,5,94 | ||||||
|  | 10157,157 bottles of beer on the wall,Option 1,0.8559997239,23MAR1962,01JAN1960:12:03:38,0:00:08,96,68 | ||||||
|  | 10158,158 bottles of beer on the wall,Option 1,0.2293701918,13AUG1960,01JAN1960:06:36:47,0:00:07,87,87 | ||||||
|  | 10159,159 bottles of beer on the wall,Option 1,0.0007910165,20SEP1962,01JAN1960:11:49:02,0:00:55,18,69 | ||||||
|  | 10160,160 bottles of beer on the wall,Option 1,0.5876370373,08JAN1960,01JAN1960:00:59:15,0:01:26,27,36 | ||||||
|  | 10161,161 bottles of beer on the wall,Option 1,0.2354667514,11OCT1961,01JAN1960:01:27:14,0:01:04,90,28 | ||||||
|  | 10162,162 bottles of beer on the wall,Option 1,0.0144103263,11AUG1961,01JAN1960:02:37:41,0:01:39,92,8 | ||||||
|  | 10163,163 bottles of beer on the wall,Option 1,0.7087855668,03JUL1962,01JAN1960:02:07:23,0:01:35,51,33 | ||||||
|  | 10164,164 bottles of beer on the wall,Option 1,0.7251478106,20MAY1960,01JAN1960:12:15:23,0:01:28,58,7 | ||||||
|  | 10165,165 bottles of beer on the wall,Option 1,0.9629398403,06APR1962,01JAN1960:01:05:24,0:01:39,84,6 | ||||||
|  | 10166,166 bottles of beer on the wall,Option 1,0.5155049164,14OCT1960,01JAN1960:04:02:06,0:00:30,63,96 | ||||||
|  | 10167,167 bottles of beer on the wall,Option 1,0.1016342775,11MAY1960,01JAN1960:05:55:03,0:00:01,31,44 | ||||||
|  | 10168,168 bottles of beer on the wall,Option 1,0.3690353596,12NOV1961,01JAN1960:12:49:02,0:00:03,2,79 | ||||||
|  | 10169,169 bottles of beer on the wall,Option 1,0.5573803501,02SEP1962,01JAN1960:12:59:56,0:00:31,7,95 | ||||||
|  | 10170,170 bottles of beer on the wall,Option 1,0.2008119497,10JUN1961,01JAN1960:05:59:06,0:01:29,88,4 | ||||||
|  | 10171,171 bottles of beer on the wall,Option 1,0.6939068505,25MAY1962,01JAN1960:07:20:06,0:01:08,87,8 | ||||||
|  | 10172,172 bottles of beer on the wall,Option 1,0.7013406594,14JUL1960,01JAN1960:04:04:24,0:00:11,44,96 | ||||||
|  | 10173,173 bottles of beer on the wall,Option 1,0.83506724,30APR1961,01JAN1960:12:44:40,0:00:10,74,70 | ||||||
|  | 10174,174 bottles of beer on the wall,Option 1,0.9339991943,26JAN1962,01JAN1960:04:59:32,0:01:09,13,66 | ||||||
|  | 10175,175 bottles of beer on the wall,Option 1,0.8333402787,18FEB1961,01JAN1960:07:25:44,0:00:28,47,2 | ||||||
|  | 10176,176 bottles of beer on the wall,Option 1,0.5998844433,03MAR1962,01JAN1960:03:45:33,0:00:52,2,61 | ||||||
|  | 10177,177 bottles of beer on the wall,Option 1,0.6161394634,18DEC1960,01JAN1960:05:35:25,0:00:50,70,22 | ||||||
|  | 10178,178 bottles of beer on the wall,Option 1,0.0821002392,21APR1960,01JAN1960:10:08:56,0:01:40,28,9 | ||||||
|  | 10179,179 bottles of beer on the wall,Option 1,0.6845213462,23MAY1960,01JAN1960:13:15:46,0:00:03,89,13 | ||||||
|  | 10180,180 bottles of beer on the wall,Option 1,0.3839034477,14MAY1960,01JAN1960:03:22:17,0:01:11,15,38 | ||||||
|  | 10181,181 bottles of beer on the wall,Option 1,0.7949567609,21AUG1962,01JAN1960:02:41:01,0:00:57,90,93 | ||||||
|  | 10182,182 bottles of beer on the wall,Option 1,0.5079025419,23SEP1962,01JAN1960:02:22:31,0:01:07,83,32 | ||||||
|  | 10183,183 bottles of beer on the wall,Option 1,0.3215162574,26DEC1961,01JAN1960:09:03:00,0:01:38,46,94 | ||||||
|  | 10184,184 bottles of beer on the wall,Option 1,0.3322958058,12MAY1961,01JAN1960:02:48:05,0:00:46,80,54 | ||||||
|  | 10185,185 bottles of beer on the wall,Option 1,0.6510801453,07SEP1960,01JAN1960:11:49:02,0:00:59,51,47 | ||||||
|  | 10186,186 bottles of beer on the wall,Option 1,0.060995535,15AUG1960,01JAN1960:02:21:08,0:01:40,5,61 | ||||||
|  | 10187,187 bottles of beer on the wall,Option 1,0.8541180551,14SEP1960,01JAN1960:13:29:33,0:01:23,17,14 | ||||||
|  | 10188,188 bottles of beer on the wall,Option 1,0.9427926219,23JUL1960,01JAN1960:05:19:05,0:01:13,22,97 | ||||||
|  | 10189,189 bottles of beer on the wall,Option 1,0.2325015186,01FEB1960,01JAN1960:11:50:07,0:01:22,6,53 | ||||||
|  | 10190,190 bottles of beer on the wall,Option 1,0.3687101493,21FEB1962,01JAN1960:06:44:23,0:00:13,16,30 | ||||||
|  | 10191,191 bottles of beer on the wall,Option 1,0.7647511232,09JAN1960,01JAN1960:13:06:29,0:01:35,6,97 | ||||||
|  | 10192,192 bottles of beer on the wall,Option 1,0.4105463565,17AUG1961,01JAN1960:11:04:32,0:01:14,38,33 | ||||||
|  | 10193,193 bottles of beer on the wall,Option 1,0.8785403831,12JUL1962,01JAN1960:04:11:05,0:00:29,19,82 | ||||||
|  | 10194,194 bottles of beer on the wall,Option 1,0.9304303433,11JUL1961,01JAN1960:12:36:57,0:01:02,20,35 | ||||||
|  | 10195,195 bottles of beer on the wall,Option 1,0.7302505256,01MAR1961,01JAN1960:01:38:35,0:00:42,16,35 | ||||||
|  | 10196,196 bottles of beer on the wall,Option 1,0.2536906177,04SEP1962,01JAN1960:05:18:23,0:01:18,91,50 | ||||||
|  | 10197,197 bottles of beer on the wall,Option 1,0.1181504503,08AUG1961,01JAN1960:09:27:54,0:01:26,20,13 | ||||||
|  | 10198,198 bottles of beer on the wall,Option 1,0.9275614228,17JUL1961,01JAN1960:01:52:18,0:00:06,52,73 | ||||||
|  | 10199,199 bottles of beer on the wall,Option 1,0.7495222128,04APR1961,01JAN1960:09:28:04,0:00:42,30,41 | ||||||
|  | 10200,200 bottles of beer on the wall,Option 1,0.925741082,02FEB1962,01JAN1960:12:23:10,0:00:07,79,17 | ||||||
|  | 10201,201 bottles of beer on the wall,Option 1,0.2591843359,04DEC1960,01JAN1960:12:46:41,0:00:00,53,58 | ||||||
|  | 10202,202 bottles of beer on the wall,Option 1,0.4289995704,17NOV1961,01JAN1960:02:20:52,0:00:35,41,25 | ||||||
|  | 10203,203 bottles of beer on the wall,Option 1,0.4625803807,24JAN1960,01JAN1960:08:20:44,0:01:11,84,66 | ||||||
|  | 10204,204 bottles of beer on the wall,Option 1,0.858440102,31AUG1962,01JAN1960:08:51:40,0:00:12,18,51 | ||||||
|  | 10205,205 bottles of beer on the wall,Option 1,0.8964499016,01SEP1962,01JAN1960:05:33:47,0:00:23,34,77 | ||||||
|  | 10206,206 bottles of beer on the wall,Option 1,0.5742789063,24OCT1961,01JAN1960:02:31:04,0:01:08,27,66 | ||||||
|  | 10207,207 bottles of beer on the wall,Option 1,0.4864150954,29SEP1960,01JAN1960:09:27:46,0:01:28,31,26 | ||||||
|  | 10208,208 bottles of beer on the wall,Option 1,0.4511992249,04DEC1960,01JAN1960:09:39:26,0:00:42,49,98 | ||||||
|  | 10209,209 bottles of beer on the wall,Option 1,0.4218624157,13SEP1961,01JAN1960:01:40:55,0:01:39,35,50 | ||||||
|  | 10210,210 bottles of beer on the wall,Option 1,0.1572868331,15FEB1960,01JAN1960:07:01:15,0:00:51,43,1 | ||||||
|  | 10211,211 bottles of beer on the wall,Option 1,0.713915177,23MAR1960,01JAN1960:11:08:53,0:00:15,18,61 | ||||||
|  | 10212,212 bottles of beer on the wall,Option 1,0.5677882165,19MAY1960,01JAN1960:01:27:23,0:01:02,34,89 | ||||||
|  | 10213,213 bottles of beer on the wall,Option 1,0.7552938581,12SEP1961,01JAN1960:11:47:33,0:00:38,44,46 | ||||||
|  | 10214,214 bottles of beer on the wall,Option 1,0.6071256071,28DEC1961,01JAN1960:05:28:18,0:01:23,84,66 | ||||||
|  | 10215,215 bottles of beer on the wall,Option 1,0.7717189266,12MAR1960,01JAN1960:01:21:26,0:01:00,28,22 | ||||||
|  | 10216,216 bottles of beer on the wall,Option 1,0.8985594329,24MAR1961,01JAN1960:10:48:58,0:01:31,93,2 | ||||||
|  | 10217,217 bottles of beer on the wall,Option 1,0.3156879904,13AUG1960,01JAN1960:07:10:46,0:01:18,100,54 | ||||||
|  | 10218,218 bottles of beer on the wall,Option 1,0.3408455315,08JUN1961,01JAN1960:02:26:49,0:00:05,65,82 | ||||||
|  | 10219,219 bottles of beer on the wall,Option 1,0.6263580553,08JUN1962,01JAN1960:05:59:46,0:01:03,76,88 | ||||||
|  | 10220,220 bottles of beer on the wall,Option 1,0.2878925355,19DEC1961,01JAN1960:08:23:41,0:00:00,92,1 | ||||||
|  | 10221,221 bottles of beer on the wall,Option 1,0.0901017348,19JUL1962,01JAN1960:09:50:47,0:00:43,21,84 | ||||||
|  | 10222,222 bottles of beer on the wall,Option 1,0.8967759362,14SEP1960,01JAN1960:12:25:58,0:01:22,34,50 | ||||||
|  | 10223,223 bottles of beer on the wall,Option 1,0.9878171943,03DEC1961,01JAN1960:03:43:09,0:00:17,11,84 | ||||||
|  | 10224,224 bottles of beer on the wall,Option 1,0.5275036886,13DEC1961,01JAN1960:03:12:56,0:01:36,85,49 | ||||||
|  | 10225,225 bottles of beer on the wall,Option 1,0.442012436,12JUN1960,01JAN1960:11:40:23,0:01:40,76,87 | ||||||
|  | 10226,226 bottles of beer on the wall,Option 1,0.582103689,10FEB1961,01JAN1960:01:50:49,0:00:59,53,29 | ||||||
|  | 10227,227 bottles of beer on the wall,Option 1,0.5757669842,01NOV1960,01JAN1960:13:47:33,0:00:43,55,6 | ||||||
|  | 10228,228 bottles of beer on the wall,Option 1,0.4786617507,07JAN1960,01JAN1960:13:36:24,0:01:22,91,53 | ||||||
|  | 10229,229 bottles of beer on the wall,Option 1,0.1386274957,06APR1962,01JAN1960:03:48:29,0:01:27,36,48 | ||||||
|  | 10230,230 bottles of beer on the wall,Option 1,0.4188394893,31MAY1962,01JAN1960:10:30:51,0:00:54,5,87 | ||||||
|  | 10231,231 bottles of beer on the wall,Option 1,0.9250617777,18OCT1960,01JAN1960:04:29:52,0:00:38,34,94 | ||||||
|  | 10232,232 bottles of beer on the wall,Option 1,0.3077528124,05FEB1960,01JAN1960:09:37:42,0:01:13,58,75 | ||||||
|  | 10233,233 bottles of beer on the wall,Option 1,0.7316332277,29NOV1960,01JAN1960:08:56:57,0:01:13,34,53 | ||||||
|  | 10234,234 bottles of beer on the wall,Option 1,0.5666298352,21NOV1960,01JAN1960:07:51:09,0:01:08,97,71 | ||||||
|  | 10235,235 bottles of beer on the wall,Option 1,0.5736639409,03JUL1962,01JAN1960:11:57:25,0:00:51,15,49 | ||||||
|  | 10236,236 bottles of beer on the wall,Option 1,0.6785667616,11FEB1962,01JAN1960:09:47:20,0:00:50,65,21 | ||||||
|  | 10237,237 bottles of beer on the wall,Option 1,0.3721726869,05JUL1962,01JAN1960:11:58:22,0:01:32,82,21 | ||||||
|  | 10238,238 bottles of beer on the wall,Option 1,0.0332283876,17AUG1961,01JAN1960:13:11:34,0:00:54,83,30 | ||||||
|  | 10239,239 bottles of beer on the wall,Option 1,0.9734656848,02JAN1961,01JAN1960:00:36:43,0:00:19,31,54 | ||||||
|  | 10240,240 bottles of beer on the wall,Option 1,0.3022106021,16FEB1961,01JAN1960:13:50:38,0:00:40,22,66 | ||||||
|  | 10241,241 bottles of beer on the wall,Option 1,0.7546903294,06JUL1961,01JAN1960:12:36:17,0:01:29,16,85 | ||||||
|  | 10242,242 bottles of beer on the wall,Option 1,0.2509871834,07MAR1962,01JAN1960:10:38:28,0:00:39,7,8 | ||||||
|  | 10243,243 bottles of beer on the wall,Option 1,0.9526996668,15JAN1960,01JAN1960:04:24:42,0:01:01,69,80 | ||||||
|  | 10244,244 bottles of beer on the wall,Option 1,0.1816610122,06FEB1962,01JAN1960:08:46:51,0:00:54,89,91 | ||||||
|  | 10245,245 bottles of beer on the wall,Option 1,0.3928658876,21JUL1962,01JAN1960:12:59:42,0:00:38,24,27 | ||||||
|  | 10246,246 bottles of beer on the wall,Option 1,0.3774878524,18FEB1961,01JAN1960:07:40:49,0:01:31,88,93 | ||||||
|  | 10247,247 bottles of beer on the wall,Option 1,0.6063659362,01NOV1960,01JAN1960:01:19:07,0:00:05,82,73 | ||||||
|  | 10248,248 bottles of beer on the wall,Option 1,0.119603098,14JUN1960,01JAN1960:04:29:22,0:00:58,87,47 | ||||||
|  | 10249,249 bottles of beer on the wall,Option 1,0.4833748445,03JUL1960,01JAN1960:01:53:54,0:00:37,34,33 | ||||||
|  | 10250,250 bottles of beer on the wall,Option 1,0.2244539946,10AUG1961,01JAN1960:06:19:01,0:01:15,87,97 | ||||||
|  | 10251,251 bottles of beer on the wall,Option 1,0.9368193191,11JUN1962,01JAN1960:06:37:14,0:00:46,94,39 | ||||||
|  | 10252,252 bottles of beer on the wall,Option 1,0.1791427751,10NOV1961,01JAN1960:00:49:22,0:00:47,96,21 | ||||||
|  | 10253,253 bottles of beer on the wall,Option 1,0.5836302874,06JUN1961,01JAN1960:08:39:34,0:01:01,78,49 | ||||||
|  | 10254,254 bottles of beer on the wall,Option 1,0.1289398275,28DEC1960,01JAN1960:12:25:05,0:00:43,67,99 | ||||||
|  | 10255,255 bottles of beer on the wall,Option 1,0.7833669785,05SEP1962,01JAN1960:02:47:35,0:00:20,25,2 | ||||||
|  | 10256,256 bottles of beer on the wall,Option 1,0.4945342483,29JAN1960,01JAN1960:00:54:13,0:01:13,72,56 | ||||||
|  | 10257,257 bottles of beer on the wall,Option 1,0.0635836129,05JAN1961,01JAN1960:08:10:04,0:00:52,11,10 | ||||||
|  | 10258,258 bottles of beer on the wall,Option 1,0.8188241654,09FEB1962,01JAN1960:06:33:00,0:01:21,41,96 | ||||||
|  | 10259,259 bottles of beer on the wall,Option 1,0.3398916076,11FEB1960,01JAN1960:07:12:29,0:00:56,18,76 | ||||||
|  | 10260,260 bottles of beer on the wall,Option 1,0.0814064155,21MAY1961,01JAN1960:11:03:51,0:01:18,78,29 | ||||||
|  | 10261,261 bottles of beer on the wall,Option 1,0.6653245542,20JAN1962,01JAN1960:08:03:31,0:00:18,39,95 | ||||||
|  | 10262,262 bottles of beer on the wall,Option 1,0.4036777021,04AUG1962,01JAN1960:12:32:27,0:00:08,57,63 | ||||||
|  | 10263,263 bottles of beer on the wall,Option 1,0.8931138603,07JAN1962,01JAN1960:09:04:24,0:00:32,6,27 | ||||||
|  | 10264,264 bottles of beer on the wall,Option 1,0.528584433,06APR1962,01JAN1960:09:43:19,0:01:00,24,41 | ||||||
|  | 10265,265 bottles of beer on the wall,Option 1,0.8267822945,29JUL1960,01JAN1960:00:48:11,0:00:01,81,78 | ||||||
|  | 10266,266 bottles of beer on the wall,Option 1,0.7218411401,17FEB1960,01JAN1960:07:30:38,0:00:08,35,81 | ||||||
|  | 10267,267 bottles of beer on the wall,Option 1,0.1475262773,11NOV1960,01JAN1960:13:44:20,0:00:57,36,68 | ||||||
|  | 10268,268 bottles of beer on the wall,Option 1,0.9412727286,30DEC1960,01JAN1960:02:46:30,0:01:19,5,92 | ||||||
|  | 10269,269 bottles of beer on the wall,Option 1,0.3038877548,27NOV1960,01JAN1960:10:50:10,0:01:21,43,95 | ||||||
|  | 10270,270 bottles of beer on the wall,Option 1,0.2756435532,15APR1962,01JAN1960:09:05:28,0:01:34,11,14 | ||||||
|  | 10271,271 bottles of beer on the wall,Option 1,0.7056001121,31AUG1960,01JAN1960:08:48:52,0:00:02,9,51 | ||||||
|  | 10272,272 bottles of beer on the wall,Option 1,0.5273708508,21SEP1962,01JAN1960:12:58:13,0:00:28,97,69 | ||||||
|  | 10273,273 bottles of beer on the wall,Option 1,0.6002807215,03MAY1960,01JAN1960:10:14:48,0:00:40,52,32 | ||||||
|  | 10274,274 bottles of beer on the wall,Option 1,0.6100557971,20JUN1960,01JAN1960:08:11:55,0:00:27,90,14 | ||||||
|  | 10275,275 bottles of beer on the wall,Option 1,0.4197408638,07JUN1961,01JAN1960:12:07:18,0:00:26,64,100 | ||||||
|  | 10276,276 bottles of beer on the wall,Option 1,0.4903712498,19JAN1960,01JAN1960:01:06:26,0:00:03,35,24 | ||||||
|  | 10277,277 bottles of beer on the wall,Option 1,0.6658435406,04NOV1960,01JAN1960:00:04:17,0:00:37,7,84 | ||||||
|  | 10278,278 bottles of beer on the wall,Option 1,0.5491365942,14JAN1961,01JAN1960:04:12:49,0:00:27,99,47 | ||||||
|  | 10279,279 bottles of beer on the wall,Option 1,0.4473488622,13MAY1961,01JAN1960:12:06:34,0:01:16,19,20 | ||||||
|  | 10280,280 bottles of beer on the wall,Option 1,0.4511988663,06JUL1962,01JAN1960:10:05:51,0:00:56,76,34 | ||||||
|  | 10281,281 bottles of beer on the wall,Option 1,0.0783031066,11JUN1961,01JAN1960:09:58:43,0:01:05,9,63 | ||||||
|  | 10282,282 bottles of beer on the wall,Option 1,0.776985302,20JUL1962,01JAN1960:10:44:29,0:01:00,59,10 | ||||||
|  | 10283,283 bottles of beer on the wall,Option 1,0.468099362,31AUG1962,01JAN1960:05:26:33,0:00:20,35,52 | ||||||
|  | 10284,284 bottles of beer on the wall,Option 1,0.4040679696,20FEB1962,01JAN1960:06:27:25,0:00:04,76,30 | ||||||
|  | 10285,285 bottles of beer on the wall,Option 1,0.4549995947,20FEB1962,01JAN1960:10:36:57,0:00:34,2,43 | ||||||
|  | 10286,286 bottles of beer on the wall,Option 1,0.7455339361,16SEP1961,01JAN1960:08:39:35,0:01:00,42,44 | ||||||
|  | 10287,287 bottles of beer on the wall,Option 1,0.0209561712,04JAN1960,01JAN1960:05:52:58,0:00:24,32,7 | ||||||
|  | 10288,288 bottles of beer on the wall,Option 1,0.4955981842,04JAN1962,01JAN1960:02:56:03,0:00:30,85,31 | ||||||
|  | 10289,289 bottles of beer on the wall,Option 1,0.4131368219,10FEB1960,01JAN1960:11:57:31,0:00:16,37,88 | ||||||
|  | 10290,290 bottles of beer on the wall,Option 1,0.3282186721,17OCT1960,01JAN1960:10:54:04,0:00:56,72,28 | ||||||
|  | 10291,291 bottles of beer on the wall,Option 1,0.2116929005,18JAN1962,01JAN1960:06:56:27,0:00:11,87,82 | ||||||
|  | 10292,292 bottles of beer on the wall,Option 1,0.8483731937,12FEB1962,01JAN1960:05:05:41,0:01:36,12,83 | ||||||
|  | 10293,293 bottles of beer on the wall,Option 1,0.1560111345,13NOV1960,01JAN1960:10:04:22,0:00:03,94,4 | ||||||
|  | 10294,294 bottles of beer on the wall,Option 1,0.7046207808,12APR1962,01JAN1960:13:50:47,0:00:32,31,97 | ||||||
|  | 10295,295 bottles of beer on the wall,Option 1,0.2716620403,04AUG1961,01JAN1960:01:52:29,0:00:57,99,44 | ||||||
|  | 10296,296 bottles of beer on the wall,Option 1,0.5543203496,12SEP1960,01JAN1960:13:43:54,0:00:44,49,1 | ||||||
|  | 10297,297 bottles of beer on the wall,Option 1,0.983109036,31JUL1962,01JAN1960:01:07:33,0:00:36,4,10 | ||||||
|  | 10298,298 bottles of beer on the wall,Option 1,0.8123072115,14SEP1962,01JAN1960:06:16:12,0:01:25,88,96 | ||||||
|  | 10299,299 bottles of beer on the wall,Option 1,0.4276896559,05OCT1960,01JAN1960:02:55:07,0:00:58,83,76 | ||||||
|  | 10300,300 bottles of beer on the wall,Option 1,0.8921809042,19JAN1962,01JAN1960:02:05:38,0:00:12,80,13 | ||||||
|  | 10301,301 bottles of beer on the wall,Option 1,0.6041374279,10DEC1961,01JAN1960:01:06:29,0:01:27,62,9 | ||||||
|  | 10302,302 bottles of beer on the wall,Option 1,0.0460550185,31MAY1962,01JAN1960:03:03:56,0:00:05,33,88 | ||||||
|  | 10303,303 bottles of beer on the wall,Option 1,0.1868385622,12APR1962,01JAN1960:12:42:44,0:01:05,65,18 | ||||||
|  | 10304,304 bottles of beer on the wall,Option 1,0.3386632657,28SEP1961,01JAN1960:11:24:06,0:00:42,2,93 | ||||||
|  | 10305,305 bottles of beer on the wall,Option 1,0.6400271019,01JUN1960,01JAN1960:13:33:07,0:01:30,60,72 | ||||||
|  | 10306,306 bottles of beer on the wall,Option 1,0.9534907304,18NOV1961,01JAN1960:02:02:51,0:00:54,7,57 | ||||||
|  | 10307,307 bottles of beer on the wall,Option 1,0.6663103745,06SEP1961,01JAN1960:05:36:49,0:00:43,88,2 | ||||||
|  | 10308,308 bottles of beer on the wall,Option 1,0.5392553073,13FEB1962,01JAN1960:11:28:18,0:01:08,16,8 | ||||||
|  | 10309,309 bottles of beer on the wall,Option 1,0.0747909025,17OCT1961,01JAN1960:08:36:12,0:00:41,49,42 | ||||||
|  | 10310,310 bottles of beer on the wall,Option 1,0.3249381847,30SEP1960,01JAN1960:08:12:54,0:00:09,96,89 | ||||||
|  | 10311,311 bottles of beer on the wall,Option 1,0.9231011951,19MAY1962,01JAN1960:05:10:33,0:00:50,30,9 | ||||||
|  | 10312,312 bottles of beer on the wall,Option 1,0.4658221637,21MAY1961,01JAN1960:12:55:25,0:01:39,16,20 | ||||||
|  | 10313,313 bottles of beer on the wall,Option 1,0.7215524673,21FEB1960,01JAN1960:02:00:07,0:01:40,95,94 | ||||||
|  | 10314,314 bottles of beer on the wall,Option 1,0.7328679942,28OCT1961,01JAN1960:09:07:00,0:00:25,42,71 | ||||||
|  | 10315,315 bottles of beer on the wall,Option 1,0.1276036776,12JUN1960,01JAN1960:01:54:08,0:00:56,57,42 | ||||||
|  | 10316,316 bottles of beer on the wall,Option 1,0.1270824723,15SEP1960,01JAN1960:03:19:25,0:00:21,85,9 | ||||||
|  | 10317,317 bottles of beer on the wall,Option 1,0.3750520117,13JUN1961,01JAN1960:04:33:09,0:01:15,24,20 | ||||||
|  | 10318,318 bottles of beer on the wall,Option 1,0.5777822102,10DEC1960,01JAN1960:13:32:14,0:00:09,98,28 | ||||||
|  | 10319,319 bottles of beer on the wall,Option 1,0.140476402,27AUG1962,01JAN1960:08:52:46,0:01:08,64,83 | ||||||
|  | 10320,320 bottles of beer on the wall,Option 1,0.2589205551,31MAY1961,01JAN1960:08:33:06,0:00:53,28,98 | ||||||
|  | 10321,321 bottles of beer on the wall,Option 1,0.7350722825,16SEP1962,01JAN1960:05:47:44,0:01:17,79,95 | ||||||
|  | 10322,322 bottles of beer on the wall,Option 1,0.1476364542,15JAN1960,01JAN1960:12:21:20,0:00:20,86,62 | ||||||
|  | 10323,323 bottles of beer on the wall,Option 1,0.8700561099,15MAY1962,01JAN1960:00:47:05,0:00:20,90,15 | ||||||
|  | 10324,324 bottles of beer on the wall,Option 1,0.6408788802,12SEP1962,01JAN1960:11:50:31,0:00:53,41,72 | ||||||
|  | 10325,325 bottles of beer on the wall,Option 1,0.6961101623,27NOV1960,01JAN1960:00:10:49,0:01:17,28,72 | ||||||
|  | 10326,326 bottles of beer on the wall,Option 1,0.1467710059,24FEB1961,01JAN1960:01:13:38,0:00:33,14,5 | ||||||
|  | 10327,327 bottles of beer on the wall,Option 1,0.0215573572,09JUN1961,01JAN1960:11:47:17,0:00:21,57,10 | ||||||
|  | 10328,328 bottles of beer on the wall,Option 1,0.4173900054,25JUL1962,01JAN1960:12:28:20,0:00:23,73,90 | ||||||
|  | 10329,329 bottles of beer on the wall,Option 1,0.6395625713,02NOV1961,01JAN1960:08:49:34,0:00:37,77,79 | ||||||
|  | 10330,330 bottles of beer on the wall,Option 1,0.0091438908,18MAY1962,01JAN1960:05:10:05,0:00:41,15,31 | ||||||
|  | 10331,331 bottles of beer on the wall,Option 1,0.1024675197,11DEC1960,01JAN1960:13:12:57,0:00:23,50,13 | ||||||
|  | 10332,332 bottles of beer on the wall,Option 1,0.057470562,11MAY1961,01JAN1960:03:43:04,0:00:17,48,14 | ||||||
|  | 10333,333 bottles of beer on the wall,Option 1,0.8478633872,21JUL1961,01JAN1960:03:45:42,0:01:31,22,40 | ||||||
|  | 10334,334 bottles of beer on the wall,Option 1,0.3442252541,24JUN1960,01JAN1960:01:19:31,0:00:48,82,25 | ||||||
|  | 10335,335 bottles of beer on the wall,Option 1,0.7338460184,06JUN1962,01JAN1960:03:32:34,0:01:04,6,31 | ||||||
|  | 10336,336 bottles of beer on the wall,Option 1,0.6217917342,09MAR1961,01JAN1960:06:37:39,0:00:50,70,84 | ||||||
|  | 10337,337 bottles of beer on the wall,Option 1,0.6684890807,10OCT1961,01JAN1960:05:34:24,0:01:20,66,18 | ||||||
|  | 10338,338 bottles of beer on the wall,Option 1,0.3695247562,05SEP1962,01JAN1960:00:25:21,0:01:18,48,37 | ||||||
|  | 10339,339 bottles of beer on the wall,Option 1,0.9429265987,06DEC1960,01JAN1960:07:11:17,0:00:38,59,1 | ||||||
|  | 10340,340 bottles of beer on the wall,Option 1,0.9266307265,17JUL1960,01JAN1960:06:33:59,0:00:21,12,13 | ||||||
|  | 10341,341 bottles of beer on the wall,Option 1,0.7280535543,23FEB1961,01JAN1960:05:01:10,0:00:34,73,25 | ||||||
|  | 10342,342 bottles of beer on the wall,Option 1,0.488654495,15AUG1962,01JAN1960:01:24:33,0:00:56,59,25 | ||||||
|  | 10343,343 bottles of beer on the wall,Option 1,0.9526806548,28DEC1960,01JAN1960:07:26:17,0:00:58,97,61 | ||||||
|  | 10344,344 bottles of beer on the wall,Option 1,0.526025336,14JAN1960,01JAN1960:10:02:08,0:00:55,11,77 | ||||||
|  | 10345,345 bottles of beer on the wall,Option 1,0.807215352,03JUL1961,01JAN1960:12:49:47,0:00:01,40,7 | ||||||
|  | 10346,346 bottles of beer on the wall,Option 1,0.9305162979,28FEB1960,01JAN1960:09:46:40,0:00:59,56,28 | ||||||
|  | 10347,347 bottles of beer on the wall,Option 1,0.7591318552,18FEB1962,01JAN1960:13:25:32,0:01:10,41,9 | ||||||
|  | 10348,348 bottles of beer on the wall,Option 1,0.4177664911,11SEP1961,01JAN1960:09:55:17,0:01:39,76,82 | ||||||
|  | 10349,349 bottles of beer on the wall,Option 1,0.4690050443,05DEC1961,01JAN1960:11:05:15,0:01:09,63,40 | ||||||
|  | 10350,350 bottles of beer on the wall,Option 1,0.7541399979,31AUG1961,01JAN1960:12:30:45,0:00:33,57,12 | ||||||
|  | 10351,351 bottles of beer on the wall,Option 1,0.1392844325,17MAR1962,01JAN1960:08:20:38,0:00:41,85,45 | ||||||
|  | 10352,352 bottles of beer on the wall,Option 1,0.1020530235,23DEC1961,01JAN1960:09:46:20,0:00:01,55,56 | ||||||
|  | 10353,353 bottles of beer on the wall,Option 1,0.0257998794,04DEC1961,01JAN1960:09:47:10,0:00:31,100,2 | ||||||
|  | 10354,354 bottles of beer on the wall,Option 1,0.1238113316,20MAR1962,01JAN1960:09:15:30,0:00:01,74,11 | ||||||
|  | 10355,355 bottles of beer on the wall,Option 1,0.4214245292,24NOV1960,01JAN1960:04:24:09,0:01:01,79,83 | ||||||
|  | 10356,356 bottles of beer on the wall,Option 1,0.3243381057,12FEB1961,01JAN1960:00:55:59,0:00:50,30,52 | ||||||
|  | 10357,357 bottles of beer on the wall,Option 1,0.9735697345,24NOV1960,01JAN1960:07:10:56,0:01:33,64,2 | ||||||
|  | 10358,358 bottles of beer on the wall,Option 1,0.4796259461,28JAN1961,01JAN1960:11:51:29,0:01:03,19,29 | ||||||
|  | 10359,359 bottles of beer on the wall,Option 1,0.003359966,01SEP1960,01JAN1960:13:24:25,0:00:09,66,60 | ||||||
|  | 10360,360 bottles of beer on the wall,Option 1,0.5773700334,21JAN1960,01JAN1960:10:15:32,0:00:40,9,21 | ||||||
|  | 10361,361 bottles of beer on the wall,Option 1,0.0792848342,25JAN1962,01JAN1960:06:00:35,0:01:25,73,73 | ||||||
|  | 10362,362 bottles of beer on the wall,Option 1,0.9339359365,30MAY1961,01JAN1960:09:08:13,0:00:56,12,56 | ||||||
|  | 10363,363 bottles of beer on the wall,Option 1,0.3517632132,12FEB1961,01JAN1960:12:07:19,0:00:01,74,69 | ||||||
|  | 10364,364 bottles of beer on the wall,Option 1,0.4088954895,17MAR1961,01JAN1960:08:04:51,0:01:01,70,66 | ||||||
|  | 10365,365 bottles of beer on the wall,Option 1,0.1254259623,30DEC1961,01JAN1960:06:47:12,0:00:01,79,43 | ||||||
|  | 10366,366 bottles of beer on the wall,Option 1,0.9190132958,28MAY1961,01JAN1960:06:35:42,0:00:07,19,31 | ||||||
|  | 10367,367 bottles of beer on the wall,Option 1,0.3033860015,17MAY1962,01JAN1960:05:32:03,0:00:32,57,73 | ||||||
|  | 10368,368 bottles of beer on the wall,Option 1,0.1463442846,02SEP1962,01JAN1960:01:24:29,0:00:53,19,64 | ||||||
|  | 10369,369 bottles of beer on the wall,Option 1,0.5516236343,18JUN1962,01JAN1960:10:52:23,0:00:11,51,40 | ||||||
|  | 10370,370 bottles of beer on the wall,Option 1,0.5205378246,19JAN1960,01JAN1960:06:54:14,0:00:58,2,72 | ||||||
|  | 10371,371 bottles of beer on the wall,Option 1,0.9941610768,28MAR1962,01JAN1960:04:55:58,0:00:22,46,65 | ||||||
|  | 10372,372 bottles of beer on the wall,Option 1,0.065678,07MAY1961,01JAN1960:10:17:35,0:00:10,54,100 | ||||||
|  | 10373,373 bottles of beer on the wall,Option 1,0.7222646138,17JUL1961,01JAN1960:01:47:32,0:00:51,26,96 | ||||||
|  | 10374,374 bottles of beer on the wall,Option 1,0.8772228294,23JUL1960,01JAN1960:00:30:51,0:00:40,18,45 | ||||||
|  | 10375,375 bottles of beer on the wall,Option 1,0.3651081847,11DEC1961,01JAN1960:00:46:15,0:00:15,14,90 | ||||||
|  | 10376,376 bottles of beer on the wall,Option 1,0.3800431529,15AUG1960,01JAN1960:05:30:55,0:00:19,13,74 | ||||||
|  | 10377,377 bottles of beer on the wall,Option 1,0.1077003503,26FEB1960,01JAN1960:04:48:40,0:00:08,51,53 | ||||||
|  | 10378,378 bottles of beer on the wall,Option 1,0.7945664035,06MAR1961,01JAN1960:05:36:47,0:00:45,65,39 | ||||||
|  | 10379,379 bottles of beer on the wall,Option 1,0.8754883054,06JUN1960,01JAN1960:06:09:33,0:00:04,3,3 | ||||||
|  | 10380,380 bottles of beer on the wall,Option 1,0.9975561108,10AUG1960,01JAN1960:10:34:33,0:00:28,92,29 | ||||||
|  | 10381,381 bottles of beer on the wall,Option 1,0.9817031599,07JUL1960,01JAN1960:01:40:00,0:00:39,63,45 | ||||||
|  | 10382,382 bottles of beer on the wall,Option 1,0.4427802341,07JAN1962,01JAN1960:01:21:32,0:01:31,7,54 | ||||||
|  | 10383,383 bottles of beer on the wall,Option 1,0.03180474,17JUL1962,01JAN1960:07:15:54,0:01:08,72,60 | ||||||
|  | 10384,384 bottles of beer on the wall,Option 1,0.1031627707,10MAY1962,01JAN1960:02:52:58,0:01:31,6,64 | ||||||
|  | 10385,385 bottles of beer on the wall,Option 1,0.911744344,01MAY1960,01JAN1960:03:51:16,0:01:04,1,54 | ||||||
|  | 10386,386 bottles of beer on the wall,Option 1,0.4912374353,13FEB1961,01JAN1960:07:22:49,0:01:21,70,71 | ||||||
|  | 10387,387 bottles of beer on the wall,Option 1,0.8803869509,04JUL1960,01JAN1960:12:14:05,0:00:18,78,88 | ||||||
|  | 10388,388 bottles of beer on the wall,Option 1,0.0596609544,17DEC1960,01JAN1960:08:29:20,0:00:53,13,84 | ||||||
|  | 10389,389 bottles of beer on the wall,Option 1,0.6625022132,12JAN1961,01JAN1960:11:15:39,0:01:05,10,31 | ||||||
|  | 10390,390 bottles of beer on the wall,Option 1,0.2669677358,05OCT1961,01JAN1960:00:48:03,0:01:33,29,31 | ||||||
|  | 10391,391 bottles of beer on the wall,Option 1,0.8095563454,04DEC1961,01JAN1960:07:54:13,0:01:15,95,2 | ||||||
|  | 10392,392 bottles of beer on the wall,Option 1,0.6406704796,27JAN1961,01JAN1960:03:35:55,0:00:36,92,47 | ||||||
|  | 10393,393 bottles of beer on the wall,Option 1,0.2188341847,02MAR1960,01JAN1960:04:05:21,0:00:11,28,34 | ||||||
|  | 10394,394 bottles of beer on the wall,Option 1,0.7052043424,09JUN1962,01JAN1960:07:00:21,0:01:14,84,19 | ||||||
|  | 10395,395 bottles of beer on the wall,Option 1,0.9843204464,18APR1960,01JAN1960:04:54:31,0:01:29,46,46 | ||||||
|  | 10396,396 bottles of beer on the wall,Option 1,0.329249541,10SEP1961,01JAN1960:13:21:04,0:00:21,70,11 | ||||||
|  | 10397,397 bottles of beer on the wall,Option 1,0.2073628675,13JUL1962,01JAN1960:11:16:26,0:01:23,17,97 | ||||||
|  | 10398,398 bottles of beer on the wall,Option 1,0.7881676665,29JUN1962,01JAN1960:11:36:47,0:00:50,39,72 | ||||||
|  | 10399,399 bottles of beer on the wall,Option 1,0.4347905537,31AUG1962,01JAN1960:02:30:30,0:00:21,86,61 | ||||||
|  | 10400,400 bottles of beer on the wall,Option 1,0.2937454084,05APR1962,01JAN1960:02:44:06,0:00:32,27,81 | ||||||
|  | 10401,401 bottles of beer on the wall,Option 1,0.6659902565,10APR1961,01JAN1960:11:53:59,0:00:01,80,17 | ||||||
|  | 10402,402 bottles of beer on the wall,Option 1,0.7637229686,07APR1962,01JAN1960:05:45:25,0:01:28,11,17 | ||||||
|  | 10403,403 bottles of beer on the wall,Option 1,0.3325480941,19MAR1961,01JAN1960:09:57:05,0:00:27,9,99 | ||||||
|  | 10404,404 bottles of beer on the wall,Option 1,0.580015553,10AUG1960,01JAN1960:06:15:44,0:00:25,27,99 | ||||||
|  | 10405,405 bottles of beer on the wall,Option 1,0.2514696071,08APR1961,01JAN1960:10:37:45,0:00:17,29,59 | ||||||
|  | 10406,406 bottles of beer on the wall,Option 1,0.3792107284,19MAR1962,01JAN1960:04:49:30,0:00:07,44,38 | ||||||
|  | 10407,407 bottles of beer on the wall,Option 1,0.4702913125,13DEC1961,01JAN1960:09:03:31,0:00:38,95,90 | ||||||
|  | 10408,408 bottles of beer on the wall,Option 1,0.4207190659,03FEB1961,01JAN1960:00:37:47,0:00:38,37,65 | ||||||
|  | 10409,409 bottles of beer on the wall,Option 1,0.2485069117,08DEC1961,01JAN1960:07:18:28,0:01:39,91,64 | ||||||
|  | 10410,410 bottles of beer on the wall,Option 1,0.3257893502,27NOV1961,01JAN1960:11:33:02,0:00:54,41,7 | ||||||
|  | 10411,411 bottles of beer on the wall,Option 1,0.0967283533,11AUG1962,01JAN1960:07:23:15,0:00:21,90,1 | ||||||
|  | 10412,412 bottles of beer on the wall,Option 1,0.532676542,01SEP1960,01JAN1960:02:46:29,0:00:45,31,48 | ||||||
|  | 10413,413 bottles of beer on the wall,Option 1,0.2564602151,15JAN1961,01JAN1960:01:09:11,0:01:21,16,89 | ||||||
|  | 10414,414 bottles of beer on the wall,Option 1,0.0865634024,20MAR1962,01JAN1960:12:56:45,0:00:05,75,51 | ||||||
|  | 10415,415 bottles of beer on the wall,Option 1,0.2926342321,07FEB1960,01JAN1960:06:48:55,0:00:36,66,33 | ||||||
|  | 10416,416 bottles of beer on the wall,Option 1,0.0678594029,06OCT1961,01JAN1960:07:14:58,0:01:26,50,16 | ||||||
|  | 10417,417 bottles of beer on the wall,Option 1,0.6376664767,09JUN1960,01JAN1960:05:00:13,0:01:37,7,92 | ||||||
|  | 10418,418 bottles of beer on the wall,Option 1,0.4795483525,14JUN1962,01JAN1960:00:00:50,0:00:50,33,74 | ||||||
|  | 10419,419 bottles of beer on the wall,Option 1,0.3492934342,22MAR1960,01JAN1960:08:54:52,0:01:26,61,72 | ||||||
|  | 10420,420 bottles of beer on the wall,Option 1,0.5085071356,14MAR1960,01JAN1960:09:47:51,0:00:56,36,63 | ||||||
|  | 10421,421 bottles of beer on the wall,Option 1,0.414953564,25MAR1961,01JAN1960:03:35:19,0:00:30,47,30 | ||||||
|  | 10422,422 bottles of beer on the wall,Option 1,0.2431976652,25SEP1960,01JAN1960:08:47:25,0:00:17,6,42 | ||||||
|  | 10423,423 bottles of beer on the wall,Option 1,0.2370444998,04MAR1962,01JAN1960:00:16:44,0:01:36,54,100 | ||||||
|  | 10424,424 bottles of beer on the wall,Option 1,0.8687893324,12OCT1961,01JAN1960:03:16:33,0:00:56,97,25 | ||||||
|  | 10425,425 bottles of beer on the wall,Option 1,0.0470510335,10MAY1960,01JAN1960:02:44:07,0:01:05,83,59 | ||||||
|  | 10426,426 bottles of beer on the wall,Option 1,0.7114342887,12APR1960,01JAN1960:02:17:40,0:00:39,57,97 | ||||||
|  | 10427,427 bottles of beer on the wall,Option 1,0.6176668283,05JUL1962,01JAN1960:09:09:12,0:01:03,92,38 | ||||||
|  | 10428,428 bottles of beer on the wall,Option 1,0.0657907743,01JUL1962,01JAN1960:11:03:37,0:01:35,97,63 | ||||||
|  | 10429,429 bottles of beer on the wall,Option 1,0.280104912,13MAR1962,01JAN1960:10:35:08,0:01:07,42,35 | ||||||
|  | 10430,430 bottles of beer on the wall,Option 1,0.3639827088,02APR1960,01JAN1960:12:00:50,0:00:40,91,84 | ||||||
|  | 10431,431 bottles of beer on the wall,Option 1,0.2130561137,25AUG1961,01JAN1960:12:07:22,0:01:37,70,9 | ||||||
|  | 10432,432 bottles of beer on the wall,Option 1,0.9656705889,23DEC1960,01JAN1960:02:36:48,0:01:28,23,92 | ||||||
|  | 10433,433 bottles of beer on the wall,Option 1,0.5824601052,22JAN1961,01JAN1960:01:19:48,0:00:32,73,6 | ||||||
|  | 10434,434 bottles of beer on the wall,Option 1,0.5207124942,23APR1961,01JAN1960:10:45:16,0:01:08,45,85 | ||||||
|  | 10435,435 bottles of beer on the wall,Option 1,0.9280530661,01NOV1960,01JAN1960:12:46:59,0:01:36,57,4 | ||||||
|  | 10436,436 bottles of beer on the wall,Option 1,0.8982810149,29NOV1961,01JAN1960:09:23:13,0:01:15,100,68 | ||||||
|  | 10437,437 bottles of beer on the wall,Option 1,0.3268259467,04MAR1960,01JAN1960:01:22:42,0:00:04,99,61 | ||||||
|  | 10438,438 bottles of beer on the wall,Option 1,0.2038002704,12FEB1962,01JAN1960:09:03:24,0:00:40,97,78 | ||||||
|  | 10439,439 bottles of beer on the wall,Option 1,0.0798850833,11MAY1960,01JAN1960:00:32:50,0:00:37,7,49 | ||||||
|  | 10440,440 bottles of beer on the wall,Option 1,0.6697000566,01SEP1960,01JAN1960:08:04:03,0:00:30,7,68 | ||||||
|  | 10441,441 bottles of beer on the wall,Option 1,0.550699767,11JAN1960,01JAN1960:06:33:15,0:01:09,19,10 | ||||||
|  | 10442,442 bottles of beer on the wall,Option 1,0.9514032798,07FEB1962,01JAN1960:06:03:16,0:01:13,65,87 | ||||||
|  | 10443,443 bottles of beer on the wall,Option 1,0.1182718324,08FEB1960,01JAN1960:11:28:52,0:00:46,61,59 | ||||||
|  | 10444,444 bottles of beer on the wall,Option 1,0.5772118874,11FEB1962,01JAN1960:01:14:29,0:01:07,16,22 | ||||||
|  | 10445,445 bottles of beer on the wall,Option 1,0.9828714463,06DEC1960,01JAN1960:05:47:08,0:01:40,66,4 | ||||||
|  | 10446,446 bottles of beer on the wall,Option 1,0.1229167269,02JUN1960,01JAN1960:09:43:51,0:01:34,29,24 | ||||||
|  | 10447,447 bottles of beer on the wall,Option 1,0.2102257522,09MAR1961,01JAN1960:04:06:09,0:01:27,53,78 | ||||||
|  | 10448,448 bottles of beer on the wall,Option 1,0.9958884935,20FEB1962,01JAN1960:06:43:53,0:00:07,42,65 | ||||||
|  | 10449,449 bottles of beer on the wall,Option 1,0.3530408085,04MAY1961,01JAN1960:11:26:59,0:01:37,76,35 | ||||||
|  | 10450,450 bottles of beer on the wall,Option 1,0.2848354258,03SEP1960,01JAN1960:13:26:36,0:00:25,69,38 | ||||||
|  | 10451,451 bottles of beer on the wall,Option 1,0.1461860818,10JUN1960,01JAN1960:01:24:25,0:00:02,61,23 | ||||||
|  | 10452,452 bottles of beer on the wall,Option 1,0.2033735766,29AUG1960,01JAN1960:12:30:17,0:00:19,32,75 | ||||||
|  | 10453,453 bottles of beer on the wall,Option 1,0.7973149958,26JUN1961,01JAN1960:04:46:06,0:00:21,94,14 | ||||||
|  | 10454,454 bottles of beer on the wall,Option 1,0.6812086425,07NOV1961,01JAN1960:01:59:51,0:00:12,89,59 | ||||||
|  | 10455,455 bottles of beer on the wall,Option 1,0.6111464974,24FEB1962,01JAN1960:07:42:56,0:00:15,23,92 | ||||||
|  | 10456,456 bottles of beer on the wall,Option 1,0.9542484209,09APR1961,01JAN1960:09:09:17,0:01:26,87,93 | ||||||
|  | 10457,457 bottles of beer on the wall,Option 1,0.9818577077,18AUG1960,01JAN1960:12:24:47,0:01:08,18,20 | ||||||
|  | 10458,458 bottles of beer on the wall,Option 1,0.9331120881,06JUL1961,01JAN1960:10:10:02,0:01:40,78,36 | ||||||
|  | 10459,459 bottles of beer on the wall,Option 1,0.8953045587,22APR1960,01JAN1960:04:47:34,0:00:37,13,46 | ||||||
|  | 10460,460 bottles of beer on the wall,Option 1,0.1584420461,08JAN1961,01JAN1960:07:47:49,0:00:05,85,61 | ||||||
|  | 10461,461 bottles of beer on the wall,Option 1,0.9324573814,24DEC1960,01JAN1960:02:13:36,0:00:22,55,94 | ||||||
|  | 10462,462 bottles of beer on the wall,Option 1,0.0813189871,21AUG1961,01JAN1960:09:18:28,0:00:36,42,49 | ||||||
|  | 10463,463 bottles of beer on the wall,Option 1,0.8736312594,17MAY1962,01JAN1960:12:52:58,0:00:30,45,7 | ||||||
|  | 10464,464 bottles of beer on the wall,Option 1,0.1894013794,06NOV1960,01JAN1960:12:56:13,0:01:30,11,50 | ||||||
|  | 10465,465 bottles of beer on the wall,Option 1,0.9149773772,07JUN1960,01JAN1960:08:53:28,0:00:22,23,17 | ||||||
|  | 10466,466 bottles of beer on the wall,Option 1,0.6329479598,27JUN1962,01JAN1960:07:38:23,0:00:06,68,14 | ||||||
|  | 10467,467 bottles of beer on the wall,Option 1,0.1897066413,21AUG1961,01JAN1960:10:15:28,0:01:12,59,46 | ||||||
|  | 10468,468 bottles of beer on the wall,Option 1,0.8352205767,16MAR1961,01JAN1960:06:48:50,0:00:44,60,37 | ||||||
|  | 10469,469 bottles of beer on the wall,Option 1,0.3339443432,09OCT1960,01JAN1960:00:52:53,0:00:27,70,44 | ||||||
|  | 10470,470 bottles of beer on the wall,Option 1,0.1096137567,09JUL1962,01JAN1960:08:28:10,0:01:15,88,81 | ||||||
|  | 10471,471 bottles of beer on the wall,Option 1,0.1481936733,10JAN1962,01JAN1960:06:38:41,0:00:56,21,77 | ||||||
|  | 10472,472 bottles of beer on the wall,Option 1,0.5630390432,01MAR1960,01JAN1960:06:35:09,0:01:08,70,32 | ||||||
|  | 10473,473 bottles of beer on the wall,Option 1,0.0604085713,12APR1962,01JAN1960:02:41:26,0:00:46,36,17 | ||||||
|  | 10474,474 bottles of beer on the wall,Option 1,0.6011423606,25DEC1961,01JAN1960:02:57:45,0:01:11,36,18 | ||||||
|  | 10475,475 bottles of beer on the wall,Option 1,0.7698335782,31JUL1962,01JAN1960:07:46:29,0:01:34,72,17 | ||||||
|  | 10476,476 bottles of beer on the wall,Option 1,0.3823641224,17MAR1962,01JAN1960:03:34:37,0:00:09,27,86 | ||||||
|  | 10477,477 bottles of beer on the wall,Option 1,0.9225378446,12JAN1962,01JAN1960:08:41:25,0:01:06,45,20 | ||||||
|  | 10478,478 bottles of beer on the wall,Option 1,0.2979750453,23JUN1962,01JAN1960:10:06:36,0:01:28,88,7 | ||||||
|  | 10479,479 bottles of beer on the wall,Option 1,0.4988665942,15JUL1961,01JAN1960:02:33:06,0:01:33,85,1 | ||||||
|  | 10480,480 bottles of beer on the wall,Option 1,0.5243853585,29JUL1960,01JAN1960:00:26:18,0:00:31,48,77 | ||||||
|  | 10481,481 bottles of beer on the wall,Option 1,0.6049248826,12JUL1962,01JAN1960:10:01:41,0:00:16,66,45 | ||||||
|  | 10482,482 bottles of beer on the wall,Option 1,0.6312438024,01OCT1961,01JAN1960:05:18:12,0:00:04,43,27 | ||||||
|  | 10483,483 bottles of beer on the wall,Option 1,0.3366865974,17MAY1962,01JAN1960:01:44:17,0:00:01,31,25 | ||||||
|  | 10484,484 bottles of beer on the wall,Option 1,0.0813266188,08AUG1962,01JAN1960:06:18:27,0:00:08,71,63 | ||||||
|  | 10485,485 bottles of beer on the wall,Option 1,0.3601163455,23JAN1962,01JAN1960:11:26:01,0:00:37,41,88 | ||||||
|  | 10486,486 bottles of beer on the wall,Option 1,0.9033777345,13AUG1961,01JAN1960:00:16:14,0:00:56,82,33 | ||||||
|  | 10487,487 bottles of beer on the wall,Option 1,0.1716986667,23DEC1960,01JAN1960:12:06:34,0:01:13,2,32 | ||||||
|  | 10488,488 bottles of beer on the wall,Option 1,0.9734818912,11SEP1961,01JAN1960:00:34:41,0:00:28,17,7 | ||||||
|  | 10489,489 bottles of beer on the wall,Option 1,0.0618140223,17DEC1961,01JAN1960:06:26:30,0:00:38,94,40 | ||||||
|  | 10490,490 bottles of beer on the wall,Option 1,0.0204490144,22AUG1960,01JAN1960:01:50:18,0:00:19,40,56 | ||||||
|  | 10491,491 bottles of beer on the wall,Option 1,0.8719323929,23MAY1960,01JAN1960:12:36:06,0:00:08,49,44 | ||||||
|  | 10492,492 bottles of beer on the wall,Option 1,0.2656292181,28JUN1962,01JAN1960:05:32:50,0:01:35,15,39 | ||||||
|  | 10493,493 bottles of beer on the wall,Option 1,0.37794835,23JUL1962,01JAN1960:13:15:43,0:00:56,6,86 | ||||||
|  | 10494,494 bottles of beer on the wall,Option 1,0.6395865733,17JAN1961,01JAN1960:12:20:12,0:01:31,22,87 | ||||||
|  | 10495,495 bottles of beer on the wall,Option 1,0.4408595098,05JUL1960,01JAN1960:12:21:53,0:01:40,25,27 | ||||||
|  | 10496,496 bottles of beer on the wall,Option 1,0.4846261169,02DEC1961,01JAN1960:12:41:08,0:00:06,7,63 | ||||||
|  | 10497,497 bottles of beer on the wall,Option 1,0.7903671478,14MAY1962,01JAN1960:05:09:21,0:01:03,98,25 | ||||||
|  | 10498,498 bottles of beer on the wall,Option 1,0.60474184,13OCT1961,01JAN1960:03:52:59,0:01:22,98,98 | ||||||
|  | 10499,499 bottles of beer on the wall,Option 1,0.1213259218,23APR1962,01JAN1960:06:29:47,0:01:35,38,70 | ||||||
|  | 10500,500 bottles of beer on the wall,Option 1,0.7882370487,17FEB1962,01JAN1960:02:00:25,0:00:12,1,74 | ||||||
| 
 | 
| @@ -1,255 +0,0 @@ | |||||||
| 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 |  | ||||||
|               } |  | ||||||
|             } |  | ||||||
|           }) |  | ||||||
|         }) |  | ||||||
|       }) |  | ||||||
|     }) |  | ||||||
| } |  | ||||||
| @@ -1,246 +0,0 @@ | |||||||
| 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}`) |  | ||||||
| } |  | ||||||
| @@ -1,527 +0,0 @@ | |||||||
| 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;') |  | ||||||
| } |  | ||||||
| @@ -1,376 +0,0 @@ | |||||||
| 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}`) |  | ||||||
| } |  | ||||||
| @@ -1,719 +0,0 @@ | |||||||
| 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}`) |  | ||||||
| } |  | ||||||
| @@ -1,149 +0,0 @@ | |||||||
| 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() |  | ||||||
|                 }) |  | ||||||
|             }) |  | ||||||
|         }) |  | ||||||
|     }) |  | ||||||
| } |  | ||||||
| @@ -1,61 +0,0 @@ | |||||||
| 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}`) |  | ||||||
| } |  | ||||||
| @@ -1,624 +0,0 @@ | |||||||
| 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}`) |  | ||||||
| } |  | ||||||
							
								
								
									
										
											BIN
										
									
								
								client/libraries/clr-angular-17.9.0.tgz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								client/libraries/clr-angular-17.9.0.tgz
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								client/libraries/clr-ui-17.9.0.tgz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								client/libraries/clr-ui-17.9.0.tgz
									
									
									
									
									
										Normal file
									
								
							
										
											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@14.4.0;handsontable@14.4.0;hyperformula@2.7.0;jackspeak@2.2.0;path-scurry@1.7.0' |           '@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' | ||||||
|       }, |       }, | ||||||
|       (error, json) => { |       (error, json) => { | ||||||
|         if (error) { |         if (error) { | ||||||
|   | |||||||
							
								
								
									
										42
									
								
								client/lighthouserc.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								client/lighthouserc.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | 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' } | ||||||
|  |         ] | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										13007
									
								
								client/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										13007
									
								
								client/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -31,7 +31,8 @@ | |||||||
|     "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": { | ||||||
| @@ -44,28 +45,28 @@ | |||||||
|     "@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.10.0", |     "@cds/core": "^6.15.1", | ||||||
|     "@clr/angular": "^17.0.1", |     "@clr/angular": "file:libraries/clr-angular-17.9.0.tgz", | ||||||
|     "@clr/icons": "^13.0.2", |     "@clr/icons": "^13.0.2", | ||||||
|     "@clr/ui": "^17.0.1", |     "@clr/ui": "file:libraries/clr-ui-17.9.0.tgz", | ||||||
|     "@handsontable/angular": "^14.3.0", |     "@handsontable/angular-wrapper": "16.0.1", | ||||||
|     "@sasjs/adapter": "4.10.2", |     "@sasjs/adapter": "^4.12.2", | ||||||
|     "@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", | ||||||
|     "@types/text-encoding": "0.0.35", |     "@types/text-encoding": "0.0.35", | ||||||
|     "base64-arraybuffer": "^0.2.0", |     "base64-arraybuffer": "^0.2.0", | ||||||
|     "buffer": "^5.4.3", |     "buffer": "^5.4.3", | ||||||
|     "crypto-browserify": "3.12.0", |     "crypto-browserify": "^3.12.1", | ||||||
|     "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": "^14.3.0", |     "handsontable": "^16.0.1", | ||||||
|     "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", | ||||||
|     "jquery-datetimepicker": "^2.5.21", |     "jquery-datetimepicker": "^2.5.21", | ||||||
|     "jsrsasign": "^10.2.0", |     "jsrsasign": "^11.1.0", | ||||||
|     "marked": "^5.0.0", |     "marked": "^5.0.0", | ||||||
|     "moment": "^2.26.0", |     "moment": "^2.26.0", | ||||||
|     "ngx-clipboard": "^16.0.0", |     "ngx-clipboard": "^16.0.0", | ||||||
| @@ -95,6 +96,7 @@ | |||||||
|     "@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,5 +148,8 @@ export const globals: { | |||||||
|   }, |   }, | ||||||
|   handsontable: { |   handsontable: { | ||||||
|     darkTableHeaderClass: 'darkTH' |     darkTableHeaderClass: 'darkTH' | ||||||
|  |   }, | ||||||
|  |   userDropdownConfig: { | ||||||
|  |     closeOnDebugClick: false | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -139,10 +139,15 @@ | |||||||
|         [routerLink]="['/']" |         [routerLink]="['/']" | ||||||
|         class="nav-link" |         class="nav-link" | ||||||
|       > |       > | ||||||
|         <img class="without-text d-block d-md-none" src="images/dc-logo.svg" /> |         <img | ||||||
|  |           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> | ||||||
|  |  | ||||||
| @@ -283,7 +288,11 @@ | |||||||
|  |  | ||||||
| <!-- App Loading Page --> | <!-- App Loading Page --> | ||||||
| <div *ngIf="!startupDataLoaded" class="app-loading"> | <div *ngIf="!startupDataLoaded" class="app-loading"> | ||||||
|   <img class="loading-logo" src="images/datacontroller.svg" /> |   <img | ||||||
|  |     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> | ||||||
|   | |||||||
| @@ -1,447 +0,0 @@ | |||||||
| @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,4 +1,9 @@ | |||||||
| import { ChangeDetectorRef, Component, ElementRef } from '@angular/core' | import { | ||||||
|  |   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' | ||||||
| @@ -36,7 +41,8 @@ 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> |           <p class="mt-0"> | ||||||
|             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 | ||||||
|   | |||||||
| @@ -1,50 +0,0 @@ | |||||||
| .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 } from '@angular/core' | import { Component, OnInit, ViewEncapsulation } 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,7 +13,8 @@ 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 | ||||||
| @@ -56,25 +57,6 @@ 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,14 +9,17 @@ | |||||||
|       <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 class="deploy-status-row"> |       <div | ||||||
|  |         *ngIf="autoDeployStatus.deployServicePack !== null" | ||||||
|  |         class="deploy-status-row" | ||||||
|  |       > | ||||||
|         <clr-icon |         <clr-icon | ||||||
|           *ngIf="autoDeployStatus.deployServicePack" |           *ngIf="autoDeployStatus.deployServicePack === true" | ||||||
|           class="deploy-success" |           class="deploy-success" | ||||||
|           shape="success-standard" |           shape="success-standard" | ||||||
|         ></clr-icon> |         ></clr-icon> | ||||||
|         <clr-icon |         <clr-icon | ||||||
|           *ngIf="!autoDeployStatus.deployServicePack" |           *ngIf="!autoDeployStatus.deployServicePack === false" | ||||||
|           class="deploy-error" |           class="deploy-error" | ||||||
|           shape="times-circle" |           shape="times-circle" | ||||||
|         ></clr-icon> |         ></clr-icon> | ||||||
| @@ -52,7 +55,7 @@ | |||||||
|             class="deploy-error" |             class="deploy-error" | ||||||
|             shape="times-circle" |             shape="times-circle" | ||||||
|           ></clr-icon> |           ></clr-icon> | ||||||
|           LAUNCH / CONFIGURE |           LAUNCH | ||||||
|         </button> |         </button> | ||||||
|  |  | ||||||
|         <button |         <button | ||||||
| @@ -94,20 +97,72 @@ | |||||||
| </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"> | <div class="mb-10 clr-control-container dc-loc-input-wrapper"> | ||||||
|   <div class="clr-input-wrapper"> |   <div class="clr-input-wrapper small-mt"> | ||||||
|     <p class="mt-0">{{ dcPath }}</p> |     <input clrInput name="dcloc" [(ngModel)]="dcPath" /> | ||||||
|   </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="mb-10 clr-control-container"> | ||||||
|   <div class="clr-input-wrapper"> |   <div class="clr-input-wrapper small-mt"> | ||||||
|     <p class="mt-0">{{ selectedAdminGroup }}</p> |     <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> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
| <clr-checkbox-wrapper> | <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="clr-input-wrapper"> | ||||||
|  |       <p class="mt-0">{{ runningAsUser }}</p> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </ng-container> | ||||||
|  |  | ||||||
|  | <!-- Keeping this for a reference in case future VIYA changes and starts allowing separate backend and frontend) --> | ||||||
|  |  | ||||||
|  | <!-- <clr-checkbox-wrapper> | ||||||
|   <input |   <input | ||||||
|     clrCheckbox |     clrCheckbox | ||||||
|     [(ngModel)]="recreateDatabase" |     [(ngModel)]="recreateDatabase" | ||||||
| @@ -116,19 +171,28 @@ | |||||||
|     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" | ||||||
| > | > | ||||||
| @@ -140,7 +204,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> | ||||||
|   | |||||||
| @@ -1,61 +0,0 @@ | |||||||
| .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,15 +1,35 @@ | |||||||
| import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' | import { | ||||||
|  |   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 | ||||||
| @@ -21,6 +41,7 @@ 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 | ||||||
| @@ -28,8 +49,19 @@ 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. | ||||||
| @@ -46,14 +78,138 @@ 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 loggerService: LoggerService |     private sasViyaService: SasViyaService, | ||||||
|  |     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 | ||||||
| @@ -63,7 +219,6 @@ 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 { | ||||||
| @@ -98,11 +253,19 @@ 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 { | ||||||
|       this.autodeployDone = true |       if (!this.deployInNewWindow) this.autodeployDone = true | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -119,45 +282,160 @@ 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: false, |       useComputeApi: null, | ||||||
|       contextName: this.sasJsConfig.contextName, |       contextName: selectedComputeContextName, | ||||||
|       debug: true |       debug: true | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     this.sasJs |     if (this.deployInNewWindow) { | ||||||
|       .request(`services/admin/makedata`, data, overrideConfig, () => { |       this.runMakedataInNewWindow({ | ||||||
|         this.sasService.shouldLogin.next(true) |         contextName: selectedComputeContextName, | ||||||
|  |         admin: this.selectedAdminGroup, | ||||||
|  |         dcPath: this.dcPath | ||||||
|       }) |       }) | ||||||
|       .then((res: any) => { |     } else { | ||||||
|         this.autodeployDone = true |       this.sasJs | ||||||
|  |         .request(`services/admin/makedata`, data, overrideConfig, () => { | ||||||
|  |           this.sasService.shouldLogin.next(true) | ||||||
|  |         }) | ||||||
|  |         .then((res: any) => { | ||||||
|  |           this.autodeployDone = true | ||||||
|  |  | ||||||
|         try { |           try { | ||||||
|           this.makeDataResponse = JSON.stringify(res) |             this.makeDataResponse = JSON.stringify(res) | ||||||
|         } catch { |           } catch { | ||||||
|           this.makeDataResponse = res |             this.makeDataResponse = res | ||||||
|         } |           } | ||||||
|  |  | ||||||
|         if (res.result && res.result.length > 0) { |           if (res.result && res.result.length > 0) { | ||||||
|           this.autoDeployStatus.runMakeData = true |             this.autoDeployStatus.runMakeData = true | ||||||
|         } else { |           } else { | ||||||
|  |             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) => { | ||||||
|  |           this.eventService.showAbortModal('makedata', JSON.stringify(err)) | ||||||
|           this.autoDeployStatus.runMakeData = false |           this.autoDeployStatus.runMakeData = false | ||||||
|         } |           this.autodeployDone = true | ||||||
|       }) |  | ||||||
|       .catch((err: any) => { |  | ||||||
|         this.autoDeployStatus.runMakeData = false |  | ||||||
|         this.autodeployDone = true |  | ||||||
|  |  | ||||||
|         try { |           try { | ||||||
|           this.makeDataResponse = JSON.stringify(err) |             this.makeDataResponse = JSON.stringify(err) | ||||||
|         } catch { |           } catch { | ||||||
|           this.makeDataResponse = err |             this.makeDataResponse = err | ||||||
|         } |           } | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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}`) | ||||||
|       }) |       }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +0,0 @@ | |||||||
| .clear-memory-button { |  | ||||||
|   right: 10px; |  | ||||||
|   top: 2px; |  | ||||||
| } |  | ||||||
| @@ -1,4 +1,11 @@ | |||||||
| import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core' | import { | ||||||
|  |   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' | ||||||
| @@ -10,7 +17,8 @@ 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 | ||||||
| @@ -266,7 +274,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: false, |       useComputeApi: null, | ||||||
|       contextName: this.sasJsConfig.contextName, |       contextName: this.sasJsConfig.contextName, | ||||||
|       debug: true |       debug: true | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -10,11 +10,13 @@ | |||||||
| </p> | </p> | ||||||
|  |  | ||||||
| <p class="m-0 mt-10"> | <p class="m-0 mt-10"> | ||||||
|   Please specify a physical directory below, to which user |   Please specify a physical directory (on the | ||||||
|   <strong>{{ SYSUSERID }}</strong> can write, on behalf of Data Controller: |   <strong> {{ SYSHOSTNAME }}</strong> | ||||||
|  |   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 Directory</label> | <label class="mt-20 clr-control-label">DC Staging 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 | ||||||
|   | |||||||
| @@ -1,23 +0,0 @@ | |||||||
| .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,5 +1,12 @@ | |||||||
| import { Location } from '@angular/common' | import { Location } from '@angular/common' | ||||||
| import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' | import { | ||||||
|  |   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' | ||||||
| @@ -12,7 +19,8 @@ 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 | ||||||
|   | |||||||
| @@ -1,238 +0,0 @@ | |||||||
| .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,5 +1,12 @@ | |||||||
| import { KeyValue } from '@angular/common' | import { KeyValue } from '@angular/common' | ||||||
| import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' | import { | ||||||
|  |   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' | ||||||
| @@ -16,7 +23,8 @@ 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 | ||||||
| @@ -163,23 +171,8 @@ export class EditRecordComponent implements OnInit { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   public copyToClip(text: string) { |   public copyToClip(text: string) { | ||||||
|     const modalElement = document.querySelector('#recordModalRef .modal-title') |     navigator.clipboard.writeText(text) | ||||||
|  |     this.generatedRecordUrl = text | ||||||
|     if (modalElement) { |  | ||||||
|       const selBox = document.createElement('textarea') |  | ||||||
|       selBox.style.position = 'fixed' |  | ||||||
|       selBox.style.left = '0' |  | ||||||
|       selBox.style.top = '0' |  | ||||||
|       selBox.style.opacity = '0' |  | ||||||
|       selBox.style.zIndex = '5000' |  | ||||||
|       selBox.value = text |  | ||||||
|       modalElement.appendChild(selBox) |  | ||||||
|       selBox.focus() |  | ||||||
|       selBox.select() |  | ||||||
|       document.execCommand('copy') |  | ||||||
|       modalElement.removeChild(selBox) |  | ||||||
|       this.generatedRecordUrl = text |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   async generateEditRecordUrl() { |   async generateEditRecordUrl() { | ||||||
|   | |||||||
| @@ -1,8 +0,0 @@ | |||||||
| :host { |  | ||||||
|     display: block; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| p { |  | ||||||
|     margin: 0; |  | ||||||
|     text-align: center; |  | ||||||
| } |  | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { Component, OnInit } from '@angular/core' | import { Component, OnInit, ViewEncapsulation } 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,7 +10,8 @@ import { Component, OnInit } 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-i" clrInline> |               <clr-radio-container class="mt-0" clrInline> | ||||||
|                 <clr-radio-wrapper> |                 <clr-radio-wrapper> | ||||||
|                   <input |                   <input | ||||||
|                     type="radio" |                     type="radio" | ||||||
| @@ -193,13 +193,14 @@ | |||||||
|               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-i" |             class="editor-title text-center mt-0" | ||||||
|           > |           > | ||||||
|             <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> | ||||||
| @@ -407,12 +408,11 @@ | |||||||
|           <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" | ||||||
|               [licenseKey]="hotTable.licenseKey" |               [data]="hotTable.data" | ||||||
|  |               [settings]="hotTableSettings" | ||||||
|             > |             > | ||||||
|             </hot-table> |             </hot-table> | ||||||
|           </div> |           </div> | ||||||
|   | |||||||
| @@ -1,246 +0,0 @@ | |||||||
| .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; |  | ||||||
| // } |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -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' | import { HotTableModule } from '@handsontable/angular-wrapper' | ||||||
| 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.forRoot(), |     HotTableModule, | ||||||
|     AppSharedModule, |     AppSharedModule, | ||||||
|     DirectivesModule, |     DirectivesModule, | ||||||
|     SharedModule, |     SharedModule, | ||||||
|   | |||||||
| @@ -1,85 +0,0 @@ | |||||||
| @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 } from '@angular/core' | import { Component, OnInit, ViewEncapsulation } 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,7 +14,8 @@ 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"> | ||||||
|       <clr-icon |       <img | ||||||
|         shape="warning-standard" |         src="images/select-table.png" | ||||||
|         size="60" |         class="select-table-icon" | ||||||
|         class="is-info icon-dc-fill" |         alt="select table icon" | ||||||
|       ></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" | ||||||
|   | |||||||
| @@ -1,32 +0,0 @@ | |||||||
| 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 } from '@angular/core' | import { Component, AfterContentInit, ViewEncapsulation } 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,7 +18,8 @@ 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 | ||||||
|   | |||||||
| @@ -1,51 +0,0 @@ | |||||||
| :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 } from '@angular/core' | import { Component, OnInit, ViewEncapsulation } 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,7 +14,8 @@ 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 | ||||||
| @@ -51,6 +52,7 @@ export class LicensingComponent implements OnInit { | |||||||
|  |  | ||||||
|   constructor( |   constructor( | ||||||
|     private route: ActivatedRoute, |     private route: ActivatedRoute, | ||||||
|  |     private router: Router, | ||||||
|     private licenceService: LicenceService, |     private licenceService: LicenceService, | ||||||
|     private sasService: SasService, |     private sasService: SasService, | ||||||
|     private appService: AppService |     private appService: AppService | ||||||
| @@ -123,7 +125,9 @@ export class LicensingComponent implements OnInit { | |||||||
|           res.adapterResponse.return[0] && |           res.adapterResponse.return[0] && | ||||||
|           res.adapterResponse.return[0].MSG === 'SUCCESS' |           res.adapterResponse.return[0].MSG === 'SUCCESS' | ||||||
|         ) { |         ) { | ||||||
|           location.replace(location.href.split('#')[0]) |           this.router.navigateByUrl('/').then(() => { | ||||||
|  |             window.location.reload() | ||||||
|  |           }) | ||||||
|         } |         } | ||||||
|       }) |       }) | ||||||
|       .finally(() => { |       .finally(() => { | ||||||
|   | |||||||
| @@ -1,85 +0,0 @@ | |||||||
| @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 } from '@angular/core' | import { Component, ViewEncapsulation } 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,7 +18,8 @@ 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 | ||||||
| @@ -745,28 +746,13 @@ export class LineageComponent { | |||||||
|     return URL.createObjectURL(svg_blob) |     return URL.createObjectURL(svg_blob) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private getSVGBlob() { |  | ||||||
|     let svg: any = document.getElementById('graph') |  | ||||||
|     let serializer = new XMLSerializer() |  | ||||||
|     let svg_blob = new Blob([serializer.serializeToString(svg)], { |  | ||||||
|       type: 'image/svg+xml' |  | ||||||
|     }) |  | ||||||
|     return svg_blob |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   downloadSVG() { |   downloadSVG() { | ||||||
|     d3Viz.graphviz('#graph').resetZoom() |     d3Viz.graphviz('#graph').resetZoom() | ||||||
|  |  | ||||||
|     if (navigator.appVersion.toString().indexOf('.NET') > 0) { |     let downloadLink = document.createElement('a') | ||||||
|       window.navigator.msSaveBlob(this.getSVGBlob(), this.constructName('svg')) |     downloadLink.href = this.getSVGURL() | ||||||
|     } else { |     downloadLink.download = this.constructName('svg') | ||||||
|       let downloadLink = document.createElement('a') |     downloadLink.click() | ||||||
|       downloadLink.href = this.getSVGURL() |  | ||||||
|       downloadLink.download = this.constructName('svg') |  | ||||||
|       document.body.appendChild(downloadLink) |  | ||||||
|       downloadLink.click() |  | ||||||
|       document.body.removeChild(downloadLink) |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   async downloadPNG() { |   async downloadPNG() { | ||||||
| @@ -794,16 +780,11 @@ export class LineageComponent { | |||||||
|     var a = document.createElement('a') |     var a = document.createElement('a') | ||||||
|     var blob = new Blob([csvArray], { type: 'text/csv' }) |     var blob = new Blob([csvArray], { type: 'text/csv' }) | ||||||
|  |  | ||||||
|     if (navigator.appVersion.toString().indexOf('.NET') > 0) { |     var url = window.URL.createObjectURL(blob) | ||||||
|       window.navigator.msSaveBlob(blob, this.constructName('csv')) |     a.href = url | ||||||
|     } else { |     a.download = this.constructName('csv') | ||||||
|       var url = window.URL.createObjectURL(blob) |     a.click() | ||||||
|       a.href = url |     window.URL.revokeObjectURL(url) | ||||||
|       a.download = this.constructName('csv') |  | ||||||
|       a.click() |  | ||||||
|       window.URL.revokeObjectURL(url) |  | ||||||
|       a.remove() |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private getDotUrl() { |   private getDotUrl() { | ||||||
| @@ -812,23 +793,11 @@ export class LineageComponent { | |||||||
|     return window.URL.createObjectURL(dot_blob) |     return window.URL.createObjectURL(dot_blob) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private getDotBlob() { |  | ||||||
|     let data = this.vizInput |  | ||||||
|     let dot_blob = new Blob([data], { type: 'text/plain' }) |  | ||||||
|     return dot_blob |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   downloadDot() { |   downloadDot() { | ||||||
|     if (navigator.appVersion.toString().indexOf('.NET') > 0) { |     let downloadLink = document.createElement('a') | ||||||
|       window.navigator.msSaveBlob(this.getDotBlob(), this.constructName('txt')) |     downloadLink.href = this.getDotUrl() | ||||||
|     } else { |     downloadLink.download = this.constructName('txt') | ||||||
|       let downloadLink = document.createElement('a') |     downloadLink.click() | ||||||
|       downloadLink.href = this.getDotUrl() |  | ||||||
|       downloadLink.download = this.constructName('txt') |  | ||||||
|       document.body.appendChild(downloadLink) |  | ||||||
|       downloadLink.click() |  | ||||||
|       document.body.removeChild(downloadLink) |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public showSvg() { |   public showSvg() { | ||||||
|   | |||||||
| @@ -1,82 +0,0 @@ | |||||||
| ::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 } from '@angular/core' | import { Component, OnInit, ViewEncapsulation } 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,7 +50,8 @@ 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 | ||||||
| @@ -98,6 +99,11 @@ 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,4 +8,5 @@ export interface Libinfo { | |||||||
|   LIBID: string |   LIBID: string | ||||||
|   LIBSIZE: number |   LIBSIZE: number | ||||||
|   TABLE_CNT: number |   TABLE_CNT: number | ||||||
|  |   CATALOG_CNT: number | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,30 +1,14 @@ | |||||||
| 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 { | ||||||
|   | |||||||
							
								
								
									
										28
									
								
								client/src/app/models/sas/public-getchangeinfo.model.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								client/src/app/models/sas/public-getchangeinfo.model.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | 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="warning-standard" |         shape="upload-cloud" | ||||||
|         size="40" |         size="40" | ||||||
|         class="is-info icon-dc-fill" |         class="is-info icon-dc-fill" | ||||||
|       ></clr-icon> |       ></clr-icon> | ||||||
| @@ -166,13 +166,10 @@ | |||||||
|             > |             > | ||||||
|  |  | ||||||
|             <hot-table |             <hot-table | ||||||
|               hotId="hotInstanceUserDataset" |               #hotInstanceUserDataset | ||||||
|               id="hotTableUserDataset" |               id="hotTableUserDataset" | ||||||
|               class="mt-15" |               class="mt-15" | ||||||
|               [afterGetColHeader]="afterGetColHeader" |               [settings]="hotUserDatasetsSettings" | ||||||
|               [settings]="hotUserDatasets" |  | ||||||
|               [licenseKey]="hotTableLicenseKey" |  | ||||||
|               stretchH="all" |  | ||||||
|             > |             > | ||||||
|             </hot-table> |             </hot-table> | ||||||
|  |  | ||||||
| @@ -360,17 +357,10 @@ | |||||||
|           </div> |           </div> | ||||||
|  |  | ||||||
|           <hot-table |           <hot-table | ||||||
|             hotId="hotInstance" |             #hotInstanceMain | ||||||
|             id="hotTable" |             id="hotTable" | ||||||
|             class="mt-15" |             class="mt-15" | ||||||
|             [afterGetColHeader]="afterGetColHeader" |             [settings]="hotMainTableSettings" | ||||||
|             [className]="['htDark', 'htCustomHidden']" |  | ||||||
|             [licenseKey]="hotTableLicenseKey" |  | ||||||
|             [multiColumnSorting]="true" |  | ||||||
|             [viewportRowRenderingOffset]="50" |  | ||||||
|             [manualColumnResize]="true" |  | ||||||
|             [filters]="true" |  | ||||||
|             stretchH="all" |  | ||||||
|           > |           > | ||||||
|           </hot-table> |           </hot-table> | ||||||
|         </ng-container> |         </ng-container> | ||||||
|   | |||||||
| @@ -1,54 +0,0 @@ | |||||||
| .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,7 +4,9 @@ import { | |||||||
|   ElementRef, |   ElementRef, | ||||||
|   HostBinding, |   HostBinding, | ||||||
|   OnInit, |   OnInit, | ||||||
|   ViewChild |   AfterViewInit, | ||||||
|  |   ViewChild, | ||||||
|  |   ViewEncapsulation | ||||||
| } from '@angular/core' | } from '@angular/core' | ||||||
| import { | import { | ||||||
|   EventService, |   EventService, | ||||||
| @@ -21,7 +23,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 { HotTableRegisterer } from '@handsontable/angular' | import { HotTableComponent } from '@handsontable/angular-wrapper' | ||||||
| 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' | ||||||
| @@ -45,9 +47,10 @@ 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 { | export class MultiDatasetComponent implements OnInit, AfterViewInit { | ||||||
|   @HostBinding('class.content-container') contentContainerClass = true |   @HostBinding('class.content-container') contentContainerClass = true | ||||||
|   @ViewChild('contentArea', { static: true }) contentAreaRef!: ElementRef |   @ViewChild('contentArea', { static: true }) contentAreaRef!: ElementRef | ||||||
|  |  | ||||||
| @@ -87,7 +90,13 @@ export class MultiDatasetComponent implements OnInit { | |||||||
|  |  | ||||||
|   public hotInstance!: Handsontable |   public hotInstance!: Handsontable | ||||||
|   public hotInstanceUserDataset!: Handsontable |   public hotInstanceUserDataset!: Handsontable | ||||||
|   private hotRegisterer: HotTableRegisterer |   @ViewChild('hotInstanceMain', { static: false }) | ||||||
|  |   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 = '' | ||||||
| @@ -134,7 +143,36 @@ export class MultiDatasetComponent implements OnInit { | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     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 | ||||||
| @@ -147,16 +185,28 @@ export class MultiDatasetComponent implements OnInit { | |||||||
|     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 { | ||||||
| @@ -231,7 +281,10 @@ export class MultiDatasetComponent implements OnInit { | |||||||
|       } |       } | ||||||
|  |  | ||||||
|       this.initUserInputHot() |       this.initUserInputHot() | ||||||
|       this.onAutoDetectColumns() |       // Call onAutoDetectColumns after HOT is initialized | ||||||
|  |       setTimeout(() => { | ||||||
|  |         this.onAutoDetectColumns() | ||||||
|  |       }, 100) | ||||||
|     } else if (matchedExtension === 'csv') { |     } else if (matchedExtension === 'csv') { | ||||||
|       this.onMultiCsvFiles(event.target.files) |       this.onMultiCsvFiles(event.target.files) | ||||||
|     } else { |     } else { | ||||||
| @@ -390,84 +443,112 @@ export class MultiDatasetComponent implements OnInit { | |||||||
|  |  | ||||||
|   initHot() { |   initHot() { | ||||||
|     setTimeout(() => { |     setTimeout(() => { | ||||||
|       this.hotInstance = this.hotRegisterer.getInstance('hotInstance') |       if (this.hotTableMainComponent?.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) { | ||||||
|         this.hotInstance.updateSettings({ |           // Update settings without data - data will be loaded manually | ||||||
|           data: this.activeParsedDataset.datasource || [], |           this.hotInstance.updateSettings({ | ||||||
|           colHeaders: this.activeParsedDataset.datasetInfo.headerColumns, |             colHeaders: this.activeParsedDataset.datasetInfo.headerColumns, | ||||||
|           columns: this.activeParsedDataset.datasetInfo.dcValidator?.getRules(), |             columns: | ||||||
|           readOnly: true, |               this.activeParsedDataset.datasetInfo.dcValidator?.getRules(), | ||||||
|           height: hotHeight || '300px', |             readOnly: true, | ||||||
|           className: 'htDark' |             height: hotHeight || '300px', | ||||||
|         }) |             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(() => { | ||||||
|       this.hotInstanceUserDataset = this.hotRegisterer.getInstance( |       if (this.hotTableUserDatasetComponent?.hotInstance) { | ||||||
|         'hotInstanceUserDataset' |         this.hotInstanceUserDataset = | ||||||
|       ) |           this.hotTableUserDatasetComponent.hotInstance | ||||||
|  |  | ||||||
|       this.hotInstanceUserDataset.addHook( |         // Load initial data manually after instance is ready | ||||||
|         'beforeChange', |         setTimeout(() => { | ||||||
|         (changes: (CellChange | null)[], source: ChangeSource) => { |           if (this.hotUserDatasets.data) { | ||||||
|           if (changes) { |             this.hotInstanceUserDataset.loadData(this.hotUserDatasets.data) | ||||||
|             for (let change of changes) { |             this.hotInstanceUserDataset.render() | ||||||
|               if (change && change[3]) { |           } | ||||||
|                 change[3] = change[3].toUpperCase() |         }, 50) | ||||||
|  |  | ||||||
|  |         this.hotInstanceUserDataset.addHook( | ||||||
|  |           'beforeChange', | ||||||
|  |           (changes: (CellChange | null)[], source: ChangeSource) => { | ||||||
|  |             if (changes) { | ||||||
|  |               for (let change of changes) { | ||||||
|  |                 if (change && change[3]) { | ||||||
|  |                   change[3] = change[3].toUpperCase() | ||||||
|  |                 } | ||||||
|               } |               } | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|         } |         ) | ||||||
|       ) |  | ||||||
|  |  | ||||||
|       this.hotInstanceUserDataset.addHook( |         this.hotInstanceUserDataset.addHook( | ||||||
|         'afterChange', |           'afterChange', | ||||||
|         async (changes: CellChange[] | null, source: ChangeSource) => { |           async (changes: CellChange[] | null, source: ChangeSource) => { | ||||||
|           if (changes) { |             if (changes) { | ||||||
|             if (source === 'edit') { |               if (source === 'edit') { | ||||||
|               await this.onUserInputDatasetsChange() |                 await this.onUserInputDatasetsChange() | ||||||
|  |               } | ||||||
|  |  | ||||||
|  |               for (let change of changes) { | ||||||
|  |                 const row = change[0] as number | ||||||
|  |  | ||||||
|  |                 this.markUnmatchedRows(row) | ||||||
|  |               } | ||||||
|  |  | ||||||
|  |               this.dynamicCellValidations() | ||||||
|  |  | ||||||
|  |               this.hotInstanceUserDataset.render() | ||||||
|             } |             } | ||||||
|  |           } | ||||||
|  |         ) | ||||||
|  |  | ||||||
|             for (let change of changes) { |         this.hotInstanceUserDataset.addHook( | ||||||
|               const row = change[0] as number |           'afterRemoveRow', | ||||||
|  |           async ( | ||||||
|  |             index: number, | ||||||
|  |             amount: number, | ||||||
|  |             physicalRows: number[], | ||||||
|  |             source?: Handsontable.ChangeSource | undefined | ||||||
|  |           ) => { | ||||||
|  |             await this.onUserInputDatasetsChange() | ||||||
|  |  | ||||||
|  |             for (let row of physicalRows) { | ||||||
|               this.markUnmatchedRows(row) |               this.markUnmatchedRows(row) | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             this.dynamicCellValidations() |  | ||||||
|  |  | ||||||
|             this.hotInstanceUserDataset.render() |  | ||||||
|           } |           } | ||||||
|         } |         ) | ||||||
|       ) |       } | ||||||
|  |     }, 100) | ||||||
|       this.hotInstanceUserDataset.addHook( |  | ||||||
|         'afterRemoveRow', |  | ||||||
|         async ( |  | ||||||
|           index: number, |  | ||||||
|           amount: number, |  | ||||||
|           physicalRows: number[], |  | ||||||
|           source?: Handsontable.ChangeSource | undefined |  | ||||||
|         ) => { |  | ||||||
|           await this.onUserInputDatasetsChange() |  | ||||||
|  |  | ||||||
|           for (let row of physicalRows) { |  | ||||||
|             this.markUnmatchedRows(row) |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|       ) |  | ||||||
|     }) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   dynamicCellValidations() { |   dynamicCellValidations() { | ||||||
|  |     if (!this.hotInstanceUserDataset) return | ||||||
|  |  | ||||||
|     const hotData = this.hotInstanceUserDataset.getData() |     const hotData = this.hotInstanceUserDataset.getData() | ||||||
|  |  | ||||||
|     hotData.forEach((row, rowIndex) => { |     hotData.forEach((row, rowIndex) => { | ||||||
| @@ -481,6 +562,8 @@ export class MultiDatasetComponent implements OnInit { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   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) | ||||||
| @@ -554,6 +637,20 @@ export class MultiDatasetComponent implements OnInit { | |||||||
|    * 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() | ||||||
| @@ -614,7 +711,13 @@ export class MultiDatasetComponent implements OnInit { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     this.hotInstanceUserDataset.updateData(hotReadyData) |     if (this.hotInstanceUserDataset) { | ||||||
|  |       // 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' | import { HotTableModule } from '@handsontable/angular-wrapper' | ||||||
| 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 } from '@angular/core' | import { Component, OnInit, ViewEncapsulation } from '@angular/core' | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-not-found', |   selector: 'app-not-found', | ||||||
| @@ -6,7 +6,8 @@ import { Component, OnInit } 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() {} | ||||||
|   | |||||||
| @@ -1,307 +0,0 @@ | |||||||
| ::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,7 +6,8 @@ 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' | ||||||
| @@ -27,7 +28,8 @@ 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 | ||||||
|   | |||||||
| @@ -1,210 +0,0 @@ | |||||||
| @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,6 +1,11 @@ | |||||||
| import { ActivatedRoute } from '@angular/router' | import { ActivatedRoute } from '@angular/router' | ||||||
| import { SasStoreService } from '../../services/sas-store.service' | import { SasStoreService } from '../../services/sas-store.service' | ||||||
| import { Component, AfterViewInit, OnDestroy } from '@angular/core' | import { | ||||||
|  |   Component, | ||||||
|  |   AfterViewInit, | ||||||
|  |   OnDestroy, | ||||||
|  |   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' | ||||||
| @@ -8,6 +13,8 @@ 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 | ||||||
| @@ -22,7 +29,8 @@ 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 | ||||||
| @@ -70,9 +78,11 @@ 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 | ||||||
| @@ -156,6 +166,9 @@ 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) => { | ||||||
| @@ -170,7 +183,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: any) => { |       .then((res: PublicGetChangeinfo) => { | ||||||
|         this.tableDetails = res.jsparams[0] |         this.tableDetails = res.jsparams[0] | ||||||
|         this.jsParams = res.jsparams[0] |         this.jsParams = res.jsparams[0] | ||||||
|  |  | ||||||
| @@ -183,6 +196,11 @@ 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 | ||||||
| @@ -349,13 +367,12 @@ 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') { | ||||||
| @@ -378,6 +395,7 @@ 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 | ||||||
| @@ -386,8 +404,6 @@ export class ApproveDetailsComponent implements AfterViewInit, OnDestroy { | |||||||
|         this.loadingTable = true |         this.loadingTable = true | ||||||
|         this.setFocus() |         this.setFocus() | ||||||
|       }) |       }) | ||||||
|  |  | ||||||
|     this.callChangesInfo(this.tableId) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   ngOnDestroy() { |   ngOnDestroy() { | ||||||
|   | |||||||
| @@ -33,12 +33,34 @@ | |||||||
|     <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'">SUBMITTER</clr-dg-column> |           <clr-dg-column [clrDgField]="'submitter'"> | ||||||
|           <clr-dg-column [clrDgField]="'baseTable'">BASE TABLE</clr-dg-column> |             SUBMITTER | ||||||
|           <clr-dg-column [clrDgField]="'submitted'">SUBMITTED</clr-dg-column> |             <clr-dg-string-filter | ||||||
|           <clr-dg-column [clrDgField]="'submitReason'" |               [clrDgStringFilter]="submitterFilter" | ||||||
|             >SUBMIT REASON</clr-dg-column |               aria-label="Filter submitter" | ||||||
|           > |             ></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> | ||||||
|  |  | ||||||
| @@ -51,15 +73,19 @@ | |||||||
|             <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" |                 class="clr-row d-flex justify-content-around" | ||||||
|                 role="tooltip" |                 role="toolbar" | ||||||
|                 class="d-flex justify-content-around" |                 aria-label="Table actions" | ||||||
|               > |               > | ||||||
|                 <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 shape="check" size="24"></clr-icon> |                   <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 | ||||||
| @@ -70,10 +96,12 @@ | |||||||
|                     *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> | ||||||
| @@ -81,7 +109,11 @@ | |||||||
|                   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 shape="code" size="28"></clr-icon> |                   <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> | ||||||
| @@ -89,6 +121,7 @@ | |||||||
|             <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() | ||||||
|   | |||||||
| @@ -1,43 +0,0 @@ | |||||||
| @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,8 +1,14 @@ | |||||||
| import { Component, OnInit, ChangeDetectorRef } from '@angular/core' | import { | ||||||
|  |   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 | ||||||
| @@ -14,13 +20,40 @@ 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 | ||||||
| @@ -29,6 +62,12 @@ 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" |                 class="cursor-pointer table-link" | ||||||
|                 >{{ 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" |                   class="cursor-pointer table-link" | ||||||
|                   >VIEW</a |                   >VIEW</a | ||||||
|                 > |                 > | ||||||
|                 <span> / </span> |                 <span> / </span> | ||||||
|                 <a |                 <a | ||||||
|                   (click)="getEditTable(approveData[col])" |                   (click)="getEditTable(approveData[col])" | ||||||
|                   class="cursor-pointer" |                   class="cursor-pointer table-link" | ||||||
|                   >EDIT</a |                   >EDIT</a | ||||||
|                 > |                 > | ||||||
|               </div> |               </div> | ||||||
| @@ -47,7 +47,12 @@ | |||||||
|       </table> |       </table> | ||||||
|     </div> |     </div> | ||||||
|     <div class="modal-footer"> |     <div class="modal-footer"> | ||||||
|       <button type="button" class="btn btn-outline" (click)="openModal = false"> |       <button | ||||||
|  |         type="button" | ||||||
|  |         aria-label="Close modal" | ||||||
|  |         class="btn btn-outline" | ||||||
|  |         (click)="openModal = false" | ||||||
|  |       > | ||||||
|         OK |         OK | ||||||
|       </button> |       </button> | ||||||
|     </div> |     </div> | ||||||
| @@ -80,16 +85,48 @@ | |||||||
|       class="datagrid-history datagrid-custom-footer" |       class="datagrid-history datagrid-custom-footer" | ||||||
|       *ngIf="loaded" |       *ngIf="loaded" | ||||||
|     > |     > | ||||||
|       <clr-dg-column [clrDgField]="'basetable'">BASE_TABLE</clr-dg-column> |       <clr-dg-column [clrDgField]="'basetable'"> | ||||||
|       <clr-dg-column [clrDgField]="'status'">STATUS</clr-dg-column> |         BASE_TABLE | ||||||
|       <clr-dg-column [clrDgField]="'submitter'">SUBMITTER</clr-dg-column> |         <clr-dg-string-filter | ||||||
|       <clr-dg-column [clrDgField]="'submittedReason'" |           [clrDgStringFilter]="baseTableFilter" | ||||||
|         >SUBMIT REASON</clr-dg-column |           aria-label="Filter base table" | ||||||
|       > |         ></clr-dg-string-filter> | ||||||
|       <clr-dg-column [clrDgField]="'submitted'">SUBMITTED</clr-dg-column> |       </clr-dg-column> | ||||||
|       <clr-dg-column [clrDgField]="'reviewed'" |       <clr-dg-column [clrDgField]="'status'"> | ||||||
|         >APPROVED / REJECTED</clr-dg-column |         STATUS | ||||||
|       > |         <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 | ||||||
| @@ -119,6 +156,7 @@ | |||||||
|         <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()" | ||||||
|           > |           > | ||||||
|   | |||||||
| @@ -1,37 +0,0 @@ | |||||||
| .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 } from '@angular/core' | import { Component, OnInit, ViewEncapsulation } from '@angular/core' | ||||||
|  |  | ||||||
| import { Router } from '@angular/router' | import { Router } from '@angular/router' | ||||||
| import { SASjsConfig } from '@sasjs/adapter' | import { SASjsConfig } from '@sasjs/adapter' | ||||||
| @@ -8,6 +8,55 @@ 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', | ||||||
| @@ -15,7 +64,8 @@ import { | |||||||
|   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> = [] | ||||||
| @@ -28,6 +78,14 @@ 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' | import { HotTableModule } from '@handsontable/angular-wrapper' | ||||||
| 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.forRoot(), |     HotTableModule, | ||||||
|     DirectivesModule, |     DirectivesModule, | ||||||
|     SharedModule |     SharedModule | ||||||
|   ] |   ] | ||||||
|   | |||||||
| @@ -44,10 +44,20 @@ | |||||||
|         <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'">SUBMITTED</clr-dg-column> |             <clr-dg-column [clrDgField]="'submitted'"> | ||||||
|             <clr-dg-column [clrDgField]="'submitReason'" |               SUBMITTED | ||||||
|               >SUBMIT REASON</clr-dg-column |               <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 class="d-flex justify-content-center" |             <clr-dg-column class="d-flex justify-content-center" | ||||||
|               >ACTION</clr-dg-column |               >ACTION</clr-dg-column | ||||||
|             > |             > | ||||||
| @@ -64,7 +74,11 @@ | |||||||
|               <!-- <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 class="row justify-content-around" role="tooltip"> |                 <div | ||||||
|  |                   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)" | ||||||
| @@ -79,6 +93,7 @@ | |||||||
|               <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> | ||||||
|   | |||||||
| @@ -1,17 +0,0 @@ | |||||||
| @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,7 +1,13 @@ | |||||||
| import { Component, AfterViewInit, OnInit } from '@angular/core' | import { | ||||||
|  |   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 | ||||||
| @@ -11,13 +17,30 @@ 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 | ||||||
| @@ -31,6 +54,10 @@ 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 } from '@angular/core' | import { Component, OnInit, ViewEncapsulation } 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,7 +12,8 @@ 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 } from '@angular/core' | import { Component, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core' | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-home-route', |   selector: 'app-home-route', | ||||||
| @@ -6,7 +6,8 @@ import { Component, OnInit, OnDestroy } 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 } from '@angular/core' | import { Component, OnInit, ViewEncapsulation } from '@angular/core' | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-review-route', |   selector: 'app-review-route', | ||||||
| @@ -6,7 +6,8 @@ import { Component, OnInit } 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 } from '@angular/core' | import { Component, OnInit, ViewEncapsulation } from '@angular/core' | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-usernav-route', |   selector: 'app-usernav-route', | ||||||
| @@ -6,7 +6,8 @@ import { Component, OnInit } 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 } from '@angular/core' | import { Component, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core' | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-view-route', |   selector: 'app-view-route', | ||||||
| @@ -6,7 +6,8 @@ import { Component, OnInit, OnDestroy } 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 } from '@angular/core' | import { Component, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core' | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-xlmap-route', |   selector: 'app-xlmap-route', | ||||||
| @@ -6,7 +6,8 @@ import { Component, OnInit, OnDestroy } 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,6 +1,7 @@ | |||||||
| 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 | ||||||
|  |  | ||||||
| @@ -12,7 +13,7 @@ export class HelperService { | |||||||
|   public loadMoreCount: number = librariesToShow |   public loadMoreCount: number = librariesToShow | ||||||
|   public isMicrosoft: boolean = false |   public isMicrosoft: boolean = false | ||||||
|  |  | ||||||
|   constructor() { |   constructor(private sasService: SasService) { | ||||||
|     this.isMicrosoft = this.isIEorEDGE() |     this.isMicrosoft = this.isIEorEDGE() | ||||||
|     console.log('Is IE or Edge?', this.isMicrosoft) |     console.log('Is IE or Edge?', this.isMicrosoft) | ||||||
|   } |   } | ||||||
| @@ -314,6 +315,20 @@ 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: false |         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 | ||||||
|       }) |       }) | ||||||
|     ).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: false |         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 | ||||||
|       }) |       }) | ||||||
|     ).adapterResponse |     ).adapterResponse | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -1,9 +1,21 @@ | |||||||
| import { HttpClient } from '@angular/common/http' | import { | ||||||
|  |   HttpClient, | ||||||
|  |   HttpContext, | ||||||
|  |   HttpErrorResponse, | ||||||
|  |   HttpHeaders, | ||||||
|  |   HttpParams | ||||||
|  | } from '@angular/common/http' | ||||||
| import { Injectable } from '@angular/core' | import { Injectable } from '@angular/core' | ||||||
| import { Observable } from 'rxjs' | import { catchError, Observable, throwError } 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' | ||||||
| @@ -22,7 +34,8 @@ export class SasViyaService { | |||||||
|     }, |     }, | ||||||
|     Compute: { |     Compute: { | ||||||
|       jobs: '/jobDefinitions', |       jobs: '/jobDefinitions', | ||||||
|       jobExecution: '/jobExecution' |       jobExecution: '/jobExecution', | ||||||
|  |       contexts: '/compute/contexts' | ||||||
|     }, |     }, | ||||||
|     Decision_Management: { |     Decision_Management: { | ||||||
|       modelManagement: '/modelManagement', |       modelManagement: '/modelManagement', | ||||||
| @@ -57,15 +70,23 @@ 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 |     // example collection request | ||||||
|     this.getByCollection('jobs').subscribe((res) => { |     // this.getByCollection('jobs').subscribe((res) => { | ||||||
|       console.log('res', res) |     //   console.log('res', res) | ||||||
|     }) |     // }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -82,7 +103,7 @@ export class SasViyaService { | |||||||
|    * @returns |    * @returns | ||||||
|    */ |    */ | ||||||
|   getByUrl(url: string): Observable<Collection> { |   getByUrl(url: string): Observable<Collection> { | ||||||
|     return this.http.get<Collection>(`${this.serverUrl}${url}`, { |     return this.get<Collection>(`${this.serverUrl}${url}`, { | ||||||
|       withCredentials: true |       withCredentials: true | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
| @@ -93,8 +114,106 @@ export class SasViyaService { | |||||||
|    * @returns |    * @returns | ||||||
|    */ |    */ | ||||||
|   getByCollection(apiCollection: string): Observable<Collection> { |   getByCollection(apiCollection: string): Observable<Collection> { | ||||||
|     return this.http.get<Collection>(`${this.serverUrl}${apiCollection}`, { |     return this.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,6 +16,9 @@ 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' | ||||||
| @@ -38,6 +41,7 @@ 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 | ||||||
|   ) {} |   ) {} | ||||||
| @@ -51,6 +55,7 @@ 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( | ||||||
| @@ -328,6 +333,10 @@ 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 || [] | ||||||
|   } |   } | ||||||
| @@ -356,13 +365,18 @@ export class SasService { | |||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
|       (err: any) => { |       (err: any) => { | ||||||
|         if (err.error.includes('Unauthorized')) { |         const errorMessage = | ||||||
|  |           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 (err.error.includes(`Folder doesn't exist.`)) { |         } else if (errorMessage.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.' | ||||||
|           ) |           ) | ||||||
| @@ -410,7 +424,11 @@ export class SasService { | |||||||
|             } |             } | ||||||
|           }, |           }, | ||||||
|           (err: any) => { |           (err: any) => { | ||||||
|             if (err.error.includes(`Folder doesn't exist.`)) { |             const errorMessage = | ||||||
|  |               typeof err.error === 'string' | ||||||
|  |                 ? err.error | ||||||
|  |                 : JSON.stringify(err.error || err) | ||||||
|  |             if (errorMessage.includes(`Folder doesn't exist.`)) { | ||||||
|               reject() |               reject() | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
| @@ -423,21 +441,93 @@ 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) { | ||||||
|       appLocExists = await this.appLocCheck(path) |       const results = 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) { | ||||||
|       this.loadStartupServiceEmitter.emit() |       // 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() | ||||||
|  |           } 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) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public appLocCheck(path: string): Promise<boolean> { |   private async viyaMakedataSuccessfull(): 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 statusNotFound: boolean = false |       let fetchError: string = '' | ||||||
|  |  | ||||||
|       let res: any |       let res: any | ||||||
|  |  | ||||||
| @@ -448,20 +538,21 @@ export class SasService { | |||||||
|           this.appLocCheckPending = true |           this.appLocCheckPending = true | ||||||
|           this.shouldLogin.next(true) |           this.shouldLogin.next(true) | ||||||
|  |  | ||||||
|           resolve(false) |           resolve({ found: false }) | ||||||
|  |         } else if (err.name === 'NotFoundeError') { | ||||||
|  |           fetchError = err.message | ||||||
|         } else { |         } else { | ||||||
|           statusNotFound = true |           fetchError = | ||||||
|  |             'Viya services are not present on the current appLoc, or API not reachable. Check the ADAPTER configuration.' | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       if (statusNotFound) { |       if (fetchError.length) { | ||||||
|         console.warn('Viya services are not present on the current appLoc.') |         console.warn(fetchError) | ||||||
|         this.eventService.startupDataLoaded() |         return resolve({ found: false, errorMessage: fetchError }) | ||||||
|         this.router.navigateByUrl('/deploy') |  | ||||||
|         return resolve(false) |  | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       resolve(true) |       resolve({ found: true }) | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -579,4 +670,17 @@ 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,7 +7,10 @@ | |||||||
| > | > | ||||||
|   <h3 class="modal-title"> |   <h3 class="modal-title"> | ||||||
|     {{ data.modalTitle }} |     {{ data.modalTitle }} | ||||||
|     <p *ngIf="data.sasService && data.sasService.length > 0" class="sasService"> |     <p | ||||||
|  |       *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> | ||||||
|   | |||||||
| @@ -1,43 +0,0 @@ | |||||||
| .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,4 +1,11 @@ | |||||||
| import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' | import { | ||||||
|  |   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' | ||||||
| @@ -9,7 +16,8 @@ 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() | ||||||
|   | |||||||
| @@ -1,3 +0,0 @@ | |||||||
| clr-alerts { |  | ||||||
|   display: block; |  | ||||||
| } |  | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user