Compare commits
	
		
			137 Commits
		
	
	
		
			v6.8.0
			...
			fix-nldatm
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f246e75eab | |||
|  | 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 | ||
|  | c89a3049fa | ||
| bb12bff9db | |||
|   | 30d5e51d0b | ||
|  | cd3bcc0ee3 | ||
| ef1ba824c3 | |||
| bc45e92138 | |||
|  | c2f36f5419 | ||
| 31a31612fc | |||
|   | f9c2491ab6 | ||
|   | 3de095fe77 | ||
|  | e4e04a193f | ||
|  | a1a90519c5 | ||
|  | 522979835c | ||
| fe049256b5 | |||
|  | 31d1870198 | ||
|  | 6edf0dfb31 | ||
| 3bf3dceaa2 | |||
|  | ce503653cd | ||
|  | caa9854ff0 | ||
|  | 20c3a338c5 | ||
|  | 6547461637 | ||
|  | bbb725c64c | ||
| 403d08c86a | |||
|  | f66d9f511a | ||
|  | dd2138ac5e | ||
| 74e9979c67 | |||
|  | ddc22e5200 | ||
|  | 4218da91cd | ||
|  | 857b94f44f | ||
|  | a3ce367950 | ||
|  | 75dac54591 | ||
| a156c0111b | |||
| e5d93fd7d6 | |||
|  | 67436f4ff9 | ||
|  | b712f851a2 | ||
| e2c0b8da86 | |||
|  | 1b4560061d | ||
|  | d94df7f0eb | ||
|  | fa04d7bf4e | ||
|  | 4d4cabb465 | ||
|  | d4fee791a7 | ||
|  | 82c285e348 | ||
|  | 4d276657b3 | ||
|  | cffeab813d | ||
|  | 18363bbbeb | ||
|  | 6df7d8d2ba | ||
|  | 5deba44d2b | ||
|  | 0a8b1e764c | ||
|  | fc52e8f41a | ||
|  | efcdc694dd | ||
|  | eb7c44333c | ||
|  | 3c62ff6913 | ||
| f4f589af94 | |||
| 3521579dea | |||
| e97a6f52da | |||
|  | 3584aa35c7 | ||
|  | b553520abe | ||
|  | e32d44b1bc | ||
|  | 105da1503f | ||
| 29298072e5 | |||
|  | 154c10fee5 | ||
| 7853f7cb6a | |||
|  | e54ecc8a35 | ||
| d8b95c5739 | |||
|  | 2f8d0b764a | ||
|  | d7732ed206 | ||
|  | 6e631cd9a5 | ||
|  | 0a9e5dd834 | ||
|  | d14a4eaadd | ||
|  | 5f7c7fcc7b | ||
|  | 978f152ab6 | ||
|  | 68a2a606f3 | ||
|  | bad43135d7 | ||
|  | 110ad9a6e9 | ||
|  | e98f288302 | ||
|  | 9a79f37bf1 | ||
|  | 85909cfc1e | ||
|  | 4330da520f | ||
|  | 27907ed00f | ||
|  | 31c90f3190 | ||
|  | 35844e0cf1 | ||
|  | afa7e380aa | ||
| cb9a5f0eb4 | |||
|  | da522c557d | ||
|  | 2c0afd0268 | ||
|  | 20255c69c2 | ||
| 74f1c5416b | |||
| feed7f1ded | |||
| 967698e4ce | |||
|  | 71bd81ae47 | ||
| 89a5153bb3 | |||
| c11bd9a2c5 | |||
| 59d46a9926 | |||
|  | 39c3e5411f | ||
| aad419c55d | |||
|  | ee58fd5b4b | ||
|  | 5564aea9c2 | ||
| aedd2c451b | |||
|  | 6d597611b6 | ||
|  | 9ffe5efe5d | ||
|  | 2715950d86 | ||
|  | 77a7190f4d | ||
| cc65890fea | |||
|  | 93758efb27 | ||
|  | c0dc9191e3 | ||
|  | 485783a782 | ||
| ee07bef2b8 | |||
|  | 0de8481314 | ||
|  | ec0f539a33 | 
| @@ -8,9 +8,9 @@ jobs: | |||||||
|  |  | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v3 |       - uses: actions/checkout@v3 | ||||||
|       - uses: actions/setup-node@v3 |       - uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version: 20 |           node-version: 20.14.0 | ||||||
|  |  | ||||||
|       - name: Install Google Chrome |       - name: Install Google Chrome | ||||||
|         run: | |         run: | | ||||||
| @@ -31,11 +31,9 @@ jobs: | |||||||
|       - name: Install dependencies |       - name: Install dependencies | ||||||
|         run: | |         run: | | ||||||
|           cd client |           cd client | ||||||
|           npm ci |  | ||||||
|           # Decrypt and Install sheet |           # Decrypt and Install sheet | ||||||
|           echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg |           echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg | ||||||
|           npm i ./libraries/sheet-crypto.tgz |           npm ci | ||||||
|           # End |  | ||||||
|  |  | ||||||
|       - name: Licence checker |       - name: Licence checker | ||||||
|         run: | |         run: | | ||||||
| @@ -46,3 +44,98 @@ jobs: | |||||||
|         run: | |         run: | | ||||||
|           cd client |           cd client | ||||||
|           npm run test:headless |           npm run test:headless | ||||||
|  |  | ||||||
|  |       - name: Production Build | ||||||
|  |         run: | | ||||||
|  |           cd client | ||||||
|  |           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.14.0 | ||||||
|  |  | ||||||
|  |       - 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 libgconf-2-4 libnss3 libxss1 libasound2 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 | ||||||
|  |           npm start & npx wait-on http://localhost:4200 && npx cypress run --browser chrome --spec "cypress/e2e/liveness.cy.ts,cypress/e2e/editor.cy.ts,cypress/e2e/excel-multi-load.cy.ts,cypress/e2e/excel.cy.ts,cypress/e2e/csv.cy.ts,cypress/e2e/filtering.cy.ts,cypress/e2e/licensing.cy.ts" | ||||||
|  |  | ||||||
|  |       - name: Zip Cypress videos | ||||||
|  |         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 | ||||||
|   | |||||||
| @@ -11,9 +11,9 @@ jobs: | |||||||
|  |  | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v3 |       - uses: actions/checkout@v3 | ||||||
|       - uses: actions/setup-node@v3 |       - uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version: 20 |           node-version: 20.14.0 | ||||||
|  |  | ||||||
|       - name: Write .npmrc file |       - name: Write .npmrc file | ||||||
|         run: | |         run: | | ||||||
| @@ -36,11 +36,9 @@ jobs: | |||||||
|       - name: Install dependencies |       - name: Install dependencies | ||||||
|         run: | |         run: | | ||||||
|           cd client |           cd client | ||||||
|           npm ci |  | ||||||
|           # Decrypt and Install sheet |           # Decrypt and Install sheet | ||||||
|           echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg |           echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg | ||||||
|           npm i ./libraries/sheet-crypto.tgz |           npm ci | ||||||
|           # End |  | ||||||
|  |  | ||||||
|       - name: Check audit |       - name: Check audit | ||||||
|       # Audit should fail and stop the CI if critical vulnerability found |       # Audit should fail and stop the CI if critical vulnerability found | ||||||
| @@ -68,9 +66,9 @@ jobs: | |||||||
|  |  | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v3 |       - uses: actions/checkout@v3 | ||||||
|       - uses: actions/setup-node@v3 |       - uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version: 20 |           node-version: 20.14.0 | ||||||
|  |  | ||||||
|       - name: Write .npmrc file |       - name: Write .npmrc file | ||||||
|         run: | |         run: | | ||||||
| @@ -94,11 +92,9 @@ jobs: | |||||||
|       - name: Install dependencies |       - name: Install dependencies | ||||||
|         run: | |         run: | | ||||||
|           cd client |           cd client | ||||||
|           npm ci |  | ||||||
|           # Decrypt and Install sheet |           # Decrypt and Install sheet | ||||||
|           echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg |           echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg | ||||||
|           npm i ./libraries/sheet-crypto.tgz |           npm ci | ||||||
|           # End |  | ||||||
|  |  | ||||||
|       # Install pm2 and prepare SASJS server |       # Install pm2 and prepare SASJS server | ||||||
|       - run: npm i -g pm2 |       - run: npm i -g pm2 | ||||||
| @@ -140,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.cy.ts,cypress/e2e/filtering.cy.ts,cypress/e2e/licensing.cy.ts" |           npm start & npx wait-on http://localhost:4200 && npx cypress run --browser chrome --spec "cypress/e2e/liveness.cy.ts,cypress/e2e/editor.cy.ts,cypress/e2e/excel-multi-load.cy.ts,cypress/e2e/excel.cy.ts,cypress/e2e/csv.cy.ts,cypress/e2e/filtering.cy.ts,cypress/e2e/licensing.cy.ts" | ||||||
|  |  | ||||||
|       - name: Zip Cypress videos |       - name: Zip Cypress videos | ||||||
|         if: always() |         if: always() | ||||||
| @@ -160,9 +156,9 @@ jobs: | |||||||
|  |  | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v3 |       - uses: actions/checkout@v3 | ||||||
|       - uses: actions/setup-node@v3 |       - uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version: 20 |           node-version: 20.14.0 | ||||||
|  |  | ||||||
|       - name: Write .npmrc file |       - name: Write .npmrc file | ||||||
|         run: | |         run: | | ||||||
| @@ -184,6 +180,16 @@ jobs: | |||||||
|           apt-get update |           apt-get update | ||||||
|           apt-get install doxygen -y |           apt-get install doxygen -y | ||||||
|  |  | ||||||
|  |       - name: Frontend Preliminary Build | ||||||
|  |         description: We want to prevent creating empty release if frontend fails | ||||||
|  |         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 i webpack | ||||||
|  |           npm run build | ||||||
|  |  | ||||||
|       - name: Create Empty Release (assets are posted later) |       - name: Create Empty Release (assets are posted later) | ||||||
|         run: | |         run: | | ||||||
|           npm i |           npm i | ||||||
| @@ -196,11 +202,6 @@ jobs: | |||||||
|         description: Must be created AFTER the release as the version (git tag) is used in the interface |         description: Must be created AFTER the release as the version (git tag) is used in the interface | ||||||
|         run: | |         run: | | ||||||
|           cd client |           cd client | ||||||
|           npm ci |  | ||||||
|           # Decrypt and Install sheet |  | ||||||
|           echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg |  | ||||||
|           npm i ./libraries/sheet-crypto.tgz |  | ||||||
|           # End |  | ||||||
|           npm run build |           npm run build | ||||||
|  |  | ||||||
|       - name: Build SAS9 EBI Release |       - name: Build SAS9 EBI Release | ||||||
| @@ -244,6 +245,7 @@ jobs: | |||||||
|           rm sasjsbuild/services/clickme.html |           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 | ||||||
|  |  | ||||||
|       - name: Zip Frontend (including viya.json for full viya deploy) |       - name: Zip Frontend (including viya.json for full viya deploy) | ||||||
|         run: | |         run: | | ||||||
| @@ -279,3 +281,4 @@ jobs: | |||||||
|           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.sas | ||||||
|  |           curl -k $URL -F attachment=@sas/viya.json | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -11,7 +11,9 @@ client/cypress/screenshots | |||||||
| client/cypress/results | client/cypress/results | ||||||
| client/cypress/videos | client/cypress/videos | ||||||
| client/documentation | client/documentation | ||||||
| client/sheet-crypto* | client/**/sheet-crypto.tgz | ||||||
|  | client/.nx | ||||||
|  | client/libraries/sheet-crypto.tgz | ||||||
| cypress.env.json | cypress.env.json | ||||||
| sasjsbuild | sasjsbuild | ||||||
| sasjsresults | sasjsresults | ||||||
|   | |||||||
							
								
								
									
										169
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										169
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,3 +1,172 @@ | |||||||
|  | # [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) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Bug Fixes | ||||||
|  |  | ||||||
|  | * adding missing=STRING to three services ([30d5e51](https://git.datacontroller.io/dc/dc/commit/30d5e51d0b9cf27774038476bd90559600952304)) | ||||||
|  |  | ||||||
|  | ## [6.12.2](https://git.datacontroller.io/dc/dc/compare/v6.12.1...v6.12.2) (2025-01-27) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Bug Fixes | ||||||
|  |  | ||||||
|  | * unnecessary zeros when adding new row (data schema default values) ([a1a9051](https://git.datacontroller.io/dc/dc/commit/a1a90519c535ca25e00822b4d3358c991ac9662e)) | ||||||
|  |  | ||||||
|  | ## [6.12.1](https://git.datacontroller.io/dc/dc/compare/v6.12.0...v6.12.1) (2024-12-31) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Bug Fixes | ||||||
|  |  | ||||||
|  | * no upcase of pk fields in MPE_TABLES in delete scenario ([3de095f](https://git.datacontroller.io/dc/dc/commit/3de095fe7797cde60f0e232c188305fe423c27eb)), closes [#134](https://git.datacontroller.io/dc/dc/issues/134) | ||||||
|  | * reduce length of tmp table names.  Closes [#130](https://git.datacontroller.io/dc/dc/issues/130) ([f9c2491](https://git.datacontroller.io/dc/dc/commit/f9c2491ab6e7b528b7ffc011fd9e45c963c5f6bf)) | ||||||
|  |  | ||||||
|  | # [6.12.0](https://git.datacontroller.io/dc/dc/compare/v6.11.1...v6.12.0) (2024-09-02) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Bug Fixes | ||||||
|  |  | ||||||
|  | * added appLoc to the system page ([dd2138a](https://git.datacontroller.io/dc/dc/commit/dd2138ac5e6067de310e83d16fccc9b9764ba3ff)) | ||||||
|  | * bumping core for passthrough fix, [#124](https://git.datacontroller.io/dc/dc/issues/124) ([caa9854](https://git.datacontroller.io/dc/dc/commit/caa9854ff0431ccbb6ff1d6d3509dc877362cceb)) | ||||||
|  | * excel with password flow, introducing web worker for XLSX.read ([a3ce367](https://git.datacontroller.io/dc/dc/commit/a3ce36795007a4e3b6ac3499ffd119dc3758f387)) | ||||||
|  | * implemented the new request wrapper usage, added XLSX read with a Web Worker, multi load preview data, full height ([4218da9](https://git.datacontroller.io/dc/dc/commit/4218da91cd193aa45346ad7e34ccc00ca89df4fb)) | ||||||
|  | * **multi load:** xlsx read file ahead of time, while user choose datasets ([6547461](https://git.datacontroller.io/dc/dc/commit/65474616379e1dacc1329b3bdc5eb14f34428bb1)) | ||||||
|  | * refactored adapter request wrapper function to return job log as well ([67436f4](https://git.datacontroller.io/dc/dc/commit/67436f4ff9bb4d77d5f897f47a3e3d472981f275)) | ||||||
|  | * using temporary names for temporary tables ([ce50365](https://git.datacontroller.io/dc/dc/commit/ce503653cd9fc36f72fb172bd14816e07c792e14)), closes [#124](https://git.datacontroller.io/dc/dc/issues/124) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Features | ||||||
|  |  | ||||||
|  | * searching data in excel files using new algorithm (massive performance improvement) ([bbb725c](https://git.datacontroller.io/dc/dc/commit/bbb725c64cc23ed701b189623992408c42fdde8f)) | ||||||
|  |  | ||||||
|  | ## [6.11.1](https://git.datacontroller.io/dc/dc/compare/v6.11.0...v6.11.1) (2024-07-02) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Bug Fixes | ||||||
|  |  | ||||||
|  | * adding SYSSITE, part of [#116](https://git.datacontroller.io/dc/dc/issues/116) ([a156c01](https://git.datacontroller.io/dc/dc/commit/a156c0111b3de5e3744e38d377d6e9aa09915803)) | ||||||
|  | * ensuring review_reason_txt in output.  Closes [#117](https://git.datacontroller.io/dc/dc/issues/117) ([e5d93fd](https://git.datacontroller.io/dc/dc/commit/e5d93fd7d6d86bc47ff56664bd812b4d9d0749a5)) | ||||||
|  |  | ||||||
|  | # [6.11.0](https://git.datacontroller.io/dc/dc/compare/v6.10.1...v6.11.0) (2024-06-27) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Bug Fixes | ||||||
|  |  | ||||||
|  | * addressing PR comments ([d94df7f](https://git.datacontroller.io/dc/dc/commit/d94df7f0ebae8feab5e1d5cf8011af8c8be2ca18)) | ||||||
|  | * **multi load:** fixed parsing algorithm reused for the multi load, the fix affects the normal upload as well. ([d4fee79](https://git.datacontroller.io/dc/dc/commit/d4fee791a72021e449cf9680c3e3a525dce41ac1)) | ||||||
|  | * **multi load:** label rename ([fa04d7b](https://git.datacontroller.io/dc/dc/commit/fa04d7bf4e5ba337146bdaa926c60488f8851449)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Features | ||||||
|  |  | ||||||
|  | * **multi load:** added HOT for user datasets input ([18363bb](https://git.datacontroller.io/dc/dc/commit/18363bbbeb9cf96183ba4841da8134b2f66f735c)) | ||||||
|  | * **multi load:** implemented matching libds and parsing of the multiple sheets ([efcdc69](https://git.datacontroller.io/dc/dc/commit/efcdc694dd275cdb9a4e19f26e5522b8dadc5fd9)) | ||||||
|  | * **multi load:** licence submit limits ([cffeab8](https://git.datacontroller.io/dc/dc/commit/cffeab813d8d4b324f82710dfd73953d4cbf8ffe)) | ||||||
|  | * **multi load:** multiple csv files ([4d27665](https://git.datacontroller.io/dc/dc/commit/4d276657b35a147a2233a03afcb1716348555f52)) | ||||||
|  | * **multi load:** refactored range find function, unlocking excel with password is reusable ([eb7c443](https://git.datacontroller.io/dc/dc/commit/eb7c44333c865e7f7bbfb54dd7f73bfc110f86a7)) | ||||||
|  | * **multi load:** submitting multiple found tables at once ([5deba44](https://git.datacontroller.io/dc/dc/commit/5deba44d2b7352866d821b70dbbfbbf54955dc47)) | ||||||
|  |  | ||||||
|  | ## [6.10.1](https://git.datacontroller.io/dc/dc/compare/v6.10.0...v6.10.1) (2024-06-07) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Bug Fixes | ||||||
|  |  | ||||||
|  | * adding 60 more colours to crayons table. Closes [#112](https://git.datacontroller.io/dc/dc/issues/112) ([3521579](https://git.datacontroller.io/dc/dc/commit/3521579dead089eebf62455686be3aee88bde687)) | ||||||
|  | * terms and conditions colours, editor on smaller screens show only icons ([e32d44b](https://git.datacontroller.io/dc/dc/commit/e32d44b1bcdfeea43d19b21ec0ddf4af1ce3992a)) | ||||||
|  |  | ||||||
|  | # [6.10.0](https://git.datacontroller.io/dc/dc/compare/v6.9.0...v6.10.0) (2024-06-07) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Features | ||||||
|  |  | ||||||
|  | * updated handsontable to v14 ([2f8d0b7](https://git.datacontroller.io/dc/dc/commit/2f8d0b764a957ad8c11cd1088fad5e0670aa1731)) | ||||||
|  |  | ||||||
|  | # [6.9.0](https://git.datacontroller.io/dc/dc/compare/v6.8.5...v6.9.0) (2024-05-31) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Bug Fixes | ||||||
|  |  | ||||||
|  | * added colors.scss file, start of a refactor ([110ad9a](https://git.datacontroller.io/dc/dc/commit/110ad9a6e9ed39bd5591ae65c2d0005ba47ca758)) | ||||||
|  | * added stealFocus directive ([9a79f37](https://git.datacontroller.io/dc/dc/commit/9a79f37bf143a1e05df7407358e2687c678e3e68)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Features | ||||||
|  |  | ||||||
|  | * added app settings service to handle theme persistance, fix: optimised dark mode contrast ([35844e0](https://git.datacontroller.io/dc/dc/commit/35844e0cf1a639553269f2ab0f8666a56ab5cc47)) | ||||||
|  | * **dark mode:** clarity optimizations ([afa7e38](https://git.datacontroller.io/dc/dc/commit/afa7e380aa3bdabd380c038522b9d73d9a8a3b91)) | ||||||
|  | * **dark mode:** lineage and metadata ([27907ed](https://git.datacontroller.io/dc/dc/commit/27907ed00fe81f4c752ffe99d2fb029d5c884f0a)) | ||||||
|  | * **dark mode:** refactoring clarity to enable dark mode, added toggle button ([5564aea](https://git.datacontroller.io/dc/dc/commit/5564aea9c25f8e81ff85afa8352325b9992e4043)) | ||||||
|  | * **dark mode:** removing custom css rules so clarity can handle dark/light modes. Handsontable css for dark mode ([2c0afd0](https://git.datacontroller.io/dc/dc/commit/2c0afd02684cdf3bda374731b0359665e00ed95d)) | ||||||
|  |  | ||||||
|  | ## [6.8.5](https://git.datacontroller.io/dc/dc/compare/v6.8.4...v6.8.5) (2024-05-23) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Bug Fixes | ||||||
|  |  | ||||||
|  | * bitemporal load issue [#105](https://git.datacontroller.io/dc/dc/issues/105) ([967698e](https://git.datacontroller.io/dc/dc/commit/967698e4ce1e0abcbc6f0aff8a4be6c512dee93c)) | ||||||
|  |  | ||||||
|  | ## [6.8.4](https://git.datacontroller.io/dc/dc/compare/v6.8.3...v6.8.4) (2024-05-22) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Bug Fixes | ||||||
|  |  | ||||||
|  | * new approach to fixing [#105](https://git.datacontroller.io/dc/dc/issues/105) ([c11bd9a](https://git.datacontroller.io/dc/dc/commit/c11bd9a2c55e49f10451962cb2e222c21206bce5)) | ||||||
|  |  | ||||||
|  | ## [6.8.3](https://git.datacontroller.io/dc/dc/compare/v6.8.2...v6.8.3) (2024-05-09) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Bug Fixes | ||||||
|  |  | ||||||
|  | * updating core to increase filename length, closes [#103](https://git.datacontroller.io/dc/dc/issues/103) ([ee58fd5](https://git.datacontroller.io/dc/dc/commit/ee58fd5b4bc0dd3e3f232c4f26bb85b2e7fe2b54)) | ||||||
|  |  | ||||||
|  | ## [6.8.2](https://git.datacontroller.io/dc/dc/compare/v6.8.1...v6.8.2) (2024-05-03) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Bug Fixes | ||||||
|  |  | ||||||
|  | * dc_request_logs option feature ([93758ef](https://git.datacontroller.io/dc/dc/commit/93758efb275966c181f1ee8b6c752010909a0282)) | ||||||
|  | * release process ([c0dc919](https://git.datacontroller.io/dc/dc/commit/c0dc9191e3b95ea6f7e5021fc0bdbcab0af4cc64)) | ||||||
|  |  | ||||||
|  | ## [6.8.1](https://git.datacontroller.io/dc/dc/compare/v6.8.0...v6.8.1) (2024-05-02) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Bug Fixes | ||||||
|  |  | ||||||
|  | * hide approve button when table revertable ([ec0f539](https://git.datacontroller.io/dc/dc/commit/ec0f539a337b176c83a661ff520a6892d47efa02)) | ||||||
|  |  | ||||||
| # [6.8.0](https://git.datacontroller.io/dc/dc/compare/v6.7.0...v6.8.0) (2024-05-02) | # [6.8.0](https://git.datacontroller.io/dc/dc/compare/v6.7.0...v6.8.0) (2024-05-02) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -69,7 +69,8 @@ | |||||||
|             ], |             ], | ||||||
|             "scripts": [ |             "scripts": [ | ||||||
|               "node_modules/marked/marked.min.js" |               "node_modules/marked/marked.min.js" | ||||||
|             ] |             ], | ||||||
|  |             "webWorkerTsConfig": "tsconfig.worker.json" | ||||||
|           }, |           }, | ||||||
|           "configurations": { |           "configurations": { | ||||||
|             "production": { |             "production": { | ||||||
| @@ -148,7 +149,8 @@ | |||||||
|               "src/styles.scss" |               "src/styles.scss" | ||||||
|             ], |             ], | ||||||
|             "scripts": [], |             "scripts": [], | ||||||
|             "karmaConfig": "karma.conf.js" |             "karmaConfig": "karma.conf.js", | ||||||
|  |             "webWorkerTsConfig": "tsconfig.worker.json" | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|         "lint": { |         "lint": { | ||||||
|   | |||||||
							
								
								
									
										115
									
								
								client/cypress/e2e/csv.cy.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								client/cypress/e2e/csv.cy.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,115 @@ | |||||||
|  | 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;') | ||||||
|  | } | ||||||
| @@ -15,9 +15,6 @@ context('editor tests: ', function () { | |||||||
|  |  | ||||||
|   this.beforeEach(() => { |   this.beforeEach(() => { | ||||||
|     cy.visit(hostUrl + appLocation) |     cy.visit(hostUrl + appLocation) | ||||||
|     // cy.get('input.username').type(username) |  | ||||||
|     // cy.get('input.password').type(password) |  | ||||||
|     // cy.get('.login-group button').click() |  | ||||||
|  |  | ||||||
|     visitPage('home') |     visitPage('home') | ||||||
|   }) |   }) | ||||||
| @@ -118,10 +115,6 @@ context('editor tests: ', function () { | |||||||
|       }) |       }) | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   this.afterEach(() => { |  | ||||||
|     // cy.visit(`${hostUrl}/SASLogon/logout`) |  | ||||||
|   }) |  | ||||||
| }) | }) | ||||||
|  |  | ||||||
| const clickOnEdit = (callback?: any) => { | const clickOnEdit = (callback?: any) => { | ||||||
|   | |||||||
							
								
								
									
										228
									
								
								client/cypress/e2e/excel-multi-load.cy.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								client/cypress/e2e/excel-multi-load.cy.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,228 @@ | |||||||
|  | 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_multi_load/' | ||||||
|  |  | ||||||
|  | const library = 'DC996664' | ||||||
|  | const mpeXTestTable = 'MPE_X_TEST' | ||||||
|  | const mpeTablesTable = 'MPE_TABLES' | ||||||
|  |  | ||||||
|  | context('excel multi load tests: ', function () { | ||||||
|  |   this.beforeAll(() => { | ||||||
|  |     cy.visit(`${hostUrl}/SASLogon/logout`) | ||||||
|  |     cy.loginAndUpdateValidKey(true) | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   this.beforeEach(() => { | ||||||
|  |     cy.visit(hostUrl + appLocation) | ||||||
|  |  | ||||||
|  |     visitPage('home/multi-load') | ||||||
|  |  | ||||||
|  |     colorLog( | ||||||
|  |       `TEST START ---> ${ | ||||||
|  |         Cypress.mocha.getRunner().suite.ctx.currentTest.title | ||||||
|  |       }`, | ||||||
|  |       '#3498DB' | ||||||
|  |     ) | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   it('1 | Uploads Excel file with multiple sheets, 3 sheets including data, 2 sheets matched with dataset', (done) => { | ||||||
|  |     attachExcelFile('multi_load_test_2.xlsx', () => { | ||||||
|  |       checkHotUserDatasetTable('hotTableUserDataset', [ | ||||||
|  |         [library, mpeXTestTable], | ||||||
|  |         [library, mpeTablesTable] | ||||||
|  |       ], () => { | ||||||
|  |         cy.get('#continue-btn').trigger('click').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) => { | ||||||
|  |     attachExcelFile('multi_load_test_1.xlsx', () => { | ||||||
|  |       checkHotUserDatasetTable('hotTableUserDataset', [ | ||||||
|  |         [library, mpeXTestTable], | ||||||
|  |         [library, mpeTablesTable] | ||||||
|  |       ], () => { | ||||||
|  |         cy.get('#continue-btn').trigger('click').then(() => { | ||||||
|  |           checkIfTreeHasTables([`${library}.${mpeXTestTable}`, `${library}.${mpeTablesTable}`], `${library}.${mpeXTestTable}`, (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) => { | ||||||
|  |                     if (valid) done() | ||||||
|  |                   }) | ||||||
|  |  | ||||||
|  |                 }) | ||||||
|  |               }) | ||||||
|  |             } | ||||||
|  |           }) | ||||||
|  |         }) | ||||||
|  |       }) | ||||||
|  |     }) | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   it('3 | Uploads Excel file with multiple sheets, 1 sheets has 2 tables', (done) => { | ||||||
|  |     attachExcelFile('multi_load_test_1.xlsx', () => { | ||||||
|  |       checkHotUserDatasetTable('hotTableUserDataset', [ | ||||||
|  |         [library, mpeXTestTable], | ||||||
|  |         [library, mpeTablesTable] | ||||||
|  |       ], () => { | ||||||
|  |         cy.get('#continue-btn').trigger('click').then(() => { | ||||||
|  |           checkIfTreeHasTables([`${library}.${mpeXTestTable}`, `${library}.${mpeTablesTable}`], `${library}.${mpeXTestTable}`, (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'] | ||||||
|  |                 ], () => { | ||||||
|  |                   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) => { | ||||||
|  |                             if (valid) done() | ||||||
|  |                           }) | ||||||
|  |  | ||||||
|  |                         }) | ||||||
|  |                       }) | ||||||
|  |                     }) | ||||||
|  |                   }) | ||||||
|  |                 }) | ||||||
|  |               }) | ||||||
|  |             } | ||||||
|  |           }) | ||||||
|  |         }) | ||||||
|  |       }) | ||||||
|  |     }) | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   this.afterEach(() => { | ||||||
|  |     colorLog(`TEST END -------------`, '#3498DB') | ||||||
|  |   }) | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | const attachExcelFile = (excelFilename: string, callback?: any) => { | ||||||
|  |   cy.get('#browse-file') | ||||||
|  |     .should('exist') | ||||||
|  |     .click() | ||||||
|  |     .then(() => { | ||||||
|  |       cy.get('input[type="file"]#file-upload') | ||||||
|  |         .attachFile(`/${fixturePath}/${excelFilename}`) | ||||||
|  |         .then(() => { | ||||||
|  |           if (callback) callback() | ||||||
|  |         }) | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const checkHotUserDatasetTable = (hotId: string, dataToContain: any[][], callback?: () => void) => { | ||||||
|  |   cy.get(`#${hotId}`, { timeout: longerCommandTimeout }) | ||||||
|  |   .find('div.ht_master.handsontable') | ||||||
|  |   .find('div.wtHolder') | ||||||
|  |   .find('div.wtHider') | ||||||
|  |   .find('div.wtSpreader') | ||||||
|  |   .find('table.htCore') | ||||||
|  |   .find('tbody') | ||||||
|  |   .then((data) => { | ||||||
|  |     cy.wait(2000).then(() => { | ||||||
|  |       for (let rowI = 0; rowI < dataToContain.length; rowI++) { | ||||||
|  |         for (let colI = 0; colI < dataToContain[rowI].length; colI++) { | ||||||
|  |           expect(data[0].children[rowI].children[colI]).to.contain(dataToContain[rowI][colI]) | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (callback) callback() | ||||||
|  |     }) | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const clickOnTreeNode = (clickOnNode: string, callback?: () => void) => { | ||||||
|  |   cy.get('.nav-tree clr-tree > clr-tree-node').then((treeNodes: any) => { | ||||||
|  |     for (let node of treeNodes) { | ||||||
|  |       if (node.innerText.toUpperCase().trim().includes(clickOnNode)) { | ||||||
|  |         cy.get(node).trigger('click') | ||||||
|  |         if (callback) callback() | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const checkIfTreeHasTables = (tables: string[], clickOnNode?: string, callback?: (includes: boolean) => void) => { | ||||||
|  |   cy.get('.nav-tree clr-tree > clr-tree-node').then((treeNodes: any) => { | ||||||
|  |     let datasets = tables | ||||||
|  |     let nodesCorrect = true | ||||||
|  |     let nodeToClick | ||||||
|  |  | ||||||
|  |     for (let node of treeNodes) { | ||||||
|  |       if (!datasets.includes(node.innerText.toUpperCase().trim())) { | ||||||
|  |         nodesCorrect = false | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (clickOnNode) { | ||||||
|  |         if (node.innerText.toUpperCase().trim().includes(clickOnNode)) { | ||||||
|  |           nodeToClick = node | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (nodeToClick) { | ||||||
|  |       cy.wait(1000) | ||||||
|  |       cy.get(nodeToClick).trigger('click') | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (callback) callback(nodesCorrect) | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const submitTables = () => { | ||||||
|  |   cy.get('#submit-all').trigger('click') | ||||||
|  |   cy.get('#submit-tables').trigger('click') | ||||||
|  |   cy.wait(1000) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const hasSuccessSubmits = (expectedNoOfSubmits: number, 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) => { | ||||||
|  |   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) => { | ||||||
|  |   cy.visit(`${hostUrl}${appLocation}/#/${url}`) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const colorLog = (msg: string, color: string) => { | ||||||
|  |   console.log('%c' + msg, 'color:' + color + ';font-weight:bold;') | ||||||
|  | } | ||||||
| @@ -17,9 +17,6 @@ context('excel tests: ', function () { | |||||||
|  |  | ||||||
|   this.beforeEach(() => { |   this.beforeEach(() => { | ||||||
|     cy.visit(hostUrl + appLocation) |     cy.visit(hostUrl + appLocation) | ||||||
|     // cy.get('input.username').type(username) |  | ||||||
|     // cy.get('input.password').type(password) |  | ||||||
|     // cy.get('.login-group button').click() |  | ||||||
|  |  | ||||||
|     visitPage('home') |     visitPage('home') | ||||||
|  |  | ||||||
| @@ -112,13 +109,8 @@ context('excel tests: ', function () { | |||||||
|     openTableFromTree(libraryToOpenIncludes, 'mpe_x_test') |     openTableFromTree(libraryToOpenIncludes, 'mpe_x_test') | ||||||
|  |  | ||||||
|     attachExcelFile('duplicate_column_excel.xlsx', () => { |     attachExcelFile('duplicate_column_excel.xlsx', () => { | ||||||
|       cy.get('.abortMsg', { timeout: longerCommandTimeout }) |       submitExcel() | ||||||
|         .should('exist') |       rejectExcel(done) | ||||||
|         .then((elements: any) => { |  | ||||||
|           if (elements[0]) { |  | ||||||
|             if (elements[0].innerText.toLowerCase().includes('missing')) done() |  | ||||||
|           } |  | ||||||
|         }) |  | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
| @@ -337,7 +329,6 @@ context('excel tests: ', function () { | |||||||
|  |  | ||||||
|   this.afterEach(() => { |   this.afterEach(() => { | ||||||
|     colorLog(`TEST END -------------`, '#3498DB') |     colorLog(`TEST END -------------`, '#3498DB') | ||||||
|     // cy.visit(`${hostUrl}/SASLogon/logout`) |  | ||||||
|   }) |   }) | ||||||
| }) | }) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -15,9 +15,7 @@ context('filtering tests: ', function () { | |||||||
|  |  | ||||||
|   this.beforeEach(() => { |   this.beforeEach(() => { | ||||||
|     cy.visit(hostUrl + appLocation, { timeout: longerCommandTimeout }) |     cy.visit(hostUrl + appLocation, { timeout: longerCommandTimeout }) | ||||||
|     // cy.get('input.username').type(username) |  | ||||||
|     // cy.get('input.password').type(password) |  | ||||||
|     // cy.get('.login-group button').click() |  | ||||||
|  |  | ||||||
|     visitPage('home') |     visitPage('home') | ||||||
|   }) |   }) | ||||||
| @@ -174,10 +172,6 @@ context('filtering tests: ', function () { | |||||||
|   //     }) |   //     }) | ||||||
|   //   }) |   //   }) | ||||||
|   // }) |   // }) | ||||||
|  |  | ||||||
|   this.afterEach(() => { |  | ||||||
|     // cy.visit(`${hostUrl}/SASLogon/logout`) |  | ||||||
|   }) |  | ||||||
| }) | }) | ||||||
|  |  | ||||||
| const checkInfoBarIncludes = (text: string, callback: any) => { | const checkInfoBarIncludes = (text: string, callback: any) => { | ||||||
|   | |||||||
| @@ -23,15 +23,12 @@ interface EditConfigTableCells { | |||||||
|  |  | ||||||
| context('licensing tests: ', function () { | context('licensing tests: ', function () { | ||||||
|   this.beforeAll(() => { |   this.beforeAll(() => { | ||||||
|     // cy.visit(`${hostUrl}/SASLogon/logout`) |  | ||||||
|     cy.loginAndUpdateValidKey() |     cy.loginAndUpdateValidKey() | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   this.beforeEach(() => { |   this.beforeEach(() => { | ||||||
|     cy.visit(hostUrl + appLocation) |     cy.visit(hostUrl + appLocation) | ||||||
|     // cy.get('input.username').type(username) |  | ||||||
|     // cy.get('input.password').type(password) |  | ||||||
|     // cy.get('.login-group button').click() |  | ||||||
|  |  | ||||||
|     visitPage('home') |     visitPage('home') | ||||||
|   }) |   }) | ||||||
| @@ -375,9 +372,7 @@ context('licensing tests: ', function () { | |||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   this.afterEach(() => { |  | ||||||
|     // cy.visit(`${hostUrl}/SASLogon/logout`) |  | ||||||
|   }) |  | ||||||
| }) | }) | ||||||
|  |  | ||||||
| const logout = (callback?: any) => { | const logout = (callback?: any) => { | ||||||
|   | |||||||
| @@ -18,9 +18,6 @@ context('liveness tests: ', function () { | |||||||
|   this.beforeEach(() => { |   this.beforeEach(() => { | ||||||
|     cy.visit(hostUrl + appLocation) |     cy.visit(hostUrl + appLocation) | ||||||
|  |  | ||||||
|     // cy.get('input.username').type(username) |  | ||||||
|     // cy.get('input.password').type(password) |  | ||||||
|     // cy.get('.login-group button').click() |  | ||||||
|  |  | ||||||
|     visitPage('home') |     visitPage('home') | ||||||
|   }) |   }) | ||||||
|   | |||||||
| @@ -16,7 +16,6 @@ context('editor tests: ', function () { | |||||||
|  |  | ||||||
|   this.beforeEach(() => { |   this.beforeEach(() => { | ||||||
|     cy.visit(hostUrl + appLocation) |     cy.visit(hostUrl + appLocation) | ||||||
|  |  | ||||||
|     cy.wait(2000) |     cy.wait(2000) | ||||||
|  |  | ||||||
|     cy.get('body').then(($body) => { |     cy.get('body').then(($body) => { | ||||||
| @@ -393,10 +392,6 @@ context('editor tests: ', function () { | |||||||
|   //       } |   //       } | ||||||
|   //     ) |   //     ) | ||||||
|   // }) |   // }) | ||||||
|  |  | ||||||
|   this.afterEach(() => { |  | ||||||
|     // cy.visit(`${hostUrl}/SASLogon/logout`) |  | ||||||
|   }) |  | ||||||
| }) | }) | ||||||
|  |  | ||||||
| const removeAllColumns = () => { | const removeAllColumns = () => { | ||||||
|   | |||||||
							
								
								
									
										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 | ||||||
| 
 | 
							
								
								
									
										
											BIN
										
									
								
								client/cypress/fixtures/excels_multi_load/multi_load_test_1.xlsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								client/cypress/fixtures/excels_multi_load/multi_load_test_1.xlsx
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								client/cypress/fixtures/excels_multi_load/multi_load_test_2.xlsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								client/cypress/fixtures/excels_multi_load/multi_load_test_2.xlsx
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								client/cypress/fixtures/excels_multi_load/multi_load_test_3.xlsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								client/cypress/fixtures/excels_multi_load/multi_load_test_3.xlsx
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -15,9 +15,6 @@ context('editor tests: ', function () { | |||||||
|  |  | ||||||
|   this.beforeEach(() => { |   this.beforeEach(() => { | ||||||
|     cy.visit(hostUrl + appLocation) |     cy.visit(hostUrl + appLocation) | ||||||
|     // cy.get('input.username').type(username) |  | ||||||
|     // cy.get('input.password').type(password) |  | ||||||
|     // cy.get('.login-group button').click() |  | ||||||
|  |  | ||||||
|     visitPage('home') |     visitPage('home') | ||||||
|   }) |   }) | ||||||
| @@ -118,10 +115,6 @@ context('editor tests: ', function () { | |||||||
|       }) |       }) | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   this.afterEach(() => { |  | ||||||
|     // cy.visit(`${hostUrl}/SASLogon/logout`) |  | ||||||
|   }) |  | ||||||
| }) | }) | ||||||
|  |  | ||||||
| const clickOnEdit = (callback?: any) => { | const clickOnEdit = (callback?: any) => { | ||||||
|   | |||||||
| @@ -19,9 +19,6 @@ context('excel tests: ', function () { | |||||||
|  |  | ||||||
|   this.beforeEach(() => { |   this.beforeEach(() => { | ||||||
|     cy.visit(hostUrl + appLocation) |     cy.visit(hostUrl + appLocation) | ||||||
|     // cy.get('input.username').type(username) |  | ||||||
|     // cy.get('input.password').type(password) |  | ||||||
|     // cy.get('.login-group button').click() |  | ||||||
|  |  | ||||||
|     visitPage('home') |     visitPage('home') | ||||||
|  |  | ||||||
| @@ -339,7 +336,6 @@ context('excel tests: ', function () { | |||||||
|  |  | ||||||
|   this.afterEach(() => { |   this.afterEach(() => { | ||||||
|     colorLog(`TEST END -------------`, '#3498DB') |     colorLog(`TEST END -------------`, '#3498DB') | ||||||
|     // cy.visit(`${hostUrl}/SASLogon/logout`) |  | ||||||
|   }) |   }) | ||||||
| }) | }) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -15,9 +15,6 @@ context('filtering tests: ', function () { | |||||||
|  |  | ||||||
|   this.beforeEach(() => { |   this.beforeEach(() => { | ||||||
|     cy.visit(hostUrl + appLocation, { timeout: longerCommandTimeout }) |     cy.visit(hostUrl + appLocation, { timeout: longerCommandTimeout }) | ||||||
|     // cy.get('input.username').type(username) |  | ||||||
|     // cy.get('input.password').type(password) |  | ||||||
|     // cy.get('.login-group button').click() |  | ||||||
|  |  | ||||||
|     visitPage('home') |     visitPage('home') | ||||||
|   }) |   }) | ||||||
| @@ -173,10 +170,6 @@ context('filtering tests: ', function () { | |||||||
|       }) |       }) | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   this.afterEach(() => { |  | ||||||
|     // cy.visit(`${hostUrl}/SASLogon/logout`) |  | ||||||
|   }) |  | ||||||
| }) | }) | ||||||
|  |  | ||||||
| const checkInfoBarIncludes = (text: string, callback: any) => { | const checkInfoBarIncludes = (text: string, callback: any) => { | ||||||
|   | |||||||
| @@ -23,15 +23,11 @@ interface EditConfigTableCells { | |||||||
|  |  | ||||||
| context('licensing tests: ', function () { | context('licensing tests: ', function () { | ||||||
|   this.beforeAll(() => { |   this.beforeAll(() => { | ||||||
|     // cy.visit(`${hostUrl}/SASLogon/logout`) |  | ||||||
|     cy.loginAndUpdateValidKey() |     cy.loginAndUpdateValidKey() | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   this.beforeEach(() => { |   this.beforeEach(() => { | ||||||
|     cy.visit(hostUrl + appLocation) |     cy.visit(hostUrl + appLocation) | ||||||
|     // cy.get('input.username').type(username) |  | ||||||
|     // cy.get('input.password').type(password) |  | ||||||
|     // cy.get('.login-group button').click() |  | ||||||
|  |  | ||||||
|     visitPage('home') |     visitPage('home') | ||||||
|   }) |   }) | ||||||
| @@ -374,10 +370,6 @@ context('licensing tests: ', function () { | |||||||
|       }) |       }) | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   this.afterEach(() => { |  | ||||||
|     // cy.visit(`${hostUrl}/SASLogon/logout`) |  | ||||||
|   }) |  | ||||||
| }) | }) | ||||||
|  |  | ||||||
| const logout = (callback?: any) => { | const logout = (callback?: any) => { | ||||||
|   | |||||||
| @@ -18,10 +18,6 @@ context('liveness tests: ', function () { | |||||||
|   this.beforeEach(() => { |   this.beforeEach(() => { | ||||||
|     cy.visit(hostUrl + appLocation) |     cy.visit(hostUrl + appLocation) | ||||||
|  |  | ||||||
|     // cy.get('input.username').type(username) |  | ||||||
|     // cy.get('input.password').type(password) |  | ||||||
|     // cy.get('.login-group button').click() |  | ||||||
|  |  | ||||||
|     visitPage('home') |     visitPage('home') | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -17,7 +17,6 @@ context('editor tests: ', function () { | |||||||
|  |  | ||||||
|   this.beforeEach(() => { |   this.beforeEach(() => { | ||||||
|     cy.visit(hostUrl + appLocation) |     cy.visit(hostUrl + appLocation) | ||||||
|  |  | ||||||
|     cy.wait(2000) |     cy.wait(2000) | ||||||
|  |  | ||||||
|     cy.get('body').then(($body) => { |     cy.get('body').then(($body) => { | ||||||
| @@ -386,10 +385,6 @@ context('editor tests: ', function () { | |||||||
|       } |       } | ||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   this.afterEach(() => { |  | ||||||
|     // cy.visit(`${hostUrl}/SASLogon/logout`) |  | ||||||
|   }) |  | ||||||
| }) | }) | ||||||
|  |  | ||||||
| const checkColumns = (columns: string[], callback: () => void) => { | const checkColumns = (columns: string[], callback: () => void) => { | ||||||
|   | |||||||
| @@ -6,5 +6,8 @@ | |||||||
|       "lib": ["es2019", "dom"], |       "lib": ["es2019", "dom"], | ||||||
|       "types": ["cypress", "cypress-real-events"] |       "types": ["cypress", "cypress-real-events"] | ||||||
|     }, |     }, | ||||||
|     "include": ["**/*.ts"] |     "include": [ | ||||||
|  |       "**/*.ts", | ||||||
|  |       "../cypress.config.ts" | ||||||
|  |     ] | ||||||
|   } |   } | ||||||
										
											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@13.1.0;handsontable@13.1.0;hyperformula@2.7.0;jackspeak@2.2.0;path-scurry@1.7.0' |           '@cds/city@1.1.0;@handsontable/angular@14.6.2;handsontable@14.6.2;hyperformula@2.7.1;jackspeak@3.4.3;path-scurry@1.11.1;package-json-from-dist@1.0.1' | ||||||
|       }, |       }, | ||||||
|       (error, json) => { |       (error, json) => { | ||||||
|         if (error) { |         if (error) { | ||||||
|   | |||||||
							
								
								
									
										10904
									
								
								client/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10904
									
								
								client/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -48,18 +48,19 @@ | |||||||
|     "@clr/angular": "^17.0.1", |     "@clr/angular": "^17.0.1", | ||||||
|     "@clr/icons": "^13.0.2", |     "@clr/icons": "^13.0.2", | ||||||
|     "@clr/ui": "^17.0.1", |     "@clr/ui": "^17.0.1", | ||||||
|     "@handsontable/angular": "^13.1.0", |     "@handsontable/angular": "^14.3.0", | ||||||
|     "@sasjs/adapter": "4.10.2", |     "@sasjs/adapter": "^4.11.0", | ||||||
|     "@sasjs/utils": "^3.4.0", |     "@sasjs/utils": "^3.4.0", | ||||||
|  |     "@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": "^13.1.0", |     "handsontable": "^14.3.0", | ||||||
|     "https-browserify": "1.0.0", |     "https-browserify": "1.0.0", | ||||||
|     "hyperformula": "^2.5.0", |     "hyperformula": "^2.5.0", | ||||||
|     "iconv-lite": "^0.5.0", |     "iconv-lite": "^0.5.0", | ||||||
| @@ -78,6 +79,8 @@ | |||||||
|     "text-encoding": "^0.7.0", |     "text-encoding": "^0.7.0", | ||||||
|     "tslib": "^2.3.0", |     "tslib": "^2.3.0", | ||||||
|     "vm": "^0.1.0", |     "vm": "^0.1.0", | ||||||
|  |     "webpack": "^5.91.0", | ||||||
|  |     "xlsx": "^0.18.5", | ||||||
|     "zone.js": "~0.14.4" |     "zone.js": "~0.14.4" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|   | |||||||
| @@ -43,6 +43,10 @@ export interface XLMapListItem { | |||||||
|   targetDS: string |   targetDS: string | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export interface HandsontableStaticConfig { | ||||||
|  |   darkTableHeaderClass: string | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Cached filtering values across whole app (editor, viewer, viewboxes) |  * Cached filtering values across whole app (editor, viewer, viewboxes) | ||||||
|  * Cached lineage libraries, tables |  * Cached lineage libraries, tables | ||||||
| @@ -62,6 +66,7 @@ export const globals: { | |||||||
|   viyaApi: any |   viyaApi: any | ||||||
|   usernav: any |   usernav: any | ||||||
|   operators: any |   operators: any | ||||||
|  |   handsontable: HandsontableStaticConfig | ||||||
|   [key: string]: any |   [key: string]: any | ||||||
| } = { | } = { | ||||||
|   rootParam: <string>'', |   rootParam: <string>'', | ||||||
| @@ -140,5 +145,8 @@ export const globals: { | |||||||
|   operators: { |   operators: { | ||||||
|     numOperators: ['=', '<', '>', '<=', '>=', 'BETWEEN', 'IN', 'NOT IN', 'NE'], |     numOperators: ['=', '<', '>', '<=', '>=', 'BETWEEN', 'IN', 'NOT IN', 'NE'], | ||||||
|     charOperators: ['=', '<', '>', '<=', '>=', 'CONTAINS', 'IN', 'NOT IN', 'NE'] |     charOperators: ['=', '<', '>', '<=', '>=', 'CONTAINS', 'IN', 'NOT IN', 'NE'] | ||||||
|  |   }, | ||||||
|  |   handsontable: { | ||||||
|  |     darkTableHeaderClass: 'darkTH' | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -245,6 +245,7 @@ | |||||||
|  |  | ||||||
|   <app-alerts *ngIf="!errTop"></app-alerts> |   <app-alerts *ngIf="!errTop"></app-alerts> | ||||||
|   <app-requests-modal [(opened)]="requestsModal"></app-requests-modal> |   <app-requests-modal [(opened)]="requestsModal"></app-requests-modal> | ||||||
|  |   <app-excel-password-modal></app-excel-password-modal> | ||||||
|  |  | ||||||
|   <!-- <app-terms *ngIf="showRegistration"></app-terms> --> |   <!-- <app-terms *ngIf="showRegistration"></app-terms> --> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | @import '../colors.scss'; | ||||||
|  |  | ||||||
| // Copyright (c) 2016 VMware, Inc. All Rights Reserved. | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. | ||||||
| // 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. | ||||||
| @@ -6,7 +8,7 @@ app-requests-modal { | |||||||
| } | } | ||||||
|  |  | ||||||
| header.app-header { | header.app-header { | ||||||
|   background: #314351 !important; |   background: $headerBackground !important; | ||||||
|   color: #fff; |   color: #fff; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -42,7 +44,7 @@ header.app-header { | |||||||
|     align-items: center; |     align-items: center; | ||||||
|     padding: 30px; |     padding: 30px; | ||||||
|     z-index: 110; |     z-index: 110; | ||||||
|     background: #314351; |     background: $headerBackground; | ||||||
|  |  | ||||||
|     .expired-notice { |     .expired-notice { | ||||||
|       color: #e0e0e0; |       color: #e0e0e0; | ||||||
| @@ -106,15 +108,6 @@ header { | |||||||
|   font-size: 12px; |   font-size: 12px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .btn.btn-success { |  | ||||||
|   border-color: #62a420; |  | ||||||
|   background-color: #16a57a!important; |  | ||||||
|   color: #fff; |  | ||||||
| } |  | ||||||
| .btn.btn-success:hover { |  | ||||||
|   background-color: #2add39; |  | ||||||
|   color: #fff; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .toggle-switch input[type=checkbox]:checked+label:before { | .toggle-switch input[type=checkbox]:checked+label:before { | ||||||
|   border-color: #61717D; |   border-color: #61717D; | ||||||
| @@ -142,59 +135,50 @@ header { | |||||||
|   color: #fff; |   color: #fff; | ||||||
| } | } | ||||||
|  |  | ||||||
| @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; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| ::ng-deep { | ::ng-deep { | ||||||
|   .htInvalid { |   .htInvalid { | ||||||
|   background: black!important; |     background: black!important; | ||||||
| } |   } | ||||||
|  |  | ||||||
|   @media screen and (max-width:480px) { |   @media screen and (max-width:480px) { | ||||||
|   h2 { |     h2 { | ||||||
|     font-size: .7rem!important; |       font-size: .7rem!important; | ||||||
|  |     } | ||||||
|  |     h3 { | ||||||
|  |       font-size: .7rem; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|   h3 { |  | ||||||
|     font-size: .7rem; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|   .nav-link { |   .nav-link { | ||||||
|     padding: 0rem 1rem 0rem 1rem; |     padding: 0rem 1rem 0rem 1rem; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   .btn-primary .btn, .btn.btn-primary { |   body[cds-theme="light"] { | ||||||
|     border-color: #314351; |     .btn-primary .btn, .btn.btn-primary { | ||||||
|     background-color: #314351; |       border-color: $headerBackground; | ||||||
|     color: #fff; |       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 { |   .btn { | ||||||
|     cursor: pointer; |     cursor: pointer; | ||||||
|     display: inline-block; |     display: inline-block; | ||||||
| @@ -215,36 +199,32 @@ header { | |||||||
|     font-weight: 500; |     font-weight: 500; | ||||||
|     height: 1.5rem; |     height: 1.5rem; | ||||||
|     padding: 0 .5rem; |     padding: 0 .5rem; | ||||||
|     border-color: #314351; |  | ||||||
|     background-color: transparent; |  | ||||||
|     color: #314351; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   .btn.btn-outline { |  | ||||||
|     border-color: #314351; |  | ||||||
|     background-color: transparent; |  | ||||||
|     color: #314351; |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   .btn.btn-outline:hover { |   .btn.btn-outline:hover { | ||||||
|     border-color: #314351; |     border-color: $headerBackground; | ||||||
|     background-color: #495A67; |     background-color: #495A67; | ||||||
|     color: #fff; |     color: #fff; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   .btn.btn-success-outline:hover { |  | ||||||
|     background-color: #5ea71f; |  | ||||||
|     color: #fff7f7; |  | ||||||
|     border-color: #9a9696; |  | ||||||
| } |  | ||||||
| // .btn.btn-success-outline { |  | ||||||
| //     border-color: #266900; |  | ||||||
| //     background-color: transparent; |  | ||||||
| //     color: #318700; |  | ||||||
| // } |  | ||||||
|   // .wtSpreader { |  | ||||||
|  |  | ||||||
|   // } |   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 { |   .htMobileEditorContainer .inputs textarea { | ||||||
|     font-size: 13pt; |     font-size: 13pt; | ||||||
| @@ -277,65 +257,68 @@ header { | |||||||
|     width: 350px; |     width: 350px; | ||||||
| } | } | ||||||
|  |  | ||||||
|   .handsontable  { |  | ||||||
|   background-color: #ffffff; |  | ||||||
|   // border: 1px solid #ccc; |  | ||||||
|   border-radius: 3px; |  | ||||||
|   } |  | ||||||
|   .handsontable th { |  | ||||||
|   background-color: #fafafa; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /* Left and right */ |   /* Left and right */ | ||||||
|   .ht_clone_left th { |  | ||||||
|     border-right: 1px solid #ccc; |  | ||||||
|     border-left: 1px solid #ccc; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /* Column headers */ |   /* Column headers */ | ||||||
|   .ht_clone_top th { |  | ||||||
|     border-top: 1px solid #ccc; |   body[cds-theme="light"] { | ||||||
|     border-right: 1px solid #ccc; |     .wtBorder { | ||||||
|     border-bottom: 1px solid #ccc; |       background-color: #495A67!important; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     .ht_master tr:nth-of-type(odd) > td { | ||||||
|  |       filter: brightness(0.95); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   .ht_clone_top_left_corner th { |   $darkBorderColor: #697c85; | ||||||
|     border-right: 1px solid #ccc; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   .ht_master tr:nth-of-type(odd) > td { |   body[cds-theme="dark"] { | ||||||
|     background-color: #f3f3f3; |     .ht_master tr:nth-of-type(odd) > td { | ||||||
|     border: 1px solid rgb(197, 197, 197); |       filter: brightness(1.2); | ||||||
|     border-bottom: 1px solid rgb(236, 235, 235); |     } | ||||||
|     // padding: 1px 1px; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   .ht_master tr:nth-of-type(even) > td { |     .ht_master:not(.emptyColumns) ~ .handsontable tbody tr th, .ht_master:not(.emptyColumns) ~ .handsontable:not(.ht_clone_top) thead tr th:first-child { | ||||||
|     background-color: white; |       background-color: #2d4048; | ||||||
|     border: 1px solid rgb(197, 197, 197); |       border-color: $darkBorderColor; | ||||||
|     border-bottom: 1px solid rgb(236, 235, 235); |     } | ||||||
|     // padding: 1px 1px; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   .wtBorder { |     .handsontable td { | ||||||
|     background-color: #495A67!important; |       // 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 { |   .handsontable .handsontable.ht_clone_top .wtHider { | ||||||
|     padding: 0 0 0px 0!important; |     padding: 0 0 0px 0!important; | ||||||
|     margin: 0px; |     margin: 0px; | ||||||
|     border-bottom: 3px solid #d6d3d3; |     border-bottom: 3px solid #d6d3d3; | ||||||
| } |  | ||||||
|  |  | ||||||
|   .content-container { |  | ||||||
|     background: #F5F6FF; |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   .card { |   body[cds-theme="light"] { | ||||||
|     box-shadow: 0 0.125rem 0 0 #d7d7d7; |     .content-container { | ||||||
|     border-radius: .0rem; |       // background: red; | ||||||
|     border: 1px solid transparent; |       background: #F5F6FF; | ||||||
|     // min-height: calc(100vh - 150px); |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   .datagrid-compact, .datagrid-history{ |   .datagrid-compact, .datagrid-history{ | ||||||
| @@ -343,8 +326,6 @@ header { | |||||||
|       border-collapse: separate; |       border-collapse: separate; | ||||||
|       border: 1px solid transparent; |       border: 1px solid transparent; | ||||||
|       border-radius: .125rem; |       border-radius: .125rem; | ||||||
|       background-color: #fff; |  | ||||||
|       color: #565656; |  | ||||||
|       margin: 0; |       margin: 0; | ||||||
|       margin-top: 1rem; |       margin-top: 1rem; | ||||||
|       max-width: 100%; |       max-width: 100%; | ||||||
| @@ -366,8 +347,8 @@ header { | |||||||
|     } |     } | ||||||
|     .datagrid-footer { |     .datagrid-footer { | ||||||
|       position: absolute; |       position: absolute; | ||||||
|       right: 15px; |       right: 30px; | ||||||
|       top: 2px; |       top: 1px; | ||||||
|     } |     } | ||||||
|     .datagrid .datagrid-head { |     .datagrid .datagrid-head { | ||||||
|       background-color: #fff; |       background-color: #fff; | ||||||
| @@ -387,7 +368,6 @@ header { | |||||||
|     -webkit-box-direction: normal; |     -webkit-box-direction: normal; | ||||||
|     -ms-flex-direction: column; |     -ms-flex-direction: column; | ||||||
|     flex-direction: column; |     flex-direction: column; | ||||||
|     background: #f5f6ff; |  | ||||||
|     padding: .5rem 0; |     padding: .5rem 0; | ||||||
|     border: 1px solid #ccc; |     border: 1px solid #ccc; | ||||||
|     box-shadow: 0 1px 0.125rem hsla(0,0%,45%,.25); |     box-shadow: 0 1px 0.125rem hsla(0,0%,45%,.25); | ||||||
| @@ -402,8 +382,6 @@ header { | |||||||
|     border-collapse: separate; |     border-collapse: separate; | ||||||
|     border: 1px solid transparent; |     border: 1px solid transparent; | ||||||
|     border-radius: 0px; |     border-radius: 0px; | ||||||
|     background-color: #fff; |  | ||||||
|     color: #565656; |  | ||||||
|     margin: 0; |     margin: 0; | ||||||
|     margin-top: 1rem; |     margin-top: 1rem; | ||||||
|     max-width: 100%; |     max-width: 100%; | ||||||
| @@ -414,7 +392,6 @@ header { | |||||||
|     font-size: .45833rem; |     font-size: .45833rem; | ||||||
|     font-weight: 600; |     font-weight: 600; | ||||||
|     letter-spacing: .03em; |     letter-spacing: .03em; | ||||||
|     background-color: #fff; |  | ||||||
|     vertical-align: bottom; |     vertical-align: bottom; | ||||||
|     border-bottom: 1px solid #ccc; |     border-bottom: 1px solid #ccc; | ||||||
|     text-transform: uppercase; |     text-transform: uppercase; | ||||||
| @@ -438,3 +415,33 @@ header { | |||||||
|     width: 100%; |     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; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -13,6 +13,25 @@ import { InfoModal } from './models/InfoModal' | |||||||
| import { DcAdapterSettings } from './models/DcAdapterSettings' | import { DcAdapterSettings } from './models/DcAdapterSettings' | ||||||
| import { AppStoreService } from './services/app-store.service' | import { AppStoreService } from './services/app-store.service' | ||||||
| import { LicenceService } from './services/licence.service' | import { LicenceService } from './services/licence.service' | ||||||
|  | import '@cds/core/icon/register.js' | ||||||
|  | import { | ||||||
|  |   ClarityIcons, | ||||||
|  |   exclamationTriangleIcon, | ||||||
|  |   moonIcon, | ||||||
|  |   processOnVmIcon, | ||||||
|  |   sunIcon, | ||||||
|  |   tableIcon, | ||||||
|  |   trashIcon | ||||||
|  | } from '@cds/core/icon' | ||||||
|  |  | ||||||
|  | ClarityIcons.addIcons( | ||||||
|  |   moonIcon, | ||||||
|  |   sunIcon, | ||||||
|  |   exclamationTriangleIcon, | ||||||
|  |   tableIcon, | ||||||
|  |   trashIcon, | ||||||
|  |   processOnVmIcon | ||||||
|  | ) | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'my-app', |   selector: 'my-app', | ||||||
|   | |||||||
| @@ -20,10 +20,10 @@ import { UsernavRouteComponent } from './routes/usernav-route/usernav-route.comp | |||||||
| import { AppService } from './services/app.service' | import { AppService } from './services/app.service' | ||||||
| import { InfoModalComponent } from './shared/abort-modal/info-modal.component' | import { InfoModalComponent } from './shared/abort-modal/info-modal.component' | ||||||
| import { RequestsModalComponent } from './shared/requests-modal/requests-modal.component' | import { RequestsModalComponent } from './shared/requests-modal/requests-modal.component' | ||||||
| import { HomeModule } from './home/home.module' |  | ||||||
| import { DirectivesModule } from './directives/directives.module' | import { DirectivesModule } from './directives/directives.module' | ||||||
| import { ViyaApiExplorerComponent } from './viya-api-explorer/viya-api-explorer.component' | import { ViyaApiExplorerComponent } from './viya-api-explorer/viya-api-explorer.component' | ||||||
| import { NgxJsonViewerModule } from 'ngx-json-viewer' | import { NgxJsonViewerModule } from 'ngx-json-viewer' | ||||||
|  | import { AppSettingsService } from './services/app-settings.service' | ||||||
|  |  | ||||||
| @NgModule({ | @NgModule({ | ||||||
|   declarations: [ |   declarations: [ | ||||||
| @@ -50,7 +50,7 @@ import { NgxJsonViewerModule } from 'ngx-json-viewer' | |||||||
|     DirectivesModule, |     DirectivesModule, | ||||||
|     NgxJsonViewerModule |     NgxJsonViewerModule | ||||||
|   ], |   ], | ||||||
|   providers: [AppService, SasStoreService, LicensingGuard], |   providers: [AppService, SasStoreService, LicensingGuard, AppSettingsService], | ||||||
|   bootstrap: [AppComponent] |   bootstrap: [AppComponent] | ||||||
| }) | }) | ||||||
| export class AppModule {} | export class AppModule {} | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core' | import { Component, Input, OnInit, Output, EventEmitter } 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 { 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' | ||||||
| @@ -303,10 +304,10 @@ export class ManualComponent implements OnInit { | |||||||
|  |  | ||||||
|     this.sasService |     this.sasService | ||||||
|       .request('public/startupservice', null) |       .request('public/startupservice', null) | ||||||
|       .then((res: any) => { |       .then((res: RequestWrapperResponse) => { | ||||||
|         this.loggerService.log(res) |         this.loggerService.log(res.adapterResponse) | ||||||
|  |  | ||||||
|         if (res.saslibs) { |         if (res.adapterResponse.saslibs) { | ||||||
|           this.validationState = 'success' |           this.validationState = 'success' | ||||||
|         } else { |         } else { | ||||||
|           this.validationState = 'error' |           this.validationState = 'error' | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ import { Component, EventEmitter, Input, OnInit, Output } 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' | ||||||
|  | import { RequestWrapperResponse } from 'src/app/models/request-wrapper/RequestWrapperResponse' | ||||||
| import { SASGroup } from 'src/app/models/sas/public-getgroups.model' | import { SASGroup } from 'src/app/models/sas/public-getgroups.model' | ||||||
| import { SASjsApiServerInfo } from 'src/app/models/sasjs-api/SASjsApiServerInfo.model' | import { SASjsApiServerInfo } from 'src/app/models/sasjs-api/SASjsApiServerInfo.model' | ||||||
| import { SasService } from 'src/app/services/sas.service' | import { SasService } from 'src/app/services/sas.service' | ||||||
| @@ -68,11 +69,11 @@ export class SasjsConfiguratorComponent implements OnInit { | |||||||
|     this.loading = true |     this.loading = true | ||||||
|  |  | ||||||
|     this.sasService.request('usernav/usergroupsbymember', null).then( |     this.sasService.request('usernav/usergroupsbymember', null).then( | ||||||
|       (res: any) => { |       (res: RequestWrapperResponse) => { | ||||||
|         this.METAPERSON = res.MF_GETUSER |         this.METAPERSON = res.adapterResponse.MF_GETUSER | ||||||
|         this.SYSUSERID = res.SYSUSERID |         this.SYSUSERID = res.adapterResponse.SYSUSERID | ||||||
|         this.SYSHOSTNAME = res.SYSHOSTNAME |         this.SYSHOSTNAME = res.adapterResponse.SYSHOSTNAME | ||||||
|         this.SYSVLONG = res.SYSVLONG |         this.SYSVLONG = res.adapterResponse.SYSVLONG | ||||||
|  |  | ||||||
|         /* |         /* | ||||||
|           We would like to present a default DCPATH (deployment path) to the |           We would like to present a default DCPATH (deployment path) to the | ||||||
| @@ -88,12 +89,14 @@ export class SasjsConfiguratorComponent implements OnInit { | |||||||
|         */ |         */ | ||||||
|         this.dcDirectory = |         this.dcDirectory = | ||||||
|           this.tmpDirectories[ |           this.tmpDirectories[ | ||||||
|             ['L', 'H', 'A', 'S'].includes(res.SYSSCPL.substring(0, 1)) |             ['L', 'H', 'A', 'S'].includes( | ||||||
|  |               res.adapterResponse.SYSSCPL.substring(0, 1) | ||||||
|  |             ) | ||||||
|               ? 'linux' |               ? 'linux' | ||||||
|               : 'windows' |               : 'windows' | ||||||
|           ] |           ] | ||||||
|  |  | ||||||
|         this.dcAdminGroupList = res.groups |         this.dcAdminGroupList = res.adapterResponse.groups | ||||||
|         this.dcAdminGroup = this.dcAdminGroupList[0].GROUPNAME |         this.dcAdminGroup = this.dcAdminGroupList[0].GROUPNAME | ||||||
|  |  | ||||||
|         this.loading = false |         this.loading = false | ||||||
|   | |||||||
| @@ -4,20 +4,23 @@ import { NgVarDirective } from './ng-var.directive' | |||||||
| import { DragNdropDirective } from './drag-ndrop.directive' | import { DragNdropDirective } from './drag-ndrop.directive' | ||||||
| import { FileDropDirective } from './file-drop.directive' | import { FileDropDirective } from './file-drop.directive' | ||||||
| import { FileSelectDirective } from './file-select.directive' | import { FileSelectDirective } from './file-select.directive' | ||||||
|  | import { StealFocusDirective } from './steal-focus.directive' | ||||||
|  |  | ||||||
| @NgModule({ | @NgModule({ | ||||||
|   declarations: [ |   declarations: [ | ||||||
|     NgVarDirective, |     NgVarDirective, | ||||||
|     DragNdropDirective, |     DragNdropDirective, | ||||||
|     FileDropDirective, |     FileDropDirective, | ||||||
|     FileSelectDirective |     FileSelectDirective, | ||||||
|  |     StealFocusDirective | ||||||
|   ], |   ], | ||||||
|   imports: [CommonModule], |   imports: [CommonModule], | ||||||
|   exports: [ |   exports: [ | ||||||
|     NgVarDirective, |     NgVarDirective, | ||||||
|     DragNdropDirective, |     DragNdropDirective, | ||||||
|     FileDropDirective, |     FileDropDirective, | ||||||
|     FileSelectDirective |     FileSelectDirective, | ||||||
|  |     StealFocusDirective | ||||||
|   ] |   ] | ||||||
| }) | }) | ||||||
| export class DirectivesModule {} | export class DirectivesModule {} | ||||||
|   | |||||||
							
								
								
									
										17
									
								
								client/src/app/directives/steal-focus.directive.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								client/src/app/directives/steal-focus.directive.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | import { Directive, HostListener } from '@angular/core' | ||||||
|  |  | ||||||
|  | @Directive({ | ||||||
|  |   selector: '[appStealFocus]' | ||||||
|  | }) | ||||||
|  | export class StealFocusDirective { | ||||||
|  |   constructor() {} | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * For some reason newest version of Clarity v17.0.1 is stealing focus when | ||||||
|  |    * clicking on the input inside of the clr-tree-view | ||||||
|  |    * This is workaround | ||||||
|  |    */ | ||||||
|  |   @HostListener('click', ['$event']) onClick(event: any) { | ||||||
|  |     event.target.focus() | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -277,7 +277,7 @@ | |||||||
|     <div> |     <div> | ||||||
|       <button |       <button | ||||||
|         type="button" |         type="button" | ||||||
|         class="btn btn-outline focusable" |         class="btn btn-outline focusable mr-5i" | ||||||
|         (click)="currentRecord!.noLinkOption = false; closeRecordEdit()" |         (click)="currentRecord!.noLinkOption = false; closeRecordEdit()" | ||||||
|       > |       > | ||||||
|         Cancel |         Cancel | ||||||
|   | |||||||
| @@ -38,7 +38,6 @@ | |||||||
|       app-soft-select { |       app-soft-select { | ||||||
|         display: block; |         display: block; | ||||||
|         width: 224px; |         width: 224px; | ||||||
|         background: #fff; |  | ||||||
|         border: 1px solid #999; |         border: 1px solid #999; | ||||||
|         color: #000; |         color: #000; | ||||||
|         padding: calc(.25rem + 2px) .5rem; |         padding: calc(.25rem + 2px) .5rem; | ||||||
| @@ -49,7 +48,6 @@ | |||||||
|         input { |         input { | ||||||
|           width: 100%; |           width: 100%; | ||||||
|           border: 0; |           border: 0; | ||||||
|           background-color: #fff; |  | ||||||
|  |  | ||||||
|           &:focus { |           &:focus { | ||||||
|             background: none; |             background: none; | ||||||
| @@ -132,7 +130,6 @@ | |||||||
|  |  | ||||||
|     clr-input-container { |     clr-input-container { | ||||||
|       width: 224px; |       width: 224px; | ||||||
|       background: #fff; |  | ||||||
|       border: 1px solid #999; |       border: 1px solid #999; | ||||||
|       color: #000; |       color: #000; | ||||||
|       padding: calc(.25rem + 2px) .5rem; |       padding: calc(.25rem + 2px) .5rem; | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
|     appFileDrop |     appFileDrop | ||||||
|     (fileOver)="fileOverBase($event)" |     (fileOver)="fileOverBase($event)" | ||||||
|     [uploader]="uploader" |     [uploader]="uploader" | ||||||
|     (fileDrop)="getFileDesc($event, true)" |     (fileDrop)="attachFile($event, true)" | ||||||
|     [clrModalSize]="'xl'" |     [clrModalSize]="'xl'" | ||||||
|     [clrModalStaticBackdrop]="false" |     [clrModalStaticBackdrop]="false" | ||||||
|     [clrModalClosable]="excelUploadState === 'Validating-DQ'" |     [clrModalClosable]="excelUploadState === 'Validating-DQ'" | ||||||
| @@ -81,7 +81,7 @@ | |||||||
|               type="file" |               type="file" | ||||||
|               appFileSelect |               appFileSelect | ||||||
|               [uploader]="uploader" |               [uploader]="uploader" | ||||||
|               (change)="getFileDesc($event)" |               (change)="attachFile($event)" | ||||||
|             /> |             /> | ||||||
|           </div> |           </div> | ||||||
|  |  | ||||||
| @@ -92,7 +92,7 @@ | |||||||
|             <button |             <button | ||||||
|               [disabled]="true" |               [disabled]="true" | ||||||
|               class="btnView btn btn-sm btn-success profile-buttons w-100" |               class="btnView btn btn-sm btn-success profile-buttons w-100" | ||||||
|               (click)="getFile()" |               (click)="uploadParsedFiles()" | ||||||
|             > |             > | ||||||
|               Upload |               Upload | ||||||
|             </button> |             </button> | ||||||
| @@ -164,19 +164,28 @@ | |||||||
|       <div |       <div | ||||||
|         class="card-header clr-row buttonBar headerBar clr-flex-md-row clr-justify-content-center clr-justify-content-lg-end" |         class="card-header clr-row buttonBar headerBar clr-flex-md-row clr-justify-content-center clr-justify-content-lg-end" | ||||||
|       > |       > | ||||||
|         <div *ngIf="tableTrue" class="clr-col-12 clr-col-lg-4 backBtn"> |         <div | ||||||
|           <span class="btn btn-sm" [routerLink]="['/home']"> |           *ngIf="tableTrue" | ||||||
|             <clr-icon shape="caret" dir="left" size="20"></clr-icon>Back to |           class="clr-col-12 clr-col-md-3 clr-col-lg-4 backBtn" | ||||||
|             table selection |         > | ||||||
|  |           <span | ||||||
|  |             class="btn icon-collapse btn-sm btn-icon btn-dimmed" | ||||||
|  |             [routerLink]="['/home']" | ||||||
|  |           > | ||||||
|  |             <clr-icon shape="caret" dir="left" size="20"></clr-icon> | ||||||
|  |             <span class="text">Back to table selection</span> | ||||||
|           </span> |           </span> | ||||||
|           <span (click)="viewboxManager()" class="btn btn-sm viewbox-open"> |           <span | ||||||
|  |             (click)="viewboxManager()" | ||||||
|  |             class="btn icon-collapse btn-sm btn-icon btn-dimmed viewbox-open" | ||||||
|  |           > | ||||||
|             <clr-icon shape="view-cards" size="20"></clr-icon> |             <clr-icon shape="view-cards" size="20"></clr-icon> | ||||||
|             Viewboxes |             <span class="text">Viewboxes</span> | ||||||
|           </span> |           </span> | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
|         <div |         <div | ||||||
|           class="clr-col-12 clr-col-lg-4 d-flex flex-column align-items-center" |           class="clr-col-12 clr-col-md-5 clr-col-lg-4 d-flex flex-column align-items-center" | ||||||
|           [class.clr-col-lg-12]="!tableTrue" |           [class.clr-col-lg-12]="!tableTrue" | ||||||
|         > |         > | ||||||
|           <h4 |           <h4 | ||||||
| @@ -231,34 +240,37 @@ | |||||||
|             </ng-container> |             </ng-container> | ||||||
|           </h4> |           </h4> | ||||||
|         </div> |         </div> | ||||||
|         <div *ngIf="tableTrue" class="clr-col-12 clr-col-lg-4 btnCtrl"> |         <div | ||||||
|  |           *ngIf="tableTrue" | ||||||
|  |           class="clr-col-12 clr-col-md-4 clr-col-lg-4 btnCtrl" | ||||||
|  |         > | ||||||
|           <ng-container *ngIf="hotTable.readOnly && !uploadPreview"> |           <ng-container *ngIf="hotTable.readOnly && !uploadPreview"> | ||||||
|             <button |             <button | ||||||
|               type="button" |               type="button" | ||||||
|               class="btnView btn btn-sm btn-icon btn-block" |               class="btnView btn icon-collapse btn-sm btn-icon btn-block btn-dimmed" | ||||||
|               (click)="openQb()" |               (click)="openQb()" | ||||||
|             > |             > | ||||||
|               <clr-icon shape="filter"></clr-icon> |               <clr-icon shape="filter"></clr-icon> | ||||||
|               <span>Filter</span> |               <span class="text">Filter</span> | ||||||
|             </button> |             </button> | ||||||
|  |  | ||||||
|             <button |             <button | ||||||
|               type="button" |               type="button" | ||||||
|               class="btn btn-sm btn-primary btn-block" |               class="btn icon-collapse btn-sm btn-primary btn-block" | ||||||
|               (click)="editTable()" |               (click)="editTable()" | ||||||
|             > |             > | ||||||
|               <clr-icon shape="note"></clr-icon> |               <clr-icon shape="note"></clr-icon> | ||||||
|               <span>Edit</span> |               <span class="text">Edit</span> | ||||||
|             </button> |             </button> | ||||||
|  |  | ||||||
|             <button |             <button | ||||||
|               *ngIf="!columnLevelSecurityFlag" |               *ngIf="!columnLevelSecurityFlag" | ||||||
|               (click)="onShowUploadModal()" |               (click)="onShowUploadModal()" | ||||||
|               type="button" |               type="button" | ||||||
|               class="btn btn-sm btn-success btn-block mr-0" |               class="btn icon-collapse btn-sm btn-success btn-block mr-0" | ||||||
|             > |             > | ||||||
|               <clr-icon shape="upload"></clr-icon> |               <clr-icon shape="upload"></clr-icon> | ||||||
|               <span>Upload</span> |               <span class="text">Upload</span> | ||||||
|             </button> |             </button> | ||||||
|           </ng-container> |           </ng-container> | ||||||
|  |  | ||||||
| @@ -362,8 +374,8 @@ | |||||||
|             <ng-container *ngIf="!getdataError"> |             <ng-container *ngIf="!getdataError"> | ||||||
|               <span class="spinner"> Loading... </span> |               <span class="spinner"> Loading... </span> | ||||||
|  |  | ||||||
|               <div> |               <div class="mt-10"> | ||||||
|                 <h3>Loading table</h3> |                 <p cds-text="section">Loading table</p> | ||||||
|               </div> |               </div> | ||||||
|             </ng-container> |             </ng-container> | ||||||
|  |  | ||||||
| @@ -372,8 +384,8 @@ | |||||||
|                 <clr-icon shape="error-standard" class="error-icon"></clr-icon> |                 <clr-icon shape="error-standard" class="error-icon"></clr-icon> | ||||||
|               </span> |               </span> | ||||||
|  |  | ||||||
|               <div> |               <div class="mt-10"> | ||||||
|                 <h3>Loading table error</h3> |                 <p cds-text="section">Loading table error</p> | ||||||
|               </div> |               </div> | ||||||
|             </ng-container> |             </ng-container> | ||||||
|           </div> |           </div> | ||||||
| @@ -398,6 +410,7 @@ | |||||||
|               hotId="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" |               [licenseKey]="hotTable.licenseKey" | ||||||
|             > |             > | ||||||
| @@ -486,11 +499,15 @@ | |||||||
|                     support@datacontroller.io</span |                     support@datacontroller.io</span | ||||||
|                   > |                   > | ||||||
|                   <div *ngIf="tableTrue" class="clr-offset-md-2 clr-col-md-8"> |                   <div *ngIf="tableTrue" class="clr-offset-md-2 clr-col-md-8"> | ||||||
|                     <div class="form-group"> |                     <div class="text-area-full-width"> | ||||||
|                       <label for="formFields_8">Message</label> |                       <label for="formFields_8" class="mb-5 d-block" | ||||||
|  |                         >Message</label | ||||||
|  |                       > | ||||||
|                       <textarea |                       <textarea | ||||||
|  |                         clrTextarea | ||||||
|                         [(ngModel)]="message" |                         [(ngModel)]="message" | ||||||
|                         [disabled]="!validationDone" |                         [disabled]="!validationDone" | ||||||
|  |                         tabindex="0" | ||||||
|                         [value]=" |                         [value]=" | ||||||
|                           !validationDone |                           !validationDone | ||||||
|                             ? 'Please wait while we validate ' + |                             ? 'Please wait while we validate ' + | ||||||
| @@ -498,10 +515,9 @@ | |||||||
|                               ' cells.' |                               ' cells.' | ||||||
|                             : '' |                             : '' | ||||||
|                         " |                         " | ||||||
|                         class="w-100" |                         class="submit-reason" | ||||||
|                         type="text" |                         type="text" | ||||||
|                         id="formFields_8" |                         id="formFields_8" | ||||||
|                         rows="5" |  | ||||||
|                       ></textarea> |                       ></textarea> | ||||||
|                     </div> |                     </div> | ||||||
|                     <!-- TODO:approvers list --> |                     <!-- TODO:approvers list --> | ||||||
| @@ -520,6 +536,7 @@ | |||||||
|                     [disabled]="!validationDone" |                     [disabled]="!validationDone" | ||||||
|                     type="submit" |                     type="submit" | ||||||
|                     class="btn btn-sm btn-success-outline m-0" |                     class="btn btn-sm btn-success-outline m-0" | ||||||
|  |                     tabindex="0" | ||||||
|                     (click)="saveTable(hotTable.data)" |                     (click)="saveTable(hotTable.data)" | ||||||
|                   > |                   > | ||||||
|                     Submit |                     Submit | ||||||
| @@ -528,6 +545,7 @@ | |||||||
|                     id="cancelSubmitBtn" |                     id="cancelSubmitBtn" | ||||||
|                     type="button" |                     type="button" | ||||||
|                     class="btn btn-sm btn-outline" |                     class="btn btn-sm btn-outline" | ||||||
|  |                     tabindex="0" | ||||||
|                     (click)="cancelSubmit(); submit = false; validationDone = 0" |                     (click)="cancelSubmit(); submit = false; validationDone = 0" | ||||||
|                   > |                   > | ||||||
|                     Cancel |                     Cancel | ||||||
| @@ -558,7 +576,7 @@ | |||||||
|               <button |               <button | ||||||
|                 type="button" |                 type="button" | ||||||
|                 class="btn btn-sm btn-primary" |                 class="btn btn-sm btn-primary" | ||||||
|                 (click)="getFile(); submitLimitNotice = false" |                 (click)="uploadParsedFiles(); submitLimitNotice = false" | ||||||
|               > |               > | ||||||
|                 Submit |                 Submit | ||||||
|               </button> |               </button> | ||||||
|   | |||||||
| @@ -21,12 +21,12 @@ hot-table { | |||||||
|  |  | ||||||
|     .handsontable tbody th.ht__highlight, .handsontable thead th.ht__highlight { |     .handsontable tbody th.ht__highlight, .handsontable thead th.ht__highlight { | ||||||
|       &.primaryKeyHeaderStyle { |       &.primaryKeyHeaderStyle { | ||||||
|         background: #306b00b0; |         background-color: #306b00b0 !important; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     .primaryKeyHeaderStyle { |     .primaryKeyHeaderStyle { | ||||||
|       background: #306b006e; |       background-color: #306b006e !important; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     th.readonlyCell { |     th.readonlyCell { | ||||||
| @@ -41,6 +41,12 @@ hot-table { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .submit-reason { | ||||||
|  |   min-height: 120px; | ||||||
|  |   max-height: 120px; | ||||||
|  |   height: 120px; | ||||||
|  | } | ||||||
|  |  | ||||||
| .infoBar { | .infoBar { | ||||||
|   margin-top:14px; |   margin-top:14px; | ||||||
|   background: #495967; |   background: #495967; | ||||||
| @@ -80,8 +86,7 @@ hot-table { | |||||||
|   -webkit-box-align: center; |   -webkit-box-align: center; | ||||||
|   -ms-flex-align: center; |   -ms-flex-align: center; | ||||||
|   align-items: center; |   align-items: center; | ||||||
|   background: #ffffff; |   background: var(--clr-vertical-nav-bg-color); | ||||||
|   background: #f5f6fe; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| .error-icon { | .error-icon { | ||||||
| @@ -196,6 +201,7 @@ hot-table { | |||||||
|  |  | ||||||
|   display: flex; |   display: flex; | ||||||
|   justify-content: center; |   justify-content: center; | ||||||
|  |   align-items: flex-start; | ||||||
|  |  | ||||||
|   margin: 1px; |   margin: 1px; | ||||||
|  |  | ||||||
| @@ -206,7 +212,10 @@ hot-table { | |||||||
|   span { |   span { | ||||||
|     font-size: 20px; |     font-size: 20px; | ||||||
|     margin-top: 20px; |     margin-top: 20px; | ||||||
|     color: #fff; |     padding: 10px; | ||||||
|  |     background: #dbdbdb; | ||||||
|  |     border-radius: 5px; | ||||||
|  |     color: black; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -218,6 +227,16 @@ hot-table { | |||||||
|   font-size: inherit !important; |   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 | // FIXME | ||||||
| // Let's leave it here for a reference if there | // Let's leave it here for a reference if there | ||||||
| // is an issue with viewboxes/filter modal overlaying | // is an issue with viewboxes/filter modal overlaying | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -11,9 +11,11 @@ export const errorRenderer = ( | |||||||
|   value: any, |   value: any, | ||||||
|   cellProperties: any |   cellProperties: any | ||||||
| ) => { | ) => { | ||||||
|  |   addDarkClass(td) | ||||||
|  |  | ||||||
|   td.innerHTML = `${ |   td.innerHTML = `${ | ||||||
|     value ? value.toString() : '' |     value ? value.toString() : '' | ||||||
|   } <clr-icon shape="exclamation-circle" status="warning"></clr-icon>` |   } <cds-icon shape="exclamation-triangle" status="warning"></cds-icon>` | ||||||
|  |  | ||||||
|   return td |   return td | ||||||
| } | } | ||||||
| @@ -31,6 +33,8 @@ export const noSpinnerRenderer = ( | |||||||
|   value: any, |   value: any, | ||||||
|   cellProperties: any |   cellProperties: any | ||||||
| ) => { | ) => { | ||||||
|  |   addDarkClass(td) | ||||||
|  |  | ||||||
|   td.innerHTML = value ? value : '' |   td.innerHTML = value ? value : '' | ||||||
|  |  | ||||||
|   return td |   return td | ||||||
| @@ -50,9 +54,20 @@ export const spinnerRenderer = ( | |||||||
|   value: any, |   value: any, | ||||||
|   cellProperties: any |   cellProperties: any | ||||||
| ) => { | ) => { | ||||||
|  |   addDarkClass(td) | ||||||
|  |  | ||||||
|   td.innerHTML = `${ |   td.innerHTML = `${ | ||||||
|     value ? value.toString() : '' |     value ? value.toString() : '' | ||||||
|   } <span class="spinner spinner-sm vertical-align-middle"></span>` |   } <span class="spinner spinner-sm vertical-align-middle"></span>` | ||||||
|  |  | ||||||
|   return td |   return td | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Adds a htDark class to a TD element if not existing | ||||||
|  |  */ | ||||||
|  | const addDarkClass = (td: any) => { | ||||||
|  |   if (!td.classList.contains('htDark')) { | ||||||
|  |     td.classList.add('htDark') | ||||||
|  |   } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
|     <clr-tree-node *ngIf="groups" class="search-node"> |     <clr-tree-node *ngIf="groups" class="search-node"> | ||||||
|       <div class="tree-search-wrapper"> |       <div class="tree-search-wrapper"> | ||||||
|         <input |         <input | ||||||
|  |           appStealFocus | ||||||
|           clrInput |           clrInput | ||||||
|           #searchLibTreeInput |           #searchLibTreeInput | ||||||
|           placeholder="Filter by Groups" |           placeholder="Filter by Groups" | ||||||
| @@ -27,7 +28,7 @@ | |||||||
|       <clr-tree-node |       <clr-tree-node | ||||||
|         (click)="groupOnClick(group)" |         (click)="groupOnClick(group)" | ||||||
|         *ngIf="!group['hidden']" |         *ngIf="!group['hidden']" | ||||||
|         [class.table-active]="group.GROUPURI === groupUri" |         [class.active]="group.GROUPURI === groupUri" | ||||||
|       > |       > | ||||||
|         <p class="m-0 cursor-pointer list-padding"> |         <p class="m-0 cursor-pointer list-padding"> | ||||||
|           <clr-icon shape="users"></clr-icon> |           <clr-icon shape="users"></clr-icon> | ||||||
|   | |||||||
| @@ -1,3 +1,43 @@ | |||||||
|  | @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{ | .sidebar-height{ | ||||||
|     height: 100%; |     height: 100%; | ||||||
| } | } | ||||||
| @@ -6,21 +46,18 @@ | |||||||
|     font-size: 20px; |     font-size: 20px; | ||||||
| } | } | ||||||
| .group-info{ | .group-info{ | ||||||
|     background-color: #f9f9f9; |     border: 1px solid; | ||||||
|     border: 1px solid #a7a7a7; |  | ||||||
|     border-radius: 3px; |     border-radius: 3px; | ||||||
|     box-shadow: 0px 2px 5px #dad7d7; |  | ||||||
| } | } | ||||||
| .group-info td{ | .group-info td{ | ||||||
|     text-align: center; |     text-align: center; | ||||||
| } | } | ||||||
|  |  | ||||||
| .group-data{ | .group-data{ | ||||||
|   background-color: #f9f9f9; |   border: 1px solid; | ||||||
|   border: 1px solid #a7a7a7; |  | ||||||
|   border-radius: 3px; |   border-radius: 3px; | ||||||
|   box-shadow: 0px 2px 5px #dad7d7; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| .group-data{ | .group-data{ | ||||||
|   min-height: auto; |   min-height: auto; | ||||||
|   h3, h5{ |   h3, h5{ | ||||||
| @@ -28,15 +65,11 @@ | |||||||
| } | } | ||||||
|  |  | ||||||
| .member-table{ | .member-table{ | ||||||
|   background-color: #f9f9f9; |  | ||||||
|   width: 100%; |   width: 100%; | ||||||
| } | } | ||||||
| .member-table thead{ |  | ||||||
|   background-color: #dadada; |  | ||||||
| } |  | ||||||
| .member-table tbody{ | .member-table tbody{ | ||||||
|   tr:hover{ |   tr:hover{ | ||||||
|       background-color: #e6e6e6; |  | ||||||
|       cursor: pointer; |       cursor: pointer; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ import { ServerType } from '@sasjs/utils/types/serverType' | |||||||
| import { HelperService } from '../services/helper.service' | import { HelperService } from '../services/helper.service' | ||||||
| import { SasService } from '../services/sas.service' | import { SasService } from '../services/sas.service' | ||||||
| import { globals } from '../_globals' | import { globals } from '../_globals' | ||||||
|  | import { RequestWrapperResponse } from '../models/request-wrapper/RequestWrapperResponse' | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-group', |   selector: 'app-group', | ||||||
| @@ -82,11 +83,13 @@ export class GroupComponent implements OnInit { | |||||||
|               globals.usernav.groupList = groups |               globals.usernav.groupList = groups | ||||||
|             }) |             }) | ||||||
|         } else { |         } else { | ||||||
|           this.sasService.request('public/getgroups', null).then((res: any) => { |           this.sasService | ||||||
|             this.loading = false |             .request('public/getgroups', null) | ||||||
|             this.groups = res.groups |             .then((res: RequestWrapperResponse) => { | ||||||
|             globals.usernav.groupList = res.groups |               this.loading = false | ||||||
|           }) |               this.groups = res.adapterResponse.groups | ||||||
|  |               globals.usernav.groupList = res.adapterResponse.groups | ||||||
|  |             }) | ||||||
|         } |         } | ||||||
|       } else { |       } else { | ||||||
|         this.groups = globals.usernav.groupList |         this.groups = globals.usernav.groupList | ||||||
| @@ -128,14 +131,15 @@ export class GroupComponent implements OnInit { | |||||||
|           let data = { iwant: [{ groupid: this.paramURI }] } |           let data = { iwant: [{ groupid: this.paramURI }] } | ||||||
|           this.sasService |           this.sasService | ||||||
|             .request('usernav/usermembersbygroup', data) |             .request('usernav/usermembersbygroup', data) | ||||||
|             .then((res: any) => { |             .then((res: RequestWrapperResponse) => { | ||||||
|               this.groupMembers = res.sasmembers |               this.groupMembers = res.adapterResponse.sasmembers | ||||||
|               this.groupMemberCount = res.sasmembers.length |               this.groupMemberCount = res.adapterResponse.sasmembers.length | ||||||
|               if (res.sasmembers[0] !== undefined) { |               if (res.adapterResponse.sasmembers[0] !== undefined) { | ||||||
|                 this.loading = false |                 this.loading = false | ||||||
|                 this.groupUri = res.sasmembers[0].URIMEM || this.paramURI |                 this.groupUri = | ||||||
|                 this.groupName = res.sasmembers[0].GROUPNAME |                   res.adapterResponse.sasmembers[0].URIMEM || this.paramURI | ||||||
|                 this.groupDesc = res.sasmembers[0].GROUPDESC |                 this.groupName = res.adapterResponse.sasmembers[0].GROUPNAME | ||||||
|  |                 this.groupDesc = res.adapterResponse.sasmembers[0].GROUPDESC | ||||||
|  |  | ||||||
|                 if (!this.groupName) { |                 if (!this.groupName) { | ||||||
|                   this.groupName = this.paramURI |                   this.groupName = this.paramURI | ||||||
| @@ -202,13 +206,13 @@ export class GroupComponent implements OnInit { | |||||||
|  |  | ||||||
|       this.sasService |       this.sasService | ||||||
|         .request('usernav/usermembersbygroup', data) |         .request('usernav/usermembersbygroup', data) | ||||||
|         .then((res: any) => { |         .then((res: RequestWrapperResponse) => { | ||||||
|           this.loading = false |           this.loading = false | ||||||
|           this.groupUri = group.GROUPURI |           this.groupUri = group.GROUPURI | ||||||
|           this.groupName = group.GROUPNAME |           this.groupName = group.GROUPNAME | ||||||
|           this.groupDesc = group.GROUPDESC |           this.groupDesc = group.GROUPDESC | ||||||
|           this.groupMembers = res.sasmembers |           this.groupMembers = res.adapterResponse.sasmembers | ||||||
|           this.groupMemberCount = res.sasmembers.length |           this.groupMemberCount = res.adapterResponse.sasmembers.length | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ import { RouterModule, Routes } from '@angular/router' | |||||||
| import { HomeRouteComponent } from '../routes/home-route/home-route.component' | import { HomeRouteComponent } from '../routes/home-route/home-route.component' | ||||||
| import { HomeComponent } from './home.component' | import { HomeComponent } from './home.component' | ||||||
| import { XLMapModule } from '../xlmap/xlmap.module' | import { XLMapModule } from '../xlmap/xlmap.module' | ||||||
|  | import { MultiDatasetModule } from '../multi-dataset/multi-dataset.module' | ||||||
|  |  | ||||||
| const routes: Routes = [ | const routes: Routes = [ | ||||||
|   { |   { | ||||||
| @@ -11,7 +12,8 @@ const routes: Routes = [ | |||||||
|     children: [ |     children: [ | ||||||
|       { path: '', pathMatch: 'full', redirectTo: 'tables' }, |       { path: '', pathMatch: 'full', redirectTo: 'tables' }, | ||||||
|       { path: 'tables', component: HomeComponent }, |       { path: 'tables', component: HomeComponent }, | ||||||
|       { path: 'files', loadChildren: () => XLMapModule } |       { path: 'excel-maps', loadChildren: () => XLMapModule }, | ||||||
|  |       { path: 'multi-load', loadChildren: () => MultiDatasetModule } | ||||||
|     ] |     ] | ||||||
|   } |   } | ||||||
| ] | ] | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ | |||||||
|       <div class="tree-search-wrapper"> |       <div class="tree-search-wrapper"> | ||||||
|         <input |         <input | ||||||
|           clrInput |           clrInput | ||||||
|  |           appStealFocus | ||||||
|           #searchLibTreeInput |           #searchLibTreeInput | ||||||
|           placeholder="Libraries" |           placeholder="Libraries" | ||||||
|           name="input" |           name="input" | ||||||
| @@ -46,6 +47,7 @@ | |||||||
|         <clr-tree-node *ngIf="library['tables']" class="search-node"> |         <clr-tree-node *ngIf="library['tables']" class="search-node"> | ||||||
|           <div class="tree-search-wrapper"> |           <div class="tree-search-wrapper"> | ||||||
|             <input |             <input | ||||||
|  |               appStealFocus | ||||||
|               clrInput |               clrInput | ||||||
|               #searchTreeInput |               #searchTreeInput | ||||||
|               placeholder="Tables" |               placeholder="Tables" | ||||||
| @@ -85,7 +87,7 @@ | |||||||
|               (click)="!tableLocked ? onTableClick(libTable, library) : ''" |               (click)="!tableLocked ? onTableClick(libTable, library) : ''" | ||||||
|               class="clr-treenode-link" |               class="clr-treenode-link" | ||||||
|               [class.dc-locked-control]="tableLocked" |               [class.dc-locked-control]="tableLocked" | ||||||
|               [class.table-active]="libTabActive(library.LIBRARYREF, libTable)" |               [class.active]="libTabActive(library.LIBRARYREF, libTable)" | ||||||
|             > |             > | ||||||
|               <ng-container [ngSwitch]="libTable.includes('-FC')"> |               <ng-container [ngSwitch]="libTable.includes('-FC')"> | ||||||
|                 <clr-icon *ngSwitchCase="true" shape="bolt"></clr-icon> |                 <clr-icon *ngSwitchCase="true" shape="bolt"></clr-icon> | ||||||
| @@ -126,12 +128,20 @@ | |||||||
|         size="60" |         size="60" | ||||||
|         class="is-info icon-dc-fill" |         class="is-info icon-dc-fill" | ||||||
|       ></clr-icon> |       ></clr-icon> | ||||||
|       <h3 *ngIf="treeNodeLibraries?.length! > 0" class="text-center color-gray"> |       <p | ||||||
|  |         *ngIf="treeNodeLibraries?.length! > 0" | ||||||
|  |         class="text-center color-gray mt-10" | ||||||
|  |         cds-text="section" | ||||||
|  |       > | ||||||
|         Please select a table |         Please select a table | ||||||
|       </h3> |       </p> | ||||||
|       <h3 *ngIf="treeNodeLibraries?.length! < 1" class="text-center color-gray"> |       <p | ||||||
|  |         *ngIf="treeNodeLibraries?.length! < 1" | ||||||
|  |         class="text-center color-gray mt-10" | ||||||
|  |         cds-text="section" | ||||||
|  |       > | ||||||
|         No Editable Tables Configured |         No Editable Tables Configured | ||||||
|       </h3> |       </p> | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
| </div> | </div> | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core' | |||||||
| import { ActivatedRoute, Router } from '@angular/router' | import { ActivatedRoute, Router } from '@angular/router' | ||||||
| import { AppService, LicenceService, SasService } from '../services' | import { AppService, LicenceService, SasService } from '../services' | ||||||
| import { LicenseKeyData } from '../models/LicenseKeyData' | import { LicenseKeyData } from '../models/LicenseKeyData' | ||||||
|  | import { RequestWrapperResponse } from '../models/request-wrapper/RequestWrapperResponse' | ||||||
|  |  | ||||||
| enum LicenseActions { | enum LicenseActions { | ||||||
|   key = 'key', |   key = 'key', | ||||||
| @@ -116,8 +117,12 @@ export class LicensingComponent implements OnInit { | |||||||
|  |  | ||||||
|     this.sasService |     this.sasService | ||||||
|       .request('admin/registerkey', table) |       .request('admin/registerkey', table) | ||||||
|       .then((res: any) => { |       .then((res: RequestWrapperResponse) => { | ||||||
|         if (res.return && res.return[0] && res.return[0].MSG === 'SUCCESS') { |         if ( | ||||||
|  |           res.adapterResponse.return && | ||||||
|  |           res.adapterResponse.return[0] && | ||||||
|  |           res.adapterResponse.return[0].MSG === 'SUCCESS' | ||||||
|  |         ) { | ||||||
|           location.replace(location.href.split('#')[0]) |           location.replace(location.href.split('#')[0]) | ||||||
|         } |         } | ||||||
|       }) |       }) | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
|     <clr-tree-node *ngIf="libraryList" class="search-node"> |     <clr-tree-node *ngIf="libraryList" class="search-node"> | ||||||
|       <div class="tree-search-wrapper"> |       <div class="tree-search-wrapper"> | ||||||
|         <input |         <input | ||||||
|  |           appStealFocus | ||||||
|           clrInput |           clrInput | ||||||
|           #searchLibTreeInput |           #searchLibTreeInput | ||||||
|           placeholder="Libraries" |           placeholder="Libraries" | ||||||
| @@ -42,6 +43,7 @@ | |||||||
|         <clr-tree-node *ngIf="library['tables']" class="search-node"> |         <clr-tree-node *ngIf="library['tables']" class="search-node"> | ||||||
|           <div class="tree-search-wrapper"> |           <div class="tree-search-wrapper"> | ||||||
|             <input |             <input | ||||||
|  |               appStealFocus | ||||||
|               clrInput |               clrInput | ||||||
|               #searchTreeInput |               #searchTreeInput | ||||||
|               placeholder="Tables" |               placeholder="Tables" | ||||||
| @@ -85,6 +87,7 @@ | |||||||
|           <clr-tree-node *ngIf="libTable['columns']" class="search-node"> |           <clr-tree-node *ngIf="libTable['columns']" class="search-node"> | ||||||
|             <div class="tree-search-wrapper"> |             <div class="tree-search-wrapper"> | ||||||
|               <input |               <input | ||||||
|  |                 appStealFocus | ||||||
|                 clrInput |                 clrInput | ||||||
|                 #searchTreeInput |                 #searchTreeInput | ||||||
|                 placeholder="Columns" |                 placeholder="Columns" | ||||||
| @@ -138,7 +141,9 @@ | |||||||
|         size="60" |         size="60" | ||||||
|         class="is-info icon-dc-fill" |         class="is-info icon-dc-fill" | ||||||
|       ></clr-icon> |       ></clr-icon> | ||||||
|       <h3 class="text-center color-gray">Please select a column or table</h3> |       <p class="text-center color-gray mt-10" cds-text="section"> | ||||||
|  |         Please select a column or table | ||||||
|  |       </p> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|     <ng-container *ngIf="column || table"> |     <ng-container *ngIf="column || table"> | ||||||
| @@ -180,13 +185,13 @@ | |||||||
|             <button |             <button | ||||||
|               (click)="limitDotDepth = true" |               (click)="limitDotDepth = true" | ||||||
|               type="button" |               type="button" | ||||||
|               class="btn btn-outline" |               class="btn btn-outline mr-5" | ||||||
|             > |             > | ||||||
|               Limit depth |               Limit depth | ||||||
|             </button> |             </button> | ||||||
|  |  | ||||||
|             <!-- <button class="btn btn-outline" (click)='showSvg()'> Open in New Tab </button> --> |             <!-- <button class="btn btn-outline" (click)='showSvg()'> Open in New Tab </button> --> | ||||||
|             <div class="btn-group d-block"> |             <div class="btn-group direction d-block"> | ||||||
|               <div |               <div | ||||||
|                 class="radio btn" |                 class="radio btn" | ||||||
|                 (click)=" |                 (click)=" | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
|  | @import '../../colors.scss'; | ||||||
|  |  | ||||||
| .toggle-switch input[type=checkbox]:checked+label:before { | .toggle-switch input[type=checkbox]:checked+label:before { | ||||||
|     border-color: #314351; |     border-color: $headerBackground; | ||||||
|     background-color: #314351!important; |     background-color: $headerBackground !important; | ||||||
|     transition: .15s ease-in; |     transition: .15s ease-in; | ||||||
|     transition-property: border-color,background-color; |     transition-property: border-color,background-color; | ||||||
| } | } | ||||||
| @@ -41,6 +43,10 @@ clr-tree-node button { | |||||||
|     white-space: nowrap; |     white-space: nowrap; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .btn-group.direction { | ||||||
|  |     margin-left: var(--cds-global-space-6); | ||||||
|  | } | ||||||
|  |  | ||||||
| .graph-render-spinner { | .graph-render-spinner { | ||||||
|     position: absolute; |     position: absolute; | ||||||
|     top: 0; |     top: 0; | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import { SasService } from '../services/sas.service' | |||||||
| import * as saveSvg from 'save-svg-as-png' | import * as saveSvg from 'save-svg-as-png' | ||||||
| import { LoggerService } from '../services/logger.service' | import { LoggerService } from '../services/logger.service' | ||||||
| import { LicenceService } from '../services/licence.service' | import { LicenceService } from '../services/licence.service' | ||||||
|  | import { RequestWrapperResponse } from '../models/request-wrapper/RequestWrapperResponse' | ||||||
| const moment = require('moment') | const moment = require('moment') | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
| @@ -115,8 +116,8 @@ export class LineageComponent { | |||||||
|  |  | ||||||
|     await this.sasService |     await this.sasService | ||||||
|       .request('lineage/getmetacols', libTable) |       .request('lineage/getmetacols', libTable) | ||||||
|       .then((res: any) => { |       .then((res: RequestWrapperResponse) => { | ||||||
|         this.columnsList = res.metacols |         this.columnsList = res.adapterResponse.metacols | ||||||
|         if (this.columnsList && this.columnsList.length > 0) { |         if (this.columnsList && this.columnsList.length > 0) { | ||||||
|           // this.column = this.columnsList[0]['COLURI'] |           // this.column = this.columnsList[0]['COLURI'] | ||||||
|  |  | ||||||
| @@ -174,8 +175,8 @@ export class LineageComponent { | |||||||
|     let libTable = { SASControlTable: [{ liburi: $event }] } |     let libTable = { SASControlTable: [{ liburi: $event }] } | ||||||
|     await this.sasService |     await this.sasService | ||||||
|       .request('lineage/getmetatables', libTable) |       .request('lineage/getmetatables', libTable) | ||||||
|       .then((res: any) => { |       .then((res: RequestWrapperResponse) => { | ||||||
|         this.tablesList = res.metatables |         this.tablesList = res.adapterResponse.metatables | ||||||
|  |  | ||||||
|         if (this.tablesList && this.tablesList.length > 0) { |         if (this.tablesList && this.tablesList.length > 0) { | ||||||
|           library['tables'] = this.tablesList |           library['tables'] = this.tablesList | ||||||
| @@ -295,8 +296,8 @@ export class LineageComponent { | |||||||
|       } else { |       } else { | ||||||
|         await this.sasService |         await this.sasService | ||||||
|           .request('public/viewlibs', null) |           .request('public/viewlibs', null) | ||||||
|           .then((res: any) => { |           .then((res: RequestWrapperResponse) => { | ||||||
|             this.libraryList = res.saslibs |             this.libraryList = res.adapterResponse.saslibs | ||||||
|             this.helperService.displayLibraries(this.libraryList) |             this.helperService.displayLibraries(this.libraryList) | ||||||
|  |  | ||||||
|             if (this.libraryList) { |             if (this.libraryList) { | ||||||
| @@ -402,8 +403,8 @@ export class LineageComponent { | |||||||
|     return new Promise<void>((resolve, reject) => { |     return new Promise<void>((resolve, reject) => { | ||||||
|       this.sasService |       this.sasService | ||||||
|         .request('lineage/fetchtablelineage', libTable) |         .request('lineage/fetchtablelineage', libTable) | ||||||
|         .then(async (res: any) => { |         .then(async (res: RequestWrapperResponse) => { | ||||||
|           if (res.flatdata.length > 0) { |           if (res.adapterResponse.flatdata.length > 0) { | ||||||
|             if (this.licenceService.checkLineageLimit()) { |             if (this.licenceService.checkLineageLimit()) { | ||||||
|               this.eventService.showInfoModal( |               this.eventService.showInfoModal( | ||||||
|                 'Notice', |                 'Notice', | ||||||
| @@ -421,20 +422,22 @@ export class LineageComponent { | |||||||
|           } |           } | ||||||
|  |  | ||||||
|           this.lineageTableName = |           this.lineageTableName = | ||||||
|             res.info[0].LIBREF + '.' + res.info[0].TABLENAME |             res.adapterResponse.info[0].LIBREF + | ||||||
|  |             '.' + | ||||||
|  |             res.adapterResponse.info[0].TABLENAME | ||||||
|  |  | ||||||
|           let dotArray = res.finalfinal |           let dotArray = res.adapterResponse.finalfinal | ||||||
|           let vizTmp: string = '' |           let vizTmp: string = '' | ||||||
|  |  | ||||||
|           for (let i = 0; i < dotArray.length; i++) { |           for (let i = 0; i < dotArray.length; i++) { | ||||||
|             vizTmp += unescape(dotArray[i].LINE) + '\n' |             vizTmp += unescape(dotArray[i].LINE) + '\n' | ||||||
|           } |           } | ||||||
|  |  | ||||||
|           this.flatdata = res.flatdata |           this.flatdata = res.adapterResponse.flatdata | ||||||
|  |  | ||||||
|           if (this.libraryList) { |           if (this.libraryList) { | ||||||
|             let libraryToSelect = this.libraryList.find((library: any) => |             let libraryToSelect = this.libraryList.find((library: any) => | ||||||
|               res.info[0].LIBURI.toUpperCase().includes( |               res.adapterResponse.info[0].LIBURI.toUpperCase().includes( | ||||||
|                 library.LIBRARYID.toUpperCase() |                 library.LIBRARYID.toUpperCase() | ||||||
|               ) |               ) | ||||||
|             ) |             ) | ||||||
| @@ -450,7 +453,7 @@ export class LineageComponent { | |||||||
|               if (libraryToSelect['tables']) { |               if (libraryToSelect['tables']) { | ||||||
|                 tableToSelect = libraryToSelect['tables'].find((table: any) => |                 tableToSelect = libraryToSelect['tables'].find((table: any) => | ||||||
|                   table.TABLEURI.toUpperCase().includes( |                   table.TABLEURI.toUpperCase().includes( | ||||||
|                     res.info[0].TABLEID.toUpperCase() |                     res.adapterResponse.info[0].TABLEID.toUpperCase() | ||||||
|                   ) |                   ) | ||||||
|                 ) |                 ) | ||||||
|  |  | ||||||
| @@ -495,10 +498,10 @@ export class LineageComponent { | |||||||
|             .replace(/\sds:/g, '\nds:') |             .replace(/\sds:/g, '\nds:') | ||||||
|             .replace(/\s\n/g, '\n') |             .replace(/\s\n/g, '\n') | ||||||
|  |  | ||||||
|           this.idlookup = res.idlookup |           this.idlookup = res.adapterResponse.idlookup | ||||||
|  |  | ||||||
|           if (res.finalfinal.length > this.largeDotFileLimit) { |           if (res.adapterResponse.finalfinal.length > this.largeDotFileLimit) { | ||||||
|             this.largeDotFileLines = res.finalfinal.length |             this.largeDotFileLines = res.adapterResponse.finalfinal.length | ||||||
|           } else { |           } else { | ||||||
|             this.buildGraph() |             this.buildGraph() | ||||||
|           } |           } | ||||||
| @@ -619,8 +622,8 @@ export class LineageComponent { | |||||||
|     return new Promise<void>((resolve, reject) => { |     return new Promise<void>((resolve, reject) => { | ||||||
|       this.sasService |       this.sasService | ||||||
|         .request('lineage/fetchcollineage', libTable) |         .request('lineage/fetchcollineage', libTable) | ||||||
|         .then(async (res: any) => { |         .then(async (res: RequestWrapperResponse) => { | ||||||
|           if (res.flatdata.length > 0) { |           if (res.adapterResponse.flatdata.length > 0) { | ||||||
|             if (this.licenceService.checkLineageLimit()) { |             if (this.licenceService.checkLineageLimit()) { | ||||||
|               this.eventService.showInfoModal( |               this.eventService.showInfoModal( | ||||||
|                 'Notice', |                 'Notice', | ||||||
| @@ -631,18 +634,21 @@ export class LineageComponent { | |||||||
|             } |             } | ||||||
|           } |           } | ||||||
|  |  | ||||||
|           if (typeof res === 'string') { |           if (typeof res.adapterResponse === 'string') { | ||||||
|             this.vizInput = 'digraph G {SAS Error}' |             this.vizInput = 'digraph G {SAS Error}' | ||||||
|             this.buildGraph() |             this.buildGraph() | ||||||
|             return |             return | ||||||
|           } |           } | ||||||
|  |  | ||||||
|           this.lineageTableName = res.info[0].LIBREF + '.' + res.info[0].TABNAME |           this.lineageTableName = | ||||||
|           this.lineageColumnName = res.info[0].COLNAME |             res.adapterResponse.info[0].LIBREF + | ||||||
|  |             '.' + | ||||||
|  |             res.adapterResponse.info[0].TABNAME | ||||||
|  |           this.lineageColumnName = res.adapterResponse.info[0].COLNAME | ||||||
|  |  | ||||||
|           this.idlookup = res.idlookup |           this.idlookup = res.adapterResponse.idlookup | ||||||
|  |  | ||||||
|           let dotArray = res.fromsas |           let dotArray = res.adapterResponse.fromsas | ||||||
|           let vizTmp: string = '' |           let vizTmp: string = '' | ||||||
|           for (let i = 0; i < dotArray.length; i++) { |           for (let i = 0; i < dotArray.length; i++) { | ||||||
|             vizTmp += unescape(dotArray[i].STRING) + '\n' |             vizTmp += unescape(dotArray[i].STRING) + '\n' | ||||||
| @@ -653,11 +659,11 @@ export class LineageComponent { | |||||||
|             .replace(/\sds:/g, '\nds:') |             .replace(/\sds:/g, '\nds:') | ||||||
|             .replace(/\s\n/g, '\n') |             .replace(/\s\n/g, '\n') | ||||||
|  |  | ||||||
|           this.flatdata = res.flatdata |           this.flatdata = res.adapterResponse.flatdata | ||||||
|  |  | ||||||
|           if (this.libraryList) { |           if (this.libraryList) { | ||||||
|             let libraryToSelect = this.libraryList.find((library: any) => |             let libraryToSelect = this.libraryList.find((library: any) => | ||||||
|               res.info[0]?.LIBURI?.toUpperCase()?.includes( |               res.adapterResponse.info[0]?.LIBURI?.toUpperCase()?.includes( | ||||||
|                 library?.LIBRARYID?.toUpperCase() |                 library?.LIBRARYID?.toUpperCase() | ||||||
|               ) |               ) | ||||||
|             ) |             ) | ||||||
| @@ -672,7 +678,8 @@ export class LineageComponent { | |||||||
|  |  | ||||||
|               if (libraryToSelect['tables']) { |               if (libraryToSelect['tables']) { | ||||||
|                 tableToSelect = libraryToSelect['tables'].find( |                 tableToSelect = libraryToSelect['tables'].find( | ||||||
|                   (table: any) => table.TABLEURI === res.info[0].TABURI |                   (table: any) => | ||||||
|  |                     table.TABLEURI === res.adapterResponse.info[0].TABURI | ||||||
|                 ) |                 ) | ||||||
|  |  | ||||||
|                 if (tableToSelect) { |                 if (tableToSelect) { | ||||||
| @@ -714,8 +721,8 @@ export class LineageComponent { | |||||||
|             } |             } | ||||||
|           } |           } | ||||||
|  |  | ||||||
|           if (res.fromsas.length > this.largeDotFileLimit) { |           if (res.adapterResponse.fromsas.length > this.largeDotFileLimit) { | ||||||
|             this.largeDotFileLines = res.fromsas.length |             this.largeDotFileLines = res.adapterResponse.fromsas.length | ||||||
|           } else { |           } else { | ||||||
|             this.buildGraph() |             this.buildGraph() | ||||||
|           } |           } | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ | |||||||
|     <clr-tree-node *ngIf="metaDataList" class="search-node"> |     <clr-tree-node *ngIf="metaDataList" class="search-node"> | ||||||
|       <div class="tree-search-wrapper"> |       <div class="tree-search-wrapper"> | ||||||
|         <input |         <input | ||||||
|  |           appStealFocus | ||||||
|           clrInput |           clrInput | ||||||
|           #searchLibTreeInput |           #searchLibTreeInput | ||||||
|           placeholder="search SAS Types" |           placeholder="search SAS Types" | ||||||
| @@ -72,7 +73,9 @@ | |||||||
|         size="60" |         size="60" | ||||||
|         class="is-info icon-dc-fill" |         class="is-info icon-dc-fill" | ||||||
|       ></clr-icon> |       ></clr-icon> | ||||||
|       <h3 class="text-center color-gray">Please select a type</h3> |       <p class="text-center color-gray mt-10" cds-text="section"> | ||||||
|  |         Please select a type | ||||||
|  |       </p> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|     <div class="loadingSpinner" *ngIf="loading"> |     <div class="loadingSpinner" *ngIf="loading"> | ||||||
| @@ -123,12 +126,18 @@ | |||||||
|                         [class.object-header]="!entry.count" |                         [class.object-header]="!entry.count" | ||||||
|                         class="full-width" |                         class="full-width" | ||||||
|                       > |                       > | ||||||
|                         <clr-icon |                         <div> | ||||||
|                           *ngIf="!entry.count" |                           <clr-icon | ||||||
|                           shape="rack-server" |                             *ngIf="!entry.count" | ||||||
|                         ></clr-icon> |                             shape="rack-server" | ||||||
|                         <clr-icon *ngIf="entry.count" shape="block"></clr-icon> |                           ></clr-icon> | ||||||
|                         {{ entry.display }} |                           <clr-icon | ||||||
|  |                             *ngIf="entry.count" | ||||||
|  |                             shape="block" | ||||||
|  |                           ></clr-icon> | ||||||
|  |                           {{ entry.display }} | ||||||
|  |                         </div> | ||||||
|  |  | ||||||
|                         <p class="float-right object-uri" *ngIf="!entry.count"> |                         <p class="float-right object-uri" *ngIf="!entry.count"> | ||||||
|                           {{ entry.URI }} |                           {{ entry.URI }} | ||||||
|                         </p> |                         </p> | ||||||
| @@ -163,9 +172,15 @@ | |||||||
|                 [clrExpandable]="true" |                 [clrExpandable]="true" | ||||||
|               > |               > | ||||||
|                 <div [class.object-header]="!entry.count" class="full-width"> |                 <div [class.object-header]="!entry.count" class="full-width"> | ||||||
|                   <clr-icon *ngIf="!entry.count" shape="rack-server"></clr-icon> |                   <div> | ||||||
|                   <clr-icon *ngIf="entry.count" shape="block"></clr-icon> |                     <clr-icon | ||||||
|                   {{ entry.display }} |                       *ngIf="!entry.count" | ||||||
|  |                       shape="rack-server" | ||||||
|  |                     ></clr-icon> | ||||||
|  |                     <clr-icon *ngIf="entry.count" shape="block"></clr-icon> | ||||||
|  |                     {{ entry.display }} | ||||||
|  |                   </div> | ||||||
|  |  | ||||||
|                   <p class="float-right object-uri" *ngIf="!entry.count"> |                   <p class="float-right object-uri" *ngIf="!entry.count"> | ||||||
|                     {{ entry.URI }} |                     {{ entry.URI }} | ||||||
|                   </p> |                   </p> | ||||||
|   | |||||||
| @@ -1,14 +1,27 @@ | |||||||
|  | ::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{ | .objects-col{ | ||||||
|   height: 75vh; |   height: 75vh; | ||||||
|   overflow: scroll; |   overflow: scroll; | ||||||
|   border: 1px solid #cccccc; |   border: 1px solid #cccccc; | ||||||
|   background: white; |  | ||||||
|   border-radius: 4px; |   border-radius: 4px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .cols-head { | .cols-head { | ||||||
|   background: #fafafa; |  | ||||||
|   border: 1px solid #cccccc; |   border: 1px solid #cccccc; | ||||||
|   padding: 10px; |   padding: 10px; | ||||||
|   display: flex; |   display: flex; | ||||||
| @@ -40,11 +53,13 @@ | |||||||
|   margin-top: 5px; |   margin-top: 5px; | ||||||
| } | } | ||||||
| .object-header{ | .object-header{ | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   justify-content: space-between; | ||||||
|   padding-left: 3px; |   padding-left: 3px; | ||||||
|   padding-right: 3px; |   padding-right: 3px; | ||||||
| } | } | ||||||
| .object-header:hover{ | .object-header:hover{ | ||||||
|   background-color: #d8e3e9; |  | ||||||
|   border-radius: 3px; |   border-radius: 3px; | ||||||
| } | } | ||||||
| .datagrid-host{ | .datagrid-host{ | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import { SasService } from '../services/sas.service' | |||||||
| import { globals } from '../_globals' | import { globals } from '../_globals' | ||||||
|  |  | ||||||
| import { Injectable } from '@angular/core' | import { Injectable } from '@angular/core' | ||||||
|  | import { RequestWrapperResponse } from '../models/request-wrapper/RequestWrapperResponse' | ||||||
|  |  | ||||||
| interface MetaData { | interface MetaData { | ||||||
|   NAME: any |   NAME: any | ||||||
| @@ -109,44 +110,52 @@ export class MetadataComponent implements OnInit { | |||||||
|       this.metatypesLoading = false |       this.metatypesLoading = false | ||||||
|       this.metaDataSearch = globals.metadata.metaDataSearch |       this.metaDataSearch = globals.metadata.metaDataSearch | ||||||
|     } else { |     } else { | ||||||
|       this.sasService.request('metanav/metatypes', null).then((res: any) => { |       this.sasService | ||||||
|         this.metaDataList = res.types |         .request('metanav/metatypes', null) | ||||||
|         globals.metadata.metaDataList = this.metaDataList |         .then((res: RequestWrapperResponse) => { | ||||||
|         this.loading = false |           this.metaDataList = res.adapterResponse.types | ||||||
|         this.metatypesLoading = false |           globals.metadata.metaDataList = this.metaDataList | ||||||
|       }) |           this.loading = false | ||||||
|  |           this.metatypesLoading = false | ||||||
|  |         }) | ||||||
|  |  | ||||||
|       this.sasService.request('metanav/metarepos', null).then((res: any) => { |       this.sasService | ||||||
|         let foundation = false |         .request('metanav/metarepos', null) | ||||||
|         this.repositories = [] |         .then((res: RequestWrapperResponse) => { | ||||||
|         for (let index = 0; index < res.outrepos.length; index++) { |           let foundation = false | ||||||
|           this.repositories.push(res.outrepos[index].NAME) |           this.repositories = [] | ||||||
|           if (res.outrepos[index].NAME === 'Foundation') { |           for ( | ||||||
|             foundation = true |             let index = 0; | ||||||
|  |             index < res.adapterResponse.outrepos.length; | ||||||
|  |             index++ | ||||||
|  |           ) { | ||||||
|  |             this.repositories.push(res.adapterResponse.outrepos[index].NAME) | ||||||
|  |             if (res.adapterResponse.outrepos[index].NAME === 'Foundation') { | ||||||
|  |               foundation = true | ||||||
|  |             } | ||||||
|           } |           } | ||||||
|         } |           if (foundation) { | ||||||
|         if (foundation) { |             this.repository = 'Foundation' | ||||||
|           this.repository = 'Foundation' |           } else { | ||||||
|         } else { |             this.repository = res.adapterResponse.outrepos[0].NAME | ||||||
|           this.repository = res.outrepos[0].NAME |           } | ||||||
|         } |           globals.metadata.metaRepositories = this.repositories | ||||||
|         globals.metadata.metaRepositories = this.repositories |           globals.metadata.selectedRepository = this.repository | ||||||
|         globals.metadata.selectedRepository = this.repository |           if (this.objectRoute) { | ||||||
|         if (this.objectRoute) { |             this.eventService.closeSidebar() | ||||||
|           this.eventService.closeSidebar() |             this.showData = true | ||||||
|           this.showData = true |             let name = '' | ||||||
|           let name = '' |             let id = this.route.snapshot.params['objectID'] | ||||||
|           let id = this.route.snapshot.params['objectID'] |             // let temp = this.router.url.split("%20").join(" ").split("/").reverse(); | ||||||
|           // let temp = this.router.url.split("%20").join(" ").split("/").reverse(); |             this.metaObjectList = [] | ||||||
|           this.metaObjectList = [] |             this.metaObjectList.push({ ID: id, NAME: name }) | ||||||
|           this.metaObjectList.push({ ID: id, NAME: name }) |             this.metaObjectShowList = this.metaObjectList | ||||||
|           this.metaObjectShowList = this.metaObjectList |             this.metaObjectOnClick( | ||||||
|           this.metaObjectOnClick( |               this.metaObjectShowList[0].ID, | ||||||
|             this.metaObjectShowList[0].ID, |               this.metaObjectShowList[0] | ||||||
|             this.metaObjectShowList[0] |             ) | ||||||
|           ) |           } | ||||||
|         } |         }) | ||||||
|       }) |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -183,56 +192,64 @@ export class MetadataComponent implements OnInit { | |||||||
|     const data: any = { |     const data: any = { | ||||||
|       SASControlTable: [{ metatype: $event, repo: this.repository }] |       SASControlTable: [{ metatype: $event, repo: this.repository }] | ||||||
|     } |     } | ||||||
|     this.sasService.request('metanav/metaobjects', data).then((res: any) => { |     this.sasService | ||||||
|       this.metaObjectList = res.objects |       .request('metanav/metaobjects', data) | ||||||
|       this.getMetaObjectAttributes(this.metaObjectSize) |       .then((res: RequestWrapperResponse) => { | ||||||
|       this.loading = false |         this.metaObjectList = res.adapterResponse.objects | ||||||
|       this.assoTypeSelected = $event |         this.getMetaObjectAttributes(this.metaObjectSize) | ||||||
|       this.eventService.closeSidebar() |         this.loading = false | ||||||
|       this.showData = true |         this.assoTypeSelected = $event | ||||||
|     }) |         this.eventService.closeSidebar() | ||||||
|  |         this.showData = true | ||||||
|  |       }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public async selectmetaObject($event: any, metaData?: any) { |   public async selectmetaObject($event: any, metaData?: any) { | ||||||
|     let data: any = { |     let data: any = { | ||||||
|       SASControlTable: [{ objecturi: $event }] |       SASControlTable: [{ objecturi: $event }] | ||||||
|     } |     } | ||||||
|     this.sasService.request('metanav/metadetails', data).then((res: any) => { |     this.sasService | ||||||
|       this.metaObjectAssociations = res.associations |       .request('metanav/metadetails', data) | ||||||
|       this.root$ = of(this.getAssosiationsCount(res.associations)) |       .then((res: RequestWrapperResponse) => { | ||||||
|       this.showAcc = true |         this.metaObjectAssociations = res.adapterResponse.associations | ||||||
|       this.showTable = true |         this.root$ = of( | ||||||
|       let metaObjectName = res.attributes.find( |           this.getAssosiationsCount(res.adapterResponse.associations) | ||||||
|         (x: any) => x.NAME === 'Name' |  | ||||||
|       ).VALUE |  | ||||||
|       this.assoObjectSelected = metaObjectName |  | ||||||
|       metaData.NAME = metaObjectName |  | ||||||
|       let url = this.router.url |  | ||||||
|       if (this.objectRoute) { |  | ||||||
|         // this.location.replaceState(url.slice(0, url.lastIndexOf("object")) + "object/" + $event.slice(1 + $event.indexOf("\\")) + "/" + escape(metaData.NAME)); |  | ||||||
|         this.location.replaceState( |  | ||||||
|           url.slice(0, url.lastIndexOf('object')) + |  | ||||||
|             'object/' + |  | ||||||
|             $event.slice(1 + $event.indexOf('\\')) |  | ||||||
|         ) |         ) | ||||||
|       } else { |         this.showAcc = true | ||||||
|         // this.location.replaceState(url + "/object/" + $event.slice(1 + $event.indexOf("\\")) + "/" + escape(metaData.NAME)); |         this.showTable = true | ||||||
|         this.location.replaceState( |         let metaObjectName = res.adapterResponse.attributes.find( | ||||||
|           url + '/object/' + $event.slice(1 + $event.indexOf('\\')) |           (x: any) => x.NAME === 'Name' | ||||||
|         ) |         ).VALUE | ||||||
|       } |         this.assoObjectSelected = metaObjectName | ||||||
|       this.metaObjectAttributes = res.attributes |         metaData.NAME = metaObjectName | ||||||
|     }) |         let url = this.router.url | ||||||
|  |         if (this.objectRoute) { | ||||||
|  |           // this.location.replaceState(url.slice(0, url.lastIndexOf("object")) + "object/" + $event.slice(1 + $event.indexOf("\\")) + "/" + escape(metaData.NAME)); | ||||||
|  |           this.location.replaceState( | ||||||
|  |             url.slice(0, url.lastIndexOf('object')) + | ||||||
|  |               'object/' + | ||||||
|  |               $event.slice(1 + $event.indexOf('\\')) | ||||||
|  |           ) | ||||||
|  |         } else { | ||||||
|  |           // this.location.replaceState(url + "/object/" + $event.slice(1 + $event.indexOf("\\")) + "/" + escape(metaData.NAME)); | ||||||
|  |           this.location.replaceState( | ||||||
|  |             url + '/object/' + $event.slice(1 + $event.indexOf('\\')) | ||||||
|  |           ) | ||||||
|  |         } | ||||||
|  |         this.metaObjectAttributes = res.adapterResponse.attributes | ||||||
|  |       }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public async selectAssosiationsDetails($event: any, metaData?: any) { |   public async selectAssosiationsDetails($event: any, metaData?: any) { | ||||||
|     let data: any = { |     let data: any = { | ||||||
|       SASControlTable: [{ objecturi: $event }] |       SASControlTable: [{ objecturi: $event }] | ||||||
|     } |     } | ||||||
|     this.sasService.request('metanav/metadetails', data).then((res: any) => { |     this.sasService | ||||||
|       this.metaObjectAttributes = res.attributes |       .request('metanav/metadetails', data) | ||||||
|       this.showTable = true |       .then((res: RequestWrapperResponse) => { | ||||||
|     }) |         this.metaObjectAttributes = res.adapterResponse.attributes | ||||||
|  |         this.showTable = true | ||||||
|  |       }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public getAssosiationsCount(assosiationList: Array<any>) { |   public getAssosiationsCount(assosiationList: Array<any>) { | ||||||
| @@ -244,7 +261,7 @@ export class MetadataComponent implements OnInit { | |||||||
|           details: [] |           details: [] | ||||||
|         }) |         }) | ||||||
|       } |       } | ||||||
|       let assocObj = assosiationsHash.get(assosiation.ASSOC) |       let assocObj: any = assosiationsHash.get(assosiation.ASSOC) | ||||||
|       assocObj.count++ |       assocObj.count++ | ||||||
|       assocObj.details.push({ |       assocObj.details.push({ | ||||||
|         ASSOCURI: assosiation.ASSOCURI, |         ASSOCURI: assosiation.ASSOCURI, | ||||||
| @@ -254,7 +271,7 @@ export class MetadataComponent implements OnInit { | |||||||
|       }) |       }) | ||||||
|     } |     } | ||||||
|     let assocGrouped: Array<any> = [] |     let assocGrouped: Array<any> = [] | ||||||
|     assosiationsHash.forEach(function (val, key) { |     assosiationsHash.forEach(function (val: any, key) { | ||||||
|       assocGrouped.push({ |       assocGrouped.push({ | ||||||
|         ASSOC: key, |         ASSOC: key, | ||||||
|         count: val.count, |         count: val.count, | ||||||
| @@ -294,9 +311,9 @@ export class MetadataComponent implements OnInit { | |||||||
|     } |     } | ||||||
|     return this.sasService |     return this.sasService | ||||||
|       .request('metanav/metadetails', data) |       .request('metanav/metadetails', data) | ||||||
|       .then((res: any) => { |       .then((res: RequestWrapperResponse) => { | ||||||
|         this.showTable = true |         this.showTable = true | ||||||
|         this.metaObjectAttributes = res.attributes |         this.metaObjectAttributes = res.adapterResponse.attributes | ||||||
|         this.assoObjectSelected = asso.NAME |         this.assoObjectSelected = asso.NAME | ||||||
|         let url = this.router.url |         let url = this.router.url | ||||||
|         if (this.objectRoute) { |         if (this.objectRoute) { | ||||||
| @@ -314,7 +331,7 @@ export class MetadataComponent implements OnInit { | |||||||
|               asso.ASSOCURI.slice(1 + asso.ASSOCURI.indexOf('\\')) |               asso.ASSOCURI.slice(1 + asso.ASSOCURI.indexOf('\\')) | ||||||
|           ) |           ) | ||||||
|         } |         } | ||||||
|         return this.getAssosiationsCount(res.associations) |         return this.getAssosiationsCount(res.adapterResponse.associations) | ||||||
|       }) |       }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										9
									
								
								client/src/app/models/AppSettings.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								client/src/app/models/AppSettings.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | export interface AppSettings { | ||||||
|  |   persistSelectedTheme: boolean | ||||||
|  |   selectedTheme: AppThemes | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export enum AppThemes { | ||||||
|  |   light = 'light', | ||||||
|  |   dark = 'dark' | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								client/src/app/models/FileUploadEncoding.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								client/src/app/models/FileUploadEncoding.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | export type FileUploadEncoding = 'UTF-8' | 'WLATIN1' | ||||||
							
								
								
									
										29
									
								
								client/src/app/models/ParseParams.interface.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								client/src/app/models/ParseParams.interface.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | import { DcValidator } from '../shared/dc-validator/dc-validator' | ||||||
|  | import { FileUploadEncoding } from './FileUploadEncoding' | ||||||
|  | import { FileUploader } from './FileUploader.class' | ||||||
|  | import { ExcelRule } from './TableData' | ||||||
|  | import XLSX from 'xlsx' | ||||||
|  |  | ||||||
|  | export interface ParseParams { | ||||||
|  |   file: File | ||||||
|  |   password?: string | ||||||
|  |   dcValidator: DcValidator | ||||||
|  |   /** | ||||||
|  |    * If workbook is provided, parse function will not run a XLSX.read() | ||||||
|  |    * it will use this property instead. So the client must do a file read beforehand | ||||||
|  |    */ | ||||||
|  |   workbook?: XLSX.WorkBook | ||||||
|  |   /** | ||||||
|  |    * Parse function will manipulate and return the uploader array which can be provided with files already in the queue | ||||||
|  |    * Otherwise new empty instance will be created. | ||||||
|  |    */ | ||||||
|  |   uploader?: FileUploader | ||||||
|  |   headerPks: string[] | ||||||
|  |   headerArray: string[] | ||||||
|  |   headerShow: string[] | ||||||
|  |   timeHeaders: string[] | ||||||
|  |   dateHeaders: string[] | ||||||
|  |   dateTimeHeaders: string[] | ||||||
|  |   xlRules: ExcelRule[] | ||||||
|  |   encoding?: FileUploadEncoding | ||||||
|  | } | ||||||
							
								
								
									
										15
									
								
								client/src/app/models/ParseResult.interface.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								client/src/app/models/ParseResult.interface.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | import { FileUploader } from './FileUploader.class' | ||||||
|  | import FoundRangeInfo from './RangeInfo' | ||||||
|  |  | ||||||
|  | export interface ParseResult { | ||||||
|  |   /** | ||||||
|  |    * In case of CSV file, won't be returned | ||||||
|  |    */ | ||||||
|  |   data?: any[] | ||||||
|  |   /** | ||||||
|  |    * In case of CSV file, won't be returned | ||||||
|  |    */ | ||||||
|  |   headerShow?: string[] | ||||||
|  |   rangeSheetRes?: FoundRangeInfo | ||||||
|  |   uploader: FileUploader | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								client/src/app/models/RangeInfo.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								client/src/app/models/RangeInfo.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | export default interface FoundRangeInfo { | ||||||
|  |   found: boolean | ||||||
|  |   sheetName: string | ||||||
|  |   rangeStartAddress: string | ||||||
|  |   rangeEndAddress: string | ||||||
|  |   rangeAddress: string | ||||||
|  |   missingHeaders: MissingHeaders[] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface MissingHeaders { | ||||||
|  |   sheetName: string | ||||||
|  |   missingHeaders: string[] | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								client/src/app/models/SearchDataExcelResult.interface.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								client/src/app/models/SearchDataExcelResult.interface.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | import { MissingHeaders } from './RangeInfo' | ||||||
|  |  | ||||||
|  | export interface SearchDataExcelResult { | ||||||
|  |   missing?: MissingHeaders[] | ||||||
|  |   found?: { | ||||||
|  |     data: any | ||||||
|  |     arrayData: any[] | ||||||
|  |     sheetName: string | ||||||
|  |     headers: string[] | ||||||
|  |     startAddress?: string | ||||||
|  |     endAddress?: string | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -7,4 +7,5 @@ export default interface SheetInfo { | |||||||
|   missingHeaders: string[] |   missingHeaders: string[] | ||||||
|   rangeStartRow: number |   rangeStartRow: number | ||||||
|   rangeStartCol: number |   rangeStartCol: number | ||||||
|  |   rangeAddress?: string | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								client/src/app/models/UploadFile.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								client/src/app/models/UploadFile.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | export interface UploadFileResponse { | ||||||
|  |   adapterResponse: any | ||||||
|  |   log?: string | ||||||
|  | } | ||||||
| @@ -0,0 +1,4 @@ | |||||||
|  | export interface RequestWrapperResponse<responseType = any> { | ||||||
|  |   adapterResponse: responseType | ||||||
|  |   log?: string | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								client/src/app/models/sas/editors-stagedata.model.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								client/src/app/models/sas/editors-stagedata.model.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | import { BaseSASResponse } from './common/BaseSASResponse' | ||||||
|  |  | ||||||
|  | export interface EditorsStageDataSASResponse extends BaseSASResponse { | ||||||
|  |   SYSDATE: string | ||||||
|  |   SYSTIME: string | ||||||
|  |   sasparams: Sasparam[] | ||||||
|  |   _DEBUG: string | ||||||
|  |   _PROGRAM: string | ||||||
|  |   AUTOEXEC: string | ||||||
|  |   MF_GETUSER: string | ||||||
|  |   SYSCC: string | ||||||
|  |   SYSENCODING: string | ||||||
|  |   SYSERRORTEXT: string | ||||||
|  |   SYSHOSTINFOLONG: string | ||||||
|  |   SYSHOSTNAME: string | ||||||
|  |   SYSPROCESSID: string | ||||||
|  |   SYSPROCESSMODE: string | ||||||
|  |   SYSPROCESSNAME: string | ||||||
|  |   SYSJOBID: string | ||||||
|  |   SYSSCPL: string | ||||||
|  |   SYSSITE: string | ||||||
|  |   SYSTCPIPHOSTNAME: string | ||||||
|  |   SYSUSERID: string | ||||||
|  |   SYSVLONG: string | ||||||
|  |   SYSWARNINGTEXT: string | ||||||
|  |   END_DTTM: string | ||||||
|  |   MEMSIZE: string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface Sasparam { | ||||||
|  |   STATUS: string | 'SUCCESS' | ||||||
|  |   DSID: string | ||||||
|  |   URL: string | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								client/src/app/multi-dataset/multi-dataset-routing.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								client/src/app/multi-dataset/multi-dataset-routing.module.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | import { NgModule } from '@angular/core' | ||||||
|  | import { RouterModule, Routes } from '@angular/router' | ||||||
|  |  | ||||||
|  | import { MultiDatasetRouteComponent } from '../routes/multi-dataset-route/multi-dataset-route.component' | ||||||
|  | import { MultiDatasetComponent } from './multi-dataset.component' | ||||||
|  |  | ||||||
|  | const routes: Routes = [ | ||||||
|  |   { | ||||||
|  |     path: '', | ||||||
|  |     component: MultiDatasetRouteComponent, | ||||||
|  |     children: [{ path: '', component: MultiDatasetComponent }] | ||||||
|  |   } | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | @NgModule({ | ||||||
|  |   imports: [RouterModule.forChild(routes)], | ||||||
|  |   exports: [RouterModule] | ||||||
|  | }) | ||||||
|  | export class MultiDatasetRoutingModule {} | ||||||
							
								
								
									
										543
									
								
								client/src/app/multi-dataset/multi-dataset.component.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										543
									
								
								client/src/app/multi-dataset/multi-dataset.component.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,543 @@ | |||||||
|  | <app-sidebar> | ||||||
|  |   <div *ngIf="datasetsLoading" class="my-10-mx-auto text-center"> | ||||||
|  |     <clr-spinner clrMedium></clr-spinner> | ||||||
|  |   </div> | ||||||
|  |  | ||||||
|  |   <div *ngIf="!parsedDatasets.length" class="text-center mb-10"> | ||||||
|  |     <button | ||||||
|  |       (click)="fileUploadInput.click()" | ||||||
|  |       id="browse-file" | ||||||
|  |       class="btn btn-primary btn-sm" | ||||||
|  |       [disabled]="selectedFile !== null || submittingCsv" | ||||||
|  |     > | ||||||
|  |       Browse file | ||||||
|  |     </button> | ||||||
|  |     <input | ||||||
|  |       hidden | ||||||
|  |       #fileUploadInput | ||||||
|  |       id="file-upload" | ||||||
|  |       type="file" | ||||||
|  |       (change)="onFileChange($event)" | ||||||
|  |       multiple | ||||||
|  |     /> | ||||||
|  |   </div> | ||||||
|  |  | ||||||
|  |   <ng-container *ngIf="parsedDatasets.length && !submittedCsvDatasets.length"> | ||||||
|  |     <div *ngIf="!excelsSubmitted" class="text-center mb-10"> | ||||||
|  |       <button (click)="onDiscard()" class="btn btn-danger btn-sm mr-10"> | ||||||
|  |         Discard | ||||||
|  |       </button> | ||||||
|  |       <button | ||||||
|  |         (click)="onSubmitAll()" | ||||||
|  |         id="submit-all" | ||||||
|  |         class="btn btn-primary btn-sm" | ||||||
|  |       > | ||||||
|  |         Submit All | ||||||
|  |       </button> | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |     <p cds-text="caption" class="ml-10 mb-10">Found tables:</p> | ||||||
|  |     <clr-tree> | ||||||
|  |       <clr-tree-node *ngFor="let dataset of parsedDatasets"> | ||||||
|  |         <button | ||||||
|  |           (click)="onParsedDatasetClick(dataset)" | ||||||
|  |           class="clr-treenode-link whitespace-nowrap d-flex clr-align-items-center" | ||||||
|  |           [class.active]="dataset.active" | ||||||
|  |         > | ||||||
|  |           <ng-container *ngIf="dataset.submitResult"> | ||||||
|  |             <cds-icon | ||||||
|  |               *ngIf="dataset.submitResult.error" | ||||||
|  |               status="danger" | ||||||
|  |               shape="exclamation-circle" | ||||||
|  |             ></cds-icon> | ||||||
|  |             <cds-icon | ||||||
|  |               *ngIf="dataset.submitResult.success" | ||||||
|  |               status="success" | ||||||
|  |               shape="check-circle" | ||||||
|  |             ></cds-icon> | ||||||
|  |           </ng-container> | ||||||
|  |  | ||||||
|  |           <ng-container *ngIf="!dataset.submitResult"> | ||||||
|  |             <ng-container *ngIf="dataset.datasource"> | ||||||
|  |               <cds-icon | ||||||
|  |                 *ngIf="!(dataset.datasource.length && dataset.parseResult)" | ||||||
|  |                 status="danger" | ||||||
|  |                 shape="exclamation-circle" | ||||||
|  |               ></cds-icon> | ||||||
|  |               <cds-icon | ||||||
|  |                 *ngIf="dataset.datasource.length && dataset.parseResult" | ||||||
|  |                 shape="table" | ||||||
|  |               ></cds-icon> | ||||||
|  |             </ng-container> | ||||||
|  |  | ||||||
|  |             <ng-container *ngIf="!dataset.datasource"> | ||||||
|  |               <cds-icon *ngIf="!dataset.parsingTable" shape="table"></cds-icon> | ||||||
|  |  | ||||||
|  |               <clr-spinner *ngIf="dataset.parsingTable" clrSmall></clr-spinner> | ||||||
|  |             </ng-container> | ||||||
|  |           </ng-container> | ||||||
|  |  | ||||||
|  |           <span class="ml-5"> {{ dataset.libds }} </span> | ||||||
|  |         </button> | ||||||
|  |       </clr-tree-node> | ||||||
|  |     </clr-tree> | ||||||
|  |   </ng-container> | ||||||
|  |  | ||||||
|  |   <ng-container *ngIf="submittedCsvDatasets.length"> | ||||||
|  |     <p cds-text="caption" class="ml-10 mb-10 mt-10">Submitted tables:</p> | ||||||
|  |     <clr-tree> | ||||||
|  |       <clr-tree-node *ngFor="let dataset of submittedCsvDatasets"> | ||||||
|  |         <button | ||||||
|  |           (click)="onSubmittedCsvDatasetClick(dataset)" | ||||||
|  |           class="clr-treenode-link whitespace-nowrap" | ||||||
|  |           [class.active]="dataset.active" | ||||||
|  |         > | ||||||
|  |           <cds-icon | ||||||
|  |             *ngIf="dataset.error" | ||||||
|  |             status="danger" | ||||||
|  |             shape="exclamation-circle" | ||||||
|  |           ></cds-icon> | ||||||
|  |           <cds-icon | ||||||
|  |             *ngIf="dataset.success" | ||||||
|  |             status="success" | ||||||
|  |             shape="check-circle" | ||||||
|  |           ></cds-icon> | ||||||
|  |           <cds-icon shape="table"></cds-icon> | ||||||
|  |           {{ dataset.libds }} | ||||||
|  |         </button> | ||||||
|  |       </clr-tree-node> | ||||||
|  |     </clr-tree> | ||||||
|  |   </ng-container> | ||||||
|  |  | ||||||
|  |   <!-- <div *ngIf="librariesPaging" class="w-100 text-center"> | ||||||
|  |     <span class="spinner spinner-sm"> Loading... </span> | ||||||
|  |   </div> --> | ||||||
|  | </app-sidebar> | ||||||
|  |  | ||||||
|  | <div #contentArea class="content-area"> | ||||||
|  |   <div class="card no-borders h-100 d-flex clr-flex-column"> | ||||||
|  |     <div | ||||||
|  |       class="header-row clr-row justify-content-between clr-justify-content-center w-100 m-0" | ||||||
|  |     > | ||||||
|  |       <p cds-text="section">Multi Dataset Load</p> | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |     <div | ||||||
|  |       *ngIf="selectedFile === null && !submittingCsv" | ||||||
|  |       class="no-table-selected pointer-events-none" | ||||||
|  |     > | ||||||
|  |       <clr-icon | ||||||
|  |         shape="warning-standard" | ||||||
|  |         size="40" | ||||||
|  |         class="is-info icon-dc-fill" | ||||||
|  |       ></clr-icon> | ||||||
|  |       <p class="text-center color-gray mt-10" cds-text="section"> | ||||||
|  |         Please upload a file | ||||||
|  |       </p> | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |     <ng-container *ngIf="selectedFile !== null || submittingCsv"> | ||||||
|  |       <ng-container *ngIf="!parsedDatasets.length && selectedFile !== null"> | ||||||
|  |         <div class="d-flex clr-justify-content-center mt-15"> | ||||||
|  |           <div class="dataset-input-wrapper"> | ||||||
|  |             <p cds-text="secondary regular" class="mb-5"> | ||||||
|  |               Selected file: <strong>{{ selectedFile.name }}</strong> | ||||||
|  |               <clr-tooltip> | ||||||
|  |                 <cds-icon | ||||||
|  |                   clrTooltipTrigger | ||||||
|  |                   (click)="onDiscardFile()" | ||||||
|  |                   shape="trash" | ||||||
|  |                   status="danger" | ||||||
|  |                   class="ml-5 cursor-pointer" | ||||||
|  |                 ></cds-icon> | ||||||
|  |                 <clr-tooltip-content> Discard the file </clr-tooltip-content> | ||||||
|  |               </clr-tooltip> | ||||||
|  |             </p> | ||||||
|  |             <p cds-text="secondary regular" class="mb-20"> | ||||||
|  |               File size: <strong>{{ selectedFile.sizeMB }} MB</strong> | ||||||
|  |             </p> | ||||||
|  |             <p cds-text="secondary regular" class="mb-15"> | ||||||
|  |               Paste or type the list of datasets to upload: | ||||||
|  |             </p> | ||||||
|  |  | ||||||
|  |             <clr-control-helper class="mb-5" | ||||||
|  |               >Each row is one dataset. We will automatically detect tables by | ||||||
|  |               the sheetname and populate if any.</clr-control-helper | ||||||
|  |             > | ||||||
|  |  | ||||||
|  |             <hot-table | ||||||
|  |               hotId="hotInstanceUserDataset" | ||||||
|  |               id="hotTableUserDataset" | ||||||
|  |               class="mt-15" | ||||||
|  |               [afterGetColHeader]="afterGetColHeader" | ||||||
|  |               [settings]="hotUserDatasets" | ||||||
|  |               [licenseKey]="hotTableLicenseKey" | ||||||
|  |               stretchH="all" | ||||||
|  |             > | ||||||
|  |             </hot-table> | ||||||
|  |  | ||||||
|  |             <div class="dataset-selection-actions text-right mt-10"> | ||||||
|  |               <button | ||||||
|  |                 (click)="onStartParsingFile()" | ||||||
|  |                 id="continue-btn" | ||||||
|  |                 class="btn btn-primary btn-sm" | ||||||
|  |                 [disabled]="!matchedDatasets.length" | ||||||
|  |                 [clrLoading]="uploadLoading" | ||||||
|  |               > | ||||||
|  |                 Continue | ||||||
|  |               </button> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </ng-container> | ||||||
|  |  | ||||||
|  |       <ng-container | ||||||
|  |         *ngIf="parsedDatasets.length && !submittedCsvDatasets.length" | ||||||
|  |       > | ||||||
|  |         <div | ||||||
|  |           *ngIf="!activeParsedDataset" | ||||||
|  |           class="no-table-selected pointer-events-none" | ||||||
|  |         > | ||||||
|  |           <ng-container *ngIf="fileLoadingState !== FileLoadingState.parsed"> | ||||||
|  |             <clr-icon | ||||||
|  |               shape="process-on-vm" | ||||||
|  |               size="40" | ||||||
|  |               class="is-info icon-dc-fill" | ||||||
|  |             ></clr-icon> | ||||||
|  |  | ||||||
|  |             <p class="text-center color-gray mt-10" cds-text="section"> | ||||||
|  |               {{ fileLoadingState }}... | ||||||
|  |             </p> | ||||||
|  |           </ng-container> | ||||||
|  |  | ||||||
|  |           <ng-container *ngIf="fileLoadingState === FileLoadingState.parsed"> | ||||||
|  |             <clr-icon | ||||||
|  |               shape="warning-standard" | ||||||
|  |               size="40" | ||||||
|  |               class="is-info icon-dc-fill" | ||||||
|  |             ></clr-icon> | ||||||
|  |             <p class="text-center color-gray mt-10" cds-text="section"> | ||||||
|  |               Please select a dataset on the left to review the data | ||||||
|  |             </p> | ||||||
|  |           </ng-container> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|  |         <ng-container *ngIf="activeParsedDataset"> | ||||||
|  |           <div | ||||||
|  |             *ngIf="activeParsedDataset.submitResult" | ||||||
|  |             class="d-flex clr-justify-content-between p-10 mt-15 submission-results" | ||||||
|  |           > | ||||||
|  |             <div> | ||||||
|  |               <p cds-text="secondary regular" class="mb-10"> | ||||||
|  |                 Submit Status: | ||||||
|  |                 <span | ||||||
|  |                   *ngIf="activeParsedDataset.submitResult?.success" | ||||||
|  |                   class="color-green" | ||||||
|  |                   ><strong>SUCCESS</strong></span | ||||||
|  |                 > | ||||||
|  |                 <span | ||||||
|  |                   *ngIf="activeParsedDataset.submitResult?.error" | ||||||
|  |                   class="color-red" | ||||||
|  |                   ><strong>ERROR</strong></span | ||||||
|  |                 > | ||||||
|  |               </p> | ||||||
|  |               <p | ||||||
|  |                 *ngIf="activeParsedDataset.submitResult?.error" | ||||||
|  |                 cds-text="secondary regular" | ||||||
|  |               > | ||||||
|  |                 Error details: | ||||||
|  |               </p> | ||||||
|  |             </div> | ||||||
|  |  | ||||||
|  |             <div> | ||||||
|  |               <button | ||||||
|  |                 *ngIf=" | ||||||
|  |                   !submittingCsv && activeParsedDataset.submitResult?.error | ||||||
|  |                 " | ||||||
|  |                 (click)="reSubmitTable(activeParsedDataset)" | ||||||
|  |                 class="btn btn-primary mt-10" | ||||||
|  |                 [clrLoading]="submitLoading" | ||||||
|  |               > | ||||||
|  |                 Resubmit | ||||||
|  |               </button> | ||||||
|  |               <button | ||||||
|  |                 (click)=" | ||||||
|  |                   downloadFile( | ||||||
|  |                     activeParsedDataset.submitResult.log || | ||||||
|  |                       activeParsedDataset.submitResult.success || | ||||||
|  |                       activeParsedDataset.submitResult.error | ||||||
|  |                   ) | ||||||
|  |                 " | ||||||
|  |                 class="btn btn-primary-outline mt-10" | ||||||
|  |               > | ||||||
|  |                 Download log | ||||||
|  |               </button> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |  | ||||||
|  |           <div | ||||||
|  |             *ngIf="activeParsedDataset.submitResult?.error" | ||||||
|  |             class="error-field mt-15" | ||||||
|  |           > | ||||||
|  |             <div class="log-wrapper"> | ||||||
|  |               {{ activeParsedDataset.submitResult?.error | json }} | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |  | ||||||
|  |           <div class="d-flex clr-justify-content-between p-10 mt-15"> | ||||||
|  |             <div> | ||||||
|  |               <p cds-text="secondary regular" class="mb-10"> | ||||||
|  |                 Found in range: | ||||||
|  |  | ||||||
|  |                 <ng-container *ngIf="activeParsedDataset.parseResult"> | ||||||
|  |                   <strong | ||||||
|  |                     >"{{ | ||||||
|  |                       activeParsedDataset.parseResult.rangeSheetRes?.sheetName | ||||||
|  |                     }}"!{{ | ||||||
|  |                       activeParsedDataset.parseResult.rangeSheetRes | ||||||
|  |                         ?.rangeAddress | ||||||
|  |                     }}</strong | ||||||
|  |                   > | ||||||
|  |                 </ng-container> | ||||||
|  |  | ||||||
|  |                 <ng-container *ngIf="!activeParsedDataset.parseResult"> | ||||||
|  |                   <strong *ngIf="!activeParsedDataset.parsingTable" | ||||||
|  |                     >No data found</strong | ||||||
|  |                   > | ||||||
|  |  | ||||||
|  |                   <span | ||||||
|  |                     *ngIf="activeParsedDataset.parsingTable" | ||||||
|  |                     class="d-flex clr-align-items-center" | ||||||
|  |                   > | ||||||
|  |                     <strong>Searching for the data...</strong> | ||||||
|  |                     <clr-spinner class="ml-5" clrSmall></clr-spinner> | ||||||
|  |                   </span> | ||||||
|  |                 </ng-container> | ||||||
|  |               </p> | ||||||
|  |               <p cds-text="secondary regular"> | ||||||
|  |                 Dataset: | ||||||
|  |                 <strong> | ||||||
|  |                   <clr-tooltip> | ||||||
|  |                     <a | ||||||
|  |                       clrTooltipTrigger | ||||||
|  |                       [routerLink]="'/editor/' + activeParsedDataset.libds" | ||||||
|  |                       >{{ activeParsedDataset.libds }}</a | ||||||
|  |                     > | ||||||
|  |                     <clr-tooltip-content | ||||||
|  |                       [clrPosition]="'top-right'" | ||||||
|  |                       [clrSize]="'sm'" | ||||||
|  |                     > | ||||||
|  |                       Click to edit the table | ||||||
|  |                     </clr-tooltip-content> | ||||||
|  |                   </clr-tooltip> | ||||||
|  |                 </strong> | ||||||
|  |               </p> | ||||||
|  |             </div> | ||||||
|  |  | ||||||
|  |             <div> | ||||||
|  |               <clr-toggle-wrapper> | ||||||
|  |                 <input | ||||||
|  |                   type="checkbox" | ||||||
|  |                   clrToggle | ||||||
|  |                   [(ngModel)]="activeParsedDataset.includeInSubmission" | ||||||
|  |                   name="options" | ||||||
|  |                   [disabled]=" | ||||||
|  |                     !( | ||||||
|  |                       activeParsedDataset.datasource && | ||||||
|  |                       activeParsedDataset.parseResult | ||||||
|  |                     ) | ||||||
|  |                   " | ||||||
|  |                   required | ||||||
|  |                   value="option1" | ||||||
|  |                 /> | ||||||
|  |                 <label>Include in submission</label> | ||||||
|  |               </clr-toggle-wrapper> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |  | ||||||
|  |           <div *ngIf="isHotHidden" class="text-center w-100"> | ||||||
|  |             <clr-spinner class="spinner-md"></clr-spinner> | ||||||
|  |           </div> | ||||||
|  |  | ||||||
|  |           <hot-table | ||||||
|  |             hotId="hotInstance" | ||||||
|  |             id="hotTable" | ||||||
|  |             class="mt-15" | ||||||
|  |             [afterGetColHeader]="afterGetColHeader" | ||||||
|  |             [className]="['htDark', 'htCustomHidden']" | ||||||
|  |             [licenseKey]="hotTableLicenseKey" | ||||||
|  |             [multiColumnSorting]="true" | ||||||
|  |             [viewportRowRenderingOffset]="50" | ||||||
|  |             [manualColumnResize]="true" | ||||||
|  |             [filters]="true" | ||||||
|  |             stretchH="all" | ||||||
|  |           > | ||||||
|  |           </hot-table> | ||||||
|  |         </ng-container> | ||||||
|  |       </ng-container> | ||||||
|  |  | ||||||
|  |       <ng-container *ngIf="submittedCsvDatasets.length"> | ||||||
|  |         <div | ||||||
|  |           *ngIf="!activeSubmittedCsvDataset" | ||||||
|  |           class="no-table-selected pointer-events-none" | ||||||
|  |         > | ||||||
|  |           <clr-icon | ||||||
|  |             shape="warning-standard" | ||||||
|  |             size="40" | ||||||
|  |             class="is-info icon-dc-fill" | ||||||
|  |           ></clr-icon> | ||||||
|  |           <p class="text-center color-gray mt-10" cds-text="section"> | ||||||
|  |             Please select a dataset on the left to review the submit results | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </ng-container> | ||||||
|  |  | ||||||
|  |       <ng-container *ngIf="activeSubmittedCsvDataset"> | ||||||
|  |         <div class="d-flex clr-justify-content-between p-10"> | ||||||
|  |           <div> | ||||||
|  |             <p cds-text="secondary regular" class="mb-10"> | ||||||
|  |               Matched with dataset: | ||||||
|  |               <strong> | ||||||
|  |                 <clr-tooltip> | ||||||
|  |                   <a | ||||||
|  |                     clrTooltipTrigger | ||||||
|  |                     [routerLink]="'/editor/' + activeSubmittedCsvDataset.libds" | ||||||
|  |                     >{{ activeSubmittedCsvDataset.libds }}</a | ||||||
|  |                   > | ||||||
|  |                   <clr-tooltip-content | ||||||
|  |                     [clrPosition]="'top-right'" | ||||||
|  |                     [clrSize]="'sm'" | ||||||
|  |                   > | ||||||
|  |                     Click to edit the table | ||||||
|  |                   </clr-tooltip-content> | ||||||
|  |                 </clr-tooltip> | ||||||
|  |               </strong> | ||||||
|  |             </p> | ||||||
|  |             <p cds-text="secondary regular" class="mb-10"> | ||||||
|  |               Status: | ||||||
|  |               <span | ||||||
|  |                 *ngIf="activeSubmittedCsvDataset.success" | ||||||
|  |                 class="color-green" | ||||||
|  |                 ><strong>SUCCESS</strong></span | ||||||
|  |               > | ||||||
|  |               <span *ngIf="activeSubmittedCsvDataset.error" class="color-red" | ||||||
|  |                 ><strong>ERROR</strong></span | ||||||
|  |               > | ||||||
|  |             </p> | ||||||
|  |             <p | ||||||
|  |               *ngIf="activeSubmittedCsvDataset.error" | ||||||
|  |               cds-text="secondary regular" | ||||||
|  |             > | ||||||
|  |               Error details: | ||||||
|  |             </p> | ||||||
|  |           </div> | ||||||
|  |  | ||||||
|  |           <div> | ||||||
|  |             <button | ||||||
|  |               (click)=" | ||||||
|  |                 downloadFile( | ||||||
|  |                   activeSubmittedCsvDataset.success || | ||||||
|  |                     activeSubmittedCsvDataset.error | ||||||
|  |                 ) | ||||||
|  |               " | ||||||
|  |               class="btn btn-primary-outline mt-10" | ||||||
|  |             > | ||||||
|  |               Download log | ||||||
|  |             </button> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|  |         <div *ngIf="activeSubmittedCsvDataset.error" class="error-field mt-15"> | ||||||
|  |           <div class="log-wrapper"> | ||||||
|  |             {{ activeSubmittedCsvDataset.error | json }} | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </ng-container> | ||||||
|  |     </ng-container> | ||||||
|  |  | ||||||
|  |     <!-- <div> | ||||||
|  |       <p | ||||||
|  |         *ngIf=" | ||||||
|  |           licenceState.value.viewer_rows_allowed !== Infinity && | ||||||
|  |           hotTable.data && | ||||||
|  |           hotTable.data.length > licenceState.value.viewer_rows_allowed | ||||||
|  |         " | ||||||
|  |         class="mt-2-i w-100 text-center" | ||||||
|  |       > | ||||||
|  |         To display more than {{ licenceState.value.viewer_rows_allowed }} rows, | ||||||
|  |         contact <contact-link /> | ||||||
|  |       </p> | ||||||
|  |     </div> --> | ||||||
|  |   </div> | ||||||
|  | </div> | ||||||
|  |  | ||||||
|  | <clr-modal [(clrModalOpen)]="showSubmitReasonModal" [clrModalClosable]="false"> | ||||||
|  |   <h3 class="modal-title"> | ||||||
|  |     Submit {{ tablesToSubmit.length }} | ||||||
|  |     {{ tablesToSubmit.length === 1 ? 'table' : 'tables' }} for approval | ||||||
|  |   </h3> | ||||||
|  |   <div class="modal-body"> | ||||||
|  |     <p | ||||||
|  |       *ngIf="licenceState.value.submit_rows_limit !== Infinity" | ||||||
|  |       cds-text="body" | ||||||
|  |       class="licence-limit-notice mt-0 mb-15" | ||||||
|  |     > | ||||||
|  |       Due to current licence, only | ||||||
|  |       {{ licenceState.value.submit_rows_limit }} rows in each file will be | ||||||
|  |       submitted. To remove the restriction, contact | ||||||
|  |       support@datacontroller.io. | ||||||
|  |     </p> | ||||||
|  |  | ||||||
|  |     <div class="text-area-full-width"> | ||||||
|  |       <label for="formFields_8" class="mb-5 d-block">Message</label> | ||||||
|  |       <textarea | ||||||
|  |         clrTextarea | ||||||
|  |         [(ngModel)]="submitReasonMessage" | ||||||
|  |         tabindex="0" | ||||||
|  |         class="submit-reason" | ||||||
|  |         type="text" | ||||||
|  |         id="formFields_8" | ||||||
|  |       ></textarea> | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |     <p cds-text="caption_clean" class="mt-10"> | ||||||
|  |       Tables will be sent sequentially, logs will be available after all tables | ||||||
|  |       are submitted. | ||||||
|  |     </p> | ||||||
|  |   </div> | ||||||
|  |   <div class="modal-footer"> | ||||||
|  |     <button | ||||||
|  |       type="button" | ||||||
|  |       class="btn btn-outline" | ||||||
|  |       [disabled]="submitLoading" | ||||||
|  |       (click)="showSubmitReasonModal = false" | ||||||
|  |     > | ||||||
|  |       Cancel | ||||||
|  |     </button> | ||||||
|  |     <button | ||||||
|  |       type="button" | ||||||
|  |       id="submit-tables" | ||||||
|  |       class="btn btn-primary" | ||||||
|  |       [clrLoading]="submitLoading" | ||||||
|  |       (click)="submitTables()" | ||||||
|  |     > | ||||||
|  |       Submit | ||||||
|  |     </button> | ||||||
|  |   </div> | ||||||
|  | </clr-modal> | ||||||
|  |  | ||||||
|  | <clr-modal [(clrModalOpen)]="csvSubmitting" [clrModalClosable]="false"> | ||||||
|  |   <h3 class="modal-title"> | ||||||
|  |     Submitting {{ csvFiles.length }} CSV | ||||||
|  |     {{ csvFiles.length === 1 ? 'file' : 'files' }} | ||||||
|  |   </h3> | ||||||
|  |   <div class="modal-body"> | ||||||
|  |     <div class="text-center"> | ||||||
|  |       <clr-spinner clrMedium></clr-spinner> | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |     <p cds-text="caption_clean" class="mt-10 text-center"> | ||||||
|  |       This will take few moments | ||||||
|  |     </p> | ||||||
|  |   </div> | ||||||
|  | </clr-modal> | ||||||
							
								
								
									
										54
									
								
								client/src/app/multi-dataset/multi-dataset.component.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								client/src/app/multi-dataset/multi-dataset.component.scss
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | .no-table-selected { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: center; | ||||||
|  |   flex-direction: column; | ||||||
|  |   align-items: center; | ||||||
|  |   position: absolute; | ||||||
|  |   background: var(--clr-vertical-nav-bg-color); | ||||||
|  |   z-index: 10; | ||||||
|  |   width: 100%; | ||||||
|  |   height: 100%; | ||||||
|  |   top: 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .header-row { | ||||||
|  |   padding: 15px 0; | ||||||
|  |   border-bottom: 1px solid #d3d3d3; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .dataset-input-wrapper { | ||||||
|  |   max-width: 500px; | ||||||
|  |   width: 100%; | ||||||
|  |  | ||||||
|  |   textarea { | ||||||
|  |     min-height: 200px; | ||||||
|  |     height: 200px; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .submit-reason { | ||||||
|  |   min-height: 70px; | ||||||
|  |   max-height: 70px; | ||||||
|  |   height: 70px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .log-wrapper { | ||||||
|  |   margin: 0 10px; | ||||||
|  |   height: auto; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ::ng-deep td.not-matched { | ||||||
|  |   background-color: #ff000054; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .dataset-selection-actions { | ||||||
|  |   border-top: 1px solid #d3d3d3; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .licence-limit-notice { | ||||||
|  |   color: var(--cds-alias-status-warning-dark); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .submission-results { | ||||||
|  |   border-bottom: 1px solid #d3d3d3; | ||||||
|  | } | ||||||
							
								
								
									
										1093
									
								
								client/src/app/multi-dataset/multi-dataset.component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1093
									
								
								client/src/app/multi-dataset/multi-dataset.component.ts
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										31
									
								
								client/src/app/multi-dataset/multi-dataset.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								client/src/app/multi-dataset/multi-dataset.module.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | import { CommonModule } from '@angular/common' | ||||||
|  | import { NgModule } from '@angular/core' | ||||||
|  | import { FormsModule } from '@angular/forms' | ||||||
|  | import { ClarityModule } from '@clr/angular' | ||||||
|  | import { HotTableModule } from '@handsontable/angular' | ||||||
|  | import { registerAllModules } from 'handsontable/registry' | ||||||
|  | import { AppSharedModule } from '../app-shared.module' | ||||||
|  | import { DirectivesModule } from '../directives/directives.module' | ||||||
|  | import { DcTreeModule } from '../shared/dc-tree/dc-tree.module' | ||||||
|  | import { MultiDatasetComponent } from './multi-dataset.component' | ||||||
|  | import { MultiDatasetRoutingModule } from './multi-dataset-routing.module' | ||||||
|  | import { MultiDatasetRouteComponent } from '../routes/multi-dataset-route/multi-dataset-route.component' | ||||||
|  |  | ||||||
|  | // register Handsontable's modules | ||||||
|  | registerAllModules() | ||||||
|  |  | ||||||
|  | @NgModule({ | ||||||
|  |   declarations: [MultiDatasetRouteComponent, MultiDatasetComponent], | ||||||
|  |   imports: [ | ||||||
|  |     HotTableModule, | ||||||
|  |     MultiDatasetRoutingModule, | ||||||
|  |     FormsModule, | ||||||
|  |     ClarityModule, | ||||||
|  |     AppSharedModule, | ||||||
|  |     CommonModule, | ||||||
|  |     DcTreeModule, | ||||||
|  |     DirectivesModule | ||||||
|  |   ], | ||||||
|  |   exports: [MultiDatasetComponent] | ||||||
|  | }) | ||||||
|  | export class MultiDatasetModule {} | ||||||
| @@ -1,3 +1,25 @@ | |||||||
|  | ::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 { | .content { | ||||||
|     display: flex; |     display: flex; | ||||||
|  |  | ||||||
| @@ -9,13 +31,12 @@ | |||||||
|             display: flex; |             display: flex; | ||||||
|             justify-content: center; |             justify-content: center; | ||||||
|             flex-direction: column; |             flex-direction: column; | ||||||
|             background: #e9e9e9; |  | ||||||
|             padding: 15px; |             padding: 15px; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         .clause-query { |         .clause-query { | ||||||
|             padding: 30px 0px 20px 20px; |             padding: 30px 0px 20px 20px; | ||||||
|             background: #fbf8f8; |  | ||||||
|             display: flex; |             display: flex; | ||||||
|             justify-content: center; |             justify-content: center; | ||||||
|             flex-direction: column; |             flex-direction: column; | ||||||
| @@ -220,8 +241,11 @@ | |||||||
|     vertical-align: middle; |     vertical-align: middle; | ||||||
|     margin: 0; |     margin: 0; | ||||||
| } | } | ||||||
| :not(pre) > code[class*="language-"], pre[class*="language-"] { |  | ||||||
|     background-color: #fbf8f8; | ::ng-deep body[cds-theme="dark"] { | ||||||
|  |     .line-numbers { | ||||||
|  |         border-color: #989797 !important; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| pre[class*="language-"] { | pre[class*="language-"] { | ||||||
|   | |||||||
| @@ -284,19 +284,15 @@ | |||||||
|                     > |                     > | ||||||
|                       <span class="label label-warning"> |                       <span class="label label-warning"> | ||||||
|                         Changed Rows |                         Changed Rows | ||||||
|                         <span class="badge badge-warning">{{ |                         <span class="badge">{{ lens.updated }}</span> | ||||||
|                           lens.updated |  | ||||||
|                         }}</span> |  | ||||||
|                       </span> |                       </span> | ||||||
|                       <span class="label label-success"> |                       <span class="label label-success"> | ||||||
|                         Added Rows |                         Added Rows | ||||||
|                         <span class="badge badge-success">{{ lens.new }}</span> |                         <span class="badge">{{ lens.new }}</span> | ||||||
|                       </span> |                       </span> | ||||||
|                       <span class="label label-danger"> |                       <span class="label label-danger"> | ||||||
|                         Deleted Rows |                         Deleted Rows | ||||||
|                         <span class="badge badge-danger">{{ |                         <span class="badge">{{ lens.deleted }}</span> | ||||||
|                           lens.deleted |  | ||||||
|                         }}</span> |  | ||||||
|                       </span> |                       </span> | ||||||
|                     </div> |                     </div> | ||||||
|                   </div> |                   </div> | ||||||
| @@ -311,8 +307,8 @@ | |||||||
|           class="h-24vh d-flex flex-column justify-content-center align-items-center" |           class="h-24vh d-flex flex-column justify-content-center align-items-center" | ||||||
|         > |         > | ||||||
|           <span class="spinner"> Loading... </span> |           <span class="spinner"> Loading... </span> | ||||||
|           <div *ngIf="!loadingTable"> |           <div *ngIf="!loadingTable" class="mt-10"> | ||||||
|             <h3>Loading table</h3> |             <p cds-text="section">Loading table</p> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
| @@ -477,15 +473,15 @@ | |||||||
|                 > |                 > | ||||||
|                   <span class="label label-warning"> |                   <span class="label label-warning"> | ||||||
|                     Changed Rows |                     Changed Rows | ||||||
|                     <span class="badge badge-warning">{{ lens.updated }}</span> |                     <span class="badge">{{ lens.updated }}</span> | ||||||
|                   </span> |                   </span> | ||||||
|                   <span class="label label-success"> |                   <span class="label label-success"> | ||||||
|                     Added Rows |                     Added Rows | ||||||
|                     <span class="badge badge-success">{{ lens.new }}</span> |                     <span class="badge">{{ lens.new }}</span> | ||||||
|                   </span> |                   </span> | ||||||
|                   <span class="label label-danger"> |                   <span class="label label-danger"> | ||||||
|                     Deleted Rows |                     Deleted Rows | ||||||
|                     <span class="badge badge-danger">{{ lens.deleted }}</span> |                     <span class="badge">{{ lens.deleted }}</span> | ||||||
|                   </span> |                   </span> | ||||||
|                 </div> |                 </div> | ||||||
|               </div> |               </div> | ||||||
| @@ -519,8 +515,8 @@ | |||||||
|           class="h-25vh d-flex flex-column justify-content-center align-items-center" |           class="h-25vh d-flex flex-column justify-content-center align-items-center" | ||||||
|         > |         > | ||||||
|           <span class="spinner"> Loading... </span> |           <span class="spinner"> Loading... </span> | ||||||
|           <div *ngIf="!loadingTable"> |           <div *ngIf="!loadingTable" class="mt-10"> | ||||||
|             <h3>Loading table</h3> |             <p cds-text="section">Loading table</p> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|         <div class="tableCont"> |         <div class="tableCont"> | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | @import '../../../colors.scss'; | ||||||
|  |  | ||||||
| .loader { | .loader { | ||||||
|   display:flex; |   display:flex; | ||||||
|   justify-content: center; |   justify-content: center; | ||||||
| @@ -10,19 +12,48 @@ | |||||||
| } | } | ||||||
|  |  | ||||||
| .addedRow { | .addedRow { | ||||||
|   background:  rgb(146, 208, 154); |  | ||||||
|   border: 1px solid rgba(9, 77, 117, 0.2); |   border: 1px solid rgba(9, 77, 117, 0.2); | ||||||
|   border-radius: 5px; |   border-radius: 5px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .deletedRow { | .deletedRow { | ||||||
|   background: rgb(230, 179, 179); |  | ||||||
|   border: 1px solid rgba(70, 71, 70, 0.2); |   border: 1px solid rgba(70, 71, 70, 0.2); | ||||||
|   border-radius: 5px; |   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 { | .updatedRow { | ||||||
|   background: #fafda8; |  | ||||||
|   border: 1px solid rgba(9, 117, 9, 0.2); |   border: 1px solid rgba(9, 117, 9, 0.2); | ||||||
|   border-radius: 5px; |   border-radius: 5px; | ||||||
| } | } | ||||||
| @@ -45,7 +76,7 @@ background: rgba(252, 135, 120, 0.4); | |||||||
|     font-size: .54167rem; |     font-size: .54167rem; | ||||||
|     font-weight: 400; |     font-weight: 400; | ||||||
|     letter-spacing: normal; |     letter-spacing: normal; | ||||||
|     background: #314351; |     background: $headerBackground; | ||||||
|     border-radius: .125rem; |     border-radius: .125rem; | ||||||
|     color: #f0f1ec;; |     color: #f0f1ec;; | ||||||
|     line-height: .75rem; |     line-height: .75rem; | ||||||
| @@ -68,8 +99,8 @@ background: rgba(252, 135, 120, 0.4); | |||||||
|     top: auto; |     top: auto; | ||||||
|     right: auto; |     right: auto; | ||||||
|     content: ""; |     content: ""; | ||||||
|     border-left: .25rem solid #314351; |     border-left: .25rem solid $headerBackground; | ||||||
|     border-top: .20833rem solid #314351; |     border-top: .20833rem solid $headerBackground; | ||||||
|     border-right: .25rem solid transparent; |     border-right: .25rem solid transparent; | ||||||
|     border-bottom: .20833rem solid transparent; |     border-bottom: .20833rem solid transparent; | ||||||
| } | } | ||||||
| @@ -79,8 +110,8 @@ border: 0px solid; | |||||||
| } | } | ||||||
|  |  | ||||||
| .toggle-switch input[type=checkbox]:checked+label:before { | .toggle-switch input[type=checkbox]:checked+label:before { | ||||||
|     border-color: #314351; |     border-color: $headerBackground; | ||||||
|     background-color: #314351!important; |     background-color: $headerBackground !important; | ||||||
|     transition: .15s ease-in; |     transition: .15s ease-in; | ||||||
|     transition-property: border-color,background-color; |     transition-property: border-color,background-color; | ||||||
| } | } | ||||||
| @@ -140,7 +171,7 @@ border: 0px solid; | |||||||
| .tooll { | .tooll { | ||||||
|     position: absolute; |     position: absolute; | ||||||
|     background: #e6b3b3; |     background: #e6b3b3; | ||||||
|     color: #314351; |     color: $headerBackground; | ||||||
|     top: 0px; |     top: 0px; | ||||||
|     height: 36px; |     height: 36px; | ||||||
|     width: 100%; |     width: 100%; | ||||||
|   | |||||||
| @@ -99,19 +99,11 @@ | |||||||
|             </clr-dg-cell> |             </clr-dg-cell> | ||||||
|           </clr-dg-row> |           </clr-dg-row> | ||||||
|  |  | ||||||
|           <clr-dg-footer class="d-flex justify-content-start"> |           <clr-dg-footer> | ||||||
|             <span>items per page</span> |             <clr-dg-pagination #pagination [clrDgPageSize]="10"> | ||||||
|             <select [(ngModel)]="itemsNum"> |               <clr-dg-page-size [clrPageSizeOptions]="[3, 5, 10, 15]" | ||||||
|               <option [ngValue]="3">3</option> |                 >Items per page</clr-dg-page-size | ||||||
|               <option [ngValue]="5">5</option> |               > | ||||||
|               <option [ngValue]="10">10</option> |  | ||||||
|               <option [ngValue]="15">15</option> |  | ||||||
|             </select> |  | ||||||
|             <clr-dg-pagination |  | ||||||
|               #pagination |  | ||||||
|               [clrDgPageSize]="itemsNum" |  | ||||||
|               class="center" |  | ||||||
|             > |  | ||||||
|               {{ pagination.firstItem + 1 }} - {{ pagination.lastItem + 1 }} of |               {{ pagination.firstItem + 1 }} - {{ pagination.lastItem + 1 }} of | ||||||
|               {{ pagination.totalItems }} approvals |               {{ pagination.totalItems }} approvals | ||||||
|             </clr-dg-pagination> |             </clr-dg-pagination> | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | @import '../../../colors.scss'; | ||||||
|  |  | ||||||
| .column-center { | .column-center { | ||||||
|     display: flex; |     display: flex; | ||||||
|     flex-direction: column; |     flex-direction: column; | ||||||
| @@ -13,11 +15,11 @@ | |||||||
| } | } | ||||||
|  |  | ||||||
| .tooltip.tooltip-bottom-left>.tooltip-content, .tooltip .tooltip-content.tooltip-bottom-left { | .tooltip.tooltip-bottom-left>.tooltip-content, .tooltip .tooltip-content.tooltip-bottom-left { | ||||||
|     background: #314351!important; |     background: $headerBackground !important; | ||||||
| } | } | ||||||
| .tooltip.tooltip-bottom-left>.tooltip-content:before, .tooltip .tooltip-content.tooltip-bottom-left:before { | .tooltip.tooltip-bottom-left>.tooltip-content:before, .tooltip .tooltip-content.tooltip-bottom-left:before { | ||||||
|     border-right: .25rem solid #314351; |     border-right: .25rem solid $headerBackground; | ||||||
|     border-bottom: .20833rem solid #314351; |     border-bottom: .20833rem solid $headerBackground; | ||||||
| } | } | ||||||
|  |  | ||||||
| .noBorder { | .noBorder { | ||||||
|   | |||||||
| @@ -86,19 +86,11 @@ | |||||||
|               </clr-dg-cell> |               </clr-dg-cell> | ||||||
|             </clr-dg-row> |             </clr-dg-row> | ||||||
|  |  | ||||||
|             <clr-dg-footer class="d-flex justify-content-start"> |             <clr-dg-footer> | ||||||
|               <span>items per page</span> |               <clr-dg-pagination #pagination [clrDgPageSize]="10"> | ||||||
|               <select [(ngModel)]="itemsNum"> |                 <clr-dg-page-size [clrPageSizeOptions]="[3, 5, 10, 15]" | ||||||
|                 <option [ngValue]="3">3</option> |                   >Items per page</clr-dg-page-size | ||||||
|                 <option [ngValue]="5">5</option> |                 > | ||||||
|                 <option [ngValue]="10">10</option> |  | ||||||
|                 <option [ngValue]="15">15</option> |  | ||||||
|               </select> |  | ||||||
|               <clr-dg-pagination |  | ||||||
|                 #pagination |  | ||||||
|                 [clrDgPageSize]="itemsNum" |  | ||||||
|                 class="center" |  | ||||||
|               > |  | ||||||
|                 {{ pagination.firstItem + 1 }} - |                 {{ pagination.firstItem + 1 }} - | ||||||
|                 {{ pagination.lastItem + 1 }} of |                 {{ pagination.lastItem + 1 }} of | ||||||
|                 {{ pagination.totalItems }} submissions |                 {{ pagination.totalItems }} submissions | ||||||
|   | |||||||
| @@ -1,13 +1,15 @@ | |||||||
|  | @import '../../../colors.scss'; | ||||||
|  |  | ||||||
| .noBorder { | .noBorder { | ||||||
|   border-bottom: 1px solid transparent!important; |   border-bottom: 1px solid transparent!important; | ||||||
| } | } | ||||||
|  |  | ||||||
| .tooltip.tooltip-bottom-left>.tooltip-content, .tooltip .tooltip-content.tooltip-bottom-left { | .tooltip.tooltip-bottom-left>.tooltip-content, .tooltip .tooltip-content.tooltip-bottom-left { | ||||||
|   background: #314351!important; |   background: $headerBackground !important; | ||||||
| } | } | ||||||
| .tooltip.tooltip-bottom-left>.tooltip-content:before, .tooltip .tooltip-content.tooltip-bottom-left:before { | .tooltip.tooltip-bottom-left>.tooltip-content:before, .tooltip .tooltip-content.tooltip-bottom-left:before { | ||||||
|   border-right: .25rem solid #314351; |   border-right: .25rem solid $headerBackground; | ||||||
|   border-bottom: .20833rem solid #314351; |   border-bottom: .20833rem solid $headerBackground; | ||||||
| } | } | ||||||
|  |  | ||||||
| .no-submitted-tables { | .no-submitted-tables { | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
|     <clr-tree-node *ngIf="roles" class="search-node"> |     <clr-tree-node *ngIf="roles" class="search-node"> | ||||||
|       <div class="tree-search-wrapper"> |       <div class="tree-search-wrapper"> | ||||||
|         <input |         <input | ||||||
|  |           appStealFocus | ||||||
|           clrInput |           clrInput | ||||||
|           #searchLibTreeInput |           #searchLibTreeInput | ||||||
|           placeholder="Filter by Roles" |           placeholder="Filter by Roles" | ||||||
| @@ -26,7 +27,7 @@ | |||||||
|       <clr-tree-node |       <clr-tree-node | ||||||
|         (click)="roleOnClick(role)" |         (click)="roleOnClick(role)" | ||||||
|         *ngIf="!role['hidden']" |         *ngIf="!role['hidden']" | ||||||
|         [class.table-active]="role.ROLEURI === roleUri" |         [class.active]="role.ROLEURI === roleUri" | ||||||
|       > |       > | ||||||
|         <p class="m-0 cursor-pointer list-padding"> |         <p class="m-0 cursor-pointer list-padding"> | ||||||
|           <clr-icon shape="blocks-group"></clr-icon> |           <clr-icon shape="blocks-group"></clr-icon> | ||||||
|   | |||||||
| @@ -1,3 +1,43 @@ | |||||||
|  | @import '../../colors.scss'; | ||||||
|  |  | ||||||
|  | ::ng-deep body[cds-theme="dark"] { | ||||||
|  |   .role { | ||||||
|  |       background-color: $headerBackground; | ||||||
|  |       border-color: $headerBackground; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .role-data { | ||||||
|  |       background-color: $headerBackground; | ||||||
|  |       border-color: $headerBackground; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .member-table tbody{ | ||||||
|  |     tr:hover{ | ||||||
|  |         background-color: #29404b; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ::ng-deep body[cds-theme="light"] { | ||||||
|  |   .role-info{ | ||||||
|  |       background-color: #f9f9f9; | ||||||
|  |       border-color: #a7a7a7; | ||||||
|  |       box-shadow: 0px 2px 5px #dad7d7; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .role-data { | ||||||
|  |       background-color: #f9f9f9; | ||||||
|  |       border-color: #a7a7a7; | ||||||
|  |       box-shadow: 0px 2px 5px #dad7d7; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .member-table tbody{ | ||||||
|  |     tr:hover{ | ||||||
|  |           background-color: #e6e6e6; | ||||||
|  |       } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| .sidebar-height{ | .sidebar-height{ | ||||||
|     height: 100%; |     height: 100%; | ||||||
| } | } | ||||||
| @@ -6,20 +46,16 @@ | |||||||
|     font-size: 20px; |     font-size: 20px; | ||||||
| } | } | ||||||
| .role-info{ | .role-info{ | ||||||
|     background-color: #f9f9f9; |     border: 1px solid; | ||||||
|     border: 1px solid #a7a7a7; |  | ||||||
|     border-radius: 3px; |     border-radius: 3px; | ||||||
|     box-shadow: 0px 2px 5px #dad7d7; |  | ||||||
| } | } | ||||||
| .role-info td{ | .role-info td{ | ||||||
|     text-align: center; |     text-align: center; | ||||||
| } | } | ||||||
|  |  | ||||||
| .role-data{ | .role-data{ | ||||||
|   background-color: #f9f9f9; |   border: 1px solid; | ||||||
|   border: 1px solid #a7a7a7; |  | ||||||
|   border-radius: 3px; |   border-radius: 3px; | ||||||
|   box-shadow: 0px 2px 5px #dad7d7; |  | ||||||
| } | } | ||||||
| .role-data{ | .role-data{ | ||||||
|   min-height: unset; |   min-height: unset; | ||||||
| @@ -28,15 +64,10 @@ | |||||||
|   } |   } | ||||||
| } | } | ||||||
| .member-table{ | .member-table{ | ||||||
|   background-color: #f9f9f9; |  | ||||||
|   width: 100%; |   width: 100%; | ||||||
| } | } | ||||||
| .member-table thead{ |  | ||||||
|   background-color: #dadada; |  | ||||||
| } |  | ||||||
| .member-table tbody{ | .member-table tbody{ | ||||||
|   tr:hover{ |   tr:hover{ | ||||||
|     background-color: #e6e6e6; |  | ||||||
|     cursor: pointer; |     cursor: pointer; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import { HelperService } from '../services/helper.service' | |||||||
| import { Location } from '@angular/common' | import { Location } from '@angular/common' | ||||||
| import { Router, ActivatedRoute } from '@angular/router' | import { Router, ActivatedRoute } from '@angular/router' | ||||||
| import { SasService } from '../services/sas.service' | import { SasService } from '../services/sas.service' | ||||||
|  | import { RequestWrapperResponse } from '../models/request-wrapper/RequestWrapperResponse' | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-role', |   selector: 'app-role', | ||||||
| @@ -47,61 +48,69 @@ export class RoleComponent implements OnInit { | |||||||
|     } else { |     } else { | ||||||
|       if (globals.usernav.roleList === undefined) { |       if (globals.usernav.roleList === undefined) { | ||||||
|         this.loading = true |         this.loading = true | ||||||
|         this.sasService.request('usernav/userroles', null).then((res: any) => { |         this.sasService | ||||||
|           this.loading = false |           .request('usernav/userroles', null) | ||||||
|           this.roles = res.roles |           .then((res: RequestWrapperResponse) => { | ||||||
|           globals.usernav.roleList = res.roles |             this.loading = false | ||||||
|           if (this.paramPresent) { |             this.roles = res.adapterResponse.roles | ||||||
|             if (this.roles !== undefined) { |             globals.usernav.roleList = res.adapterResponse.roles | ||||||
|               let validRole = this.findRole(this.roles, this.paramURI) |             if (this.paramPresent) { | ||||||
|               if (validRole !== false) { |               if (this.roles !== undefined) { | ||||||
|                 this.loading = true |                 let validRole = this.findRole(this.roles, this.paramURI) | ||||||
|                 let data = { iwant: [{ roleid: this.paramURI }] } |                 if (validRole !== false) { | ||||||
|                 this.sasService |                   this.loading = true | ||||||
|                   .request('usernav/usermembersbyrole', data) |                   let data = { iwant: [{ roleid: this.paramURI }] } | ||||||
|                   .then((res: any) => { |                   this.sasService | ||||||
|                     this.loading = false |                     .request('usernav/usermembersbyrole', data) | ||||||
|                     this.roleMembers = res.sasmembers |                     .then((res: RequestWrapperResponse) => { | ||||||
|                     this.roleMembersCount = res.sasmembers.length |                       this.loading = false | ||||||
|                     this.roleGroups = res.sasgroups |                       this.roleMembers = res.adapterResponse.sasmembers | ||||||
|                     this.roleGroupsCount = res.sasgroups.length |                       this.roleMembersCount = | ||||||
|                     this.roleUri = validRole.ROLEURI |                         res.adapterResponse.sasmembers.length | ||||||
|                     this.roleName = validRole.ROLENAME |                       this.roleGroups = res.adapterResponse.sasgroups | ||||||
|                     this.roleDesc = validRole.ROLEDESC |                       this.roleGroupsCount = | ||||||
|                   }) |                         res.adapterResponse.sasgroups.length | ||||||
|  |                       this.roleUri = validRole.ROLEURI | ||||||
|  |                       this.roleName = validRole.ROLENAME | ||||||
|  |                       this.roleDesc = validRole.ROLEDESC | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|               } |               } | ||||||
|             } |             } | ||||||
|           } |           }) | ||||||
|         }) |  | ||||||
|       } else { |       } else { | ||||||
|         this.roles = globals.usernav.roleList |         this.roles = globals.usernav.roleList | ||||||
|         this.roleSearch = globals.usernav.roleSearch |         this.roleSearch = globals.usernav.roleSearch | ||||||
|         this.sasService.request('usernav/userroles', null).then((res: any) => { |         this.sasService | ||||||
|           this.roles = res.roles |           .request('usernav/userroles', null) | ||||||
|           globals.usernav.roleList = res.roles |           .then((res: RequestWrapperResponse) => { | ||||||
|  |             this.roles = res.adapterResponse.roles | ||||||
|  |             globals.usernav.roleList = res.adapterResponse.roles | ||||||
|  |  | ||||||
|           if (this.paramPresent) { |             if (this.paramPresent) { | ||||||
|             if (this.roles !== undefined) { |               if (this.roles !== undefined) { | ||||||
|               let validRole = this.findRole(this.roles, this.paramURI) |                 let validRole = this.findRole(this.roles, this.paramURI) | ||||||
|               if (validRole !== false) { |                 if (validRole !== false) { | ||||||
|                 this.loading = true |                   this.loading = true | ||||||
|                 let data = { iwant: [{ roleid: this.paramURI }] } |                   let data = { iwant: [{ roleid: this.paramURI }] } | ||||||
|                 this.sasService |                   this.sasService | ||||||
|                   .request('usernav/usermembersbyrole', data) |                     .request('usernav/usermembersbyrole', data) | ||||||
|                   .then((res: any) => { |                     .then((res: RequestWrapperResponse) => { | ||||||
|                     this.loading = false |                       this.loading = false | ||||||
|                     this.roleMembers = res.sasmembers |                       this.roleMembers = res.adapterResponse.sasmembers | ||||||
|                     this.roleMembersCount = res.sasmembers.length |                       this.roleMembersCount = | ||||||
|                     this.roleGroups = res.sasgroups |                         res.adapterResponse.sasmembers.length | ||||||
|                     this.roleGroupsCount = res.sasgroups.length |                       this.roleGroups = res.adapterResponse.sasgroups | ||||||
|                     this.roleUri = validRole.ROLEURI |                       this.roleGroupsCount = | ||||||
|                     this.roleName = validRole.ROLENAME |                         res.adapterResponse.sasgroups.length | ||||||
|                     this.roleDesc = validRole.ROLEDESC |                       this.roleUri = validRole.ROLEURI | ||||||
|                   }) |                       this.roleName = validRole.ROLENAME | ||||||
|  |                       this.roleDesc = validRole.ROLEDESC | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|               } |               } | ||||||
|             } |             } | ||||||
|           } |           }) | ||||||
|         }) |  | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -125,12 +134,12 @@ export class RoleComponent implements OnInit { | |||||||
|     let data = { iwant: [{ roleid: role.ROLEURI }] } |     let data = { iwant: [{ roleid: role.ROLEURI }] } | ||||||
|     this.sasService |     this.sasService | ||||||
|       .request('usernav/usermembersbyrole', data) |       .request('usernav/usermembersbyrole', data) | ||||||
|       .then((res: any) => { |       .then((res: RequestWrapperResponse) => { | ||||||
|         this.loading = false |         this.loading = false | ||||||
|         this.roleMembers = res.sasmembers |         this.roleMembers = res.adapterResponse.sasmembers | ||||||
|         this.roleMembersCount = res.sasmembers.length |         this.roleMembersCount = res.adapterResponse.sasmembers.length | ||||||
|         this.roleGroups = res.sasgroups |         this.roleGroups = res.adapterResponse.sasgroups | ||||||
|         this.roleGroupsCount = res.sasgroups.length |         this.roleGroupsCount = res.adapterResponse.sasgroups.length | ||||||
|         this.roleUri = role.ROLEURI |         this.roleUri = role.ROLEURI | ||||||
|         this.roleName = role.ROLENAME |         this.roleName = role.ROLENAME | ||||||
|         this.roleDesc = role.ROLEDESC |         this.roleDesc = role.ROLEDESC | ||||||
|   | |||||||
| @@ -0,0 +1 @@ | |||||||
|  | <router-outlet></router-outlet> | ||||||
| @@ -0,0 +1,16 @@ | |||||||
|  | import { Component, OnInit, OnDestroy } from '@angular/core' | ||||||
|  |  | ||||||
|  | @Component({ | ||||||
|  |   selector: 'app-multi-dataset-route', | ||||||
|  |   templateUrl: './multi-dataset-route.component.html', | ||||||
|  |   host: { | ||||||
|  |     class: 'content-container' | ||||||
|  |   } | ||||||
|  | }) | ||||||
|  | export class MultiDatasetRouteComponent implements OnInit, OnDestroy { | ||||||
|  |   constructor() {} | ||||||
|  |  | ||||||
|  |   ngOnInit() {} | ||||||
|  |  | ||||||
|  |   ngOnDestroy() {} | ||||||
|  | } | ||||||
							
								
								
									
										56
									
								
								client/src/app/services/app-settings.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								client/src/app/services/app-settings.service.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | |||||||
|  | import { BehaviorSubject } from 'rxjs' | ||||||
|  | import { AppSettings, AppThemes } from '../models/AppSettings' | ||||||
|  |  | ||||||
|  | export class AppSettingsService { | ||||||
|  |   public defaultSettings: AppSettings = { | ||||||
|  |     persistSelectedTheme: true, | ||||||
|  |     selectedTheme: AppThemes.light | ||||||
|  |   } | ||||||
|  |   public settings = new BehaviorSubject<AppSettings>(this.defaultSettings) | ||||||
|  |  | ||||||
|  |   constructor() { | ||||||
|  |     this.restoreAppSettings() | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Restore app settings from the local storage | ||||||
|  |    */ | ||||||
|  |   restoreAppSettings() { | ||||||
|  |     try { | ||||||
|  |       const serializedSettings = localStorage.getItem('app-settings') | ||||||
|  |  | ||||||
|  |       if (serializedSettings) { | ||||||
|  |         const settings = JSON.parse(serializedSettings) | ||||||
|  |  | ||||||
|  |         this.setAppSettings(settings) | ||||||
|  |       } else { | ||||||
|  |         console.info( | ||||||
|  |           'No app settings stored in the localStorage, we will set to default values.' | ||||||
|  |         ) | ||||||
|  |       } | ||||||
|  |     } catch (err) { | ||||||
|  |       console.warn('Error restoring settings from local storgae.', err) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Save app settings to the local storage | ||||||
|  |    */ | ||||||
|  |   storeAppSettings() { | ||||||
|  |     localStorage.setItem('app-settings', JSON.stringify(this.settings.value)) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Function used in the app to update global settings object | ||||||
|  |    * | ||||||
|  |    * @param appSettings contains properties that should be updated in settings | ||||||
|  |    */ | ||||||
|  |   setAppSettings(appSettings: Partial<AppSettings>) { | ||||||
|  |     this.settings.next({ | ||||||
|  |       ...this.settings.value, | ||||||
|  |       ...appSettings | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     this.storeAppSettings() | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -7,18 +7,24 @@ import { BehaviorSubject } from 'rxjs' | |||||||
| import { LoggerService } from './logger.service' | import { LoggerService } from './logger.service' | ||||||
| import { EnvironmentInfo } from '../system/models/environment-info.model' | import { EnvironmentInfo } from '../system/models/environment-info.model' | ||||||
| import { LicenceService } from './licence.service' | import { LicenceService } from './licence.service' | ||||||
|  | import { AppSettingsService } from './app-settings.service' | ||||||
|  | import { AppThemes } from '../models/AppSettings' | ||||||
|  | import { RequestWrapperResponse } from '../models/request-wrapper/RequestWrapperResponse' | ||||||
|  | import { AppStoreService } from './app-store.service' | ||||||
|  |  | ||||||
| @Injectable() | @Injectable() | ||||||
| export class AppService { | export class AppService { | ||||||
|   public syssite = new BehaviorSubject<string[] | null>(null) |   public syssite = new BehaviorSubject<string[] | null>(null) | ||||||
|   private environmentInfo: EnvironmentInfo | null = null |   private environmentInfo: EnvironmentInfo = {} | ||||||
|  |  | ||||||
|   constructor( |   constructor( | ||||||
|     private licenceService: LicenceService, |     private licenceService: LicenceService, | ||||||
|     private eventService: EventService, |     private eventService: EventService, | ||||||
|     private sasService: SasService, |     private sasService: SasService, | ||||||
|     private loggerService: LoggerService, |     private loggerService: LoggerService, | ||||||
|     private router: Router |     private appSettingsService: AppSettingsService, | ||||||
|  |     private router: Router, | ||||||
|  |     private appStoreService: AppStoreService | ||||||
|   ) { |   ) { | ||||||
|     this.subscribe() |     this.subscribe() | ||||||
|  |  | ||||||
| @@ -29,6 +35,19 @@ export class AppService { | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|  |     const appSettings = this.appSettingsService.settings.value | ||||||
|  |  | ||||||
|  |     if (!!appSettings.persistSelectedTheme) { | ||||||
|  |       if (appSettings.selectedTheme === AppThemes.light) { | ||||||
|  |         this.eventService.toggleDarkMode(false) | ||||||
|  |       } else if (appSettings.selectedTheme === AppThemes.dark) { | ||||||
|  |         this.eventService.toggleDarkMode(true) | ||||||
|  |       } else { | ||||||
|  |         // Fallback to light mode | ||||||
|  |         this.eventService.toggleDarkMode(false) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   sasServiceInit() { |   sasServiceInit() { | ||||||
| @@ -65,16 +84,19 @@ export class AppService { | |||||||
|  |  | ||||||
|     await this.sasService |     await this.sasService | ||||||
|       .request('public/startupservice', null) |       .request('public/startupservice', null) | ||||||
|       .then(async (res: any) => { |       .then(async (res: RequestWrapperResponse) => { | ||||||
|         this.syssite.next([res.SYSSITE]) |         this.syssite.next([res.adapterResponse.SYSSITE]) | ||||||
|  |  | ||||||
|         let missingProps: string[] = [] |         let missingProps: string[] = [] | ||||||
|  |  | ||||||
|         if (!res.globvars || (res.globvars && !res.globvars[0])) |         if ( | ||||||
|  |           !res.adapterResponse.globvars || | ||||||
|  |           (res.adapterResponse.globvars && !res.adapterResponse.globvars[0]) | ||||||
|  |         ) | ||||||
|           missingProps.push('Globvars') |           missingProps.push('Globvars') | ||||||
|         if (!res.sasdatasets) missingProps.push('Sasdatasets') |         if (!res.adapterResponse.sasdatasets) missingProps.push('Sasdatasets') | ||||||
|         if (!res.saslibs) missingProps.push('Saslibs') |         if (!res.adapterResponse.saslibs) missingProps.push('Saslibs') | ||||||
|         if (!res.xlmaps) missingProps.push('XLMaps') |         if (!res.adapterResponse.xlmaps) missingProps.push('XLMaps') | ||||||
|  |  | ||||||
|         if (missingProps.length > 0) { |         if (missingProps.length > 0) { | ||||||
|           startupServiceError = true |           startupServiceError = true | ||||||
| @@ -87,22 +109,26 @@ export class AppService { | |||||||
|           return |           return | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         const dcAdapterSettings = this.appStoreService.getDcAdapterSettings() | ||||||
|  |  | ||||||
|         this.environmentInfo = { |         this.environmentInfo = { | ||||||
|           SYSSITE: res.SYSSITE, |           SYSSITE: res.adapterResponse.SYSSITE, | ||||||
|           SYSSCPL: res.SYSSCPL, |           SYSSCPL: res.adapterResponse.SYSSCPL, | ||||||
|           SYSTCPIPHOSTNAME: res.SYSTCPIPHOSTNAME, |           SYSTCPIPHOSTNAME: res.adapterResponse.SYSTCPIPHOSTNAME, | ||||||
|           SYSVLONG: res.SYSVLONG, |           SYSVLONG: res.adapterResponse.SYSVLONG, | ||||||
|           MEMSIZE: res.MEMSIZE, |           MEMSIZE: res.adapterResponse.MEMSIZE, | ||||||
|           SYSPROCESSMODE: res.SYSPROCESSMODE, |           SYSPROCESSMODE: res.adapterResponse.SYSPROCESSMODE, | ||||||
|           SYSHOSTNAME: res.SYSHOSTNAME, |           SYSHOSTNAME: res.adapterResponse.SYSHOSTNAME, | ||||||
|           SYSHOSTINFOLONG: res.SYSHOSTINFOLONG, |           SYSUSERID: res.adapterResponse.SYSUSERID, | ||||||
|           SYSENCODING: res.SYSENCODING, |           SYSHOSTINFOLONG: res.adapterResponse.SYSHOSTINFOLONG, | ||||||
|           AUTOEXEC: res.AUTOEXEC, |           SYSENCODING: res.adapterResponse.SYSENCODING, | ||||||
|           ISADMIN: res.globvars[0].ISADMIN, |           AUTOEXEC: res.adapterResponse.AUTOEXEC, | ||||||
|           DC_ADMIN_GROUP: res.globvars[0].DC_ADMIN_GROUP |           ISADMIN: res.adapterResponse.globvars[0].ISADMIN, | ||||||
|  |           DC_ADMIN_GROUP: res.adapterResponse.globvars[0].DC_ADMIN_GROUP, | ||||||
|  |           APP_LOC: dcAdapterSettings?.appLoc | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         let libs = res.sasdatasets |         let libs = res.adapterResponse.sasdatasets | ||||||
|         let libGroup: any = {} |         let libGroup: any = {} | ||||||
|         let libsAndTables |         let libsAndTables | ||||||
|         let libraries |         let libraries | ||||||
| @@ -136,7 +162,7 @@ export class AppService { | |||||||
|           globals.editor.libsAndTables = libsAndTables |           globals.editor.libsAndTables = libsAndTables | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         globals.xlmaps = res.xlmaps.map((xlmap: any) => ({ |         globals.xlmaps = res.adapterResponse.xlmaps.map((xlmap: any) => ({ | ||||||
|           id: xlmap[0], |           id: xlmap[0], | ||||||
|           description: xlmap[1], |           description: xlmap[1], | ||||||
|           targetDS: xlmap[2] |           targetDS: xlmap[2] | ||||||
| @@ -145,9 +171,9 @@ export class AppService { | |||||||
|         globals.editor.libraries = libraries |         globals.editor.libraries = libraries | ||||||
|         globals.editor.startupSet = true |         globals.editor.startupSet = true | ||||||
|  |  | ||||||
|         globals.dcLib = res.globvars[0].DCLIB |         globals.dcLib = res.adapterResponse.globvars[0].DCLIB | ||||||
|  |  | ||||||
|         await this.licenceService.activation(res) |         await this.licenceService.activation(res.adapterResponse) | ||||||
|       }) |       }) | ||||||
|       .catch((err: any) => { |       .catch((err: any) => { | ||||||
|         startupServiceError = true |         startupServiceError = true | ||||||
|   | |||||||
| @@ -1,6 +1,9 @@ | |||||||
| import { Injectable, Output, EventEmitter } from '@angular/core' | import { Injectable, Output, EventEmitter } from '@angular/core' | ||||||
| import { InfoModal, AbortDetails } from '../models/InfoModal' | import { InfoModal, AbortDetails } from '../models/InfoModal' | ||||||
| import { AlertsService } from '../shared/alerts/alerts.service' | import { AlertsService } from '../shared/alerts/alerts.service' | ||||||
|  | import { BehaviorSubject } from 'rxjs' | ||||||
|  | import { AppSettingsService } from './app-settings.service' | ||||||
|  | import { AppThemes } from '../models/AppSettings' | ||||||
|  |  | ||||||
| @Injectable({ | @Injectable({ | ||||||
|   providedIn: 'root' |   providedIn: 'root' | ||||||
| @@ -17,7 +20,23 @@ export class EventService { | |||||||
|   public viewLastUrl: string | null = null |   public viewLastUrl: string | null = null | ||||||
|   public sidebarCloseLimit = 1280 |   public sidebarCloseLimit = 1280 | ||||||
|  |  | ||||||
|   constructor(private alertsService: AlertsService) {} |   public darkMode: BehaviorSubject<boolean> = new BehaviorSubject(false) | ||||||
|  |  | ||||||
|  |   constructor(private appSettingsService: AppSettingsService) {} | ||||||
|  |  | ||||||
|  |   toggleDarkMode(value: boolean) { | ||||||
|  |     this.darkMode.next(value) | ||||||
|  |  | ||||||
|  |     if (value) { | ||||||
|  |       document.body.setAttribute('cds-theme', 'dark') | ||||||
|  |     } else { | ||||||
|  |       document.body.setAttribute('cds-theme', 'light') | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     this.appSettingsService.setAppSettings({ | ||||||
|  |       selectedTheme: value ? AppThemes.dark : AppThemes.light | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  |  | ||||||
|   public showDemoLimitModal(featureName: string) { |   public showDemoLimitModal(featureName: string) { | ||||||
|     this.onDemoLimitModalShow.emit(featureName) |     this.onDemoLimitModalShow.emit(featureName) | ||||||
|   | |||||||
| @@ -17,10 +17,11 @@ import { LoggerService } from './logger.service' | |||||||
| import { isSpecialMissing } from '@sasjs/utils/input/validators' | import { isSpecialMissing } from '@sasjs/utils/input/validators' | ||||||
| import { Col } from '../shared/dc-validator/models/col.model' | import { Col } from '../shared/dc-validator/models/col.model' | ||||||
| import { get } from 'lodash-es' | import { get } from 'lodash-es' | ||||||
|  | import { EditorsStageDataSASResponse } from '../models/sas/editors-stagedata.model' | ||||||
|  | import { RequestWrapperResponse } from '../models/request-wrapper/RequestWrapperResponse' | ||||||
|  |  | ||||||
| @Injectable() | @Injectable() | ||||||
| export class SasStoreService { | export class SasStoreService { | ||||||
|   public libds!: string |  | ||||||
|   public response: Subject<any> = new Subject<any>() |   public response: Subject<any> = new Subject<any>() | ||||||
|   public changedTable: Subject<any> = new Subject<any>() |   public changedTable: Subject<any> = new Subject<any>() | ||||||
|   public details: Subject<any> = new Subject<any>() |   public details: Subject<any> = new Subject<any>() | ||||||
| @@ -56,16 +57,13 @@ export class SasStoreService { | |||||||
|     program: string, |     program: string, | ||||||
|     libds: string |     libds: string | ||||||
|   ) { |   ) { | ||||||
|     this.libds = libds |  | ||||||
|     const tables: any = {} |     const tables: any = {} | ||||||
|     tables[tableName] = [tableData] |     tables[tableName] = [tableData] | ||||||
|     const res: EditorsGetDataSASResponse = await this.sasService.request( |     const res: RequestWrapperResponse<EditorsGetDataSASResponse> = | ||||||
|       program, |       await this.sasService.request(program, tables) | ||||||
|       tables |  | ||||||
|     ) |  | ||||||
|     const response: EditorsGetDataServiceResponse = { |     const response: EditorsGetDataServiceResponse = { | ||||||
|       data: res, |       data: res.adapterResponse, | ||||||
|       libds: this.libds |       libds: libds | ||||||
|     } |     } | ||||||
|     return response |     return response | ||||||
|   } |   } | ||||||
| @@ -84,8 +82,10 @@ export class SasStoreService { | |||||||
|     tableData: any, |     tableData: any, | ||||||
|     tableName: string, |     tableName: string, | ||||||
|     program: string, |     program: string, | ||||||
|     $dataFormats: $DataFormats | null |     $dataFormats: $DataFormats | null, | ||||||
|   ) { |     suppressErrorSuccessMessages?: boolean, | ||||||
|  |     adapterConfig?: any | ||||||
|  |   ): Promise<RequestWrapperResponse<EditorsStageDataSASResponse>> { | ||||||
|     // add sp as third argument of createData call |     // add sp as third argument of createData call | ||||||
|  |  | ||||||
|     let tables: any = { |     let tables: any = { | ||||||
| @@ -100,7 +100,15 @@ export class SasStoreService { | |||||||
|  |  | ||||||
|     tables[tableName] = [tableParams] |     tables[tableName] = [tableParams] | ||||||
|  |  | ||||||
|     let res: any = await this.sasService.request(program, tables) |     let res = await this.sasService.request<EditorsStageDataSASResponse>( | ||||||
|  |       program, | ||||||
|  |       tables, | ||||||
|  |       adapterConfig, | ||||||
|  |       { | ||||||
|  |         suppressErrorAbortModal: suppressErrorSuccessMessages, | ||||||
|  |         suppressSuccessAbortModal: suppressErrorSuccessMessages | ||||||
|  |       } | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     return res |     return res | ||||||
|   } |   } | ||||||
| @@ -119,8 +127,7 @@ export class SasStoreService { | |||||||
|   ) { |   ) { | ||||||
|     let tables: any = {} |     let tables: any = {} | ||||||
|     tables[tableName] = [tableData] |     tables[tableName] = [tableData] | ||||||
|     let res: any = await this.sasService.request(program, tables) |     return (await this.sasService.request(program, tables)).adapterResponse | ||||||
|     return res |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -144,8 +151,8 @@ export class SasStoreService { | |||||||
|    * @returns All submits |    * @returns All submits | ||||||
|    */ |    */ | ||||||
|   public async getSubmitts() { |   public async getSubmitts() { | ||||||
|     let res: any = await this.sasService.request('editors/getsubmits', null) |     return (await this.sasService.request('editors/getsubmits', null)) | ||||||
|     return res |       .adapterResponse | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -153,7 +160,8 @@ export class SasStoreService { | |||||||
|    * @returns All libraries |    * @returns All libraries | ||||||
|    */ |    */ | ||||||
|   public async viewLibs() { |   public async viewLibs() { | ||||||
|     return this.sasService.request('public/viewlibs', null) |     return (await this.sasService.request('public/viewlibs', null)) | ||||||
|  |       .adapterResponse | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public async refreshLibInfo(libref: string) { |   public async refreshLibInfo(libref: string) { | ||||||
| @@ -161,30 +169,22 @@ export class SasStoreService { | |||||||
|       lib2refresh: [{ libref }] |       lib2refresh: [{ libref }] | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return this.sasService.request('public/refreshlibinfo', data) |     return (await this.sasService.request('public/refreshlibinfo', data)) | ||||||
|   } |       .adapterResponse | ||||||
|  |  | ||||||
|   public async versionHistory(libDataset: any) { |  | ||||||
|     const data = { iwant: [{ LIBDS: libDataset }] } |  | ||||||
|     let res: any = await this.sasService.request( |  | ||||||
|       'public/getversionhistory', |  | ||||||
|       data |  | ||||||
|     ) |  | ||||||
|     return res |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public async viewTables(lib: any) { |   public async viewTables(lib: any) { | ||||||
|     let tables = { SASControlTable: [{ MPLIB: lib }] } |     let tables = { SASControlTable: [{ MPLIB: lib }] } | ||||||
|     let res: any = await this.sasService.request('public/viewtables', tables) |     return (await this.sasService.request('public/viewtables', tables)) | ||||||
|     return res |       .adapterResponse | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public async viewData(libDataset: any, filter_pk: any) { |   public async viewData(libDataset: any, filter_pk: any) { | ||||||
|     let tables = { |     let tables = { | ||||||
|       SASControlTable: [{ LIBDS: libDataset, FILTER_RK: filter_pk }] |       SASControlTable: [{ LIBDS: libDataset, FILTER_RK: filter_pk }] | ||||||
|     } |     } | ||||||
|     let res: any = await this.sasService.request('public/viewdata', tables) |     return (await this.sasService.request('public/viewdata', tables)) | ||||||
|     return res |       .adapterResponse | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public async viewDataSearch( |   public async viewDataSearch( | ||||||
| @@ -205,41 +205,36 @@ export class SasStoreService { | |||||||
|         } |         } | ||||||
|       ] |       ] | ||||||
|     } |     } | ||||||
|     let res: any = await this.sasService.request('public/viewdata', tables) |  | ||||||
|     return res |     return (await this.sasService.request('public/viewdata', tables)) | ||||||
|  |       .adapterResponse | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public async getXLMapRules(id: string) { |   public async getXLMapRules(id: string) { | ||||||
|     const tables = { |     const tables = { | ||||||
|       getxlmaps_in: [{ XLMAP_ID: id }] |       getxlmaps_in: [{ XLMAP_ID: id }] | ||||||
|     } |     } | ||||||
|     const res: any = await this.sasService.request('editors/getxlmaps', tables) |     return (await this.sasService.request('editors/getxlmaps', tables)) | ||||||
|     return res |       .adapterResponse | ||||||
|   } |  | ||||||
|  |  | ||||||
|   public async getDetails(tableData: any, tableName: string, program: string) { |  | ||||||
|     let tables: any = {} |  | ||||||
|     tables[tableName] = [tableData] |  | ||||||
|  |  | ||||||
|     let res: any = await this.sasService.request(program, tables) |  | ||||||
|     return res |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public async showDiffs(tableData: any, tableName: string, program: string) { |   public async showDiffs(tableData: any, tableName: string, program: string) { | ||||||
|     let tables: any = {} |     let tables: any = {} | ||||||
|     tables[tableName] = [tableData] |     tables[tableName] = [tableData] | ||||||
|     let res: any = await this.sasService.request(program, tables, { |     return ( | ||||||
|       useComputeApi: false |       await this.sasService.request(program, tables, { | ||||||
|     }) |         useComputeApi: false | ||||||
|     return res |       }) | ||||||
|  |     ).adapterResponse | ||||||
|   } |   } | ||||||
|   public async rejecting(tableData: any, tableName: string, program: string) { |   public async rejecting(tableData: any, tableName: string, program: string) { | ||||||
|     let tables: any = {} |     let tables: any = {} | ||||||
|     tables[tableName] = [tableData] |     tables[tableName] = [tableData] | ||||||
|     let res: any = await this.sasService.request(program, tables, { |     return ( | ||||||
|       useComputeApi: false |       await this.sasService.request(program, tables, { | ||||||
|     }) |         useComputeApi: false | ||||||
|     return res |       }) | ||||||
|  |     ).adapterResponse | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public async approveTable( |   public async approveTable( | ||||||
| @@ -250,15 +245,13 @@ export class SasStoreService { | |||||||
|     let tables: any = {} |     let tables: any = {} | ||||||
|     tables[tableName] = [tableData] |     tables[tableName] = [tableData] | ||||||
|  |  | ||||||
|     let res: any = await this.sasService.request(program, tables) |     return (await this.sasService.request(program, tables)).adapterResponse | ||||||
|     return res |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public async getHistory(tableData: any, tableName: string, program: string) { |   public async getHistory(tableData: any, tableName: string, program: string) { | ||||||
|     let tables: any = {} |     let tables: any = {} | ||||||
|     tables[tableName] = [tableData] |     tables[tableName] = [tableData] | ||||||
|     let res: any = await this.sasService.request(program, tables) |     return (await this.sasService.request(program, tables)).adapterResponse | ||||||
|     return res |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   setQueryVariables(dataset: string, cols: any) { |   setQueryVariables(dataset: string, cols: any) { | ||||||
| @@ -270,8 +263,8 @@ export class SasStoreService { | |||||||
|   public async getChangeInfo(tableId: any) { |   public async getChangeInfo(tableId: any) { | ||||||
|     let obj = { TABLE: tableId } |     let obj = { TABLE: tableId } | ||||||
|     let table = { SASControlTable: [obj] } |     let table = { SASControlTable: [obj] } | ||||||
|     let res: any = await this.sasService.request('public/getchangeinfo', table) |     return (await this.sasService.request('public/getchangeinfo', table)) | ||||||
|     return res |       .adapterResponse | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public async getQueryValues( |   public async getQueryValues( | ||||||
| @@ -296,13 +289,13 @@ export class SasStoreService { | |||||||
|       tables.FILTERQUERY = filterQuery |       tables.FILTERQUERY = filterQuery | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     let res: any = await this.sasService |     return ( | ||||||
|       .request('public/getcolvals', tables) |       await this.sasService | ||||||
|       .catch((er: any) => { |         .request('public/getcolvals', tables) | ||||||
|         throw er |         .catch((er: any) => { | ||||||
|       }) |           throw er | ||||||
|  |         }) | ||||||
|     return res |     ).adapterResponse | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public async saveQuery(libds: string, filterQuery: QueryClause[]) { |   public async saveQuery(libds: string, filterQuery: QueryClause[]) { | ||||||
| @@ -311,21 +304,17 @@ export class SasStoreService { | |||||||
|       filterquery: filterQuery |       filterquery: filterQuery | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     let res: any = await this.sasService.request( |     const res = await this.sasService.request('public/validatefilter', tables) | ||||||
|       'public/validatefilter', |  | ||||||
|       tables |  | ||||||
|     ) |  | ||||||
|     this.filter.next(res) |     this.filter.next(res) | ||||||
|     return res |  | ||||||
|  |     return res.adapterResponse | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public async openTable(tableId: string) { |   public async openTable(tableId: string) { | ||||||
|     let tables = { iwant: [{ table_id: tableId }] } |     let tables = { iwant: [{ table_id: tableId }] } | ||||||
|     let res: any = await this.sasService.request( |     return (await this.sasService.request('auditors/getstagetable', tables)) | ||||||
|       'auditors/getstagetable', |       .adapterResponse | ||||||
|       tables |  | ||||||
|     ) |  | ||||||
|     return res |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public checkOperator(operator: any, value: any, type: string) { |   public checkOperator(operator: any, value: any, type: string) { | ||||||
|   | |||||||
| @@ -12,8 +12,10 @@ import { ServerType } from '@sasjs/utils/types/serverType' | |||||||
| import { DcAdapterSettings } from '../models/DcAdapterSettings' | import { DcAdapterSettings } from '../models/DcAdapterSettings' | ||||||
| import { AppStoreService } from './app-store.service' | import { AppStoreService } from './app-store.service' | ||||||
| import { LoggerService } from './logger.service' | import { LoggerService } from './logger.service' | ||||||
| import { RequestWrapperOptions } from '../models/RequestWrapperOptions' | import { RequestWrapperOptions } from '../models/request-wrapper/RequestWrapperOptions' | ||||||
| import { ErrorBody } from '../models/ErrorBody' | import { ErrorBody } from '../models/ErrorBody' | ||||||
|  | import { UploadFileResponse } from '../models/UploadFile' | ||||||
|  | import { RequestWrapperResponse } from '../models/request-wrapper/RequestWrapperResponse' | ||||||
|  |  | ||||||
| @Injectable({ | @Injectable({ | ||||||
|   providedIn: 'root' |   providedIn: 'root' | ||||||
| @@ -93,15 +95,18 @@ export class SasService { | |||||||
|    * @param url service to run reuqest against |    * @param url service to run reuqest against | ||||||
|    * @param data to be sent to backend service |    * @param data to be sent to backend service | ||||||
|    * @param config additional parameters to force eg. { debug: false } |    * @param config additional parameters to force eg. { debug: false } | ||||||
|    * @param wrapperOptions used to suppress error or success abort modals after request is finished |    * @param wrapperOptions used to provide options to the request wrapper function | ||||||
|    * @returns |    * for example to suppress error or success abort modals after request is finished | ||||||
|  |    * @returns adapter response or an error. It will return the `log` as well. | ||||||
|  |    * The log could be potentially be wrong if multiple requests happen because the log this | ||||||
|  |    * function return is the last request in the Adapter Array for the given URL. | ||||||
|    */ |    */ | ||||||
|   public request( |   public request<responseType = any>( | ||||||
|     url: string, |     url: string, | ||||||
|     data: any, |     data: any, | ||||||
|     config?: any, |     config?: any, | ||||||
|     wrapperOptions?: RequestWrapperOptions |     wrapperOptions?: RequestWrapperOptions | ||||||
|   ): Promise<any> { |   ): Promise<RequestWrapperResponse<responseType>> { | ||||||
|     url = 'services/' + url |     url = 'services/' + url | ||||||
|  |  | ||||||
|     if (!wrapperOptions) wrapperOptions = {} |     if (!wrapperOptions) wrapperOptions = {} | ||||||
| @@ -116,9 +121,16 @@ export class SasService { | |||||||
|         }) |         }) | ||||||
|         .then( |         .then( | ||||||
|           (res: any) => { |           (res: any) => { | ||||||
|  |             const sasRequest = this.sasjsAdapter | ||||||
|  |               .getSasRequests() | ||||||
|  |               .find((rq) => rq.serviceLink === url) | ||||||
|  |  | ||||||
|             if (res.login === false) { |             if (res.login === false) { | ||||||
|               this.shouldLogin.next(true) |               this.shouldLogin.next(true) | ||||||
|               reject(false) |               reject({ | ||||||
|  |                 adapterResponse: false, | ||||||
|  |                 log: sasRequest?.logFile | ||||||
|  |               }) | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (!this.userService.user && res.MF_GETUSER) { |             if (!this.userService.user && res.MF_GETUSER) { | ||||||
| @@ -140,7 +152,12 @@ export class SasService { | |||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (res.status === 404) { |             if (res.status === 404) { | ||||||
|               reject({ MESSAGE: res.body || 'SAS Responded with error' }) |               reject({ | ||||||
|  |                 adapterResponse: { | ||||||
|  |                   MESSAGE: res.body || 'SAS Responded with error' | ||||||
|  |                 }, | ||||||
|  |                 log: sasRequest?.logFile | ||||||
|  |               }) | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (typeof res.sasjsAbort !== 'undefined') { |             if (typeof res.sasjsAbort !== 'undefined') { | ||||||
| @@ -156,7 +173,12 @@ export class SasService { | |||||||
|                 this.eventService.startupDataLoaded() |                 this.eventService.startupDataLoaded() | ||||||
|                 this.router.navigateByUrl('/deploy') |                 this.router.navigateByUrl('/deploy') | ||||||
|  |  | ||||||
|                 reject({ error: abortMsg }) |                 reject({ | ||||||
|  |                   adapterResponse: { | ||||||
|  |                     error: abortMsg | ||||||
|  |                   }, | ||||||
|  |                   log: sasRequest?.logFile | ||||||
|  |                 }) | ||||||
|  |  | ||||||
|                 return |                 return | ||||||
|               } |               } | ||||||
| @@ -173,14 +195,26 @@ export class SasService { | |||||||
|                 ) |                 ) | ||||||
|               } |               } | ||||||
|  |  | ||||||
|               reject({ error: abortMsg }) |               reject({ | ||||||
|  |                 adapterResponse: { | ||||||
|  |                   error: abortMsg | ||||||
|  |                 }, | ||||||
|  |                 log: sasRequest?.logFile | ||||||
|  |               }) | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             resolve(res) |             resolve({ | ||||||
|  |               adapterResponse: res, | ||||||
|  |               log: sasRequest?.logFile | ||||||
|  |             }) | ||||||
|           }, |           }, | ||||||
|           (err: { error: ErrorBody | undefined }) => { |           (err: { error: ErrorBody | undefined }) => { | ||||||
|             console.error(err) |             console.error(err) | ||||||
|  |  | ||||||
|  |             const sasRequest = this.sasjsAdapter | ||||||
|  |               .getSasRequests() | ||||||
|  |               .find((rq) => rq.serviceLink === url) | ||||||
|  |  | ||||||
|             if (err.error) { |             if (err.error) { | ||||||
|               let errorMessage: string | undefined = err.error.message |               let errorMessage: string | undefined = err.error.message | ||||||
|               let log: string | undefined |               let log: string | undefined | ||||||
| @@ -204,10 +238,18 @@ export class SasService { | |||||||
|                   'Request error' |                   'Request error' | ||||||
|                 ) |                 ) | ||||||
|               } |               } | ||||||
|               reject({ error: errorMessage }) |               reject({ | ||||||
|  |                 adapterResponse: { | ||||||
|  |                   error: errorMessage | ||||||
|  |                 }, | ||||||
|  |                 log: sasRequest?.logFile | ||||||
|  |               }) | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             reject(err) |             reject({ | ||||||
|  |               adapterResponse: err, | ||||||
|  |               log: sasRequest?.logFile | ||||||
|  |             }) | ||||||
|           } |           } | ||||||
|         ) |         ) | ||||||
|     }) |     }) | ||||||
| @@ -221,8 +263,35 @@ export class SasService { | |||||||
|    * @param params Aditional parameters eg. { debug: false } |    * @param params Aditional parameters eg. { debug: false } | ||||||
|    * @returns HTTP Response |    * @returns HTTP Response | ||||||
|    */ |    */ | ||||||
|   public uploadFile(sasService: string, files: UploadFile[], params: any) { |   public uploadFile( | ||||||
|     return this.sasjsAdapter.uploadFile(sasService, files, params) |     sasService: string, | ||||||
|  |     files: UploadFile[], | ||||||
|  |     params: any | ||||||
|  |   ): Promise<UploadFileResponse> { | ||||||
|  |     return new Promise((resolve, reject) => { | ||||||
|  |       this.sasjsAdapter.uploadFile(sasService, files, params).then( | ||||||
|  |         (res) => { | ||||||
|  |           const sasRequest = this.sasjsAdapter | ||||||
|  |             .getSasRequests() | ||||||
|  |             .find((rq) => rq.serviceLink === 'services/editors/loadfile') | ||||||
|  |  | ||||||
|  |           resolve({ | ||||||
|  |             adapterResponse: res, | ||||||
|  |             log: sasRequest?.logFile | ||||||
|  |           }) | ||||||
|  |         }, | ||||||
|  |         (err) => { | ||||||
|  |           const sasRequest = this.sasjsAdapter | ||||||
|  |             .getSasRequests() | ||||||
|  |             .find((rq) => rq.serviceLink === 'services/editors/loadfile') | ||||||
|  |  | ||||||
|  |           reject({ | ||||||
|  |             response: err, | ||||||
|  |             log: sasRequest?.logFile | ||||||
|  |           }) | ||||||
|  |         } | ||||||
|  |       ) | ||||||
|  |     }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public async login(username: string, password: string) { |   public async login(username: string, password: string) { | ||||||
|   | |||||||
							
								
								
									
										190
									
								
								client/src/app/services/spreadsheet.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								client/src/app/services/spreadsheet.service.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,190 @@ | |||||||
|  | import { Injectable } from '@angular/core' | ||||||
|  | import { ExcelPasswordModalService } from '../shared/excel-password-modal/excel-password-modal.service' | ||||||
|  | import { EventService } from './event.service' | ||||||
|  | import { LicenceService } from './licence.service' | ||||||
|  | import { SpreadsheetUtil } from '../shared/spreadsheet-util/spreadsheet-util' | ||||||
|  | import { ParseParams } from '../models/ParseParams.interface' | ||||||
|  | import { ParseResult } from '../models/ParseResult.interface' | ||||||
|  | import { OpenOptions } from '../shared/excel-password-modal/models/options.interface' | ||||||
|  | import { Result } from '../shared/excel-password-modal/models/result.interface' | ||||||
|  | import * as XLSX from '@sheet/crypto' | ||||||
|  |  | ||||||
|  | @Injectable({ | ||||||
|  |   providedIn: 'root' | ||||||
|  | }) | ||||||
|  | export class SpreadsheetService { | ||||||
|  |   private licenceState = this.licenceService.licenceState | ||||||
|  |  | ||||||
|  |   constructor( | ||||||
|  |     private excelPasswordModalService: ExcelPasswordModalService, | ||||||
|  |     private eventService: EventService, | ||||||
|  |     private licenceService: LicenceService | ||||||
|  |   ) {} | ||||||
|  |  | ||||||
|  |   public parseExcelFile( | ||||||
|  |     parseParams: ParseParams, | ||||||
|  |     onParseStateChange?: (uploadState: string) => void, | ||||||
|  |     onTableFoundEvent?: (info: string) => void | ||||||
|  |   ): Promise<ParseResult | undefined> { | ||||||
|  |     const spreadSheetUtil = new SpreadsheetUtil({ | ||||||
|  |       licenceState: this.licenceState | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     return spreadSheetUtil.parseSpreadsheetFile( | ||||||
|  |       parseParams, | ||||||
|  |       this.promptExcelPassword, | ||||||
|  |       onParseStateChange, | ||||||
|  |       onTableFoundEvent | ||||||
|  |     ) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Reads the excel file using the XLSX.read() function | ||||||
|  |    * If possible, function will use the web worker to read it in background thread | ||||||
|  |    * otherwise fallback method will be used | ||||||
|  |    * | ||||||
|  |    * @param file selected in an <input> | ||||||
|  |    * @returns WorkBook | ||||||
|  |    */ | ||||||
|  |   public xlsxReadFile(file: any): Promise<XLSX.WorkBook> { | ||||||
|  |     return new Promise((resolve, reject) => { | ||||||
|  |       const spreadSheetUtil = new SpreadsheetUtil({ | ||||||
|  |         licenceState: this.licenceState | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       let reader: FileReader = new FileReader() | ||||||
|  |  | ||||||
|  |       reader.onload = (fileReaderResponse: any) => { | ||||||
|  |         spreadSheetUtil | ||||||
|  |           .xslxStartReading(fileReaderResponse, this.promptExcelPassword) | ||||||
|  |           .then((response) => { | ||||||
|  |             resolve(response) | ||||||
|  |           }) | ||||||
|  |           .catch((err) => { | ||||||
|  |             reject(err) | ||||||
|  |           }) | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       reader.readAsArrayBuffer(file) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Read the file minimally just to get the sheet names, not reading full file | ||||||
|  |    * to help boost the performance | ||||||
|  |    * | ||||||
|  |    * @returns sheet names in string array | ||||||
|  |    */ | ||||||
|  |   public async parseExcelSheetNames(file: File): Promise<{ | ||||||
|  |     sheetNames: string[] | ||||||
|  |     password?: string | ||||||
|  |   }> { | ||||||
|  |     return new Promise((resolve, reject) => { | ||||||
|  |       const reader = new FileReader() | ||||||
|  |  | ||||||
|  |       if (!file) { | ||||||
|  |         console.warn('file is missing') | ||||||
|  |         return resolve({ sheetNames: [] }) | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       reader.onload = async (event: ProgressEvent<FileReader>) => { | ||||||
|  |         if (!event?.target) { | ||||||
|  |           console.warn('File reader event.target is missing') | ||||||
|  |           return | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         let wb: XLSX.WorkBook | undefined = undefined | ||||||
|  |         let fileUnlocking: boolean = false | ||||||
|  |         let password: string | undefined | ||||||
|  |         const data = event.target.result | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |           wb = XLSX.read(data, { | ||||||
|  |             // Load file minimally to parse sheets | ||||||
|  |             bookSheets: true, | ||||||
|  |             type: 'binary' | ||||||
|  |           }) | ||||||
|  |         } catch (err: any) { | ||||||
|  |           if (err.message.toLowerCase().includes('password')) { | ||||||
|  |             fileUnlocking = true | ||||||
|  |  | ||||||
|  |             let passwordError = false | ||||||
|  |  | ||||||
|  |             while (fileUnlocking) { | ||||||
|  |               password = await this.promptExcelPassword({ | ||||||
|  |                 error: passwordError | ||||||
|  |               }) | ||||||
|  |  | ||||||
|  |               if (password) { | ||||||
|  |                 try { | ||||||
|  |                   wb = XLSX.read(data, { | ||||||
|  |                     // Load file minimally to parse sheets | ||||||
|  |                     bookSheets: true, | ||||||
|  |                     type: 'binary', | ||||||
|  |                     password: password | ||||||
|  |                   }) | ||||||
|  |  | ||||||
|  |                   fileUnlocking = false | ||||||
|  |                   passwordError = false | ||||||
|  |                 } catch (err: any) { | ||||||
|  |                   passwordError = true | ||||||
|  |  | ||||||
|  |                   if (!err.message.toLowerCase().includes('password')) { | ||||||
|  |                     fileUnlocking = false | ||||||
|  |                   } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (!password) | ||||||
|  |                   return reject('Invalid password, failed to decrypt the file') | ||||||
|  |               } else { | ||||||
|  |                 fileUnlocking = false | ||||||
|  |                 return reject('No password provided') | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } else { | ||||||
|  |             return reject('Error reading the file') | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (!wb) return reject('Error parsing the workbook') | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |           const sheetNames = wb.SheetNames | ||||||
|  |  | ||||||
|  |           return resolve({ | ||||||
|  |             sheetNames: sheetNames, | ||||||
|  |             password: password | ||||||
|  |           }) | ||||||
|  |         } catch (e) { | ||||||
|  |           console.error(e) | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       reader.onerror = function (ex) { | ||||||
|  |         console.log(ex) | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       reader.readAsBinaryString(file) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public bytesToMB(size: number): number { | ||||||
|  |     return parseFloat((size / (1024 * 1024)).toFixed(2)) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * When excel is password protected we will display the password prompt for user to type password in. | ||||||
|  |    * @returns Password user input or undefined if discarded by user | ||||||
|  |    */ | ||||||
|  |   private promptExcelPassword = ( | ||||||
|  |     options?: OpenOptions | ||||||
|  |   ): Promise<string | undefined> => { | ||||||
|  |     return new Promise((resolve, reject) => { | ||||||
|  |       this.excelPasswordModalService | ||||||
|  |         .open(options) | ||||||
|  |         .subscribe((result: Result) => { | ||||||
|  |           resolve(result.password) | ||||||
|  |         }) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| .input-val { | .input-val { | ||||||
|   border: 0px; |   border: 0px; | ||||||
|   background: #fbf8f8; |   background: transparent; | ||||||
|   border-bottom: 1px solid #999999; |   border-bottom: 1px solid #999999; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -13,6 +13,29 @@ input { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | ::ng-deep { | ||||||
|  |   body[cds-theme="dark"] { | ||||||
|  |     .datalist { | ||||||
|  |       background: #21333b; | ||||||
|  |       border: 1px solid #575757; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     input { | ||||||
|  |       color: #fff; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     .datalist option { | ||||||
|  |       color: #fff | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   body[cds-theme="light"] { | ||||||
|  |     .datalist { | ||||||
|  |       background: #fff; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| .autocomplete-wrapper { | .autocomplete-wrapper { | ||||||
|   .overlay { |   .overlay { | ||||||
|     position: fixed; |     position: fixed; | ||||||
| @@ -25,7 +48,6 @@ input { | |||||||
|  |  | ||||||
|   .datalist { |   .datalist { | ||||||
|       position: fixed; |       position: fixed; | ||||||
|       background: white; |  | ||||||
|       box-shadow: 0px 3px 10px -1px #0000002b; |       box-shadow: 0px 3px 10px -1px #0000002b; | ||||||
|       overflow: auto; |       overflow: auto; | ||||||
|       z-index: 2000; |       z-index: 2000; | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
|   <clr-tree-node *ngIf="libraryList" class="search-node"> |   <clr-tree-node *ngIf="libraryList" class="search-node"> | ||||||
|     <div class="tree-search-wrapper"> |     <div class="tree-search-wrapper"> | ||||||
|       <input |       <input | ||||||
|  |         appStealFocus | ||||||
|         clrInput |         clrInput | ||||||
|         #searchLibTreeInput |         #searchLibTreeInput | ||||||
|         placeholder="Libraries" |         placeholder="Libraries" | ||||||
| @@ -45,6 +46,7 @@ | |||||||
|       <clr-tree-node *ngIf="library['tables']" class="search-node"> |       <clr-tree-node *ngIf="library['tables']" class="search-node"> | ||||||
|         <div class="tree-search-wrapper"> |         <div class="tree-search-wrapper"> | ||||||
|           <input |           <input | ||||||
|  |             appStealFocus | ||||||
|             clrInput |             clrInput | ||||||
|             #searchTreeInput |             #searchTreeInput | ||||||
|             placeholder="Tables" |             placeholder="Tables" | ||||||
| @@ -91,7 +93,7 @@ | |||||||
|             " |             " | ||||||
|             class="clr-treenode-link" |             class="clr-treenode-link" | ||||||
|             [class.dc-locked-control]="tableLocked" |             [class.dc-locked-control]="tableLocked" | ||||||
|             [class.table-active]="libTabActive(library.LIBRARYREF, libTable)" |             [class.active]="libTabActive(library.LIBRARYREF, libTable)" | ||||||
|           > |           > | ||||||
|             <ng-container [ngSwitch]="libTable.includes('-FC')"> |             <ng-container [ngSwitch]="libTable.includes('-FC')"> | ||||||
|               <clr-icon *ngSwitchCase="true" shape="bolt"></clr-icon> |               <clr-icon *ngSwitchCase="true" shape="bolt"></clr-icon> | ||||||
| @@ -114,6 +116,7 @@ | |||||||
|             <clr-tree-node *ngIf="libTable['columns']" class="search-node"> |             <clr-tree-node *ngIf="libTable['columns']" class="search-node"> | ||||||
|               <div class="tree-search-wrapper"> |               <div class="tree-search-wrapper"> | ||||||
|                 <input |                 <input | ||||||
|  |                   appStealFocus | ||||||
|                   clrInput |                   clrInput | ||||||
|                   #searchTreeInput |                   #searchTreeInput | ||||||
|                   placeholder="Columns" |                   placeholder="Columns" | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ import { getHotDataSchema } from '../utils/getHotDataSchema' | |||||||
|  |  | ||||||
| describe('DC Validator - hot data schema', () => { | describe('DC Validator - hot data schema', () => { | ||||||
|   it('should return correct value for col type', () => { |   it('should return correct value for col type', () => { | ||||||
|     expect(getHotDataSchema('numeric')).toEqual(0) |     expect(getHotDataSchema('numeric')).toEqual('') | ||||||
|     expect( |     expect( | ||||||
|       getHotDataSchema('autocomplete', { data: '', source: [1, 2, 3] }) |       getHotDataSchema('autocomplete', { data: '', source: [1, 2, 3] }) | ||||||
|     ).toEqual(1) |     ).toEqual(1) | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import { DcValidation } from '../models/dc-validation.model' | import { DcValidation } from '../models/dc-validation.model' | ||||||
|  |  | ||||||
| const schemaTypeMap: { [key: string]: any } = { | const schemaTypeMap: { [key: string]: any } = { | ||||||
|   numeric: 0, |   numeric: '', | ||||||
|   default: '' |   default: '' | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,44 @@ | |||||||
|  | <div *ngIf="options$ | async as options" class="excel-password-root"> | ||||||
|  |   <clr-modal | ||||||
|  |     [clrModalOpen]="options.open" | ||||||
|  |     [clrModalSize]="'md'" | ||||||
|  |     [clrModalClosable]="false" | ||||||
|  |   > | ||||||
|  |     <h3 class="modal-title center text-center color-darker-gray"> | ||||||
|  |       Password Protected File | ||||||
|  |     </h3> | ||||||
|  |     <div class="modal-body d-flex clr-justify-content-center"> | ||||||
|  |       <p class="m-0">Please enter password:</p> | ||||||
|  |       <input | ||||||
|  |         #filePasswordInput | ||||||
|  |         [(ngModel)]="passwordInput" | ||||||
|  |         data-lpignore="true" | ||||||
|  |         autocomplete="off" | ||||||
|  |         id="filePasswordInput" | ||||||
|  |         type="text" | ||||||
|  |         class="clr-input disable-password-manager" | ||||||
|  |       /> | ||||||
|  |     </div> | ||||||
|  |     <div class="modal-footer"> | ||||||
|  |       <div> | ||||||
|  |         <p *ngIf="options.error" class="m-0 color-red"> | ||||||
|  |           Sorry that didn't work, try again. | ||||||
|  |         </p> | ||||||
|  |       </div> | ||||||
|  |  | ||||||
|  |       <div class="buttons"> | ||||||
|  |         <button type="button" class="btn btn-sm btn-outline" (click)="close()"> | ||||||
|  |           Cancel | ||||||
|  |         </button> | ||||||
|  |         <button | ||||||
|  |           type="button" | ||||||
|  |           class="btn btn-sm btn-success-outline" | ||||||
|  |           [disabled]="filePasswordInput.value.length < 1" | ||||||
|  |           (click)="close(filePasswordInput.value)" | ||||||
|  |         > | ||||||
|  |           Unlock | ||||||
|  |         </button> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </clr-modal> | ||||||
|  | </div> | ||||||
| @@ -0,0 +1,18 @@ | |||||||
|  | .excel-password-root { | ||||||
|  |   ::ng-deep { | ||||||
|  |     .modal { | ||||||
|  |       z-index: 1060; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .modal-footer { | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   justify-content: space-between; | ||||||
|  |  | ||||||
|  |   .buttons { | ||||||
|  |     display: flex; | ||||||
|  |     gap: 5px; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -0,0 +1,24 @@ | |||||||
|  | import { Component } from '@angular/core' | ||||||
|  | import { Observable } from 'rxjs' | ||||||
|  | import { ExcelPasswordModalService } from './excel-password-modal.service' | ||||||
|  | import { Options } from './models/options.interface' | ||||||
|  |  | ||||||
|  | @Component({ | ||||||
|  |   selector: 'app-excel-password-modal', | ||||||
|  |   styleUrls: ['./excel-password-modal.component.scss'], | ||||||
|  |   templateUrl: './excel-password-modal.component.html' | ||||||
|  | }) | ||||||
|  | export class ExcelPasswordModalComponent { | ||||||
|  |   options$: Observable<Options> = this.excelPasswordModalService.optionsSubject$ | ||||||
|  |  | ||||||
|  |   fileUnlockError: boolean = false | ||||||
|  |  | ||||||
|  |   passwordInput: string = '' | ||||||
|  |  | ||||||
|  |   constructor(private excelPasswordModalService: ExcelPasswordModalService) {} | ||||||
|  |  | ||||||
|  |   close(password?: string) { | ||||||
|  |     this.passwordInput = '' | ||||||
|  |     this.excelPasswordModalService.close(password) | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -0,0 +1,35 @@ | |||||||
|  | import { Injectable } from '@angular/core' | ||||||
|  | import { Subject, Observable } from 'rxjs' | ||||||
|  | import { OpenOptions, Options } from './models/options.interface' | ||||||
|  | import { Result } from './models/result.interface' | ||||||
|  |  | ||||||
|  | @Injectable({ | ||||||
|  |   providedIn: 'root' | ||||||
|  | }) | ||||||
|  | export class ExcelPasswordModalService { | ||||||
|  |   public optionsSubject$: Subject<Options> = new Subject() | ||||||
|  |   public resultChange$: Subject<Result> = new Subject() | ||||||
|  |  | ||||||
|  |   constructor() {} | ||||||
|  |  | ||||||
|  |   public open(openOptions?: OpenOptions): Observable<Result> { | ||||||
|  |     this.optionsSubject$.next({ | ||||||
|  |       open: true, | ||||||
|  |       ...openOptions | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     this.resultChange$ = new Subject<Result>() | ||||||
|  |     return this.resultChange$.asObservable() | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   close(password?: string) { | ||||||
|  |     this.optionsSubject$.next({ | ||||||
|  |       open: false | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     this.resultChange$.next({ | ||||||
|  |       password | ||||||
|  |     }) | ||||||
|  |     this.resultChange$.complete() | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -0,0 +1,7 @@ | |||||||
|  | export interface OpenOptions { | ||||||
|  |   error?: boolean | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface Options extends OpenOptions { | ||||||
|  |   open: boolean | ||||||
|  | } | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | export interface Result { | ||||||
|  |   password: string | undefined | ||||||
|  | } | ||||||
| @@ -1,5 +1,7 @@ | |||||||
|  | @import '../../../colors.scss'; | ||||||
|  |  | ||||||
| .sideBarProps { | .sideBarProps { | ||||||
|   background: #314351!important; |   background: $headerBackground !important; | ||||||
|   color: #e0e0e0; |   color: #e0e0e0; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user