Compare commits
561 Commits
v6.0.0
...
9264ce2a60
Author | SHA1 | Date | |
---|---|---|---|
9264ce2a60 | |||
cbd69df708 | |||
ca7caa25b6 | |||
c10330627f | |||
d80c59afce | |||
abdbb67471 | |||
037a97b6ff | |||
a0a529ad38 | |||
72239558af | |||
d2097ad6dd | |||
1bd542cddb | |||
fb1c1ee874 | |||
624a7a8f37 | |||
381378f532 | |||
af05486c0e | |||
4b558948d9 | |||
d9cff42f5c | |||
2a3d2b8d0d | |||
d0f453d291 | |||
8e65dd0eae | |||
da4d0b28c7 | |||
6c96ef7fb0 | |||
997f09adde | |||
0e8503ed2b | |||
97dfcd79b1 | |||
9a12a2e41f | |||
5c114e562b | |||
ae696a0be0 | |||
22d46a5dcc | |||
f3125ff464 | |||
9682b548e6 | |||
ec11a74265 | |||
4be0614604 | |||
27cbff2bc5 | |||
c41c8963f2 | |||
7249d4fa29 | |||
2e0c60cc0d | |||
7c5e47f5e4 | |||
f9decbd366 | |||
1dc69341ca | |||
75ae19fa8e | |||
9de04e9a0c | |||
983f59cd51 | |||
7b5e7ae184 | |||
6e96b1daec | |||
9604661f3b | |||
4bd215491f | |||
6a7dd451b5 | |||
841201adab | |||
e013e62776 | |||
6c171a6394 | |||
a377f6e8d6 | |||
0337318e0b | |||
2844c70f95 | |||
7e11c8f375 | |||
23cbbce964 | |||
c63fcdd465 | |||
82412b2659 | |||
eef3832e40 | |||
63b75a1c61 | |||
d1f0879f0a | |||
36416aab2e | |||
7f3577c3ef | |||
69f883034f | |||
1c56af01d0 | |||
43c0f73c21 | |||
e8cd3d63da | |||
b7f564cb21 | |||
3f5cb1e2de | |||
7d8c0472f0 | |||
b8b516ba77 | |||
83b3d775b6 | |||
aea252ccc6 | |||
d8908f9c7f | |||
95289aa952 | |||
149e318a87 | |||
8657826e60 | |||
a45f5bb3b2 | |||
ae9a91a7a1 | |||
3638bde633 | |||
3a2361f42c | |||
1bd0eef913 | |||
85aa3b38b7 | |||
678859a68d | |||
e531acee3f | |||
4a45ebfe3b | |||
8fb9a344f2 | |||
8829b60220 | |||
4e64f28732 | |||
a0749de700 | |||
f9623e046e | |||
bce1fd57ef | |||
c6a1c53b46 | |||
e36229e4c4 | |||
853c1bc23e | |||
3b6f6853bc | |||
b415437662 | |||
24df878abe | |||
f474673a14 | |||
c53ab85107 | |||
5c4dd7c9e3 | |||
217220ffaa | |||
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 | |||
173ee2daff | |||
5c0091b5e8 | |||
84dce7f6e8 | |||
e8a943a35a | |||
b3171a8125 | |||
d77f2eb674 | |||
5474fad9cc | |||
3dd85cc60b | |||
510e412ff2 | |||
3dfdccbc6b | |||
a55661548a | |||
69363b37e9 | |||
2d6a753921 | |||
dc989e5668 | |||
227ac480d5 | |||
c5e4650327 | |||
56cf271e77 | |||
fa8396f039 | |||
904ca30f91 | |||
549f35766b | |||
1589c799ec | |||
604c2e70bd | |||
297a84d3a4 | |||
aaad9f7207 | |||
a4028562ce | |||
5ab3f98855 | |||
eba21e96b4 | |||
9ad7ae47b5 | |||
cf54e4c8f3 | |||
57aa6fa0fc | |||
4a01f3d490 | |||
80a0db951d | |||
3202cb8e08 | |||
ddf36230bf | |||
b67c2be968 | |||
dc2c8da92b | |||
5d6c3701d0 | |||
b024e263b4 | |||
b706864e40 | |||
60cc666b67 | |||
efff4dd553 | |||
2b1dad8e48 | |||
8c7de5aad7 | |||
1db8bc2573 | |||
c60dd65a16 | |||
f7f59a4b0a | |||
ec7615e7e3 | |||
f411c33754 | |||
79121168e4 | |||
ef81e33f70 | |||
96066c66cb | |||
b1819b776d | |||
bd7a392ffc | |||
7997b77158 | |||
d1966bcdc5 | |||
7b5bbe024d | |||
4d84f15aca | |||
928937daab | |||
3bd8d247e5 | |||
cf6c9dd5f2 | |||
ff55cbbaad | |||
3eda4e2c58 | |||
02a8a1c565 | |||
8769841f08 | |||
7208fe1c3b | |||
801c8c6a9f | |||
51ebd25aa3 | |||
c6595c1f61 | |||
a267666e99 | |||
b27fea5b91 | |||
f8a14d4bde | |||
633e35338d | |||
8003da94e6 | |||
c3af97ef57 | |||
31d4e5c727 | |||
fbbcf90956 | |||
f522038b8d | |||
ace599b39f | |||
963562621d | |||
5171d07441 | |||
9a0b9573d5 | |||
4733311ef3 | |||
432450a15b | |||
47638becc0 | |||
bdd3a95685 | |||
38601346a5 | |||
dc3a6ae6a1 | |||
f668b1e7f7 | |||
eb1c09d790 | |||
9bf324c74b | |||
f13e909478 | |||
6a0fe287dd | |||
5a48f2e6e3 | |||
6565834ad4 | |||
837821fd01 | |||
cff5989559 | |||
60510a4d68 | |||
2b54034973 | |||
347b0f9065 | |||
eac0104d7a | |||
1c8e4604de | |||
e9624635ed | |||
f9beda1ddb | |||
53400de110 | |||
cf37ddab22 | |||
625af199f4 | |||
56e9217f4b | |||
86f1af7926 | |||
7737f8455d | |||
b0f1677fcc | |||
4406e0d4b4 | |||
cf19381060 | |||
802d8a3b08 | |||
2a852496e9 | |||
4653097225 | |||
8afee29e02 | |||
233eca39ef | |||
1a96bb1233 | |||
93702c63dc | |||
df065562d1 | |||
802c99adf9 | |||
482c7455f5 | |||
731b96dccc | |||
9550ae4d11 | |||
2d6e747db9 | |||
fd94945466 | |||
d3b0c09332 | |||
01915a2db9 | |||
51b043b6d2 | |||
c144fd8087 | |||
12b15df78c | |||
d6ecd12cea | |||
1c3d498da6 | |||
d75e10aef5 | |||
f0f9d85558 | |||
86f3411896 | |||
6daef39268 | |||
7d1720a360 | |||
b11a4884b4 | |||
50696bb926 | |||
d67d4e2f86 | |||
2f01c4d251 | |||
9ffa30ab74 | |||
5d93346b52 | |||
39762b36c6 | |||
e40ebdff05 | |||
8d12d9e51e | |||
23708c9aae | |||
c86fba9dc7 | |||
e747e6e4e7 | |||
5aec024242 | |||
b473b198a6 | |||
516e5a2062 | |||
fb3abbe491 | |||
3e009f3037 | |||
e63d304953 | |||
3cd90c2d47 | |||
a485c3b787 | |||
2702bb3c84 | |||
56264ecc69 | |||
cc4535245c | |||
6ae31de1dd | |||
2d4d068413 | |||
271543a446 | |||
8f796aec36 | |||
6eb1aa85d2 | |||
ac59b77ad5 | |||
3efccc4cf3 | |||
8cbcd18f4b | |||
6bb2378790 | |||
e7d0ffe8c0 | |||
ab89600c73 | |||
830e3816a0 | |||
dadac4f13f | |||
1de48a49af | |||
687a1e1cb5 | |||
665a04f5c5 | |||
fdb18d242b | |||
ec173da4ce | |||
bb35cc15d2 | |||
181f52eaea | |||
fc7c8101ed | |||
a347603fe0 | |||
09022c995f | |||
3609943f30 | |||
a1d308ea07 | |||
5579db0eaf | |||
3a3e488b23 | |||
0a82ec0a70 | |||
bc1d89218e | |||
817b9adeac | |||
a7aa42a59b | |||
34f239036d | |||
91f128c2fe | |||
a00ebea692 | |||
c27cdab3fc | |||
d7da2d7890 | |||
76f0fd4232 | |||
008b45ad17 | |||
4d49263816 | |||
6a2482e5c6 | |||
b5c3fb2af4 | |||
fa2c8eb839 | |||
f3e82b4ee2 | |||
ba67248155 | |||
a6d962bfaa | |||
95cddb52d4 | |||
5a5118d775 | |||
1b1cdd7a4b | |||
a9ddf7f7dd | |||
b54b3f1778 | |||
349a63c591 | |||
293d33912f | |||
357b9849e7 | |||
0c8a9eef32 | |||
112b1d0da4 | |||
a05007416a | |||
9f7dd55583 | |||
11b06f6416 | |||
adb7eb7755 | |||
b776b80728 | |||
73a149ea7b | |||
ef8784093b | |||
b30c788e3d | |||
23899bdff3 | |||
8bd0dd22c2 | |||
c55b00c74f | |||
c895f509b0 | |||
5968915331 | |||
44ffc082f6 | |||
b716ae5675 | |||
01a0b59494 | |||
8ebc3da0bb | |||
133577a4fa | |||
a19615db41 | |||
32b212a6bf | |||
00ec4529cd | |||
102d03888f | |||
9f8247320e | |||
ef871de30e | |||
b3a15ce26b | |||
270695aec2 | |||
ad7392a326 | |||
92a50a42e2 | |||
a3a8856d8c | |||
150c19b1b0 | |||
f04c51ee4e | |||
c4338bf957 | |||
5b06f4ede8 | |||
e7ab2cc956 | |||
5ebf8a66f7 | |||
3d4e886b9b | |||
a00d31caf3 | |||
40fe707287 | |||
8296be01ba | |||
dbeb003292 | |||
f048501c48 | |||
498350b3f3 | |||
91e82c9c65 | |||
24067ea82b | |||
aa7deddba0 | |||
b2d13203d1 | |||
19c1092b5b | |||
94ab949df8 | |||
9eb2451c2f | |||
6e521bfa3e | |||
239720fe0c | |||
8571e01e44 | |||
6f482ec6d9 | |||
5e30dc0f89 | |||
3d76d12c86 | |||
93f1b81d70 | |||
e2b65ddd82 | |||
b64bbe91d4 | |||
1f2ce55f24 | |||
921157da9e | |||
413acf7d05 | |||
725f75aa74 | |||
c64ab8a577 | |||
1154c99e0a | |||
5bcdef77b8 | |||
e4a0089102 | |||
ba022d8a35 | |||
c8ac859d2e | |||
d6fd72e880 | |||
61cc360c85 | |||
08e39c4fca | |||
8c2ee441fc | |||
0a7d23c763 | |||
7b54fff26e | |||
cd3e0f614b | |||
81c0aec202 | |||
3193bdd720 | |||
b9a12454e1 | |||
01a857f7c6 | |||
2bb2eee80e | |||
c054ea500d | |||
d7f8201246 | |||
622cfcc6fe | |||
303240e4d2 | |||
a8b849aede | |||
ca281b70c9 | |||
e056ece223 | |||
0ae35214fb | |||
34ffac39cb | |||
52d4b3eefc | |||
908d2761f2 | |||
7c98ad8c5b | |||
5bb55e6484 | |||
e48e47bc63 | |||
2a3f4f755c | |||
ddfae9227c | |||
c74378423d | |||
0e020623a5 | |||
eeb4000efe | |||
2053c858ac | |||
4c2c5d5526 | |||
7ccc745eb2 | |||
e93367fb42 | |||
250fb1e8cf | |||
1c2b87940d | |||
25fed203c6 | |||
bba2a6cc9d | |||
b036cc2abe | |||
218b15918d | |||
19489a68a7 | |||
7d7061a78a | |||
1aceb297ba | |||
cdee7480c2 | |||
bb2fb235aa | |||
b2f88e203a | |||
c2f1af99ef | |||
00e9dff1d9 | |||
82d74e6fee | |||
f1e2770574 | |||
5ce1701657 | |||
2f79487aea | |||
fefdfaf17b | |||
c1e6f71430 | |||
a8310fa63f | |||
e28ac814b8 | |||
45fed59a65 | |||
310399112b | |||
01238fc8a9 | |||
365e46c568 | |||
8d336e5b46 | |||
54fe7013b1 | |||
8dc40bdd4e | |||
d88ab8bf58 | |||
67030ab033 |
@ -1,5 +1,5 @@
|
||||
name: Build
|
||||
run-name: Building and testing DC
|
||||
run-name: Running Lint Check and Licence checker on Pull Request
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
@ -8,9 +8,16 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20.14.0
|
||||
|
||||
- name: Install Google Chrome
|
||||
run: |
|
||||
wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
|
||||
echo "deb http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list
|
||||
apt-get update
|
||||
apt-get install -y google-chrome-stable xvfb
|
||||
|
||||
- name: Write .npmrc file
|
||||
run: echo "$NPMRC" > client/.npmrc
|
||||
@ -18,21 +25,117 @@ jobs:
|
||||
env:
|
||||
NPMRC: ${{ secrets.NPMRC}}
|
||||
|
||||
- name: Lint check
|
||||
run: npm run lint:check
|
||||
|
||||
- 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
|
||||
|
||||
- name: Licence checker
|
||||
run: |
|
||||
cd client
|
||||
npm run license-checker
|
||||
|
||||
- name: Angular Tests
|
||||
run: |
|
||||
cd client
|
||||
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: npm run lint:check
|
||||
# Install dependencies~
|
||||
- run: npm ci --legacy-peer-deps
|
||||
# Audit should fail and stop the CI if critical vulnerability found
|
||||
- run: npm audit --audit-level=critical
|
||||
- run: |
|
||||
cd ./sas
|
||||
npm audit --audit-level=critical
|
||||
- run: |
|
||||
- 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
|
||||
npm audit --audit-level=critical
|
||||
npm test -- --no-watch --no-progress --browsers=ChromeHeadlessCI
|
||||
mv ./cypress.env.example.json ./cypress.env.json
|
||||
replace-in-files --regex='"username".*' --replacement='"username":"'${{ secrets.CYPRESS_USERNAME_SASJS }}'",' ./cypress.env.json
|
||||
replace-in-files --regex='"password".*' --replacement='"password":"'${{ secrets.CYPRESS_PWD_SASJS }}'" ' ./cypress.env.json
|
||||
cat ./cypress.env.json
|
||||
npm run postinstall
|
||||
npm run build
|
||||
# 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
|
||||
|
@ -1,172 +0,0 @@
|
||||
name: Test
|
||||
run-name: Building and testing development branch
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- development
|
||||
|
||||
jobs:
|
||||
Build-and-test-development:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Write .npmrc file
|
||||
run: echo "$NPMRC" > client/.npmrc
|
||||
shell: bash
|
||||
env:
|
||||
NPMRC: ${{ secrets.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
|
||||
# - run: 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
|
||||
# - run: eval $(ssh-agent -s)
|
||||
# - run: echo "$ssh_key" | tr -d '\r' | ssh-add -
|
||||
- name: Write cypress credentials
|
||||
run: echo "$CYPRESS_CREDS" > ./client/cypress.env.json
|
||||
shell: bash
|
||||
env:
|
||||
CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }}
|
||||
|
||||
# - run: mkdir -p ~/.ssh
|
||||
# - run: chmod 700 ~/.ssh
|
||||
- run: npm ci --legacy-peer-deps
|
||||
# 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: echo "SERVER_URL=$server_url" >> .env
|
||||
# - run: echo "SERVER_TYPE=$server_type" >> .env
|
||||
# - run: echo "CLIENT=$client_sasjs" >> .env
|
||||
# - run: echo "ACCESS_TOKEN=$access_token_sasjs" >> .env
|
||||
# - run: echo "REFRESH_TOKEN=$refresh_token_sasjs" >> .env
|
||||
# - run: cat .env
|
||||
- run: pm2 start api-linux --wait-ready
|
||||
|
||||
- name: Deploy mocked services
|
||||
run: |
|
||||
cd ./sas/mocks/sasjs
|
||||
npm install --legacy-peer-deps -g @sasjs/cli
|
||||
npm install --legacy-peer-deps -g replace-in-files-cli
|
||||
sasjs cbd -t server-ci
|
||||
# sasjs request services/admin/makedata -t server-ci -d ./deploy/makeData4GL.json -c ./deploy/requestConfig.json -o ./output.json
|
||||
|
||||
- name: Prepare frontend
|
||||
run: |
|
||||
cd ./client
|
||||
# mv ./cypress.env.example.json ./cypress.env.json
|
||||
# replace-in-files --regex='"username".*' --replacement='"username":"'$cypress_username_sasjs'",' ./cypress.env.json
|
||||
# replace-in-files --regex='"password".*' --replacement='"password":"'$cypress_pwd_sasjs'" ' ./cypress.env.json
|
||||
cat ./cypress.env.json
|
||||
npm run postinstall
|
||||
# Prepare index.html to SASJS local
|
||||
replace-in-files --regex='serverUrl=".*?"' --replacement='serverUrl="http://localhost:5000"' ./src/index.html
|
||||
replace-in-files --regex='appLoc=".*?"' --replacement='appLoc="/Public/app/devtest"' ./src/index.html
|
||||
replace-in-files --regex='serverType=".*?"' --replacement='serverType="SASJS"' ./src/index.html
|
||||
# Prepare and deploy SASJS version
|
||||
# replace-in-files --regex='serverurl=".*?"' --replacement='serverUrl="http://localhost:5000"' ./dist/index.html
|
||||
# replace-in-files --regex='apploc=".*?"' --replacement='appLoc="/30.SASApps/app/devtest"' ./dist/index.html
|
||||
# replace-in-files --regex='servertype=".*?"' --replacement='serverType="SASJS"' ./dist/index.html
|
||||
# scp -o stricthostkeychecking=no -r ./dist/* dcgitlab@sas.4gl.io:/var/www/html/dcviya/development/newadapter
|
||||
replace-in-files --regex='"hosturl".*' --replacement='hosturl:"http://localhost:4200",' ./cypress.config.ts
|
||||
# replace-in-files --regex='"appLocation".*' --replacement='appLocation:"/dcviya/development/newadapter",' ./cypress.config.ts
|
||||
cat ./cypress.config.ts
|
||||
# Start frontend and run cypress
|
||||
npm start & npx wait-on http://localhost:4200 && ./run-cypress-tests.sh
|
||||
|
||||
Build-and-test-development-latest-adapter:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Write .npmrc file
|
||||
run: echo "$NPMRC" > client/.npmrc
|
||||
shell: bash
|
||||
env:
|
||||
NPMRC: ${{ secrets.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
|
||||
# - run: 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
|
||||
# - run: eval $(ssh-agent -s)
|
||||
# - run: echo "$ssh_key" | tr -d '\r' | ssh-add -
|
||||
- name: Write cypress credentials
|
||||
run: echo "$CYPRESS_CREDS" > ./client/cypress.env.json
|
||||
shell: bash
|
||||
env:
|
||||
CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }}
|
||||
|
||||
# - run: mkdir -p ~/.ssh
|
||||
# - run: chmod 700 ~/.ssh
|
||||
- run: npm ci --legacy-peer-deps
|
||||
# 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: echo "SERVER_URL=$server_url" >> .env
|
||||
# - run: echo "SERVER_TYPE=$server_type" >> .env
|
||||
# - run: echo "CLIENT=$client_sasjs" >> .env
|
||||
# - run: echo "ACCESS_TOKEN=$access_token_sasjs" >> .env
|
||||
# - run: echo "REFRESH_TOKEN=$refresh_token_sasjs" >> .env
|
||||
# - run: cat .env
|
||||
- run: pm2 start api-linux --wait-ready
|
||||
|
||||
- name: Deploy mocked services
|
||||
run: |
|
||||
cd ./sas/mocks/sasjs
|
||||
npm install --legacy-peer-deps -g @sasjs/cli
|
||||
npm install --legacy-peer-deps -g replace-in-files-cli
|
||||
sasjs cbd -t server-ci
|
||||
# sasjs request services/admin/makedata -t server-ci -d ./deploy/makeData4GL.json -c ./deploy/requestConfig.json -o ./output.json
|
||||
|
||||
- name: Prepare frontend
|
||||
run: |
|
||||
cd ./client
|
||||
# mv ./cypress.env.example.json ./cypress.env.json
|
||||
# replace-in-files --regex='"username".*' --replacement='"username":"'$cypress_username_sasjs'",' ./cypress.env.json
|
||||
# replace-in-files --regex='"password".*' --replacement='"password":"'$cypress_pwd_sasjs'" ' ./cypress.env.json
|
||||
cat ./cypress.env.json
|
||||
npm run postinstall
|
||||
npm install --legacy-peer-deps @sasjs/adapter@latest
|
||||
# Prepare index.html to SASJS local
|
||||
replace-in-files --regex='serverUrl=".*?"' --replacement='serverUrl="http://localhost:5000"' ./src/index.html
|
||||
replace-in-files --regex='appLoc=".*?"' --replacement='appLoc="/Public/app/devtest"' ./src/index.html
|
||||
replace-in-files --regex='serverType=".*?"' --replacement='serverType="SASJS"' ./src/index.html
|
||||
# Prepare and deploy SASJS version
|
||||
# replace-in-files --regex='serverurl=".*?"' --replacement='serverUrl="http://localhost:5000"' ./dist/index.html
|
||||
# replace-in-files --regex='apploc=".*?"' --replacement='appLoc="/30.SASApps/app/devtest"' ./dist/index.html
|
||||
# replace-in-files --regex='servertype=".*?"' --replacement='serverType="SASJS"' ./dist/index.html
|
||||
# scp -o stricthostkeychecking=no -r ./dist/* dcgitlab@sas.4gl.io:/var/www/html/dcviya/development/newadapter
|
||||
replace-in-files --regex='"hosturl".*' --replacement='hosturl:"http://localhost:4200",' ./cypress.config.ts
|
||||
# replace-in-files --regex='"appLocation".*' --replacement='appLocation:"/dcviya/development/newadapter",' ./cypress.config.ts
|
||||
cat ./cypress.config.ts
|
||||
# Start frontend and run cypress
|
||||
npm start & npx wait-on http://localhost:4200 && ./run-cypress-tests.sh
|
@ -1,18 +1,164 @@
|
||||
name: Release
|
||||
run-name: Releasing DC
|
||||
run-name: Testing and Releasing DC
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
release:
|
||||
Build-production-and-ng-test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20.14.0
|
||||
|
||||
- name: Write .npmrc file
|
||||
run: |
|
||||
touch client/.npmrc
|
||||
echo '${{ secrets.NPMRC}}' > client/.npmrc
|
||||
|
||||
- name: Install Chrome for Angular tests
|
||||
run: |
|
||||
apt-get update
|
||||
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
|
||||
apt install -y ./google-chrome*.deb;
|
||||
export CHROME_BIN=/usr/bin/google-chrome
|
||||
|
||||
- name: Write cypress credentials
|
||||
run: echo "$CYPRESS_CREDS" > ./client/cypress.env.json
|
||||
shell: bash
|
||||
env:
|
||||
CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd client
|
||||
# Decrypt and Install sheet
|
||||
echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg
|
||||
npm ci
|
||||
|
||||
- name: Check audit
|
||||
# Audit should fail and stop the CI if critical vulnerability found
|
||||
run: |
|
||||
npm audit --audit-level=critical --omit=dev
|
||||
cd ./sas
|
||||
npm audit --audit-level=critical --omit=dev
|
||||
cd ../client
|
||||
npm audit --audit-level=critical --omit=dev
|
||||
|
||||
- name: Angular Tests
|
||||
run: |
|
||||
cd client
|
||||
npm run test:headless
|
||||
|
||||
- name: Angular Production Build
|
||||
run: |
|
||||
cd client
|
||||
npm run postinstall
|
||||
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
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [Build-production-and-ng-test, Build-and-test-development]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.14.0
|
||||
|
||||
- name: Write .npmrc file
|
||||
run: |
|
||||
@ -22,29 +168,117 @@ jobs:
|
||||
env:
|
||||
NPMRC: ${{ secrets.NPMRC}}
|
||||
|
||||
- name: Install ZIP
|
||||
- name: Install packages
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install zip
|
||||
apt-get install zip -y
|
||||
# sasjs cli is used to compile & build the SAS services
|
||||
npm i -g @sasjs/cli
|
||||
# jq is used to parse the release JSON
|
||||
apt-get install jq -y
|
||||
# doxygen is used for the SASJS docs
|
||||
apt-get update
|
||||
apt-get install doxygen -y
|
||||
|
||||
- name: release-build
|
||||
- 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
|
||||
zip -r dist.zip ./dist
|
||||
|
||||
- name: Install Semantic Release and plugins
|
||||
- name: Create Empty Release (assets are posted later)
|
||||
run: |
|
||||
npm i
|
||||
npm i -g semantic-release
|
||||
|
||||
- name: Release
|
||||
run: |
|
||||
# We do a semantic-release DRY RUN to make the job fail if there are no changes to release
|
||||
GITEA_TOKEN=${{ secrets.RELEASE_TOKEN }} GITEA_URL=https://git.datacontroller.io semantic-release --dry-run | grep -q "There are no relevant changes, so no new version is released." && exit 1
|
||||
GITEA_TOKEN=${{ secrets.RELEASE_TOKEN }} GITEA_URL=https://git.datacontroller.io semantic-release
|
||||
|
||||
- name: Frontend Build
|
||||
description: Must be created AFTER the release as the version (git tag) is used in the interface
|
||||
run: |
|
||||
cd client
|
||||
npm run build
|
||||
|
||||
- name: Build SAS9 EBI Release
|
||||
description: Compile SAS 9 services, remove tests & create deployment program
|
||||
run: |
|
||||
cd sas
|
||||
npm i
|
||||
sasjs c -t sas9
|
||||
rm -rf sasjsbuild/tests
|
||||
sasjs b -t sas9
|
||||
cp sasjsbuild/mysas9deploy.sas ./demostream_sas9.sas
|
||||
#
|
||||
# remove streamed component and rebuild SAS 9 services
|
||||
#
|
||||
rm -rf sasjsbuild/services/web9
|
||||
rm sasjsbuild/services/clickme.sas
|
||||
sasjs b -t sas9
|
||||
cp sasjsbuild/mysas9deploy.sas ./sas9.sas
|
||||
|
||||
- name: Build SASjs Server Release
|
||||
description: Compile Base (SASjs) services, remove tests & create deployment JSON
|
||||
run: |
|
||||
cd sas
|
||||
cp sasjs/utils/favicon.ico ../client/dist/favicon.ico
|
||||
sasjs c -t server
|
||||
rm -rf sasjsbuild/tests
|
||||
sasjs b -t server
|
||||
cp sasjsbuild/server.json.zip ./sasjs_server.json.zip
|
||||
|
||||
- name: Build Viya Release
|
||||
description: compile Viya Streaming Deploy (without tests)
|
||||
run: |
|
||||
cd sas
|
||||
sasjs c -t viya
|
||||
rm -rf sasjsbuild/tests
|
||||
sed -i -e 's/servertype="SASJS"/servertype="SASVIYA"/g' sasjsbuild/services/DC.html
|
||||
sasjs b -t viya
|
||||
cp sasjsbuild/viya.sas ./viya.sas
|
||||
# compile Viya Full deploy (without web)
|
||||
rm -rf sasjsbuild/services/web
|
||||
rm sasjsbuild/services/DC.html
|
||||
sasjs b -t viya
|
||||
cp sasjsbuild/viya.sas ./viya_noweb.sas
|
||||
cp sasjsbuild/viya.json ./viya_noweb.json
|
||||
|
||||
- name: Zip Frontend (including viya.json for full viya deploy)
|
||||
run: |
|
||||
cd sas
|
||||
cp sasjsbuild/viya.json ../client/dist/viya.json
|
||||
cd ..
|
||||
zip -r frontend.zip ./client/dist
|
||||
|
||||
- name: Release Typedoc
|
||||
run: |
|
||||
cd client
|
||||
npm run typedoc
|
||||
# deploy docs
|
||||
npm -g install cloudron-surfer
|
||||
npm run compodoc:build
|
||||
surfer put --token ${{ secrets.TSDOC_TOKEN }} --server webdoc.datacontroller.io documentation/* /
|
||||
|
||||
- name: Release code.datacontroller.io
|
||||
run: |
|
||||
cd sas
|
||||
sasjs doc
|
||||
surfer put --token ${{ secrets.CODE_DATACONTROLLER_IO }} --server code.datacontroller.io sasjsbuild/sasdocs/* /
|
||||
|
||||
- name: Upload assets to release
|
||||
run: |
|
||||
RELEASE_ID=`curl -k 'https://git.datacontroller.io/api/v1/repos/dc/dc/releases/latest?access_token=${{ secrets.RELEASE_TOKEN }}' | jq -r '.id'`
|
||||
RELEASE_BODY=`curl -k 'https://git.datacontroller.io/api/v1/repos/dc/dc/releases/latest?access_token=${{ secrets.RELEASE_TOKEN }}' | jq -r '.body'`
|
||||
# Update body
|
||||
curl --data '{"draft": false,"body":"'"$RELEASE_BODY\n\nFor installation instructions, please visit https://docs.datacontroller.io/"'"}' -X PATCH --header 'Content-Type: application/json' -k https://git.datacontroller.io/api/v1/repos/dc/dc/releases/$RELEASE_ID?access_token=${{ secrets.RELEASE_TOKEN }}
|
||||
# Upload assets
|
||||
URL="https://git.datacontroller.io/api/v1/repos/dc/dc/releases/$RELEASE_ID/assets?access_token=${{ secrets.RELEASE_TOKEN }}"
|
||||
curl -k $URL -F attachment=@frontend.zip
|
||||
curl -k $URL -F attachment=@sas/demostream_sas9.sas
|
||||
curl -k $URL -F attachment=@sas/viya.sas
|
||||
curl -k $URL -F attachment=@sas/sasjs_server.json.zip
|
||||
curl -k $URL -F attachment=@sas/sas9.sas
|
||||
curl -k $URL -F attachment=@sas/viya_noweb.sas
|
||||
curl -k $URL -F attachment=@sas/viya_noweb.json
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -10,6 +10,10 @@ client/src/environments/version.ts
|
||||
client/cypress/screenshots
|
||||
client/cypress/results
|
||||
client/cypress/videos
|
||||
client/documentation
|
||||
client/**/sheet-crypto.tgz
|
||||
client/.nx
|
||||
client/libraries/sheet-crypto.tgz
|
||||
cypress.env.json
|
||||
sasjsbuild
|
||||
sasjsresults
|
||||
|
50
.releaserc
50
.releaserc
@ -1,22 +1,46 @@
|
||||
{
|
||||
"branches": ["main"],
|
||||
"branches": [
|
||||
"main"
|
||||
],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer",
|
||||
"@semantic-release/release-notes-generator",
|
||||
"@semantic-release/changelog",
|
||||
"@semantic-release/npm",
|
||||
[
|
||||
"@semantic-release/git",
|
||||
{
|
||||
"assets": [
|
||||
"CHANGELOG.md"
|
||||
]
|
||||
}
|
||||
"@semantic-release/git",
|
||||
{
|
||||
"assets": [
|
||||
"CHANGELOG.md",
|
||||
"package.json"
|
||||
]
|
||||
}
|
||||
],
|
||||
["@saithodev/semantic-release-gitea", {
|
||||
"giteaUrl": "https://git.datacontroller.io",
|
||||
"assets": [
|
||||
{"path": "client/dist.zip"}
|
||||
]
|
||||
}]
|
||||
[
|
||||
"@saithodev/semantic-release-gitea",
|
||||
{
|
||||
"giteaUrl": "https://git.datacontroller.io",
|
||||
"assets": [
|
||||
{
|
||||
"path": "sas/demostream_sas9.sas"
|
||||
},
|
||||
{
|
||||
"path": "sas/demostream_viya.sas"
|
||||
},
|
||||
{
|
||||
"path": "sas/frontend.zip"
|
||||
},
|
||||
{
|
||||
"path": "sas/sasjs_server.json.zip"
|
||||
},
|
||||
{
|
||||
"path": "sas/sas9.sas"
|
||||
},
|
||||
{
|
||||
"path": "sas/viya.sas"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
35
.vscode/settings.json
vendored
35
.vscode/settings.json
vendored
@ -1,18 +1,19 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"SYSERRORTEXT",
|
||||
"SYSWARNINGTEXT"
|
||||
],
|
||||
"editor.rulers": [
|
||||
80
|
||||
],
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"[markdown]": {
|
||||
"files.trimTrailingWhitespace": false
|
||||
},
|
||||
"workbench.colorCustomizations": {
|
||||
"titleBar.activeForeground": "#ebe8e8",
|
||||
"titleBar.activeBackground": "#95ff0053",
|
||||
},
|
||||
"terminal.integrated.wordSeparators": " ()[]{}',\"`─‘’"
|
||||
}
|
||||
"cSpell.words": [
|
||||
"Licence",
|
||||
"SYSERRORTEXT",
|
||||
"SYSWARNINGTEXT",
|
||||
"xlmaprules",
|
||||
"xlmaps"
|
||||
],
|
||||
"editor.rulers": [80],
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"[markdown]": {
|
||||
"files.trimTrailingWhitespace": false
|
||||
},
|
||||
"workbench.colorCustomizations": {
|
||||
"titleBar.activeForeground": "#ebe8e8",
|
||||
"titleBar.activeBackground": "#95ff0053"
|
||||
},
|
||||
"terminal.integrated.wordSeparators": " ()[]{}',\"`─‘’"
|
||||
}
|
||||
|
@ -1,9 +0,0 @@
|
||||
pipeline:
|
||||
build:
|
||||
image: debian
|
||||
commands:
|
||||
- echo "This is the build step"
|
||||
a-test-step:
|
||||
image: debian
|
||||
commands:
|
||||
- echo "Testing.."
|
510
CHANGELOG.md
510
CHANGELOG.md
@ -1,3 +1,513 @@
|
||||
## [6.15.2](https://git.datacontroller.io/dc/dc/compare/v6.15.1...v6.15.2) (2025-06-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* pipeline updates for DC.html ([624a7a8](https://git.datacontroller.io/dc/dc/commit/624a7a8f37f0265cf576da310ac330c75aa417cf))
|
||||
|
||||
## [6.15.1](https://git.datacontroller.io/dc/dc/compare/v6.15.0...v6.15.1) (2025-06-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* updating pipeline to default to streaming on viya ([4b55894](https://git.datacontroller.io/dc/dc/commit/4b558948d997f456ff25a12a58827fe0d2075493))
|
||||
|
||||
# [6.15.0](https://git.datacontroller.io/dc/dc/compare/v6.14.10...v6.15.0) (2025-06-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* makedata with context name ([da4d0b2](https://git.datacontroller.io/dc/dc/commit/da4d0b28c7109afd6f96455e1e0e80a40d25a942))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* viya deploy context ([6c96ef7](https://git.datacontroller.io/dc/dc/commit/6c96ef7fb0a55754a84ff0a8bbab838b78c1acaf))
|
||||
|
||||
## [6.14.10](https://git.datacontroller.io/dc/dc/compare/v6.14.9...v6.14.10) (2025-06-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* bump core ([0e8503e](https://git.datacontroller.io/dc/dc/commit/0e8503ed2bb22a0fc3924ac929e7f19626772e0a))
|
||||
* default to home directory for SAS Drive in Viya ([9682b54](https://git.datacontroller.io/dc/dc/commit/9682b548e6106d99d97dcc023a35d93addfd5170))
|
||||
|
||||
## [6.14.9](https://git.datacontroller.io/dc/dc/compare/v6.14.8...v6.14.9) (2025-06-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* default DC path for viya ([f3125ff](https://git.datacontroller.io/dc/dc/commit/f3125ff4641e47e33cb203228f5b1014ea3343bc))
|
||||
|
||||
## [6.14.8](https://git.datacontroller.io/dc/dc/compare/v6.14.7...v6.14.8) (2025-05-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* CSP issues, clarity local library build, fixed some style issues ([841201a](https://git.datacontroller.io/dc/dc/commit/841201adab582149b1cca3a42e75f7cac75167f9))
|
||||
* deploy page, makedata error handling, added local build of clarity, to address clr-stack-view CSP issues (inline styles) ([7b5e7ae](https://git.datacontroller.io/dc/dc/commit/7b5e7ae18414152f9b9d8f2d94fc94de43152003))
|
||||
* improved deploy flow for Viya ([9604661](https://git.datacontroller.io/dc/dc/commit/9604661f3b76111387bc9474cc26348d73ab112e))
|
||||
* requests modal causing VIYA CSP errors ([1dc6934](https://git.datacontroller.io/dc/dc/commit/1dc69341cadb837e1f11624d5cf35788bbb98d96))
|
||||
* sas viya service init timing issue ([9de04e9](https://git.datacontroller.io/dc/dc/commit/9de04e9a0ce016e1a9fb8b19c656077079ddcf2f))
|
||||
* scss of components transferred to the global styles.scss so we do not cause CSP (inline styles) issues when streaming to Viya ([6c171a6](https://git.datacontroller.io/dc/dc/commit/6c171a6394aba8104fe0f50aa8a4e6b9fa8023a2))
|
||||
* viya deploy page improved flow ([4bd2154](https://git.datacontroller.io/dc/dc/commit/4bd215491f8cdc68f78bade68e7cb98e07edc81e))
|
||||
|
||||
## [6.14.7](https://git.datacontroller.io/dc/dc/compare/v6.14.6...v6.14.7) (2025-05-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* updated hot, clarity and improved accessibility score. ([2844c70](https://git.datacontroller.io/dc/dc/commit/2844c70f9507036216b8b621900c2bb9010c1d34))
|
||||
|
||||
## [6.14.6](https://git.datacontroller.io/dc/dc/compare/v6.14.5...v6.14.6) (2025-04-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* history table modal links styling ([c63fcdd](https://git.datacontroller.io/dc/dc/commit/c63fcdd465950ada439d7d69622a3886e8f3a783))
|
||||
|
||||
## [6.14.5](https://git.datacontroller.io/dc/dc/compare/v6.14.4...v6.14.5) (2025-03-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improving accessibility lighthouse score ([7f3577c](https://git.datacontroller.io/dc/dc/commit/7f3577c3ef9f44e55a58bc64fbf89a3a64006dd4))
|
||||
* prevent errors when using sqlrc in a DI job in a HOOK ([d1f0879](https://git.datacontroller.io/dc/dc/commit/d1f0879f0acf7e816c80f7635fd02f4f284214ed))
|
||||
* user profile style fix, new select library and table icons ([69f8830](https://git.datacontroller.io/dc/dc/commit/69f883034fabbed31aa5d832e20561c4ae3042db))
|
||||
|
||||
## [6.14.4](https://git.datacontroller.io/dc/dc/compare/v6.14.3...v6.14.4) (2025-03-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* removing cli dependency warnings2 ([43c0f73](https://git.datacontroller.io/dc/dc/commit/43c0f73c2189ff762986a964caae6b0b108164fc))
|
||||
|
||||
## [6.14.3](https://git.datacontroller.io/dc/dc/compare/v6.14.2...v6.14.3) (2025-03-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* NLDAT & NLDATM formats are now being staged ([3f5cb1e](https://git.datacontroller.io/dc/dc/commit/3f5cb1e2defe390220e904e4bf04a165cb31fec4))
|
||||
|
||||
## [6.14.2](https://git.datacontroller.io/dc/dc/compare/v6.14.1...v6.14.2) (2025-03-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improving instructions for setup ([83b3d77](https://git.datacontroller.io/dc/dc/commit/83b3d775b6e33653b087ca9f4eb3ad5b0dbbd479))
|
||||
|
||||
## [6.14.1](https://git.datacontroller.io/dc/dc/compare/v6.14.0...v6.14.1) (2025-03-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* handle national language datetime formats ([149e318](https://git.datacontroller.io/dc/dc/commit/149e318a8787be0109f25aeec3a1270ea75a97b2))
|
||||
* updating logic to use NLDAT formats ([95289aa](https://git.datacontroller.io/dc/dc/commit/95289aa9524d3cb2b1c248cfb84f6b0d0a490c32))
|
||||
|
||||
# [6.14.0](https://git.datacontroller.io/dc/dc/compare/v6.13.2...v6.14.0) (2025-02-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* uses SORTSEQ=LINGUISTIC for the services/metanav/metadetails service ([a45f5bb](https://git.datacontroller.io/dc/dc/commit/a45f5bb3b27a86da5f55ff28c9c7669956484ddf))
|
||||
|
||||
## [6.13.2](https://git.datacontroller.io/dc/dc/compare/v6.13.1...v6.13.2) (2025-02-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* get metadata email if exists ([1bd0eef](https://git.datacontroller.io/dc/dc/commit/1bd0eef913593579771c647d80010ce628c00819))
|
||||
|
||||
## [6.13.1](https://git.datacontroller.io/dc/dc/compare/v6.13.0...v6.13.1) (2025-02-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Avoiding LATIN1 unprintables in various UI locations ([bce1fd5](https://git.datacontroller.io/dc/dc/commit/bce1fd57ef397cfdd030552c3f424a8407174729))
|
||||
* updated @sasjs/adapter, crypto-browserify ([4e64f28](https://git.datacontroller.io/dc/dc/commit/4e64f28732868b07ec2ab403912bd384c62c7e1d))
|
||||
|
||||
# [6.13.0](https://git.datacontroller.io/dc/dc/compare/v6.12.3...v6.13.0) (2025-01-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* editor page csv upload ([217220f](https://git.datacontroller.io/dc/dc/commit/217220ffaaf688133321cc68d770aaf1e50590a9))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* csv test ([c53ab85](https://git.datacontroller.io/dc/dc/commit/c53ab85107f10c023117a099cc06321afc3e1f03))
|
||||
|
||||
## [6.12.3](https://git.datacontroller.io/dc/dc/compare/v6.12.2...v6.12.3) (2025-01-27)
|
||||
|
||||
|
||||
### 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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* ci sheet lib, submit message auto focus ([c5e4650](https://git.datacontroller.io/dc/dc/commit/c5e46503272f3f3d9cd83ac04225babf79d4de44))
|
||||
* **clarity:** new version style issues ([8c7de5a](https://git.datacontroller.io/dc/dc/commit/8c7de5aad7e7e32a64769696af9b93eb9a6225d3))
|
||||
* cypress tests ([3dd85cc](https://git.datacontroller.io/dc/dc/commit/3dd85cc60bd5ac99bc930b6b9c89a8e707e4d51d))
|
||||
* ensuring that only restorable versions are restorable ([a402856](https://git.datacontroller.io/dc/dc/commit/a4028562ce91b32ff971ab9821328b97cd23f381))
|
||||
* ensuring version history only includes loaded versions ([51ebd25](https://git.datacontroller.io/dc/dc/commit/51ebd25aa362aa8e66c83b29b2c64aa0f206f5bd))
|
||||
* final testing on restore feature ([297a84d](https://git.datacontroller.io/dc/dc/commit/297a84d3a4ebb47bef7f3ca9758978d727afed8d))
|
||||
* issue with multiple adds/deletes, [#84](https://git.datacontroller.io/dc/dc/issues/84) ([904ca30](https://git.datacontroller.io/dc/dc/commit/904ca30f918da085fa05dae066367b512933d1a9))
|
||||
* load_ref var ([aaad9f7](https://git.datacontroller.io/dc/dc/commit/aaad9f7207115599a006980fff099d59738dd2cd))
|
||||
* removing alerts dummy data, closes [#93](https://git.datacontroller.io/dc/dc/issues/93) ([eba21e9](https://git.datacontroller.io/dc/dc/commit/eba21e96b4fa34e63b4477281f47d9a01d621f2e))
|
||||
* restore table version improvement ([549f357](https://git.datacontroller.io/dc/dc/commit/549f35766ba7b5bbe55694845e85bfefc4193375))
|
||||
* **sas:** viewer versions fix ([c6595c1](https://git.datacontroller.io/dc/dc/commit/c6595c1f618803d9202cba1a1fe76986449cf2e2))
|
||||
* stage and approve buttons renaming ([ef81e33](https://git.datacontroller.io/dc/dc/commit/ef81e33f704d0b4f99405d96498e1d29ac817982))
|
||||
* supporting SCD2 data reversions ([fa8396f](https://git.datacontroller.io/dc/dc/commit/fa8396f0394cbddb6dbacb4b355de078fad49980))
|
||||
* table info modal, versions - column names ([801c8c6](https://git.datacontroller.io/dc/dc/commit/801c8c6a9fb95388a06a6c6284fec4dc25bb77c5))
|
||||
* **updates:** angular, clarity, resolved legacy-peer-deps ([c60dd65](https://git.datacontroller.io/dc/dc/commit/c60dd65a1637333f11a0c39ef697c7292a6ede07))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* backend to show in getchangeinfo whether a user is allowed to restore ([8769841](https://git.datacontroller.io/dc/dc/commit/8769841f08694f672ef7ae1a17beacd0dbedda52))
|
||||
* list versions of target tables (backend) ([f8a14d4](https://git.datacontroller.io/dc/dc/commit/f8a14d4bdef055b99930491d1f6fabe55a50a497))
|
||||
* restore ([604c2e7](https://git.datacontroller.io/dc/dc/commit/604c2e70bdeeeb1aa5bb18b94f525ebd049397fa))
|
||||
* SAS services & tests for RESTORE, [#84](https://git.datacontroller.io/dc/dc/issues/84) ([9ad7ae4](https://git.datacontroller.io/dc/dc/commit/9ad7ae47b5e793ce68ab21c9eeb8dee6cb85e496))
|
||||
* staging page, restore buttons ([02a8a1c](https://git.datacontroller.io/dc/dc/commit/02a8a1c5654350cafc53b749cceb686fc6848c33))
|
||||
* table metadata modal, versions tab (and link) ([b27fea5](https://git.datacontroller.io/dc/dc/commit/b27fea5b91e33b4673a3a991aedae558e366ca29))
|
||||
* **versions:** getting list of versions (plus test) ([8003da9](https://git.datacontroller.io/dc/dc/commit/8003da94e615463ed3ddfd60b0cbf2e58615eab1))
|
||||
|
||||
# [6.7.0](https://git.datacontroller.io/dc/dc/compare/v6.6.4...v6.7.0) (2024-04-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* numeric values in hot dropdown aligned right ([9635626](https://git.datacontroller.io/dc/dc/commit/963562621ddf0e8d24a29a8481c5e6da1b040708))
|
||||
|
||||
## [6.6.4](https://git.datacontroller.io/dc/dc/compare/v6.6.3...v6.6.4) (2024-04-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* ordering SOFTSELECT numerically in dropdown ([f522038](https://git.datacontroller.io/dc/dc/commit/f522038b8ddb1da14b8adbf8346d0a4539a94cc8)), closes [#85](https://git.datacontroller.io/dc/dc/issues/85)
|
||||
* reverting col ([fbbcf90](https://git.datacontroller.io/dc/dc/commit/fbbcf90956bf538b032b0107c07b8576d20353b9))
|
||||
* typo ([31d4e5c](https://git.datacontroller.io/dc/dc/commit/31d4e5c727f790d428fb2ea8da60dca929561805))
|
||||
|
||||
## [6.6.3](https://git.datacontroller.io/dc/dc/compare/v6.6.2...v6.6.3) (2024-02-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow empty clause value when NE or CONTAINS ([432450a](https://git.datacontroller.io/dc/dc/commit/432450a15b51a269821ba1d430854f5d1dd04703))
|
||||
|
||||
## [6.6.2](https://git.datacontroller.io/dc/dc/compare/v6.6.1...v6.6.2) (2024-02-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* excel with commas getting wrapped in quotes ([3860134](https://git.datacontroller.io/dc/dc/commit/38601346a529cfe3787bb286a639e0293c365020))
|
||||
|
||||
## [6.6.1](https://git.datacontroller.io/dc/dc/compare/v6.6.0...v6.6.1) (2024-02-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **client:** bumped @sasjs/adapter with fixed redirected login ([eb1c09d](https://git.datacontroller.io/dc/dc/commit/eb1c09d7909ba07faf763da261545dc1efaec1b3))
|
||||
|
||||
# [6.6.0](https://git.datacontroller.io/dc/dc/compare/v6.5.2...v6.6.0) (2024-02-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* adjust the col numbers in extracted data ([cff5989](https://git.datacontroller.io/dc/dc/commit/cff598955930d2581349e5c6e8b2dd3f9ac96b4c))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* extra table metadata for [#75](https://git.datacontroller.io/dc/dc/issues/75) ([837821f](https://git.datacontroller.io/dc/dc/commit/837821fd01477d340524dfdaf8dd3d3758cf3095))
|
||||
* show dsnote on hover title ([6565834](https://git.datacontroller.io/dc/dc/commit/6565834ad4089ecf2de39967e6ed6f217ee4a0a5))
|
||||
|
||||
## [6.5.2](https://git.datacontroller.io/dc/dc/compare/v6.5.1...v6.5.2) (2024-02-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* ordering mpe_selectbox data by the data values after selectbox_order ([2b54034](https://git.datacontroller.io/dc/dc/commit/2b5403497317632a4be8a00f21455c036f1e6461))
|
||||
|
||||
## [6.5.1](https://git.datacontroller.io/dc/dc/compare/v6.5.0...v6.5.1) (2024-02-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* ensuring submitter email can be pulled from mpe_emails ([eac0104](https://git.datacontroller.io/dc/dc/commit/eac0104d7aebaf98ff1d1c504c1ce3b25d4a0ce8))
|
||||
|
||||
# [6.5.0](https://git.datacontroller.io/dc/dc/compare/v6.4.0...v6.5.0) (2024-01-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* filtering by reference to Variables as well as Values ([6eb1aa8](https://git.datacontroller.io/dc/dc/commit/6eb1aa85d29294d63e6af377e622fbed7fd1fab8))
|
||||
|
||||
# [6.4.0](https://git.datacontroller.io/dc/dc/compare/v6.3.1...v6.4.0) (2024-01-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add dcLib to globals ([5d93346](https://git.datacontroller.io/dc/dc/commit/5d93346b52eda27c2829770e96686a713296d373))
|
||||
* add service to get xlmap rules and fixed interface name ([9ffa30a](https://git.datacontroller.io/dc/dc/commit/9ffa30ab747f5b62acbd452431a5e6e440afcb80))
|
||||
* increasing length of mpe_excel_map cols to ([2d4d068](https://git.datacontroller.io/dc/dc/commit/2d4d068413dcdac98581f08939e74bde65b73428))
|
||||
* providing info on mapids to FE ([fd94945](https://git.datacontroller.io/dc/dc/commit/fd94945466c1a797ddc89815258a65624a9cb0cf))
|
||||
* removing tables from EDIT menu that are in xlmaps ([9550ae4](https://git.datacontroller.io/dc/dc/commit/9550ae4d1154a0272f8a2427ac9d2afdfd699c96))
|
||||
* removing XLMAP_TARGETLIBDS from mpe_xlmaps_rules table ([93702c6](https://git.datacontroller.io/dc/dc/commit/93702c63dc280cdba1e46f0fd8fe0deaec879611))
|
||||
* renaming TABLE macvar to LOAD_REF in postdata.sas ([01915a2](https://git.datacontroller.io/dc/dc/commit/01915a2db9a4dfb94e4e8213e2c32181da36d349))
|
||||
* reverting xlmap in getdata change ([2d6e747](https://git.datacontroller.io/dc/dc/commit/2d6e747db9b84e9fb0dfcf9102a2f7dd2cb51891))
|
||||
* update edit tab to load ([516e5a2](https://git.datacontroller.io/dc/dc/commit/516e5a206216f79ab1dce9f4eab0d31115743160))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* adding ability to define the target table for excel maps ([c86fba9](https://git.datacontroller.io/dc/dc/commit/c86fba9dc75ddc6033132f469ad1c31b9131b12e))
|
||||
* adding ismap attribute to getdata response (and fixing test) ([2702bb3](https://git.datacontroller.io/dc/dc/commit/2702bb3c84c45903def1aa2b8cc20a6dd080281b))
|
||||
* Complex Excel Uploads ([cf19381](https://git.datacontroller.io/dc/dc/commit/cf193810606f287b8d6f864c4eb64d43c5ab5f3c)), closes [#69](https://git.datacontroller.io/dc/dc/issues/69)
|
||||
* Create Tables / Files dropdown under load tab ([b473b19](https://git.datacontroller.io/dc/dc/commit/b473b198a61f468dff74cd8e64692e7847084a80))
|
||||
* display list of maps in sidebar ([5aec024](https://git.datacontroller.io/dc/dc/commit/5aec0242429942f8a989b5fb79f8d3865e9de01a))
|
||||
* implemented the logic for xlmap component ([50696bb](https://git.datacontroller.io/dc/dc/commit/50696bb926dd00472db65a008771a4b6352871be))
|
||||
* model changes for [#69](https://git.datacontroller.io/dc/dc/issues/69) ([271543a](https://git.datacontroller.io/dc/dc/commit/271543a446a2116718f99f0540e3cd911f9f5fe7))
|
||||
* new getxlmaps service to return rules for a particular xlmap_id ([56264ec](https://git.datacontroller.io/dc/dc/commit/56264ecc6908bf6c8e3e666dfeba7068d6195df8))
|
||||
* validating the excel map after stage (adding load-ref) ([a485c3b](https://git.datacontroller.io/dc/dc/commit/a485c3b78724a36f7bacb264fb02140cc62d6512))
|
||||
|
||||
## [6.3.1](https://git.datacontroller.io/dc/dc/compare/v6.3.0...v6.3.1) (2024-01-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* enabling excel uploads to tables with retained keys, also adding more validation to MPE_TABLES updates ([3efccc4](https://git.datacontroller.io/dc/dc/commit/3efccc4cf3752763d049836724f2491c287f65db))
|
||||
|
||||
# [6.3.0](https://git.datacontroller.io/dc/dc/compare/v6.2.8...v6.3.0) (2023-12-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* viewer row handle ([dadac4f](https://git.datacontroller.io/dc/dc/commit/dadac4f13f85b5446198b6340cad28844defc94d))
|
||||
|
||||
## [6.2.8](https://git.datacontroller.io/dc/dc/compare/v6.2.7...v6.2.8) (2023-12-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* bumping sasjs/core to fix mp_loadformat issue ([a1d308e](https://git.datacontroller.io/dc/dc/commit/a1d308ea078786b27bf7ec940d018fc657d4c398))
|
||||
* new logic for -fc suffix. Closes [#63](https://git.datacontroller.io/dc/dc/issues/63) ([5579db0](https://git.datacontroller.io/dc/dc/commit/5579db0eafc668b1bc310099b7cc3062e0598fc4))
|
||||
|
||||
## [6.2.7](https://git.datacontroller.io/dc/dc/compare/v6.2.6...v6.2.7) (2023-11-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **audit:** updated crypto-js (hashing rows in dynamic cell validation) ([a7aa42a](https://git.datacontroller.io/dc/dc/commit/a7aa42a59b71597399924b8d2d06010c806321f3))
|
||||
* missing dependency and avoiding label length limit issue ([91f128c](https://git.datacontroller.io/dc/dc/commit/91f128c2fead1e4f72267d689e67f49ec9a2ab35))
|
||||
|
||||
## [6.2.6](https://git.datacontroller.io/dc/dc/compare/v6.2.5...v6.2.6) (2023-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* bumping core to address mm_assigndirectlib issue ([c27cdab](https://git.datacontroller.io/dc/dc/commit/c27cdab3fccbde814a29424d0344173a73ea816c))
|
||||
|
||||
## [6.2.5](https://git.datacontroller.io/dc/dc/compare/v6.2.4...v6.2.5) (2023-10-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* enabling AUTHDOMAIN in MM_ASSIGNDIRECTLIB ([008b45a](https://git.datacontroller.io/dc/dc/commit/008b45ad175ec0e6026f5ef3bc210470226e328f))
|
||||
|
||||
## [6.2.4](https://git.datacontroller.io/dc/dc/compare/v6.2.3...v6.2.4) (2023-10-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Enable display of metadata-only tables. Closes [#56](https://git.datacontroller.io/dc/dc/issues/56) ([f3e82b4](https://git.datacontroller.io/dc/dc/commit/f3e82b4ee2a9c1c851f812ac60e9eaf05f91a0f9))
|
||||
|
||||
## [6.2.3](https://git.datacontroller.io/dc/dc/compare/v6.2.2...v6.2.3) (2023-10-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* bumping core library to avoid non-ascii char in mp_validatecols.sas. [#50](https://git.datacontroller.io/dc/dc/issues/50) ([11b06f6](https://git.datacontroller.io/dc/dc/commit/11b06f6416300b6d70b1570c415d5a5c004976db))
|
||||
* removing copyright symbol from mpe_alerts macro. [#50](https://git.datacontroller.io/dc/dc/issues/50) ([adb7eb7](https://git.datacontroller.io/dc/dc/commit/adb7eb77550c68a2dab15a6ff358129820e9b612))
|
||||
|
||||
## [6.2.2](https://git.datacontroller.io/dc/dc/compare/v6.2.1...v6.2.2) (2023-10-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* updated SheetJS (crypto) to the latest ([8bd0dd2](https://git.datacontroller.io/dc/dc/commit/8bd0dd22c258911672303869e4df893a98e93575))
|
||||
|
||||
## [6.2.1](https://git.datacontroller.io/dc/dc/compare/v6.2.0...v6.2.1) (2023-10-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* approve, history and submit pages grouped in review module ([e056ece](https://git.datacontroller.io/dc/dc/commit/e056ece2234ef6aab050f6a5b1f8de633b163d91))
|
||||
* closes [#39](https://git.datacontroller.io/dc/dc/issues/39) upcase issue in MPE_SECURITY ([a00d31c](https://git.datacontroller.io/dc/dc/commit/a00d31caf3c5634cd61a4700fb175e76856edbb6))
|
||||
* handsontable v13 ([6f482ec](https://git.datacontroller.io/dc/dc/commit/6f482ec6d909907a304ef9975262889e2370035f))
|
||||
* latest adapter ([5e30dc0](https://git.datacontroller.io/dc/dc/commit/5e30dc0f892fab2af41f4ea56e30f27ec3b3912e))
|
||||
* sasjs/cli and sasjs/core updated to the latest ([8571e01](https://git.datacontroller.io/dc/dc/commit/8571e01e44a8cb6df9d150d271c34bb75bffdf31))
|
||||
* updating editors/stagedata to address issues in particular viya configurations as described in issue [#33](https://git.datacontroller.io/dc/dc/issues/33) ([94ab949](https://git.datacontroller.io/dc/dc/commit/94ab949df8c75072525751a2156b7a32c2e641dc))
|
||||
* updating logic for REPLACE loadtype ([1f2ce55](https://git.datacontroller.io/dc/dc/commit/1f2ce55f249f4af56f0cacdec47e69246cd47431))
|
||||
|
||||
# [6.2.0](https://git.datacontroller.io/dc/dc/compare/v6.1.0...v6.2.0) (2023-08-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* re-enabling full REPLACE uploads ([08e39c4](https://git.datacontroller.io/dc/dc/commit/08e39c4fca570406f9aad3d907cb04596421d074))
|
||||
|
||||
### Features
|
||||
|
||||
* support for European numeric formats ([e48e47b](https://git.datacontroller.io/dc/dc/commit/e48e47bc635452b59e107b235e597c26e748875e))
|
||||
|
||||
# [6.1.0](https://git.datacontroller.io/dc/dc/compare/v6.0.0...v6.1.0) (2023-07-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* missing mf_existds dependency in bitemporal_dataloader ([5ce1701](https://git.datacontroller.io/dc/dc/commit/5ce1701657136f2cf792441412230513ff52e7e8))
|
||||
* reducing audit data volumes. Closes [#4](https://git.datacontroller.io/dc/dc/issues/4) ([54fe701](https://git.datacontroller.io/dc/dc/commit/54fe7013b1a25be228eeb2aba3553f6219952de6))
|
||||
* release script, excel upload duplicate primary keys, cypress fix ([2f79487](https://git.datacontroller.io/dc/dc/commit/2f79487aeaf6268b027a8fa52fcdaae2b449de3d))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* full format deletion, closes [#2](https://git.datacontroller.io/dc/dc/issues/2) ([8dc40bd](https://git.datacontroller.io/dc/dc/commit/8dc40bdd4e3a7ad5c1e6582b4130f24bc445eb77))
|
||||
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
|
||||
|
@ -1,6 +1,9 @@
|
||||
# Data Controller
|
||||
# Contributing
|
||||
|
||||
## Workflow guidelines
|
||||
[Wiki Page](https://git.datacontroller.io/dc/dc/wiki/Git-Workflow)
|
||||
|
||||
## Dependencies that requires licences
|
||||
|
||||
[SheetJS Pro Version](https://www.npmjs.com/package/sheetjs)
|
||||
@ -50,6 +53,17 @@ npm run lint:fix
|
||||
Typedoc is used for generating typescript documentation based on the code.
|
||||
That part is automated and beign done as a part of CI job.
|
||||
|
||||
# Release
|
||||
Release is automated as a part of CI job. Workflow file: `.gitea/workflows/release.yaml`.
|
||||
It will run automatically when branch merged to the `main` branch.
|
||||
IMPORTANT!
|
||||
If release job fails, after it has been created empty release and a tag, we must not re-run the relase job until we removed the newly create GIT TAG and RELEASE.
|
||||
To remove the git tag run:
|
||||
```
|
||||
git push -d origin vX.X.X
|
||||
```
|
||||
To remove the release, you need to do it with repo administration over at [https://git.datacontroller.io/dc/dc](https://git.datacontroller.io/dc/dc)
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
## Makedata service "could not create directory" error
|
||||
|
22
LICENCE.md
22
LICENCE.md
@ -3,25 +3,15 @@ Licence Agreement for Data Controller for SAS®
|
||||
|
||||
Copyright (c) Bowe IO Ltd
|
||||
|
||||
Data Controller is a software distributed by 4GL Apps, a brand owned by Bowe IO Ltd, a UK Limited Company headquarted in 29 Oldfield Rd, Cumbria, registered by companies house under number 08777171, VAT number: 203914240
|
||||
Data Controller software is distributed by 4GL Apps, a brand owned by Bowe IO Ltd, a UK Limited Company headquarted in 29 Oldfield Rd, Cumbria, registered at Companies House with company number 08777171, VAT number: 203914240
|
||||
|
||||
This software is protected by applicable copyright laws, including international treaties, and dual-
|
||||
licensed – depending on whether your use for commercial purposes, meaning intended for or
|
||||
resulting in commercial advantage or monetary compensation, or not.
|
||||
This software is protected by applicable copyright laws, including international treaties, and dual-licensed – depending on whether your use for commercial purposes, meaning intended for or resulting in commercial advantage or monetary compensation, or not.
|
||||
|
||||
If your use is strictly personal or solely for evaluation purposes, meaning for the purposes of testing
|
||||
the suitability, performance, and usefulness of this software outside the production environment,
|
||||
you agree to be bound by the terms included in the "licence-non-commercial-datacontroller.md" file.
|
||||
If your use is strictly personal or solely for evaluation purposes, meaning for the purposes of testing the suitability, performance, and usefulness of this software outside the production environment, you agree to be bound by the terms included in the "licence-non-commercial-datacontroller.md" file available here: https://git.datacontroller.io/dc/dc/src/branch/main/licence-non-commercial-datacontroller.md
|
||||
|
||||
Your use of this software for commercial purposes is subject to the terms included in an applicable
|
||||
license agreement.
|
||||
Your use of this software for commercial purposes is subject to the terms included in an applicable license agreement.
|
||||
|
||||
In any case, you must not make any such use of this software as to develop software which may be
|
||||
considered competitive with this software.
|
||||
In any case, you must not make any such use of this software as to develop software which may be considered competitive with this software.
|
||||
|
||||
UNLESS EXPRESSLY AGREED OTHERWISE, 4GL APPS PROVIDES THIS SOFTWARE ON AN "AS IS"
|
||||
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, AND IN NO EVENT AND UNDER NO
|
||||
LEGAL THEORY, SHALL 4GL APPS BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT,
|
||||
INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER ARISING FROM
|
||||
USE OR INABILITY TO USE THIS SOFTWARE.
|
||||
UNLESS EXPRESSLY AGREED OTHERWISE, 4GL APPS PROVIDES THIS SOFTWARE ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, AND IN NO EVENT AND UNDER NO LEGAL THEORY, SHALL 4GL APPS BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER ARISING FROM USE OR INABILITY TO USE THIS SOFTWARE.
|
||||
|
||||
|
32
README.md
32
README.md
@ -0,0 +1,32 @@
|
||||
# Data Controller for SAS
|
||||
|
||||
**Control your manual data modifications!**
|
||||
|
||||
_Alternatives to Data Controller include:_
|
||||
|
||||
* 💾 Developing / testing / deploying / scheduling overnight batch jobs to load files from shared drives
|
||||
* 🔒 Opening (and locking) datasets in Enterprise Guide or SAS® Table Viewer to perform direct updates
|
||||
* ❓ Asking a #DBA to run validated code after a change management process
|
||||
* 🌐 Building & maintaining your own custom web application
|
||||
* 🏃 Running #SAS or #SQL updates in production
|
||||
|
||||
_Problems with the above include:_
|
||||
|
||||
* Legacy 'black box' solutions with little to no testing, documentation or support
|
||||
* End users requiring direct write access to critical data sources in production
|
||||
* Upload routines that must be manually modified when the data model changes
|
||||
* Breaches due to unnecessary parties having access to the data
|
||||
* Inability to trace who made a change, when, and why
|
||||
* Reliance on key individuals to perform updates
|
||||
* Building bespoke ETL for every new data source
|
||||
* High risk of manual error / data corruption
|
||||
|
||||
Data Controller for SAS® solves all these issues in a simple-to-install, user-friendly, secure, documented, battle-tested web application. Available on Viya, SAS 9 EBI, and [SASjs Server](https://server.sasjs.io).
|
||||
|
||||
For more information:
|
||||
|
||||
* Main site: https://datacontroller.io
|
||||
* Docs: https://docs.datacontroller.io
|
||||
* Code: https://code.datacontroller.io
|
||||
|
||||
For support, contact support@4gl.io or reach out on [Matrix](https://matrix.to/#/#dc:4gl.io)!
|
@ -45,6 +45,7 @@
|
||||
"numbro",
|
||||
"@clr/icons",
|
||||
"@sasjs/adapter",
|
||||
"@sasjs/utils/types/serverType",
|
||||
"@sasjs/utils/input/validators",
|
||||
"@sasjs/utils/utils/bytesToSize",
|
||||
"base64-arraybuffer",
|
||||
@ -67,9 +68,9 @@
|
||||
"src/styles.scss"
|
||||
],
|
||||
"scripts": [
|
||||
"node_modules/@clr/icons/clr-icons.min.js",
|
||||
"node_modules/marked/marked.min.js"
|
||||
]
|
||||
],
|
||||
"webWorkerTsConfig": "tsconfig.worker.json"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
@ -116,10 +117,10 @@
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "datacontroller:build:production"
|
||||
"buildTarget": "datacontroller:build:production"
|
||||
},
|
||||
"development": {
|
||||
"browserTarget": "datacontroller:build:development"
|
||||
"buildTarget": "datacontroller:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
@ -127,31 +128,29 @@
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "datacontroller:build"
|
||||
"buildTarget": "datacontroller:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"codeCoverage": true,
|
||||
"polyfills": [
|
||||
"src/polyfills.ts",
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.scss"
|
||||
],
|
||||
"scripts": [
|
||||
|
||||
],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"karmaConfig": "karma.conf.js"
|
||||
"styles": [
|
||||
"src/styles.scss"
|
||||
],
|
||||
"scripts": [],
|
||||
"karmaConfig": "karma.conf.js",
|
||||
"webWorkerTsConfig": "tsconfig.worker.json"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
|
@ -1,17 +1,22 @@
|
||||
import { defineConfig } from 'cypress'
|
||||
import { defineConfig } from "cypress";
|
||||
|
||||
export default defineConfig({
|
||||
reporter: 'mochawesome',
|
||||
reporter: "mochawesome",
|
||||
|
||||
reporterOptions: {
|
||||
reportDir: 'cypress/results',
|
||||
reportDir: "cypress/results",
|
||||
overwrite: false,
|
||||
html: true,
|
||||
json: false,
|
||||
},
|
||||
viewportHeight: 900,
|
||||
viewportWidth: 1600,
|
||||
|
||||
chromeWebSecurity: false,
|
||||
defaultCommandTimeout: 30000,
|
||||
|
||||
env: {
|
||||
hosturl:"http://localhost:4200",
|
||||
hosturl: "http://localhost:4200",
|
||||
appLocation: "",
|
||||
site_id_SAS9: "70221618",
|
||||
site_id_SASVIYA: "70253615",
|
||||
@ -23,6 +28,12 @@ export default defineConfig({
|
||||
debug: false,
|
||||
screenshotOnRunFailure: false,
|
||||
longerCommandTimeout: 50000,
|
||||
testLicenceUserLimits: false
|
||||
}
|
||||
})
|
||||
testLicenceUserLimits: false,
|
||||
},
|
||||
|
||||
e2e: {
|
||||
setupNodeEvents(on, config) {
|
||||
// implement node event listeners here
|
||||
},
|
||||
},
|
||||
});
|
||||
|
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;')
|
||||
}
|
250
client/cypress/e2e/download.cy.ts
Normal file
250
client/cypress/e2e/download.cy.ts
Normal file
@ -0,0 +1,250 @@
|
||||
const username = Cypress.env('username')
|
||||
const password = Cypress.env('password')
|
||||
const hostUrl = Cypress.env('hosturl')
|
||||
const appLocation = Cypress.env('appLocation')
|
||||
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
|
||||
const serverType = Cypress.env('serverType')
|
||||
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
|
||||
const downloadsFolder = Cypress.config('downloadsFolder')
|
||||
|
||||
import { deleteDownloadsFolder } from '../util/deleteDownloadFolder'
|
||||
|
||||
context('download files test: ', function () {
|
||||
this.beforeAll(() => {
|
||||
cy.visit(`${hostUrl}/SASLogon/logout`)
|
||||
cy.loginAndUpdateValidKey()
|
||||
})
|
||||
|
||||
this.beforeEach(() => {
|
||||
cy.visit(hostUrl + appLocation)
|
||||
cy.get('input.username').type(username)
|
||||
cy.get('input.password').type(password)
|
||||
cy.get('.login-group button').click()
|
||||
|
||||
visitPage('home')
|
||||
})
|
||||
|
||||
this.afterEach(() => {
|
||||
deleteDownloadsFolder()
|
||||
})
|
||||
|
||||
it('1 | downloads audit file', (done) => {
|
||||
visitPage('approve/toapprove')
|
||||
|
||||
cy.get('.app-loading', { timeout: longerCommandTimeout })
|
||||
.should('not.exist')
|
||||
.then(() => {
|
||||
cy.get('.btn.btn-success')
|
||||
.should('be.visible')
|
||||
.then((buttons) => {
|
||||
buttons[0].click()
|
||||
const id = buttons[0].id
|
||||
|
||||
checkForFileDownloaded(id, 'zip', () => done())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('2 | downloads viewer csv', (done) => {
|
||||
visitPage('view/data')
|
||||
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
openDownloadModal(() => {
|
||||
cy.get('select')
|
||||
.select('CSV')
|
||||
.then(() => {
|
||||
cy.get('.btn.btn-sm.btn-success-outline').then((button) => {
|
||||
button.trigger('click')
|
||||
|
||||
const id = button[0].id
|
||||
|
||||
checkForFileDownloaded(id, 'csv', () => done())
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('3 | downloads viewer excel', (done) => {
|
||||
visitPage('view/data')
|
||||
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
openDownloadModal(() => {
|
||||
cy.get('select')
|
||||
.select('Excel')
|
||||
.then(() => {
|
||||
cy.get('.btn.btn-sm.btn-success-outline').then((button) => {
|
||||
button.trigger('click')
|
||||
|
||||
const id = button[0].id
|
||||
|
||||
checkForFileDownloaded(id, 'xlsx', () => done())
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('4 | downloads viewer SAS Datalines', (done) => {
|
||||
visitPage('view/data')
|
||||
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
openDownloadModal(() => {
|
||||
cy.get('select')
|
||||
.select('SAS Datalines')
|
||||
.then(() => {
|
||||
cy.get('.btn.btn-sm.btn-success-outline').then((button) => {
|
||||
button.trigger('click')
|
||||
|
||||
const id = button[0].id
|
||||
|
||||
checkForFileDownloaded(id, 'sas', () => done())
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('5 | downloads viewer SAS DDL', (done) => {
|
||||
visitPage('view/data')
|
||||
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
openDownloadModal(() => {
|
||||
cy.get('select')
|
||||
.select('SAS DDL')
|
||||
.then(() => {
|
||||
cy.get('.btn.btn-sm.btn-success-outline').then((button) => {
|
||||
button.trigger('click')
|
||||
|
||||
const id = button[0].id
|
||||
|
||||
checkForFileDownloaded(id, 'ddl', () => done(), '_')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('6 | downloads viewer TSQL DDL', (done) => {
|
||||
visitPage('view/data')
|
||||
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
openDownloadModal(() => {
|
||||
cy.get('select')
|
||||
.select('TSQL DDL')
|
||||
.then(() => {
|
||||
cy.get('.btn.btn-sm.btn-success-outline').then((button) => {
|
||||
button.trigger('click')
|
||||
|
||||
const id = button[0].id
|
||||
|
||||
checkForFileDownloaded(id, 'ddl', () => done(), '_')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('7 | downloads viewer PGSQL DDL', (done) => {
|
||||
visitPage('view/data')
|
||||
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
openDownloadModal(() => {
|
||||
cy.get('select')
|
||||
.select('PGSQL DDL')
|
||||
.then(() => {
|
||||
cy.get('.btn.btn-sm.btn-success-outline').then((button) => {
|
||||
button.trigger('click')
|
||||
|
||||
const id = button[0].id
|
||||
|
||||
checkForFileDownloaded(id, 'ddl', () => done(), '_')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
this.afterEach(() => {
|
||||
cy.visit(`${hostUrl}/SASLogon/logout`)
|
||||
})
|
||||
})
|
||||
|
||||
const visitPage = (url: string) => {
|
||||
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
|
||||
}
|
||||
|
||||
const checkForFileDownloaded = (
|
||||
id: string,
|
||||
extension: string,
|
||||
callback?: any,
|
||||
libDivider: string = '.'
|
||||
) => {
|
||||
cy.on('url:changed', (newUrl) => {
|
||||
console.log('newUrl', newUrl)
|
||||
})
|
||||
|
||||
id = id.replace('.', libDivider)
|
||||
|
||||
const filename = downloadsFolder + '/' + id + '.' + extension
|
||||
// browser might take a while to download the file,
|
||||
// so use "cy.readFile" to retry until the file exists
|
||||
// and has length - and we assume that it has finished downloading then
|
||||
cy.readFile(filename, { timeout: longerCommandTimeout })
|
||||
.should('have.length.gt', 10)
|
||||
.then((file) => {
|
||||
if (callback) callback()
|
||||
})
|
||||
}
|
||||
|
||||
const openDownloadModal = (callback?: any) => {
|
||||
cy.get('.btn.btn-sm.btn-outline.filterSide.dropdown-toggle')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.get('clr-dropdown-menu button').then((buttons) => {
|
||||
for (let button of buttons) {
|
||||
if (button.innerText.toLowerCase().includes('download')) {
|
||||
button.click()
|
||||
|
||||
if (callback) callback()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const openTableFromTree = (libNameIncludes: string, tablename: string) => {
|
||||
cy.get('.app-loading', { timeout: longerCommandTimeout })
|
||||
.should('not.exist')
|
||||
.then(() => {
|
||||
cy.get('.nav-tree clr-tree > clr-tree-node', {
|
||||
timeout: longerCommandTimeout
|
||||
}).then((treeNodes: any) => {
|
||||
let viyaLib
|
||||
|
||||
for (let node of treeNodes) {
|
||||
if (node.innerText.toLowerCase().includes(libNameIncludes)) {
|
||||
viyaLib = node
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
console.log('viyaLib', viyaLib)
|
||||
|
||||
cy.get(viyaLib).within(() => {
|
||||
cy.get(
|
||||
'.clr-tree-node-content-container .clr-treenode-content p'
|
||||
).click()
|
||||
|
||||
cy.get('.clr-treenode-link').then((innerNodes: any) => {
|
||||
for (let innerNode of innerNodes) {
|
||||
if (innerNode.innerText.toLowerCase().includes(tablename)) {
|
||||
innerNode.click()
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
250
client/cypress/e2e/editor.cy.ts
Normal file
250
client/cypress/e2e/editor.cy.ts
Normal file
@ -0,0 +1,250 @@
|
||||
const username = Cypress.env('username')
|
||||
const password = Cypress.env('password')
|
||||
const hostUrl = Cypress.env('hosturl')
|
||||
const appLocation = Cypress.env('appLocation')
|
||||
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
|
||||
const serverType = Cypress.env('serverType')
|
||||
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
|
||||
const fixturePath = 'excels_general/'
|
||||
|
||||
context('editor tests: ', function () {
|
||||
this.beforeAll(() => {
|
||||
cy.visit(`${hostUrl}/SASLogon/logout`)
|
||||
cy.loginAndUpdateValidKey()
|
||||
})
|
||||
|
||||
this.beforeEach(() => {
|
||||
cy.visit(hostUrl + appLocation)
|
||||
|
||||
visitPage('home')
|
||||
})
|
||||
|
||||
it('1 | Submits duplicate primary keys', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_datadictionary')
|
||||
|
||||
attachExcelFile('MPE_DATADICTIONARY_duplicate_keys.xlsx', () => {
|
||||
clickOnUploadPreview(() => {
|
||||
confirmEditPreviewFile(() => {
|
||||
submitTable(() => {
|
||||
cy.get('.modal-body').then((modalBody: any) => {
|
||||
if (modalBody[0].innerText.includes(`Duplicates found:`)) {
|
||||
done()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('2 | Submits null cells which must not be null', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
clickOnEdit(() => {
|
||||
cy.get('.btn.btn-sm.btn-icon.btn-outline-danger', {
|
||||
timeout: longerCommandTimeout
|
||||
}).then(() => {
|
||||
cy.get('.ht_master tbody tr').then((rows: any) => {
|
||||
cy.get(rows[1].childNodes[2])
|
||||
.dblclick({ force: true })
|
||||
.then(() => {
|
||||
cy.focused()
|
||||
.clear()
|
||||
.type('{enter}')
|
||||
.then(() => {
|
||||
submitTable(() => {
|
||||
cy.get('.modal-body').then((modalBody: any) => {
|
||||
if (
|
||||
modalBody[0].innerHTML
|
||||
.toLowerCase()
|
||||
.includes(`invalid values are present`)
|
||||
) {
|
||||
done()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('3 | Gets basic dynamic cell validation', () => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
clickOnEdit(() => {
|
||||
cy.get('.btn.btn-sm.btn-icon.btn-outline-danger', {
|
||||
timeout: longerCommandTimeout
|
||||
}).then(() => {
|
||||
cy.get('.ht_master tbody tr').then((rows: any) => {
|
||||
cy.get(rows[1].childNodes[5])
|
||||
.click({ force: true })
|
||||
.then(($td) => {
|
||||
cy.get('.htAutocompleteArrow', { withinSubject: $td }).should(
|
||||
'exist'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('4 | Gets advanced dynamic cell validation', () => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_tables')
|
||||
|
||||
clickOnEdit(() => {
|
||||
cy.get('.btn.btn-sm.btn-icon.btn-outline-danger', {
|
||||
timeout: longerCommandTimeout
|
||||
}).then(() => {
|
||||
cy.get('.ht_master tbody tr').then((rows: any) => {
|
||||
cy.get(rows[1].childNodes[3])
|
||||
.click({ force: true })
|
||||
.then(($td) => {
|
||||
cy.get('.htAutocompleteArrow', { withinSubject: $td }).should(
|
||||
'exist'
|
||||
)
|
||||
cy.get('.htAutocompleteArrow', {
|
||||
withinSubject: rows[1].childNodes[7]
|
||||
}).should('exist')
|
||||
cy.get('.htAutocompleteArrow', {
|
||||
withinSubject: rows[1].childNodes[8]
|
||||
}).should('exist')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const clickOnEdit = (callback?: any) => {
|
||||
cy.get('.btnCtrl button.btn-primary', { timeout: longerCommandTimeout })
|
||||
.click()
|
||||
.then(() => {
|
||||
if (callback) callback()
|
||||
})
|
||||
}
|
||||
|
||||
const openTableFromTree = (libNameIncludes: string, tablename: string) => {
|
||||
cy.get('.app-loading', { timeout: longerCommandTimeout })
|
||||
.should('not.exist')
|
||||
.then(() => {
|
||||
cy.get('.nav-tree clr-tree > clr-tree-node', {
|
||||
timeout: longerCommandTimeout
|
||||
}).then((treeNodes: any) => {
|
||||
let viyaLib
|
||||
|
||||
for (let node of treeNodes) {
|
||||
if (node.innerText.toLowerCase().includes(libNameIncludes)) {
|
||||
viyaLib = node
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cy.get(viyaLib).within(() => {
|
||||
cy.get('.clr-tree-node-content-container > button').click()
|
||||
|
||||
cy.get('.clr-treenode-link').then((innerNodes: any) => {
|
||||
for (let innerNode of innerNodes) {
|
||||
if (innerNode.innerText.toLowerCase().includes(tablename)) {
|
||||
innerNode.click()
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const attachExcelFile = (excelFilename: string, callback?: any) => {
|
||||
cy.get('.buttonBar button:last-child')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.get('input[type="file"]#file-upload')
|
||||
.attachFile(`/${fixturePath}/${excelFilename}`)
|
||||
.then(() => {
|
||||
cy.get('.modal-footer .btn.btn-primary').then((modalBtn) => {
|
||||
modalBtn.click()
|
||||
if (callback) callback()
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const clickOnUploadPreview = (callback?: any) => {
|
||||
cy.get('.buttonBar button.btn-primary.btn-upload-preview')
|
||||
.click()
|
||||
.then(() => {
|
||||
if (callback) callback()
|
||||
})
|
||||
}
|
||||
|
||||
const confirmEditPreviewFile = (callback?: any) => {
|
||||
cy.get('.modal-footer button.btn-success-outline')
|
||||
.click()
|
||||
.then(() => {
|
||||
if (callback) callback()
|
||||
})
|
||||
}
|
||||
|
||||
const submitTable = (callback?: any) => {
|
||||
cy.get('.btnCtrl button.btn-primary')
|
||||
.click()
|
||||
.then(() => {
|
||||
if (callback) callback()
|
||||
})
|
||||
}
|
||||
|
||||
const submitTableMessage = (callback?: any) => {
|
||||
cy.get('.modal-footer .btn.btn-sm.btn-success-outline')
|
||||
.click()
|
||||
.then(() => {
|
||||
if (callback) callback()
|
||||
})
|
||||
}
|
||||
|
||||
const submitExcel = (callback?: any) => {
|
||||
cy.get('.buttonBar button.preview-submit')
|
||||
.click()
|
||||
.then(() => {
|
||||
if (callback) callback()
|
||||
})
|
||||
}
|
||||
|
||||
const rejectExcel = (callback?: any) => {
|
||||
cy.get('button', { timeout: longerCommandTimeout })
|
||||
.should('contain', 'Approve')
|
||||
.then((allButtons: any) => {
|
||||
for (let approvalButton of allButtons) {
|
||||
if (
|
||||
approvalButton.innerText
|
||||
.toLowerCase()
|
||||
.includes('approve')
|
||||
) {
|
||||
approvalButton.click()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cy.get('button.btn-danger')
|
||||
.should('exist')
|
||||
.should('not.be.disabled')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.get('.modal-footer button.btn-success-outline')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.get('app-history')
|
||||
.should('exist')
|
||||
.then(() => {
|
||||
if (callback) callback()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const visitPage = (url: string) => {
|
||||
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
|
||||
}
|
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;')
|
||||
}
|
528
client/cypress/e2e/excel.cy.ts
Normal file
528
client/cypress/e2e/excel.cy.ts
Normal file
@ -0,0 +1,528 @@
|
||||
import { Callbacks } from 'cypress/types/jquery/index'
|
||||
|
||||
const username = Cypress.env('username')
|
||||
const password = Cypress.env('password')
|
||||
const hostUrl = Cypress.env('hosturl')
|
||||
const appLocation = Cypress.env('appLocation')
|
||||
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
|
||||
const serverType = Cypress.env('serverType')
|
||||
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
|
||||
const fixturePath = 'excels/'
|
||||
|
||||
context('excel tests: ', function () {
|
||||
this.beforeAll(() => {
|
||||
cy.visit(`${hostUrl}/SASLogon/logout`)
|
||||
cy.loginAndUpdateValidKey(true)
|
||||
})
|
||||
|
||||
this.beforeEach(() => {
|
||||
cy.visit(hostUrl + appLocation)
|
||||
|
||||
visitPage('home')
|
||||
|
||||
colorLog(
|
||||
`TEST START ---> ${
|
||||
Cypress.mocha.getRunner().suite.ctx.currentTest.title
|
||||
}`,
|
||||
'#3498DB'
|
||||
)
|
||||
})
|
||||
|
||||
it('1 | Uploads regular Excel file', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
attachExcelFile('regular_excel.xlsx', () => {
|
||||
submitExcel()
|
||||
rejectExcel(done)
|
||||
})
|
||||
})
|
||||
|
||||
it('2 | Uploads Excel with data on the 7th tab', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
attachExcelFile('7th_tab_excel.xlsx', () => {
|
||||
submitExcel()
|
||||
rejectExcel(done)
|
||||
})
|
||||
})
|
||||
|
||||
it('3 | Uploads Excel with missing columns (should fail)', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
attachExcelFile('missing_columns_excel.xlsx', () => {
|
||||
cy.get('.abortMsg', { timeout: longerCommandTimeout })
|
||||
.should('exist')
|
||||
.then((elements: any) => {
|
||||
if (elements[0]) {
|
||||
if (elements[0].innerText.toLowerCase().includes('missing')) done()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('4 | Uploads Excel with formulas', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_datadictionary')
|
||||
|
||||
attachExcelFile('formulas_excel.xlsx', () => {
|
||||
checkResultOfFormulaUpload(done)
|
||||
})
|
||||
})
|
||||
|
||||
it('5 | Uploads Excel with no data rows', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
attachExcelFile('nodata_rows_excel.xlsx', () => {
|
||||
cy.get('.abortMsg', { timeout: longerCommandTimeout })
|
||||
.should('exist')
|
||||
.then((elements: any) => {
|
||||
if (elements[0]) {
|
||||
if (
|
||||
elements[0].innerText
|
||||
.toLowerCase()
|
||||
.includes('no relevant data found')
|
||||
)
|
||||
done()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('6 | Uploads Excel with a table that is surrounded by other data', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
attachExcelFile('surrounded_data_excel.xlsx', () => {
|
||||
submitExcel()
|
||||
rejectExcel(done)
|
||||
})
|
||||
})
|
||||
|
||||
it('7 | Uploads Excel with a extra columns in the middle', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
attachExcelFile('extra_column_excel.xlsx', () => {
|
||||
submitExcel()
|
||||
rejectExcel(done)
|
||||
})
|
||||
})
|
||||
|
||||
it('8 | Uploads Excel with a duplicate column', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
attachExcelFile('duplicate_column_excel.xlsx', () => {
|
||||
submitExcel()
|
||||
rejectExcel(done)
|
||||
})
|
||||
})
|
||||
|
||||
it('9 | Uploads Excel with a duplicate row', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
attachExcelFile('duplicate_row_excel.xlsx', () => {
|
||||
submitExcel(() => {
|
||||
cy.get('.duplicate-keys-modal', { timeout: longerCommandTimeout })
|
||||
.should('exist')
|
||||
.then((elements: any) => {
|
||||
if (elements[0]) {
|
||||
if (elements[0].innerText.toLowerCase().includes('duplicates'))
|
||||
done()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('10 | Uploads Excel with a mixed content', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
attachExcelFile('mixed_content_excel.xlsx', () => {
|
||||
submitExcel(() => {
|
||||
cy.get('.modal-body').then((modalBody: any) => {
|
||||
if (
|
||||
modalBody[0].innerHTML
|
||||
.toLowerCase()
|
||||
.includes(`invalid values are present`)
|
||||
) {
|
||||
done()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('11 | Uploads Excel with a blank columns', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
attachExcelFile('blank_columns_excel.xlsx', () => {
|
||||
cy.get('.abortMsg', { timeout: longerCommandTimeout })
|
||||
.should('exist')
|
||||
.then((elements: any) => {
|
||||
if (elements[0]) {
|
||||
if (elements[0].innerText.toLowerCase().includes('missing')) done()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('12 | Uploads Excel xls extension', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
attachExcelFile('regular_excel_xls.xls', () => {
|
||||
submitExcel()
|
||||
rejectExcel(done)
|
||||
})
|
||||
})
|
||||
|
||||
// For some strange reason this file breaks cypress. When uploaded manually in DC it is working.
|
||||
// it('13 | Uploads Excel xlsm extension', (done) => {
|
||||
// openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
// attachExcelFile('regular_excel_macro.xlsm', () => {
|
||||
// submitExcel()
|
||||
// rejectExcel(done)
|
||||
// })
|
||||
// })
|
||||
|
||||
it('14 | Uploads Excel with composite primary key', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_datadictionary')
|
||||
|
||||
attachExcelFile('MPE_DATADICTIONARY_composite_keys.xlsx', () => {
|
||||
submitExcel()
|
||||
rejectExcel(done)
|
||||
})
|
||||
})
|
||||
|
||||
it('15 | Uploads Excel with missing row (empty table)', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_datadictionary')
|
||||
|
||||
attachExcelFile('MPE_DATADICTIONARY_missing_row.xlsx', () => {
|
||||
cy.get('.abortMsg', { timeout: longerCommandTimeout })
|
||||
.should('exist')
|
||||
.then((elements: any) => {
|
||||
if (elements[0]) {
|
||||
if (
|
||||
elements[0].innerText
|
||||
.toLowerCase()
|
||||
.includes('no relevant data found')
|
||||
)
|
||||
done()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('16 | Uploads Excel with merged cells', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_datadictionary')
|
||||
|
||||
attachExcelFile('MPE_DATADICTIONARY_merged_cells.xlsx', () => {
|
||||
submitExcel()
|
||||
rejectExcel(done)
|
||||
})
|
||||
})
|
||||
|
||||
it('17 | Check uploaded values from excel with xls extension', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
attachExcelFile('regular_excel_xls.xls', () => {
|
||||
checkResultOfXLSUpload(done)
|
||||
})
|
||||
})
|
||||
|
||||
it('18 | Uploads Excel with missing row (empty table)', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
attachExcelFile('blank_column_with_header.xlsx', () => {
|
||||
cy.get('.btn-upload-preview', { timeout: 60000 })
|
||||
.should('be.visible')
|
||||
.then(() => {
|
||||
cy.get('#hotInstance', { timeout: 30000 })
|
||||
.find('div.ht_master.handsontable')
|
||||
.find('div.wtHolder')
|
||||
.find('div.wtHider')
|
||||
.find('div.wtSpreader')
|
||||
.find('table.htCore')
|
||||
.find('tbody')
|
||||
.then((data) => {
|
||||
let allEmpty = true
|
||||
|
||||
for (let col = 0; col < data[0].children.length; col++) {
|
||||
const cell: any = data[0].children[col].children[5]
|
||||
|
||||
if (cell.innerText !== '') {
|
||||
allEmpty = false
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (allEmpty) done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('19 | Uploads Excel with data on random sheet surrounded with all empty cells', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
attachExcelFile('surrounded_data_all_cells_empty_excel.xlsx', () => {
|
||||
checkResultOfXLSUpload(done)
|
||||
})
|
||||
})
|
||||
|
||||
it('20 | Uploads Excel with data surrounded with empty cells ', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
attachExcelFile('surrounded_data_empty_cells_excel.xlsx', () => {
|
||||
checkResultOfXLSUpload(done)
|
||||
})
|
||||
})
|
||||
|
||||
it('21 | Uploads regular Excel file with first row marked for Delete (yes)', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
attachExcelFile('regular_excel_with_delete.xlsx', () => {
|
||||
cy.get('.btn-upload-preview', { timeout: 60000 })
|
||||
.should('be.visible')
|
||||
.then(() => {
|
||||
cy.get('#hotInstance', { timeout: 30000 })
|
||||
.find('div.ht_master.handsontable')
|
||||
.find('div.wtHolder')
|
||||
.find('div.wtHider')
|
||||
.find('div.wtSpreader')
|
||||
.find('table.htCore')
|
||||
.find('tbody')
|
||||
.then((data: JQuery<HTMLTableSectionElement>) => {
|
||||
const firstRowFirstCol: Partial<HTMLElement> =
|
||||
data[0].children[0].children[1]
|
||||
|
||||
if (
|
||||
firstRowFirstCol.innerText &&
|
||||
!firstRowFirstCol.innerText.toLowerCase().includes('yes')
|
||||
) {
|
||||
done('Delete? column from file not applied')
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
submitExcel()
|
||||
rejectExcel(done)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Large files break Cypress
|
||||
|
||||
// it ('? | Uploads Excel with size of 5MB', (done) => {
|
||||
// attachExcelFile('5mb_excel.xlsx', () => {
|
||||
// submitExcel();
|
||||
// rejectExcel(done);
|
||||
// });
|
||||
// })
|
||||
|
||||
// it ('? | Uploads Excel with size of 15MB', (done) => {
|
||||
// attachExcelFile('15mb_excel.xlsx', () => {
|
||||
// submitExcel();
|
||||
// rejectExcel(done);
|
||||
// });
|
||||
// })
|
||||
|
||||
//Large files tests end
|
||||
|
||||
this.afterEach(() => {
|
||||
colorLog(`TEST END -------------`, '#3498DB')
|
||||
})
|
||||
})
|
||||
|
||||
const openTableFromTree = (libNameIncludes: string, tablename: string) => {
|
||||
cy.get('.app-loading', { timeout: longerCommandTimeout })
|
||||
.should('not.exist')
|
||||
.then(() => {
|
||||
cy.get('.nav-tree clr-tree > clr-tree-node', {
|
||||
timeout: longerCommandTimeout
|
||||
}).then((treeNodes: any) => {
|
||||
let viyaLib
|
||||
|
||||
for (let node of treeNodes) {
|
||||
if (node.innerText.toLowerCase().includes(libNameIncludes)) {
|
||||
viyaLib = node
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cy.get(viyaLib).within(() => {
|
||||
cy.get('.clr-tree-node-content-container > button').click()
|
||||
|
||||
cy.get('.clr-treenode-link').then((innerNodes: any) => {
|
||||
for (let innerNode of innerNodes) {
|
||||
if (innerNode.innerText.toLowerCase().includes(tablename)) {
|
||||
innerNode.click()
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const attachExcelFile = (excelFilename: string, callback?: any) => {
|
||||
cy.get('.buttonBar button:last-child')
|
||||
.should('exist')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.get('input[type="file"]#file-upload')
|
||||
.attachFile(`/${fixturePath}/${excelFilename}`)
|
||||
.then(() => {
|
||||
cy.get('.clr-abort-modal .modal-title').then((modalTitle) => {
|
||||
if (!modalTitle[0].innerHTML.includes('Abort Message')) {
|
||||
cy.get('.modal-footer .btn.btn-primary').then((modalBtn) => {
|
||||
modalBtn.click()
|
||||
if (callback) callback()
|
||||
})
|
||||
} else {
|
||||
if (callback) callback()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const submitExcel = (callback?: any) => {
|
||||
cy.get('.buttonBar button.preview-submit', { timeout: longerCommandTimeout })
|
||||
.click()
|
||||
.then(() => {
|
||||
if (callback) callback()
|
||||
})
|
||||
}
|
||||
|
||||
const rejectExcel = (callback?: any) => {
|
||||
cy.get('button', { timeout: longerCommandTimeout })
|
||||
.should('contain', 'Approve')
|
||||
.then((allButtons: any) => {
|
||||
for (let approvalButton of allButtons) {
|
||||
if (
|
||||
approvalButton.innerText
|
||||
.toLowerCase()
|
||||
.includes('approve')
|
||||
) {
|
||||
approvalButton.click()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cy.get('button.btn-danger')
|
||||
.should('exist')
|
||||
.should('not.be.disabled')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.get('.modal-footer button.btn-success-outline')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.get('app-history')
|
||||
.should('exist')
|
||||
.then(() => {
|
||||
if (callback) callback()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const acceptExcel = (callback?: any) => {
|
||||
cy.get('button', { timeout: longerCommandTimeout })
|
||||
.should('contain', 'Approve')
|
||||
.then((allButtons: any) => {
|
||||
for (let approvalButton of allButtons) {
|
||||
if (
|
||||
approvalButton.innerText
|
||||
.toLowerCase()
|
||||
.includes('approve')
|
||||
) {
|
||||
approvalButton.click()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cy.get('#acceptBtn')
|
||||
.should('exist')
|
||||
.should('not.be.disabled')
|
||||
.click()
|
||||
.then(() => {
|
||||
if (callback) {
|
||||
callback()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const checkResultOfFormulaUpload = (callback?: any) => {
|
||||
cy.get('#hotInstance', { timeout: longerCommandTimeout })
|
||||
.find('div.ht_master.handsontable')
|
||||
.find('div.wtHolder')
|
||||
.find('div.wtHider')
|
||||
.find('div.wtSpreader')
|
||||
.find('table.htCore')
|
||||
.find('tbody')
|
||||
.then((data) => {
|
||||
const cell: any = data[0].children[0].children[5]
|
||||
expect(cell.innerText).to.equal('=1+1')
|
||||
if (callback) callback()
|
||||
})
|
||||
}
|
||||
|
||||
const checkResultOfXLSUpload = (callback?: any) => {
|
||||
cy.viewport(1280, 720)
|
||||
cy.get('#hotInstance', { timeout: 30000 })
|
||||
.find('div.ht_master.handsontable')
|
||||
.find('div.wtHolder')
|
||||
.find('div.wtHider')
|
||||
.find('div.wtSpreader')
|
||||
.find('table.htCore')
|
||||
.find('tbody')
|
||||
.then((data) => {
|
||||
let cell: any = data[0].children[0].children[2]
|
||||
|
||||
expect(cell.innerText).to.equal('0')
|
||||
cell = data[0].children[0].children[3]
|
||||
expect(cell.innerText).to.equal('this is dummy data changed in excel')
|
||||
cell = data[0].children[0].children[4]
|
||||
expect(cell.innerText).to.equal('▼\nOption 1')
|
||||
cell = data[0].children[0].children[5]
|
||||
expect(cell.innerText).to.equal('42')
|
||||
cell = data[0].children[0].children[6]
|
||||
expect(cell.innerText).to.equal('▼\n1960-02-12')
|
||||
// When CI detached browser screen is smaller, below cells are not visible so test fails
|
||||
// Commenting it out now until we figure out workaround
|
||||
// cell = data[0].children[0].children[7]
|
||||
// expect(cell.innerText).to.equal('▼\n1960-01-01 00:00:42')
|
||||
// cell = data[0].children[0].children[8]
|
||||
// expect(cell.innerText).to.equal('00:00:42')
|
||||
|
||||
if (callback) callback()
|
||||
})
|
||||
|
||||
cy.get('#hotInstance', { timeout: 30000 })
|
||||
.find('div.ht_master.handsontable')
|
||||
.find('div.wtHolder')
|
||||
.scrollTo('right')
|
||||
.find('div.wtHider')
|
||||
.find('div.wtSpreader')
|
||||
.find('table.htCore')
|
||||
.find('tbody')
|
||||
.then((data) => {
|
||||
let cell: any = data[0].children[0].children[1]
|
||||
|
||||
cell = data[0].children[0].children[9]
|
||||
|
||||
expect(cell.innerText).to.equal('44')
|
||||
|
||||
if (callback) callback()
|
||||
})
|
||||
}
|
||||
|
||||
const visitPage = (url: string) => {
|
||||
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
|
||||
}
|
||||
|
||||
const colorLog = (msg: string, color: string) => {
|
||||
console.log('%c' + msg, 'color:' + color + ';font-weight:bold;')
|
||||
}
|
378
client/cypress/e2e/filtering.cy.ts
Normal file
378
client/cypress/e2e/filtering.cy.ts
Normal file
@ -0,0 +1,378 @@
|
||||
const username = Cypress.env('username')
|
||||
const password = Cypress.env('password')
|
||||
const hostUrl = Cypress.env('hosturl')
|
||||
const appLocation = Cypress.env('appLocation')
|
||||
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
|
||||
const serverType = Cypress.env('serverType')
|
||||
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
|
||||
const fixturePath = 'excels_general/'
|
||||
|
||||
context('filtering tests: ', function () {
|
||||
this.beforeAll(() => {
|
||||
cy.visit(`${hostUrl}/SASLogon/logout`, { timeout: longerCommandTimeout })
|
||||
cy.loginAndUpdateValidKey()
|
||||
})
|
||||
|
||||
this.beforeEach(() => {
|
||||
cy.visit(hostUrl + appLocation, { timeout: longerCommandTimeout })
|
||||
|
||||
|
||||
visitPage('home')
|
||||
})
|
||||
|
||||
it('1 | filter char field', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
openFilterPopup(() => {
|
||||
setFilterWithValue('SOME_CHAR', 'this is dummy data', 'value', () => {
|
||||
checkInfoBarIncludes(
|
||||
`AND,AND,0,SOME_CHAR,=,"'this is dummy data'"`,
|
||||
(includes: boolean) => {
|
||||
if (includes) done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('2 | filter number field', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
openFilterPopup(() => {
|
||||
setFilterWithValue('SOME_NUM', '42', 'value', () => {
|
||||
checkInfoBarIncludes(`AND,AND,0,SOME_NUM,=,42`, (includes: boolean) => {
|
||||
if (includes) done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('3 | filter time field', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
openFilterPopup(() => {
|
||||
setFilterWithValue('SOME_TIME', '00:00:42', 'time', () => {
|
||||
checkInfoBarIncludes(
|
||||
`AND,AND,0,SOME_TIME,=,42`,
|
||||
(includes: boolean) => {
|
||||
if (includes) done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('3.1 | Non picker - filter time field', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
openFilterPopup(() => {
|
||||
setFilterWithValue('SOME_TIME', '42', 'value', () => {
|
||||
checkInfoBarIncludes(
|
||||
`AND,AND,0,SOME_TIME,=,42`,
|
||||
(includes: boolean) => {
|
||||
if (includes) done()
|
||||
}
|
||||
)
|
||||
})
|
||||
}, false)
|
||||
})
|
||||
|
||||
it('4 | filter date field', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
openFilterPopup(() => {
|
||||
setFilterWithValue('SOME_DATE', '12/02/1960', 'date', () => {
|
||||
checkInfoBarIncludes(
|
||||
`AND,AND,0,SOME_DATE,=,42`,
|
||||
(includes: boolean) => {
|
||||
if (includes) done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('4.1 | Non picker - filter date field', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
openFilterPopup(() => {
|
||||
setFilterWithValue('SOME_DATE', '42', 'value', () => {
|
||||
checkInfoBarIncludes(
|
||||
`AND,AND,0,SOME_DATE,=,42`,
|
||||
(includes: boolean) => {
|
||||
if (includes) done()
|
||||
}
|
||||
)
|
||||
})
|
||||
}, false)
|
||||
})
|
||||
|
||||
it('5 | filter datetime field', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
openFilterPopup(() => {
|
||||
setFilterWithValue(
|
||||
'SOME_DATETIME',
|
||||
'01/01/1960 00:00:42',
|
||||
'datetime',
|
||||
() => {
|
||||
checkInfoBarIncludes(
|
||||
`AND,AND,0,SOME_DATETIME,=,42`,
|
||||
(includes: boolean) => {
|
||||
if (includes) done()
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('5.1 | Non picker - filter datetime field', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
openFilterPopup(() => {
|
||||
setFilterWithValue('SOME_DATETIME', '42', 'value', () => {
|
||||
checkInfoBarIncludes(
|
||||
`AND,AND,0,SOME_DATETIME,=,42`,
|
||||
(includes: boolean) => {
|
||||
if (includes) done()
|
||||
}
|
||||
)
|
||||
})
|
||||
}, false)
|
||||
})
|
||||
|
||||
it('6 | filter date field IN', (done) => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
openFilterPopup(() => {
|
||||
setFilterWithValue('SOME_DATE', '', 'in', () => {
|
||||
checkInfoBarIncludes(
|
||||
`AND,AND,0,SOME_DATE,IN,(0)`,
|
||||
(includes: boolean) => {
|
||||
if (includes) done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: fix
|
||||
// it('7 | filter bestnum field BETWEEN', (done) => {
|
||||
// openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
// openFilterPopup(() => {
|
||||
// setFilterWithValue('SOME_BESTNUM', '0-10', 'between', () => {
|
||||
// checkInfoBarIncludes(
|
||||
// `AND,AND,0,SOME_BESTNUM,BETWEEN,0 AND 10`,
|
||||
// (includes: boolean) => {
|
||||
// if (includes) done()
|
||||
// }
|
||||
// )
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
})
|
||||
|
||||
const checkInfoBarIncludes = (text: string, callback: any) => {
|
||||
cy.get('.infoBar b', { timeout: longerCommandTimeout }).then((el: any) => {
|
||||
const includes = el[0].innerText.toLowerCase().includes(text.toLowerCase())
|
||||
|
||||
if (callback) callback(includes)
|
||||
})
|
||||
}
|
||||
|
||||
const openFilterPopup = (
|
||||
callback?: any,
|
||||
usePickers: boolean = true,
|
||||
isViewerFiltering: boolean = false
|
||||
) => {
|
||||
const filterButton = isViewerFiltering
|
||||
? '.btn-outline.filterSide'
|
||||
: '.btnCtrl .btnView'
|
||||
|
||||
cy.get(filterButton, { timeout: longerCommandTimeout }).then(
|
||||
(optionsButton: any) => {
|
||||
optionsButton.click()
|
||||
|
||||
if (isViewerFiltering) {
|
||||
cy.wait(300)
|
||||
|
||||
cy.get('.dropdown-menu button').then(async (dropdownButtons: any) => {
|
||||
let filterButton = null
|
||||
|
||||
for (let btn of dropdownButtons) {
|
||||
if (btn.innerText.toLowerCase().includes('filter')) {
|
||||
filterButton = btn
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (filterButton) {
|
||||
filterButton.click()
|
||||
|
||||
if (usePickers) turnOnPickers()
|
||||
|
||||
if (callback) callback()
|
||||
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (usePickers) turnOnPickers()
|
||||
if (callback) callback()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const turnOnPickers = () => {
|
||||
cy.get('#usePickers')
|
||||
.should('exist')
|
||||
.then((picker: any) => {
|
||||
picker[0].click()
|
||||
})
|
||||
}
|
||||
|
||||
const setFilterWithValue = (
|
||||
variableValue: string,
|
||||
valueString: string,
|
||||
valueField: 'value' | 'time' | 'date' | 'datetime' | 'in' | 'between',
|
||||
callback?: any
|
||||
) => {
|
||||
cy.wait(600)
|
||||
|
||||
cy.focused().type(variableValue)
|
||||
cy.wait(100)
|
||||
// cy.focused().trigger('input')
|
||||
cy.get('.variable-col .autocomplete-wrapper', { withinSubject: null })
|
||||
.first()
|
||||
.trigger('keydown', { key: 'ArrowDown' })
|
||||
cy.get('.variable-col .autocomplete-wrapper', {
|
||||
withinSubject: null
|
||||
}).trigger('keydown', { key: 'Enter' })
|
||||
cy.focused().tab()
|
||||
cy.wait(100)
|
||||
|
||||
if (valueField === 'in') {
|
||||
cy.focused().select(valueField.toUpperCase()).trigger('change')
|
||||
} else if (valueField === 'between') {
|
||||
cy.focused().select(valueField.toUpperCase()).trigger('change')
|
||||
} else {
|
||||
cy.focused().tab()
|
||||
cy.wait(100)
|
||||
}
|
||||
|
||||
switch (valueField) {
|
||||
case 'value': {
|
||||
cy.focused().type(valueString)
|
||||
|
||||
break
|
||||
}
|
||||
case 'time': {
|
||||
cy.focused().type(valueString)
|
||||
|
||||
break
|
||||
}
|
||||
case 'date': {
|
||||
cy.focused().type(valueString)
|
||||
cy.focused().tab()
|
||||
|
||||
break
|
||||
}
|
||||
case 'datetime': {
|
||||
const date = valueString.split(' ')[0]
|
||||
const time = valueString.split(' ')[1]
|
||||
|
||||
cy.focused().type(date)
|
||||
cy.focused().tab()
|
||||
cy.focused().tab()
|
||||
cy.focused().type(time)
|
||||
|
||||
break
|
||||
}
|
||||
case 'in': {
|
||||
cy.get('.checkbox-vals').then(() => {
|
||||
cy.focused().tab()
|
||||
cy.focused().click()
|
||||
cy.get('.no-values')
|
||||
.should('not.exist')
|
||||
.then(() => {
|
||||
cy.get('.in-values-modal clr-checkbox-wrapper input').then((inputs: any) => {
|
||||
inputs[0].click()
|
||||
cy.get('.in-values-modal .modal-footer button').click()
|
||||
|
||||
cy.get('.modal-footer .btn-success-outline').click()
|
||||
|
||||
if (callback) callback()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
case 'between': {
|
||||
cy.focused().tab()
|
||||
|
||||
const start = valueString.split('-')[0]
|
||||
const end = valueString.split('-')[1]
|
||||
|
||||
cy.focused().type(start)
|
||||
cy.focused().tab()
|
||||
cy.focused().type(end)
|
||||
}
|
||||
default: {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cy.wait(100)
|
||||
cy.focused().tab()
|
||||
cy.wait(100)
|
||||
cy.focused().tab()
|
||||
cy.wait(100)
|
||||
cy.focused().tab()
|
||||
cy.wait(100)
|
||||
cy.focused().tab()
|
||||
cy.wait(100)
|
||||
cy.focused().click()
|
||||
|
||||
if (callback) callback()
|
||||
}
|
||||
|
||||
const openTableFromTree = (libNameIncludes: string, tablename: string) => {
|
||||
cy.get('.app-loading', { timeout: longerCommandTimeout })
|
||||
.should('not.exist')
|
||||
.then(() => {
|
||||
cy.get('.nav-tree clr-tree > clr-tree-node', {
|
||||
timeout: longerCommandTimeout
|
||||
}).then((treeNodes: any) => {
|
||||
let viyaLib
|
||||
|
||||
for (let node of treeNodes) {
|
||||
if (new RegExp(libNameIncludes).test(node.innerText.toLowerCase())) {
|
||||
viyaLib = node
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cy.get(viyaLib).within(() => {
|
||||
cy.get('.clr-tree-node-content-container p').click()
|
||||
|
||||
cy.get('.clr-treenode-link').then((innerNodes: any) => {
|
||||
for (let innerNode of innerNodes) {
|
||||
if (innerNode.innerText.toLowerCase().includes(tablename)) {
|
||||
innerNode.click()
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const visitPage = (url: string) => {
|
||||
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
|
||||
}
|
726
client/cypress/e2e/licensing.cy.ts
Normal file
726
client/cypress/e2e/licensing.cy.ts
Normal file
@ -0,0 +1,726 @@
|
||||
import { arrayBufferToBase64 } from './../util/helper-functions'
|
||||
import * as moment from 'moment'
|
||||
|
||||
const username = Cypress.env('username')
|
||||
const password = Cypress.env('password')
|
||||
const hostUrl = Cypress.env('hosturl')
|
||||
const appLocation = Cypress.env('appLocation')
|
||||
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
|
||||
const fixturePath = 'excels_general/'
|
||||
const serverType = Cypress.env('serverType')
|
||||
const site_id = Cypress.env(`site_id_${serverType}`)
|
||||
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
|
||||
const testLicenceUserLimits = Cypress.env('testLicenceUserLimits')
|
||||
|
||||
/** IMPORTANT NOTICE
|
||||
* Before running tests, make sure that table `MPE_USERS` is present
|
||||
*/
|
||||
|
||||
interface EditConfigTableCells {
|
||||
varName: string
|
||||
varValue: string
|
||||
}
|
||||
|
||||
context('licensing tests: ', function () {
|
||||
this.beforeAll(() => {
|
||||
|
||||
cy.loginAndUpdateValidKey()
|
||||
})
|
||||
|
||||
this.beforeEach(() => {
|
||||
cy.visit(hostUrl + appLocation)
|
||||
|
||||
visitPage('home')
|
||||
})
|
||||
|
||||
it('1 | key valid, not expired', (done) => {
|
||||
let keyData = {
|
||||
valid_until: moment().add(1, 'year').format('YYYY-MM-DD'),
|
||||
users_allowed: 4,
|
||||
hot_license_key: '',
|
||||
demo: false,
|
||||
site_id: site_id
|
||||
}
|
||||
|
||||
let keys: { licenseKey: any; activationKey: any }
|
||||
generateKeys(keyData, (keysGen: any) => {
|
||||
keys = keysGen
|
||||
|
||||
cy.wait(2000)
|
||||
|
||||
isLicensingPage((result: boolean) => {
|
||||
if (result) {
|
||||
inputLicenseKeyPage(keys.licenseKey, keys.activationKey)
|
||||
|
||||
cy.wait(2000)
|
||||
}
|
||||
|
||||
acceptTermsIfPresented((result: boolean) => {
|
||||
if (result) {
|
||||
cy.wait(10000)
|
||||
}
|
||||
|
||||
visitPage('home')
|
||||
|
||||
cy.get('.nav-tree clr-tree > clr-tree-node', {
|
||||
timeout: longerCommandTimeout
|
||||
}).then((treeNodes: any) => {
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('2 | Key will expire in less then 14 days, not free tier', (done) => {
|
||||
// make 2 separate for this one
|
||||
let keyData = {
|
||||
valid_until: moment().add(10, 'day').format('YYYY-MM-DD'),
|
||||
users_allowed: 4,
|
||||
hot_license_key: '',
|
||||
demo: false,
|
||||
site_id: site_id
|
||||
}
|
||||
|
||||
let keys: { licenseKey: any; activationKey: any }
|
||||
generateKeys(keyData, (keysGen: any) => {
|
||||
keys = keysGen
|
||||
console.log('keys', keys)
|
||||
|
||||
cy.wait(2000)
|
||||
|
||||
acceptTermsIfPresented((result: boolean) => {
|
||||
if (result) {
|
||||
cy.wait(20000)
|
||||
}
|
||||
updateLicenseKeyQuick(keysGen, () => {
|
||||
cy.wait(2000)
|
||||
acceptTermsIfPresented((result: boolean) => {
|
||||
if (result) {
|
||||
cy.wait(20000)
|
||||
}
|
||||
verifyLicensingWarning('This license key will expire in ', () => {
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('3 | key expired, free tier works', (done) => {
|
||||
let keyData = {
|
||||
valid_until: moment().subtract(1, 'day').format('YYYY-MM-DD'),
|
||||
users_allowed: 4,
|
||||
hot_license_key: '',
|
||||
demo: false,
|
||||
site_id: site_id
|
||||
}
|
||||
|
||||
let keys: { licenseKey: any; activationKey: any }
|
||||
generateKeys(keyData, (keysGen: any) => {
|
||||
keys = keysGen
|
||||
|
||||
cy.wait(2000)
|
||||
|
||||
acceptTermsIfPresented((result: boolean) => {
|
||||
if (result) {
|
||||
cy.wait(20000)
|
||||
}
|
||||
cy.wait(2000)
|
||||
updateLicenseKeyQuick(keysGen, () => {
|
||||
cy.wait(2000)
|
||||
acceptTermsIfPresented((result: boolean) => {
|
||||
if (result) {
|
||||
cy.wait(20000)
|
||||
}
|
||||
verifyLicensingPage(
|
||||
'Licence key is expired - please contact',
|
||||
(success: boolean) => {
|
||||
if (success) {
|
||||
verifyLicensingWarning(
|
||||
'(FREE Tier) - Problem with licence',
|
||||
() => {
|
||||
done()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('4 | key invalid, free tier works', (done) => {
|
||||
let keyData = {
|
||||
valid_until: moment().subtract(1, 'day').format('YYYY-MM-DD'),
|
||||
users_allowed: 4,
|
||||
hot_license_key: '',
|
||||
demo: false,
|
||||
site_id: site_id
|
||||
}
|
||||
|
||||
let keys: { licenseKey: any; activationKey: any }
|
||||
generateKeys(keyData, (keysGen: any) => {
|
||||
keys = keysGen
|
||||
keys.activationKey = 'invalid' + keys.activationKey
|
||||
|
||||
cy.wait(2000)
|
||||
|
||||
acceptTermsIfPresented((result: boolean) => {
|
||||
if (result) {
|
||||
cy.wait(20000)
|
||||
}
|
||||
cy.wait(2000)
|
||||
updateLicenseKeyQuick(keysGen, () => {
|
||||
cy.wait(2000)
|
||||
acceptTermsIfPresented((result: boolean) => {
|
||||
if (result) {
|
||||
cy.wait(20000)
|
||||
}
|
||||
verifyLicensingPage(
|
||||
'Licence key is invalid - please contact',
|
||||
(success: boolean) => {
|
||||
if (success) {
|
||||
verifyLicensingWarning(
|
||||
'(FREE Tier) - Problem with licence',
|
||||
() => {
|
||||
done()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('5 | key for wrong organisation, free tier works', (done) => {
|
||||
let keyData = {
|
||||
valid_until: moment().add(1, 'year').format('YYYY-MM-DD'),
|
||||
users_allowed: 4,
|
||||
hot_license_key: '',
|
||||
demo: false,
|
||||
site_id: 100
|
||||
}
|
||||
|
||||
let keys: { licenseKey: any; activationKey: any }
|
||||
generateKeys(keyData, (keysGen: any) => {
|
||||
keys = keysGen
|
||||
keys.activationKey = keys.activationKey
|
||||
|
||||
cy.wait(2000)
|
||||
|
||||
acceptTermsIfPresented((result: boolean) => {
|
||||
if (result) {
|
||||
cy.wait(20000)
|
||||
}
|
||||
cy.wait(2000)
|
||||
updateLicenseKeyQuick(keysGen, () => {
|
||||
cy.wait(2000)
|
||||
acceptTermsIfPresented((result: boolean) => {
|
||||
if (result) {
|
||||
cy.wait(20000)
|
||||
}
|
||||
verifyLicensingPage(
|
||||
'SYSSITE (below) is not found',
|
||||
(success: boolean) => {
|
||||
if (success) {
|
||||
verifyLicensingWarning(
|
||||
'(FREE Tier) - Problem with licence',
|
||||
() => {
|
||||
done()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
if (testLicenceUserLimits) {
|
||||
it('4 | User try to register when limit is reached', (done) => {
|
||||
let keyData = {
|
||||
valid_until: moment().add(1, 'month').format('YYYY-MM-DD'),
|
||||
users_allowed: 10,
|
||||
hot_license_key: '',
|
||||
demo: false,
|
||||
site_id: site_id
|
||||
}
|
||||
let keyData2 = {
|
||||
valid_until: moment().add(1, 'month').format('YYYY-MM-DD'),
|
||||
users_allowed: 1,
|
||||
hot_license_key: '',
|
||||
demo: false,
|
||||
site_id: site_id
|
||||
}
|
||||
generateKeys(keyData, (keysGen: any) => {
|
||||
generateKeys(keyData2, (keysGen2: any) => {
|
||||
cy.wait(2000)
|
||||
|
||||
acceptTermsIfPresented((result: boolean) => {
|
||||
if (result) {
|
||||
cy.wait(20000)
|
||||
}
|
||||
updateLicenseKeyQuick(keysGen, () => {
|
||||
cy.wait(2000)
|
||||
acceptTermsIfPresented((result: boolean) => {
|
||||
if (result) {
|
||||
cy.wait(20000)
|
||||
}
|
||||
|
||||
updateLicenseKeyQuick(keysGen2, () => {
|
||||
cy.wait(2000)
|
||||
|
||||
const random = Cypress._.random(0, 1000)
|
||||
const newUser = {
|
||||
username: `randomusername${random}notregistered`,
|
||||
last_seen_at: moment().add(1, 'month').format('YYYY-MM-DD'),
|
||||
registered_at: moment().add(1, 'month').format('YYYY-MM-DD')
|
||||
}
|
||||
updateUsersTable(
|
||||
{ deleteAll: true, newUsers: [newUser] },
|
||||
() => {
|
||||
logout(() => {
|
||||
cy.visit(hostUrl + appLocation)
|
||||
// cy.get('input.username').type(username)
|
||||
// cy.get('input.password').type(password)
|
||||
// cy.get('.login-group button').click()
|
||||
|
||||
visitPage('home')
|
||||
|
||||
cy.wait(2000)
|
||||
|
||||
verifyLicensingPage(
|
||||
'The registered number of users reached the limit specified for your licence.',
|
||||
(success: boolean) => {
|
||||
if (success) done()
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('5 | Show warning banner when limit is exceeded', (done) => {
|
||||
let keyData = {
|
||||
valid_until: moment().add(1, 'month').format('YYYY-MM-DD'),
|
||||
users_allowed: 10,
|
||||
hot_license_key: '',
|
||||
demo: false,
|
||||
site_id: site_id
|
||||
}
|
||||
let keyData2 = {
|
||||
valid_until: moment().add(1, 'month').format('YYYY-MM-DD'),
|
||||
users_allowed: 1,
|
||||
hot_license_key: '',
|
||||
demo: false,
|
||||
site_id: site_id
|
||||
}
|
||||
|
||||
generateKeys(keyData, (keysGen: any) => {
|
||||
generateKeys(keyData2, (keysGen2: any) => {
|
||||
cy.wait(2000)
|
||||
acceptTermsIfPresented((result: boolean) => {
|
||||
if (result) {
|
||||
cy.wait(20000)
|
||||
}
|
||||
updateLicenseKeyQuick(keysGen, () => {
|
||||
cy.wait(2000)
|
||||
acceptTermsIfPresented((result: boolean) => {
|
||||
if (result) {
|
||||
cy.wait(20000)
|
||||
}
|
||||
const random = Cypress._.random(0, 1000)
|
||||
const newUser = {
|
||||
username: `randomusername${random}`,
|
||||
last_seen_at: moment().add(1, 'month').format('YYYY-MM-DD'),
|
||||
registered_at: moment().add(1, 'month').format('YYYY-MM-DD')
|
||||
}
|
||||
updateUsersTable(
|
||||
{ deleteAll: true, keep: username, newUsers: [newUser] },
|
||||
() => {
|
||||
updateLicenseKeyQuick(keysGen2, () => {
|
||||
cy.wait(2000)
|
||||
verifyLicensingWarning(
|
||||
'The registered number of users exceeds the limit specified for your license.',
|
||||
() => {
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
|
||||
const logout = (callback?: any) => {
|
||||
cy.get('.header-actions .dropdown-toggle')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.get('.header-actions .dropdown-menu > .separator')
|
||||
.next()
|
||||
.click()
|
||||
.then(() => {
|
||||
if (callback) callback()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const acceptTermsIfPresented = (callback?: any) => {
|
||||
cy.url().then((url: string) => {
|
||||
if (url.includes('licensing/register')) {
|
||||
cy.get('.card-block')
|
||||
.scrollTo('bottom')
|
||||
.then(() => {
|
||||
cy.get('#checkbox1')
|
||||
.click()
|
||||
.then(() => {
|
||||
if (callback) callback(true)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
if (callback) callback(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const isLicensingPage = (callback: any) => {
|
||||
return cy.url().then((url: string) => {
|
||||
callback(
|
||||
url.includes('#/licensing/') && !url.includes('licensing/register')
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const verifyLicensingPage = (text: string, callback: any) => {
|
||||
// visitPage('home')
|
||||
|
||||
cy.wait(1000)
|
||||
isLicensingPage((result: boolean) => {
|
||||
if (result) {
|
||||
cy.get('p.key-error')
|
||||
.should('contain', text)
|
||||
.then((treeNodes: any) => {
|
||||
callback(true)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const verifyLicensingWarning = (text: string, callback: any) => {
|
||||
visitPage('home')
|
||||
|
||||
cy.wait(1000)
|
||||
cy.get("div[role='alert'] .alert-text")
|
||||
.invoke('text')
|
||||
.should('contain', text)
|
||||
.then(() => {
|
||||
callback()
|
||||
})
|
||||
}
|
||||
|
||||
const inputLicenseKeyPage = (licenseKey: string, activationKey: string) => {
|
||||
cy.get('button').contains('Paste licence').click()
|
||||
|
||||
cy.get('.license-key-form textarea', { timeout: longerCommandTimeout })
|
||||
.invoke('val', licenseKey)
|
||||
.trigger('input')
|
||||
.should('not.be.undefined')
|
||||
cy.get('.activation-key-form textarea', { timeout: longerCommandTimeout })
|
||||
.invoke('val', activationKey)
|
||||
.trigger('input')
|
||||
.should('not.be.undefined')
|
||||
cy.get('button.apply-keys').click()
|
||||
}
|
||||
|
||||
const updateUsersTable = (options: any, callback?: any) => {
|
||||
visitPage('home')
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_users')
|
||||
|
||||
clickOnEdit(() => {
|
||||
cy.get('.ht_master tbody tr').then((rows: any) => {
|
||||
if (options.deleteAll) {
|
||||
for (let row of rows) {
|
||||
const user_id = row.childNodes[2]
|
||||
if (!options.keep || user_id.innerText !== options.keep) {
|
||||
cy.get(row.childNodes[1])
|
||||
.dblclick()
|
||||
.then(() => {
|
||||
cy.focused().type('{selectall}').type('Yes').type('{enter}')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if (options.newUsers && options.newUsers.length) {
|
||||
for (let newUser of options.newUsers) {
|
||||
clickOnAddRow(() => {
|
||||
cy.get('#hotInstance tbody tr:last-child').then((rows: any) => {
|
||||
cy.get(rows[0].childNodes[2])
|
||||
.dblclick()
|
||||
.then(() => {
|
||||
cy.focused()
|
||||
.type('{selectall}')
|
||||
.type(newUser.username)
|
||||
.type('{enter}')
|
||||
})
|
||||
// cy.get(rows[0].childNodes[3])
|
||||
// .dblclick()
|
||||
// .then(() => {
|
||||
// cy.focused()
|
||||
// .type('{selectall}')
|
||||
// .type(newUser.last_seen_at)
|
||||
// .type('{enter}')
|
||||
// })
|
||||
// cy.get(rows[0].childNodes[4])
|
||||
// .dblclick()
|
||||
// .then(() => {
|
||||
// cy.focused()
|
||||
// .type('{selectall}')
|
||||
// .type(newUser.registered_at)
|
||||
// .type('{enter}')
|
||||
// })
|
||||
submitTable(() => {
|
||||
cy.wait(2000)
|
||||
approveTable(callback)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const changeLicenseKeyTable = (keys: any, callback?: any) => {
|
||||
visitPage('home')
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_config')
|
||||
|
||||
clickOnEdit(() => {
|
||||
editTableField(
|
||||
[
|
||||
{ varName: 'DC_ACTIVATION_KEY', varValue: keys.activationKey },
|
||||
{ varName: 'DC_LICENCE_KEY', varValue: keys.licenseKey }
|
||||
],
|
||||
() => {
|
||||
submitTable(() => {
|
||||
cy.wait(2000)
|
||||
approveTable(() => {
|
||||
cy.reload()
|
||||
if (callback) callback()
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const updateLicenseKeyQuick = (keys: any, callback: any) => {
|
||||
isLicensingPage((result: boolean) => {
|
||||
if (!result) {
|
||||
visitPage('licensing/update')
|
||||
cy.wait(2000)
|
||||
}
|
||||
inputLicenseKeyPage(keys.licenseKey, keys.activationKey)
|
||||
|
||||
callback()
|
||||
})
|
||||
}
|
||||
|
||||
const generateKeys = async (licenseData: any, resultCallback?: any) => {
|
||||
let keyPair = await window.crypto.subtle.generateKey(
|
||||
{
|
||||
name: 'RSA-OAEP',
|
||||
modulusLength: 2024,
|
||||
publicExponent: new Uint8Array([1, 0, 1]),
|
||||
hash: 'SHA-256'
|
||||
},
|
||||
true,
|
||||
['encrypt', 'decrypt']
|
||||
)
|
||||
|
||||
const encoded = new TextEncoder().encode(JSON.stringify(licenseData))
|
||||
|
||||
const cipher = await window.crypto.subtle
|
||||
.encrypt(
|
||||
{
|
||||
name: 'RSA-OAEP'
|
||||
},
|
||||
keyPair.publicKey,
|
||||
encoded
|
||||
)
|
||||
.then(
|
||||
(value) => {
|
||||
return value
|
||||
},
|
||||
(err) => {
|
||||
console.log('Encrpyt error', err)
|
||||
}
|
||||
)
|
||||
|
||||
if (!cipher) {
|
||||
alert('Encryptin keys failed')
|
||||
throw new Error('Encryptin keys failed')
|
||||
}
|
||||
|
||||
const privateKeyBytes = await window.crypto.subtle.exportKey(
|
||||
'pkcs8',
|
||||
keyPair.privateKey
|
||||
)
|
||||
|
||||
const activationKey = await arrayBufferToBase64(privateKeyBytes)
|
||||
const licenseKey = await arrayBufferToBase64(cipher)
|
||||
|
||||
if (resultCallback)
|
||||
resultCallback({
|
||||
activationKey,
|
||||
licenseKey
|
||||
})
|
||||
}
|
||||
|
||||
const editTableField = (edits: EditConfigTableCells[], callback?: any) => {
|
||||
cy.get('td').then((tdNodes: any) => {
|
||||
for (let edit of edits) {
|
||||
let correctRow = false
|
||||
|
||||
for (let node of tdNodes) {
|
||||
if (correctRow) {
|
||||
cy.get(node)
|
||||
.dblclick()
|
||||
.then(() => {
|
||||
// textarea update on long keys
|
||||
cy.focused().invoke('val', edit.varValue).type('{enter}')
|
||||
})
|
||||
|
||||
correctRow = false
|
||||
break
|
||||
}
|
||||
|
||||
if (node.innerText.includes(edit.varName)) {
|
||||
correctRow = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (callback) callback()
|
||||
})
|
||||
}
|
||||
|
||||
const openTableFromTree = (
|
||||
libNameIncludes: string,
|
||||
tablename: string,
|
||||
callback?: any
|
||||
) => {
|
||||
cy.get('.app-loading', { timeout: longerCommandTimeout })
|
||||
.should('not.exist')
|
||||
.then(() => {
|
||||
cy.get('.nav-tree clr-tree > clr-tree-node', {
|
||||
timeout: longerCommandTimeout
|
||||
}).then((treeNodes: any) => {
|
||||
let viyaLib
|
||||
|
||||
for (let node of treeNodes) {
|
||||
if (node.innerText.toLowerCase().includes(libNameIncludes)) {
|
||||
viyaLib = node
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cy.get(viyaLib).within(() => {
|
||||
cy.get('.clr-tree-node-content-container > button').click()
|
||||
|
||||
cy.get('.clr-treenode-link').then((innerNodes: any) => {
|
||||
for (let innerNode of innerNodes) {
|
||||
if (innerNode.innerText.toLowerCase().includes(tablename)) {
|
||||
innerNode.click()
|
||||
if (callback) callback()
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const clickOnAddRow = (callback?: any) => {
|
||||
cy.get('.btnCtrl button.btn-success')
|
||||
.click()
|
||||
.then(() => {
|
||||
if (callback) callback()
|
||||
})
|
||||
}
|
||||
|
||||
const clickOnEdit = (callback?: any) => {
|
||||
cy.get('.btnCtrl button.btn-primary', { timeout: longerCommandTimeout })
|
||||
.click()
|
||||
.then(() => {
|
||||
if (callback) callback()
|
||||
})
|
||||
}
|
||||
|
||||
const submitTable = (callback?: any) => {
|
||||
cy.get('.btnCtrl button.btn-primary', { timout: longerCommandTimeout })
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.get(".modal.ng-star-inserted button[type='submit']")
|
||||
.click()
|
||||
.then(() => {
|
||||
if (callback) callback()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const approveTable = (callback?: any) => {
|
||||
cy.get('button', { timeout: longerCommandTimeout })
|
||||
.should('contain', 'Approve')
|
||||
.then((allButtons: any) => {
|
||||
for (let approvalButton of allButtons) {
|
||||
if (
|
||||
approvalButton.innerText
|
||||
.toLowerCase()
|
||||
.includes('approve')
|
||||
) {
|
||||
approvalButton.click()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cy.get('button#acceptBtn', { timeout: longerCommandTimeout })
|
||||
.should('exist')
|
||||
.should('not.be.disabled')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.get('app-history', { timeout: longerCommandTimeout })
|
||||
.should('exist')
|
||||
.then(() => {
|
||||
if (callback) callback()
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const visitPage = (url: string) => {
|
||||
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
|
||||
}
|
154
client/cypress/e2e/liveness.cy.ts
Normal file
154
client/cypress/e2e/liveness.cy.ts
Normal file
@ -0,0 +1,154 @@
|
||||
const username = Cypress.env('username')
|
||||
const password = Cypress.env('password')
|
||||
const hostUrl = Cypress.env('hosturl')
|
||||
const appLocation = Cypress.env('appLocation')
|
||||
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
|
||||
const serverType = Cypress.env('serverType')
|
||||
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
|
||||
const fixturePath = 'excels/'
|
||||
|
||||
context('liveness tests: ', function () {
|
||||
this.beforeAll(() => {
|
||||
if (serverType !== 'SASJS') {
|
||||
cy.visit(`${hostUrl}/SASLogon/logout`)
|
||||
}
|
||||
cy.loginAndUpdateValidKey(true)
|
||||
})
|
||||
|
||||
this.beforeEach(() => {
|
||||
cy.visit(hostUrl + appLocation)
|
||||
|
||||
|
||||
visitPage('home')
|
||||
})
|
||||
|
||||
it('1 | Login and submit test', (done) => {
|
||||
cy.get('.nav-tree clr-tree > clr-tree-node', {
|
||||
timeout: longerCommandTimeout
|
||||
}).then((treeNodes: any) => {
|
||||
libraryExistsInTree('viya', treeNodes)
|
||||
? openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
: openTableFromTree('dc', 'mpe_x_test')
|
||||
|
||||
attachExcelFile('regular_excel.xlsx', () => {
|
||||
submitExcel()
|
||||
rejectExcel(done)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Thist part will be needed if we add more tests in future
|
||||
*/
|
||||
// this.afterEach(() => {
|
||||
// cy.visit('https://sas.4gl.io/SASLogon/logout');
|
||||
// })
|
||||
})
|
||||
|
||||
const visitPage = (url: string) => {
|
||||
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
|
||||
}
|
||||
|
||||
const libraryExistsInTree = (libName: string, nodes: any) => {
|
||||
for (let node of nodes) {
|
||||
if (node.innerText.toLowerCase().includes(libName.toLowerCase()))
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
const openTableFromTree = (
|
||||
libNameIncludes: string,
|
||||
tablename: string,
|
||||
finish: any
|
||||
) => {
|
||||
cy.get('.app-loading', { timeout: longerCommandTimeout })
|
||||
.should('not.exist')
|
||||
.then(() => {
|
||||
cy.get('.nav-tree clr-tree > clr-tree-node', {
|
||||
timeout: longerCommandTimeout
|
||||
}).then((treeNodes: any) => {
|
||||
let viyaLib
|
||||
|
||||
for (let node of treeNodes) {
|
||||
if (node.innerText.toLowerCase().includes(libNameIncludes)) {
|
||||
viyaLib = node
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!viyaLib && finish) finish(false)
|
||||
|
||||
cy.get(viyaLib).within(() => {
|
||||
cy.get('.clr-tree-node-content-container > button').click()
|
||||
|
||||
cy.get('.clr-treenode-link').then((innerNodes: any) => {
|
||||
for (let innerNode of innerNodes) {
|
||||
if (innerNode.innerText.toLowerCase().includes(tablename)) {
|
||||
innerNode.click()
|
||||
if (finish) finish(true)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const attachExcelFile = (excelFilename: string, callback?: any) => {
|
||||
cy.get('.buttonBar button:last-child')
|
||||
.should('exist')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.get('input[type="file"]#file-upload').attachFile(
|
||||
`/${fixturePath}/${excelFilename}`
|
||||
)
|
||||
cy.get('.modal-footer .btn.btn-primary').then((modalBtn) => {
|
||||
modalBtn.click()
|
||||
if (callback) callback()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const submitExcel = (callback?: any) => {
|
||||
cy.get('.buttonBar button.preview-submit', { timeout: longerCommandTimeout })
|
||||
.click()
|
||||
.then(() => {
|
||||
if (callback) callback()
|
||||
})
|
||||
}
|
||||
|
||||
const rejectExcel = (callback?: any) => {
|
||||
cy.get('button', { timeout: longerCommandTimeout })
|
||||
.should('contain', 'Approve')
|
||||
.then((allButtons: any) => {
|
||||
for (let approvalButton of allButtons) {
|
||||
if (
|
||||
approvalButton.innerText
|
||||
.toLowerCase()
|
||||
.includes('approve')
|
||||
) {
|
||||
approvalButton.click()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cy.get('button.btn-danger')
|
||||
.should('exist')
|
||||
.should('not.be.disabled')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.get('.modal-footer button.btn-success-outline')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.get('app-history')
|
||||
.should('exist')
|
||||
.then(() => {
|
||||
if (callback) callback()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
61
client/cypress/e2e/metanav.cy.ts
Normal file
61
client/cypress/e2e/metanav.cy.ts
Normal file
@ -0,0 +1,61 @@
|
||||
const username = Cypress.env('username')
|
||||
const password = Cypress.env('password')
|
||||
const hostUrl = Cypress.env('hosturl')
|
||||
const appLocation = Cypress.env('appLocation')
|
||||
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
|
||||
const serverType = Cypress.env('serverType')
|
||||
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
|
||||
const fixturePath = 'excels_general/'
|
||||
|
||||
context('metanav tests: ', function () {
|
||||
this.beforeAll(() => {
|
||||
cy.visit(`${hostUrl}/SASLogon/logout`)
|
||||
cy.loginAndUpdateValidKey()
|
||||
})
|
||||
|
||||
this.beforeEach(() => {
|
||||
cy.visit(hostUrl + appLocation)
|
||||
cy.get('input.username').type(username)
|
||||
cy.get('input.password').type(password)
|
||||
cy.get('.login-group button').click()
|
||||
|
||||
visitPage('view/metadata')
|
||||
})
|
||||
|
||||
it('1 | Opens metadata object', (done) => {
|
||||
openFirstMetadataFromTree(() => {
|
||||
// BLOCKER
|
||||
// For unkown reasons, .clr-accordion-header-button always null although it is present on the page.
|
||||
cy.get('.clr-accordion-header-button').then((panelNodes: any) => {
|
||||
panelNodes[0].querySelector('button').click()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
this.afterEach(() => {
|
||||
cy.visit(`${hostUrl}/SASLogon/logout`)
|
||||
})
|
||||
})
|
||||
|
||||
const openFirstMetadataFromTree = (callback?: any) => {
|
||||
cy.get('.app-loading', { timeout: longerCommandTimeout })
|
||||
.should('not.exist')
|
||||
.then(() => {
|
||||
cy.get('.nav-tree clr-tree > clr-tree-node', {
|
||||
timeout: longerCommandTimeout
|
||||
}).then((treeNodes: any) => {
|
||||
let firstMetaNode
|
||||
|
||||
firstMetaNode = treeNodes[1]
|
||||
|
||||
cy.get(firstMetaNode).within(() => {
|
||||
cy.get('.clr-treenode-content').click()
|
||||
callback()
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const visitPage = (url: string) => {
|
||||
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
|
||||
}
|
650
client/cypress/e2e/viewbox.cy.ts
Normal file
650
client/cypress/e2e/viewbox.cy.ts
Normal file
@ -0,0 +1,650 @@
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
const username = Cypress.env('username')
|
||||
const password = Cypress.env('password')
|
||||
const hostUrl = Cypress.env('hosturl')
|
||||
const appLocation = Cypress.env('appLocation')
|
||||
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
|
||||
const serverType = Cypress.env('serverType')
|
||||
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
|
||||
|
||||
context('editor tests: ', function () {
|
||||
this.beforeAll(() => {
|
||||
cy.visit(`${hostUrl}/SASLogon/logout`)
|
||||
cy.loginAndUpdateValidKey(true)
|
||||
})
|
||||
|
||||
this.beforeEach(() => {
|
||||
cy.visit(hostUrl + appLocation)
|
||||
cy.wait(2000)
|
||||
|
||||
cy.get('body').then(($body) => {
|
||||
const usernameInput = $body.find('input.username')[0]
|
||||
|
||||
if (usernameInput && !Cypress.dom.isHidden(usernameInput)) {
|
||||
cy.get('input.username').type(username)
|
||||
cy.get('input.password').type(password)
|
||||
|
||||
cy.get('.login-group button').click()
|
||||
}
|
||||
})
|
||||
|
||||
visitPage('home')
|
||||
})
|
||||
|
||||
it('1 | Add one viewbox', (done) => {
|
||||
const viewbox_table = 'mpe_audit'
|
||||
const columns = ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM']
|
||||
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
cy.get('.viewbox-open').click()
|
||||
openTableFromViewboxTree(libraryToOpenIncludes, [viewbox_table])
|
||||
|
||||
cy.get('.open-viewbox').then((viewboxNodes: any) => {
|
||||
for (let viewboxNode of viewboxNodes) {
|
||||
if (!viewboxNode.innerText.toLowerCase().includes(viewbox_table)) {
|
||||
return
|
||||
}
|
||||
|
||||
checkColumns(columns, () => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('2 | Add two viewboxes', (done) => {
|
||||
const viewboxes = [
|
||||
{
|
||||
viewbox_table: 'mpe_audit',
|
||||
columns: ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM']
|
||||
},
|
||||
{
|
||||
viewbox_table: 'mpe_alerts',
|
||||
columns: [
|
||||
'TX_FROM',
|
||||
'ALERT_EVENT',
|
||||
'ALERT_LIB',
|
||||
'ALERT_DS',
|
||||
'ALERT_USER'
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
cy.get('.viewbox-open').click()
|
||||
openTableFromViewboxTree(
|
||||
libraryToOpenIncludes,
|
||||
viewboxes.map((viewbox) => viewbox.viewbox_table))
|
||||
cy.get('.open-viewbox').then((viewboxNodes: any) => {
|
||||
let found = 0
|
||||
|
||||
for (let viewboxNode of viewboxNodes) {
|
||||
for (let viewbox of viewboxes) {
|
||||
if (
|
||||
viewboxNode.innerText.toLowerCase().includes(viewbox.viewbox_table)
|
||||
)
|
||||
found++
|
||||
}
|
||||
}
|
||||
|
||||
if (found < viewboxes.length) return
|
||||
|
||||
cy.get('.viewboxes-container .viewbox', { withinSubject: null }).then((viewboxNodes: any) => {
|
||||
for (let viewboxNode of viewboxNodes) {
|
||||
cy.get(viewboxNode).within(() => {
|
||||
cy.get('.table-title').then((tableTitle) => {
|
||||
const title = tableTitle[0].innerText
|
||||
const viewbox = viewboxes.find((vb) =>
|
||||
title.toLowerCase().includes(vb.viewbox_table)
|
||||
)
|
||||
|
||||
if (viewbox) {
|
||||
cy.get('.ht_master.handsontable .htCore thead tr').then(
|
||||
(viewboxColNodes: any) => {
|
||||
let allColsHtml = viewboxColNodes[0].innerHTML
|
||||
|
||||
for (let col of viewbox?.columns) {
|
||||
if (!allColsHtml.includes(col)) return
|
||||
}
|
||||
|
||||
done()
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('3 | Add viewbox, add columns', (done) => {
|
||||
const viewbox_table = 'mpe_audit'
|
||||
const columns = ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM']
|
||||
const additionalColumns = ['IS_PK']
|
||||
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
cy.get('.viewbox-open').click()
|
||||
openTableFromViewboxTree(libraryToOpenIncludes, [viewbox_table])
|
||||
|
||||
cy.get('.open-viewbox').then((viewboxNodes: any) => {
|
||||
for (let viewboxNode of viewboxNodes) {
|
||||
if (!viewboxNode.innerText.toLowerCase().includes(viewbox_table)) {
|
||||
return
|
||||
}
|
||||
|
||||
openViewboxConfig(viewbox_table)
|
||||
|
||||
removeAllColumns()
|
||||
|
||||
addColumns(additionalColumns)
|
||||
|
||||
checkColumns([...columns, ...additionalColumns], () => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('4 | Add viewbox, add columns and reorder', (done) => {
|
||||
const viewbox_table = 'mpe_audit'
|
||||
const columns = ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM']
|
||||
const additionalColumns = ['IS_PK', 'MOVE_TYPE']
|
||||
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
cy.get('.viewbox-open').click()
|
||||
openTableFromViewboxTree(libraryToOpenIncludes, [viewbox_table])
|
||||
|
||||
cy.get('.open-viewbox').then((viewboxNodes: any) => {
|
||||
for (let viewboxNode of viewboxNodes) {
|
||||
if (!viewboxNode.innerText.toLowerCase().includes(viewbox_table)) {
|
||||
return
|
||||
}
|
||||
|
||||
openViewboxConfig(viewbox_table)
|
||||
|
||||
removeAllColumns()
|
||||
|
||||
addColumns(additionalColumns, () => {
|
||||
cy.wait(1000)
|
||||
//reorder
|
||||
cy.get('.col-box.column-MOVE_TYPE')
|
||||
.realMouseDown({ button: 'left', position: 'center' })
|
||||
.realMouseMove(0, 10, { position: 'center' })
|
||||
cy.wait(200) // In our case, we wait 200ms cause we have animations which we are sure that take this amount of time
|
||||
cy.get('.col-box.column-IS_PK')
|
||||
.realMouseMove(0, 0, { position: 'center' })
|
||||
.realMouseUp()
|
||||
//reorder end
|
||||
|
||||
cy.wait(500)
|
||||
|
||||
checkColumns([...columns, ...additionalColumns.reverse()], () => {
|
||||
done()
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('5 | Add viewbox, add columns, reorder, remove column, add again', (done) => {
|
||||
const viewbox_table = 'mpe_audit'
|
||||
const columns = ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM']
|
||||
const additionalColumns = ['IS_PK', 'MOVE_TYPE']
|
||||
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
cy.get('.viewbox-open').click()
|
||||
openTableFromViewboxTree(libraryToOpenIncludes, [viewbox_table])
|
||||
|
||||
cy.get('.open-viewbox').then((viewboxNodes: any) => {
|
||||
for (let viewboxNode of viewboxNodes) {
|
||||
if (!viewboxNode.innerText.toLowerCase().includes(viewbox_table)) {
|
||||
return
|
||||
}
|
||||
|
||||
viewboxNode.click()
|
||||
|
||||
removeAllColumns()
|
||||
|
||||
addColumns(additionalColumns, () => {
|
||||
cy.wait(1000)
|
||||
//reorder
|
||||
cy.get('.col-box.column-MOVE_TYPE')
|
||||
.realMouseDown({ button: 'left', position: 'center' })
|
||||
.realMouseMove(0, 10, { position: 'center' })
|
||||
cy.wait(200) // In our case, we wait 200ms cause we have animations which we are sure that take this amount of time
|
||||
cy.get('.col-box.column-IS_PK')
|
||||
.realMouseMove(0, 0, { position: 'center' })
|
||||
.realMouseUp()
|
||||
//reorder end
|
||||
|
||||
cy.wait(500)
|
||||
|
||||
checkColumns([...columns, ...additionalColumns.reverse()], () => {
|
||||
const colToRemove = 'MOVE_TYPE'
|
||||
|
||||
removeColumn(colToRemove)
|
||||
checkColumns(
|
||||
[
|
||||
...columns,
|
||||
...additionalColumns.filter((col) => col !== colToRemove)
|
||||
],
|
||||
() => {
|
||||
addColumns([colToRemove], () => {
|
||||
checkColumns(
|
||||
[...columns, ...additionalColumns.reverse()],
|
||||
() => {
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('6 | Add viewboxes, reload and check url restored configuration', (done) => {
|
||||
const viewboxes = [
|
||||
{
|
||||
viewbox_table: 'mpe_audit',
|
||||
columns: ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM'],
|
||||
additionalColumns: ['IS_PK', 'MOVE_TYPE']
|
||||
},
|
||||
{
|
||||
viewbox_table: 'mpe_alerts',
|
||||
columns: [
|
||||
'TX_FROM',
|
||||
'ALERT_EVENT',
|
||||
'ALERT_LIB',
|
||||
'ALERT_DS',
|
||||
'ALERT_USER'
|
||||
],
|
||||
additionalColumns: ['TX_TO']
|
||||
}
|
||||
]
|
||||
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
cy.get('.viewbox-open').click()
|
||||
openTableFromViewboxTree(libraryToOpenIncludes, [
|
||||
viewboxes[0].viewbox_table,
|
||||
viewboxes[1].viewbox_table
|
||||
])
|
||||
|
||||
openViewboxConfig(viewboxes[0].viewbox_table)
|
||||
|
||||
cy.wait(500)
|
||||
|
||||
removeAllColumns()
|
||||
|
||||
addColumns(viewboxes[0].additionalColumns, () => {
|
||||
cy.wait(1000)
|
||||
|
||||
if (viewboxes[0].viewbox_table === 'mpe_audit') {
|
||||
cy.get('.col-box.column-MOVE_TYPE')
|
||||
.realMouseDown({ button: 'left', position: 'center' })
|
||||
.realMouseMove(0, 10, { position: 'center' })
|
||||
cy.wait(200) // In our case, we wait 200ms cause we have animations which we are sure that take this amount of time
|
||||
cy.get('.col-box.column-IS_PK')
|
||||
.realMouseMove(0, 0, { position: 'center' })
|
||||
.realMouseUp()
|
||||
}
|
||||
|
||||
cy.wait(1000)
|
||||
|
||||
openViewboxConfig(viewboxes[1].viewbox_table)
|
||||
|
||||
addColumns(viewboxes[1].additionalColumns, () => {
|
||||
cy.wait(1000).reload()
|
||||
|
||||
let result = 0
|
||||
|
||||
checkColumns(
|
||||
[
|
||||
...viewboxes[0].columns,
|
||||
...cloneDeep(viewboxes[0].additionalColumns.reverse())
|
||||
],
|
||||
() => {
|
||||
result++
|
||||
|
||||
if (result === 2) done()
|
||||
}
|
||||
)
|
||||
checkColumns(
|
||||
[...viewboxes[1].columns, ...viewboxes[1].additionalColumns],
|
||||
() => {
|
||||
result++
|
||||
|
||||
if (result === 2) done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// We will enable this test when we figure out how to mock filtering
|
||||
// it('7 | Add viewboxes and filter', () => {
|
||||
// const viewboxes = ['mpe_x_test', 'mpe_validations']
|
||||
|
||||
// openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
// cy.get('.viewbox-open').click()
|
||||
// openTableFromViewboxTree(libraryToOpenIncludes, viewboxes)
|
||||
|
||||
// cy.wait(1000)
|
||||
|
||||
// closeViewboxModal()
|
||||
|
||||
// cy.get('.viewboxes-container .viewbox', { withinSubject: null }).then(
|
||||
// (viewboxNodes: any) => {
|
||||
// for (let viewboxNode of viewboxNodes) {
|
||||
// cy.get(viewboxNode).within(() => {
|
||||
// cy.get('.table-title').then((title: any) => {
|
||||
// cy.get('.hot-spinner')
|
||||
// .should('not.exist')
|
||||
// .then(() => {
|
||||
// cy.get('clr-icon[shape="filter"]').then((filterButton) => {
|
||||
// filterButton[0].click()
|
||||
// })
|
||||
|
||||
// if (title[0].innerText.includes('MPE_X_TEST')) {
|
||||
// setFilterWithValue(
|
||||
// 'SOME_CHAR',
|
||||
// 'this is dummy data',
|
||||
// 'value',
|
||||
// () => {
|
||||
// cy.get('app-query', { withinSubject: null })
|
||||
// .should('not.exist')
|
||||
// .get('.ht_master.handsontable tbody tr')
|
||||
// .then((rowNodes) => {
|
||||
// const tr = rowNodes[0]
|
||||
|
||||
// expect(rowNodes).to.have.length(1)
|
||||
// expect(tr.innerText).to.equal('0')
|
||||
// })
|
||||
// }
|
||||
// )
|
||||
// } else if (title[0].innerText.includes('MPE_VALIDATIONS')) {
|
||||
// setFilterWithValue('BASE_COL', 'ALERT_LIB', 'value', () => {
|
||||
// cy.get('app-query', { withinSubject: null })
|
||||
// .should('not.exist')
|
||||
// .get('.ht_master.handsontable tbody tr')
|
||||
// .then((rowNodes) => {
|
||||
// const tr = rowNodes[0]
|
||||
|
||||
// expect(rowNodes).to.have.length(1)
|
||||
// expect(tr.innerText).to.contain('ALERT_LIB')
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
// })
|
||||
})
|
||||
|
||||
const removeAllColumns = () => {
|
||||
cy.get('.configuration-wrapper clr-icon[shape="trash"]').then(removeNodes => {
|
||||
for (let removeNode of removeNodes) {
|
||||
removeNode.click()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const checkColumns = (columns: string[], callback: () => void) => {
|
||||
cy.get('.viewboxes-container .viewbox', { withinSubject: null }).then(
|
||||
(viewboxNodes: any) => {
|
||||
for (let viewboxNode of viewboxNodes) {
|
||||
cy.get(viewboxNode).within(() => {
|
||||
cy.get('.ht_master.handsontable thead tr th').then(
|
||||
(viewboxColNodes: any) => {
|
||||
console.log('viewboxColNode', viewboxColNodes)
|
||||
console.log('columns', columns)
|
||||
for (let i = 0; i < viewboxColNodes.length; i++) {
|
||||
const col = columns[i]|| ''
|
||||
const colNode = viewboxColNodes[i]
|
||||
|
||||
if (
|
||||
!colNode.innerHTML.toLowerCase().includes(col.toLowerCase())
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
callback()
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const closeViewboxModal = () => {
|
||||
cy.get('app-viewboxes .close', { withinSubject: null }).click()
|
||||
}
|
||||
|
||||
const removeColumn = (column: string) => {
|
||||
cy.get(`.col-box.column-${column} clr-icon`, { withinSubject: null }).click()
|
||||
}
|
||||
|
||||
const addColumns = (columns: string[], callback?: () => void) => {
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
const column = columns[i]
|
||||
|
||||
cy.get('.cols-search input', { withinSubject: null }).type(column)
|
||||
cy.get('.cols-search .autocomplete-wrapper', { withinSubject: null })
|
||||
.first()
|
||||
.trigger('keydown', { key: 'ArrowDown' })
|
||||
cy.get('.cols-search .autocomplete-wrapper', { withinSubject: null })
|
||||
.first()
|
||||
.trigger('keydown', { key: 'Enter' })
|
||||
.then(() => {
|
||||
if (i === columns.length - 1 && callback) callback()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const openViewboxConfig = (viewbox_tablename: string) => {
|
||||
cy.get('.open-viewbox').then((viewboxes: any) => {
|
||||
for (let openViewbox of viewboxes) {
|
||||
if (openViewbox.innerText.toLowerCase().includes(viewbox_tablename))
|
||||
openViewbox.click()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const openTableFromTree = (libNameIncludes: string, tablename: string) => {
|
||||
cy.get('.app-loading', { timeout: longerCommandTimeout })
|
||||
.should('not.exist')
|
||||
.then(() => {
|
||||
cy.get('.nav-tree clr-tree > clr-tree-node', {
|
||||
timeout: longerCommandTimeout
|
||||
}).then((treeNodes: any) => {
|
||||
let viyaLib
|
||||
|
||||
for (let node of treeNodes) {
|
||||
if (new RegExp(libNameIncludes).test(node.innerText.toLowerCase())) {
|
||||
viyaLib = node
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cy.get(viyaLib).within(() => {
|
||||
cy.get('.clr-tree-node-content-container p').click()
|
||||
|
||||
cy.get('.clr-treenode-link').then((innerNodes: any) => {
|
||||
for (let innerNode of innerNodes) {
|
||||
if (innerNode.innerText.toLowerCase().includes(tablename)) {
|
||||
innerNode.click()
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const setFilterWithValue = (
|
||||
variableValue: string,
|
||||
valueString: string,
|
||||
valueField: 'value' | 'time' | 'date' | 'datetime' | 'in' | 'between',
|
||||
callback?: any
|
||||
) => {
|
||||
cy.wait(600)
|
||||
|
||||
cy.focused().type(variableValue)
|
||||
cy.wait(100)
|
||||
// cy.focused().trigger('input')
|
||||
cy.get('.variable-col .autocomplete-wrapper', { withinSubject: null })
|
||||
.first()
|
||||
.trigger('keydown', { key: 'ArrowDown' })
|
||||
cy.get('.variable-col .autocomplete-wrapper', {
|
||||
withinSubject: null
|
||||
}).trigger('keydown', { key: 'Enter' })
|
||||
cy.focused().tab()
|
||||
cy.wait(100)
|
||||
|
||||
if (valueField === 'in') {
|
||||
cy.focused().select(valueField.toUpperCase()).trigger('change')
|
||||
} else if (valueField === 'between') {
|
||||
cy.focused().select(valueField.toUpperCase()).trigger('change')
|
||||
} else {
|
||||
cy.focused().tab()
|
||||
cy.wait(100)
|
||||
}
|
||||
|
||||
switch (valueField) {
|
||||
case 'value': {
|
||||
cy.focused().type(valueString)
|
||||
|
||||
break
|
||||
}
|
||||
case 'time': {
|
||||
cy.focused().type(valueString)
|
||||
|
||||
break
|
||||
}
|
||||
case 'date': {
|
||||
cy.focused().type(valueString)
|
||||
cy.focused().tab()
|
||||
|
||||
break
|
||||
}
|
||||
case 'datetime': {
|
||||
const date = valueString.split(' ')[0]
|
||||
const time = valueString.split(' ')[1]
|
||||
|
||||
cy.focused().type(date)
|
||||
cy.focused().tab()
|
||||
cy.focused().tab()
|
||||
cy.focused().type(time)
|
||||
|
||||
break
|
||||
}
|
||||
case 'in': {
|
||||
cy.get('.checkbox-vals').then(() => {
|
||||
cy.focused().tab()
|
||||
cy.focused().click()
|
||||
cy.get('.no-values')
|
||||
.should('not.exist')
|
||||
.then(() => {
|
||||
cy.get('clr-checkbox-wrapper input').then((inputs: any) => {
|
||||
inputs[0].click()
|
||||
cy.get('.in-values-modal .modal-footer button').click()
|
||||
|
||||
cy.get('.modal-footer .btn-success-outline').click()
|
||||
|
||||
if (callback) callback()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
case 'between': {
|
||||
cy.focused().tab()
|
||||
|
||||
const start = valueString.split('-')[0]
|
||||
const end = valueString.split('-')[1]
|
||||
|
||||
cy.focused().type(start)
|
||||
cy.focused().tab()
|
||||
cy.focused().type(end)
|
||||
}
|
||||
default: {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cy.wait(100)
|
||||
cy.focused().tab()
|
||||
cy.wait(100)
|
||||
cy.focused().tab()
|
||||
cy.wait(100)
|
||||
cy.focused().tab()
|
||||
cy.wait(100)
|
||||
cy.focused().tab()
|
||||
cy.wait(100)
|
||||
cy.focused().click()
|
||||
|
||||
if (callback) callback()
|
||||
}
|
||||
|
||||
const openTableFromViewboxTree = (
|
||||
libNameIncludes: string,
|
||||
tablenames: string[]
|
||||
) => {
|
||||
cy.get('.add-new clr-tree > clr-tree-node', {
|
||||
timeout: longerCommandTimeout
|
||||
}).then((treeNodes: any) => {
|
||||
let viyaLib
|
||||
|
||||
for (let node of treeNodes) {
|
||||
if (node.innerText.toLowerCase().includes(libNameIncludes)) {
|
||||
viyaLib = node
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cy.get(viyaLib).within(() => {
|
||||
cy.get('p')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.get('.clr-treenode-link').then(async (innerNodes: any) => {
|
||||
for (let innerNode of innerNodes) {
|
||||
for (let tablename of tablenames) {
|
||||
await pause(300)
|
||||
if (innerNode.innerText.toLowerCase().includes(tablename)) {
|
||||
innerNode.click()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const pause = (ms: number) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
resolve(null)
|
||||
}, ms)
|
||||
})
|
||||
}
|
||||
|
||||
const visitPage = (url: string) => {
|
||||
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
|
||||
}
|
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(() => {
|
||||
cy.visit(hostUrl + appLocation)
|
||||
// cy.get('input.username').type(username)
|
||||
// cy.get('input.password').type(password)
|
||||
// cy.get('.login-group button').click()
|
||||
|
||||
visitPage('home')
|
||||
})
|
||||
@ -118,10 +115,6 @@ context('editor tests: ', function () {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
this.afterEach(() => {
|
||||
// cy.visit(`${hostUrl}/SASLogon/logout`)
|
||||
})
|
||||
})
|
||||
|
||||
const clickOnEdit = (callback?: any) => {
|
||||
@ -221,14 +214,10 @@ const submitExcel = (callback?: any) => {
|
||||
|
||||
const rejectExcel = (callback?: any) => {
|
||||
cy.get('button', { timeout: longerCommandTimeout })
|
||||
.should('contain', 'Go to approvals screen')
|
||||
.should('contain', 'Approve')
|
||||
.then((allButtons: any) => {
|
||||
for (let approvalButton of allButtons) {
|
||||
if (
|
||||
approvalButton.innerText
|
||||
.toLowerCase()
|
||||
.includes('go to approvals screen')
|
||||
) {
|
||||
if (approvalButton.innerText.toLowerCase().includes('approve')) {
|
||||
approvalButton.click()
|
||||
break
|
||||
}
|
||||
|
@ -19,9 +19,6 @@ context('excel tests: ', function () {
|
||||
|
||||
this.beforeEach(() => {
|
||||
cy.visit(hostUrl + appLocation)
|
||||
// cy.get('input.username').type(username)
|
||||
// cy.get('input.password').type(password)
|
||||
// cy.get('.login-group button').click()
|
||||
|
||||
visitPage('home')
|
||||
|
||||
@ -339,7 +336,6 @@ context('excel tests: ', function () {
|
||||
|
||||
this.afterEach(() => {
|
||||
colorLog(`TEST END -------------`, '#3498DB')
|
||||
// cy.visit(`${hostUrl}/SASLogon/logout`)
|
||||
})
|
||||
})
|
||||
|
||||
@ -407,14 +403,10 @@ const submitExcel = (callback?: any) => {
|
||||
|
||||
const rejectExcel = (callback?: any) => {
|
||||
cy.get('button', { timeout: longerCommandTimeout })
|
||||
.should('contain', 'Go to approvals screen')
|
||||
.should('contain', 'Approve')
|
||||
.then((allButtons: any) => {
|
||||
for (let approvalButton of allButtons) {
|
||||
if (
|
||||
approvalButton.innerText
|
||||
.toLowerCase()
|
||||
.includes('go to approvals screen')
|
||||
) {
|
||||
if (approvalButton.innerText.toLowerCase().includes('approve')) {
|
||||
approvalButton.click()
|
||||
break
|
||||
}
|
||||
@ -440,14 +432,10 @@ const rejectExcel = (callback?: any) => {
|
||||
|
||||
const acceptExcel = (callback?: any) => {
|
||||
cy.get('button', { timeout: longerCommandTimeout })
|
||||
.should('contain', 'Go to approvals screen')
|
||||
.should('contain', 'Approve')
|
||||
.then((allButtons: any) => {
|
||||
for (let approvalButton of allButtons) {
|
||||
if (
|
||||
approvalButton.innerText
|
||||
.toLowerCase()
|
||||
.includes('go to approvals screen')
|
||||
) {
|
||||
if (approvalButton.innerText.toLowerCase().includes('approve')) {
|
||||
approvalButton.click()
|
||||
break
|
||||
}
|
||||
|
@ -15,9 +15,6 @@ context('filtering tests: ', function () {
|
||||
|
||||
this.beforeEach(() => {
|
||||
cy.visit(hostUrl + appLocation, { timeout: longerCommandTimeout })
|
||||
// cy.get('input.username').type(username)
|
||||
// cy.get('input.password').type(password)
|
||||
// cy.get('.login-group button').click()
|
||||
|
||||
visitPage('home')
|
||||
})
|
||||
@ -173,10 +170,6 @@ context('filtering tests: ', function () {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
this.afterEach(() => {
|
||||
// cy.visit(`${hostUrl}/SASLogon/logout`)
|
||||
})
|
||||
})
|
||||
|
||||
const checkInfoBarIncludes = (text: string, callback: any) => {
|
||||
|
@ -23,15 +23,11 @@ interface EditConfigTableCells {
|
||||
|
||||
context('licensing tests: ', function () {
|
||||
this.beforeAll(() => {
|
||||
// cy.visit(`${hostUrl}/SASLogon/logout`)
|
||||
cy.loginAndUpdateValidKey()
|
||||
})
|
||||
|
||||
this.beforeEach(() => {
|
||||
cy.visit(hostUrl + appLocation)
|
||||
// cy.get('input.username').type(username)
|
||||
// cy.get('input.password').type(password)
|
||||
// cy.get('.login-group button').click()
|
||||
|
||||
visitPage('home')
|
||||
})
|
||||
@ -374,10 +370,6 @@ context('licensing tests: ', function () {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
this.afterEach(() => {
|
||||
// cy.visit(`${hostUrl}/SASLogon/logout`)
|
||||
})
|
||||
})
|
||||
|
||||
const logout = (callback?: any) => {
|
||||
@ -699,14 +691,10 @@ const submitTable = (callback?: any) => {
|
||||
|
||||
const approveTable = (callback?: any) => {
|
||||
cy.get('button', { timeout: longerCommandTimeout })
|
||||
.should('contain', 'Go to approvals screen')
|
||||
.should('contain', 'Approve')
|
||||
.then((allButtons: any) => {
|
||||
for (let approvalButton of allButtons) {
|
||||
if (
|
||||
approvalButton.innerText
|
||||
.toLowerCase()
|
||||
.includes('go to approvals screen')
|
||||
) {
|
||||
if (approvalButton.innerText.toLowerCase().includes('approve')) {
|
||||
approvalButton.click()
|
||||
break
|
||||
}
|
||||
|
@ -18,10 +18,6 @@ context('liveness tests: ', function () {
|
||||
this.beforeEach(() => {
|
||||
cy.visit(hostUrl + appLocation)
|
||||
|
||||
// cy.get('input.username').type(username)
|
||||
// cy.get('input.password').type(password)
|
||||
// cy.get('.login-group button').click()
|
||||
|
||||
visitPage('home')
|
||||
})
|
||||
|
||||
@ -125,14 +121,10 @@ const submitExcel = (callback?: any) => {
|
||||
|
||||
const rejectExcel = (callback?: any) => {
|
||||
cy.get('button', { timeout: longerCommandTimeout })
|
||||
.should('contain', 'Go to approvals screen')
|
||||
.should('contain', 'Approve')
|
||||
.then((allButtons: any) => {
|
||||
for (let approvalButton of allButtons) {
|
||||
if (
|
||||
approvalButton.innerText
|
||||
.toLowerCase()
|
||||
.includes('go to approvals screen')
|
||||
) {
|
||||
if (approvalButton.innerText.toLowerCase().includes('approve')) {
|
||||
approvalButton.click()
|
||||
break
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ context('editor tests: ', function () {
|
||||
|
||||
this.beforeEach(() => {
|
||||
cy.visit(hostUrl + appLocation)
|
||||
|
||||
cy.wait(2000)
|
||||
|
||||
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) => {
|
||||
|
23
client/cypress/support/e2e.ts
Normal file
23
client/cypress/support/e2e.ts
Normal file
@ -0,0 +1,23 @@
|
||||
// ***********************************************************
|
||||
// This example support/e2e.ts is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
|
||||
import 'cypress-plugin-tab'
|
||||
import "cypress-real-events"
|
@ -6,5 +6,8 @@
|
||||
"lib": ["es2019", "dom"],
|
||||
"types": ["cypress", "cypress-real-events"]
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"../cypress.config.ts"
|
||||
]
|
||||
}
|
@ -42,4 +42,4 @@ module.exports = function (config) {
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
BIN
client/libraries/clr-angular-17.9.0.tgz
Normal file
BIN
client/libraries/clr-angular-17.9.0.tgz
Normal file
Binary file not shown.
BIN
client/libraries/clr-ui-17.9.0.tgz
Normal file
BIN
client/libraries/clr-ui-17.9.0.tgz
Normal file
Binary file not shown.
BIN
client/libraries/sheet-crypto.tgz.gpg
Normal file
BIN
client/libraries/sheet-crypto.tgz.gpg
Normal file
Binary file not shown.
@ -10,7 +10,7 @@ const check = (cwd) => {
|
||||
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;',
|
||||
excludePackages:
|
||||
'@cds/city@1.1.0;@handsontable/angular@13.0.0;handsontable@13.0.0;hyperformula@2.5.0;jackspeak@2.2.0;path-scurry@1.7.0'
|
||||
'@cds/city@1.1.0;@handsontable/angular@15.3.0;handsontable@15.3.0;hyperformula@2.7.1;hyperformula@3.0.0;jackspeak@3.4.3;path-scurry@1.11.1;package-json-from-dist@1.0.1'
|
||||
},
|
||||
(error, json) => {
|
||||
if (error) {
|
||||
|
29116
client/package-lock.json
generated
29116
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dc-client",
|
||||
"description": "dc-client",
|
||||
"name": "data_controller-client",
|
||||
"description": "DataController Client",
|
||||
"angular-cli": {},
|
||||
"scripts": {
|
||||
"start": "node --max_old_space_size=4096 node_modules/@angular/cli/bin/ng serve",
|
||||
@ -18,8 +18,8 @@
|
||||
"deploy_sasjs": "rsync -avhe ssh ./dist/* --delete root@${npm_config_account}.4gl.io:/var/www/html/dc/dev",
|
||||
"viyabuild": "cd build; ./viyabuild.sh",
|
||||
"lint": "cd .. && npm run lint",
|
||||
"test": "ng test",
|
||||
"test:headless": "ng test --browsers ChromeHeadless",
|
||||
"test": "npx ng test",
|
||||
"test:headless": "npx ng test --no-watch --no-progress --browsers ChromeHeadlessCI",
|
||||
"watch": "ng test watch=true",
|
||||
"pree2e": "webdriver-manager update",
|
||||
"e2e": "protractor protractor.config.js",
|
||||
@ -29,36 +29,38 @@
|
||||
"cy:run": "cypress run",
|
||||
"audit:prod": "npm audit --omit=dev",
|
||||
"sasdocs": "sasjs doc && ./sasjs/utils/deploydocs.sh",
|
||||
"typedoc": "typedoc --options typedoc.json && cd ../dc-devdocs"
|
||||
"compodoc:build": "compodoc -p tsconfig.doc.json --name 'Data Controller Client'",
|
||||
"compodoc:build-and-serve": "compodoc -p tsconfig.doc.json -s --name 'Data Controller Client'",
|
||||
"compodoc:serve": "compodoc -s --name 'Data Controller Client'"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^16.1.2",
|
||||
"@angular/cdk": "^15.2.0",
|
||||
"@angular/common": "^16.1.2",
|
||||
"@angular/compiler": "^16.1.2",
|
||||
"@angular/core": "^16.1.2",
|
||||
"@angular/forms": "^16.1.2",
|
||||
"@angular/platform-browser": "^16.1.2",
|
||||
"@angular/platform-browser-dynamic": "^16.1.2",
|
||||
"@angular/router": "^16.1.2",
|
||||
"@cds/core": "^6.4.2",
|
||||
"@clr/angular": "^13.17.0",
|
||||
"@angular/animations": "^17.3.3",
|
||||
"@angular/cdk": "^17.3.3",
|
||||
"@angular/common": "^17.3.3",
|
||||
"@angular/compiler": "^17.3.3",
|
||||
"@angular/core": "^17.3.3",
|
||||
"@angular/forms": "^17.3.3",
|
||||
"@angular/platform-browser": "^17.3.3",
|
||||
"@angular/platform-browser-dynamic": "^17.3.3",
|
||||
"@angular/router": "^17.3.3",
|
||||
"@cds/core": "^6.15.1",
|
||||
"@clr/angular": "file:libraries/clr-angular-17.9.0.tgz",
|
||||
"@clr/icons": "^13.0.2",
|
||||
"@clr/ui": "^13.17.0",
|
||||
"@handsontable/angular": "^13.0.0",
|
||||
"@sasjs/adapter": "4.3.6",
|
||||
"@sasjs/utils": "^3.3.0",
|
||||
"@sheet/crypto": "1.20211122.1",
|
||||
"@clr/ui": "file:libraries/clr-ui-17.9.0.tgz",
|
||||
"@handsontable/angular": "^15.3.0",
|
||||
"@sasjs/adapter": "^4.12.0",
|
||||
"@sasjs/utils": "^3.4.0",
|
||||
"@sheet/crypto": "file:libraries/sheet-crypto.tgz",
|
||||
"@types/d3-graphviz": "^2.6.7",
|
||||
"@types/text-encoding": "0.0.35",
|
||||
"base64-arraybuffer": "^0.2.0",
|
||||
"buffer": "^5.4.3",
|
||||
"crypto-browserify": "3.12.0",
|
||||
"crypto-js": "^3.3.0",
|
||||
"crypto-browserify": "^3.12.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"d3-graphviz": "^5.0.2",
|
||||
"fs-extra": "^7.0.1",
|
||||
"handsontable": "^13.0.0",
|
||||
"handsontable": "^15.3.0",
|
||||
"https-browserify": "1.0.0",
|
||||
"hyperformula": "^2.5.0",
|
||||
"iconv-lite": "^0.5.0",
|
||||
@ -69,7 +71,6 @@
|
||||
"ngx-clipboard": "^16.0.0",
|
||||
"ngx-json-viewer": "file:libraries/ngx-json-viewer-3.2.1.tgz",
|
||||
"nodejs": "0.0.0",
|
||||
"numbro": "^2.1.1",
|
||||
"os-browserify": "0.3.0",
|
||||
"rxjs": "^7.8.0",
|
||||
"save-svg-as-png": "^1.4.17",
|
||||
@ -77,41 +78,46 @@
|
||||
"stream-http": "3.2.0",
|
||||
"text-encoding": "^0.7.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.13.0"
|
||||
"vm": "^0.1.0",
|
||||
"webpack": "^5.91.0",
|
||||
"xlsx": "^0.18.5",
|
||||
"zone.js": "~0.14.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^16.1.0",
|
||||
"@angular-eslint/builder": "16.0.3",
|
||||
"@angular-eslint/eslint-plugin": "16.0.3",
|
||||
"@angular-eslint/eslint-plugin-template": "16.0.3",
|
||||
"@angular-eslint/schematics": "16.0.3",
|
||||
"@angular-eslint/template-parser": "16.0.3",
|
||||
"@angular/cli": "^16.1.0",
|
||||
"@angular/compiler-cli": "^16.1.2",
|
||||
"@cypress/webpack-preprocessor": "^5.11.1",
|
||||
"@angular-devkit/build-angular": "^17.3.3",
|
||||
"@angular-eslint/builder": "17.3.0",
|
||||
"@angular-eslint/eslint-plugin": "17.3.0",
|
||||
"@angular-eslint/eslint-plugin-template": "17.3.0",
|
||||
"@angular-eslint/schematics": "17.3.0",
|
||||
"@angular-eslint/template-parser": "17.3.0",
|
||||
"@angular/cli": "^17.3.3",
|
||||
"@angular/compiler-cli": "^17.3.3",
|
||||
"@babel/plugin-proposal-private-methods": "^7.18.6",
|
||||
"@compodoc/compodoc": "^1.1.21",
|
||||
"@cypress/webpack-preprocessor": "^5.17.1",
|
||||
"@types/core-js": "^2.5.5",
|
||||
"@types/crypto-js": "^4.0.1",
|
||||
"@types/crypto-js": "^4.2.1",
|
||||
"@types/es6-shim": "^0.31.39",
|
||||
"@types/jasmine": "~3.6.0",
|
||||
"@types/jasmine": "~5.1.4",
|
||||
"@types/lodash-es": "^4.17.3",
|
||||
"@types/marked": "^4.3.0",
|
||||
"@types/node": "12.20.50",
|
||||
"@typescript-eslint/eslint-plugin": "^5.29.0",
|
||||
"@typescript-eslint/parser": "^5.29.0",
|
||||
"core-js": "^2.5.4",
|
||||
"cypress": "^9.5.3",
|
||||
"cypress": "12.17.1",
|
||||
"cypress-file-upload": "^5.0.8",
|
||||
"cypress-plugin-tab": "^1.0.5",
|
||||
"cypress-real-events": "^1.7.6",
|
||||
"cypress-real-events": "^1.8.1",
|
||||
"es6-shim": "^0.35.5",
|
||||
"eslint": "^8.33.0",
|
||||
"git-describe": "^4.0.4",
|
||||
"jasmine-core": "~3.6.0",
|
||||
"karma": "~6.3.0",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
"karma-coverage": "~2.1.0",
|
||||
"karma-jasmine": "~4.0.0",
|
||||
"karma-jasmine-html-reporter": "~1.7.0",
|
||||
"jasmine-core": "~5.1.2",
|
||||
"karma": "~6.4.3",
|
||||
"karma-chrome-launcher": "~3.2.0",
|
||||
"karma-coverage": "~2.2.1",
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"license-checker": "25.0.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mochawesome": "^7.1.3",
|
||||
@ -120,9 +126,8 @@
|
||||
"rimraf": "3.0.2",
|
||||
"ts-loader": "^9.2.8",
|
||||
"ts-node": "^3.3.0",
|
||||
"typedoc": "^0.23.24",
|
||||
"typescript": "~4.9.4",
|
||||
"typescript": "~5.4.4",
|
||||
"wait-on": "^6.0.1",
|
||||
"watch": "^1.0.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
npm run cy:run -- --browser chrome --spec "cypress/integration/liveness.tests.ts"
|
||||
npm run cy:run -- --browser chrome --spec "cypress/integration/editor.tests.ts"
|
||||
npm run cy:run -- --browser chrome --spec "cypress/integration/excel.tests.ts"
|
||||
npm run cy:run -- --browser chrome --spec "cypress/integration/filtering.tests.ts"
|
||||
npm run cy:run -- --browser chrome --spec "cypress/integration/licensing.tests.ts"
|
@ -1,5 +1,8 @@
|
||||
import { QueryClause } from './models/TableData'
|
||||
|
||||
/**
|
||||
* Filtering cache info, to be reused when filtering modal is re-open
|
||||
*/
|
||||
interface FilterCache {
|
||||
cols: any[]
|
||||
vals: any[]
|
||||
@ -10,12 +13,18 @@ interface FilterCache {
|
||||
query: QueryClause[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Filtering cache info in the open viewboxes, to be reused when filtering modal is re-open
|
||||
*/
|
||||
interface ViewboxCache {
|
||||
[key: number]: {
|
||||
filter: FilterCache
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial values when no cached values stored
|
||||
*/
|
||||
export const initFilter: { filter: FilterCache } = {
|
||||
filter: {
|
||||
cols: <any[]>[],
|
||||
@ -28,8 +37,27 @@ export const initFilter: { filter: FilterCache } = {
|
||||
}
|
||||
}
|
||||
|
||||
export interface XLMapListItem {
|
||||
id: string
|
||||
description: string
|
||||
targetDS: string
|
||||
}
|
||||
|
||||
export interface HandsontableStaticConfig {
|
||||
darkTableHeaderClass: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Cached filtering values across whole app (editor, viewer, viewboxes)
|
||||
* Cached lineage libraries, tables
|
||||
* Cached metadata tree
|
||||
* Cached usernav tree
|
||||
* Cached viyaApi collections, search and selected endpoint
|
||||
*/
|
||||
export const globals: {
|
||||
rootParam: string
|
||||
dcLib: string
|
||||
xlmaps: XLMapListItem[]
|
||||
editor: any
|
||||
viewer: any
|
||||
viewboxes: ViewboxCache
|
||||
@ -38,14 +66,17 @@ export const globals: {
|
||||
viyaApi: any
|
||||
usernav: any
|
||||
operators: any
|
||||
handsontable: HandsontableStaticConfig
|
||||
[key: string]: any
|
||||
} = {
|
||||
rootParam: <string>'',
|
||||
dcLib: '',
|
||||
xlmaps: [],
|
||||
editor: {
|
||||
startupSet: <boolean>false,
|
||||
treeNodeLibraries: <any[] | null>[],
|
||||
libsAndTables: <any[]>[],
|
||||
libraries: <String[] | undefined>[],
|
||||
libraries: <string[] | undefined>[],
|
||||
library: <string>'',
|
||||
table: <string>'',
|
||||
filter: <FilterCache>{
|
||||
@ -114,5 +145,11 @@ export const globals: {
|
||||
operators: {
|
||||
numOperators: ['=', '<', '>', '<=', '>=', 'BETWEEN', 'IN', 'NOT IN', 'NE'],
|
||||
charOperators: ['=', '<', '>', '<=', '>=', 'CONTAINS', 'IN', 'NOT IN', 'NE']
|
||||
},
|
||||
handsontable: {
|
||||
darkTableHeaderClass: 'darkTH'
|
||||
},
|
||||
userDropdownConfig: {
|
||||
closeOnDebugClick: false
|
||||
}
|
||||
}
|
||||
|
@ -1,40 +0,0 @@
|
||||
<div class="content-area">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex flex-column justify-content-center">
|
||||
<h3 class="text-center">
|
||||
You succesfully edited table
|
||||
<span class="color-blue font-weight-700">{{ libds }}</span>
|
||||
</h3>
|
||||
<p class="text-center">
|
||||
<b>Please choose from the following actions</b>
|
||||
</p>
|
||||
<div class="row d-flex justify-content-center mt-20">
|
||||
<button
|
||||
class="btn btn-sm btn-outline text-center"
|
||||
(click)="submittedTableScreen()"
|
||||
>
|
||||
Go to submitted table screen
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-outline text-center"
|
||||
(click)="viewerTableScreen()"
|
||||
>
|
||||
Go to base table screen
|
||||
</button>
|
||||
<button
|
||||
id="approvalBtn"
|
||||
class="btn btn-sm btn-success-outline text-center"
|
||||
(click)="approveTableScreen()"
|
||||
>
|
||||
Go to approvals screen
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-info-outline text-center"
|
||||
(click)="goBack()"
|
||||
>
|
||||
Go back to editor
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,50 +0,0 @@
|
||||
import { AfterViewInit, Component, OnInit } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
|
||||
@Component({
|
||||
selector: 'app-actions',
|
||||
templateUrl: './actions.component.html',
|
||||
styleUrls: ['./actions.component.scss'],
|
||||
host: {
|
||||
class: 'content-container'
|
||||
}
|
||||
})
|
||||
export class ActionsComponent implements OnInit, AfterViewInit {
|
||||
public dsid: any
|
||||
public libds: string | undefined
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
public submittedTableScreen() {
|
||||
this.router.navigateByUrl('/stage/' + this.dsid)
|
||||
}
|
||||
|
||||
public approveTableScreen() {
|
||||
this.router.navigateByUrl('/approve/approveDet/' + this.dsid)
|
||||
}
|
||||
|
||||
public viewerTableScreen() {
|
||||
this.router.navigateByUrl('/view/data/' + this.libds)
|
||||
}
|
||||
|
||||
public goBack() {
|
||||
this.router.navigateByUrl('/editor/' + this.libds)
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.dsid = this.route.snapshot.params['dsid']
|
||||
this.libds = this.route.snapshot.params['libds']
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
let approvalBtn: any = window.document.getElementById('approvalBtn')
|
||||
if (!!approvalBtn) {
|
||||
approvalBtn.focus()
|
||||
}
|
||||
}, 700)
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@
|
||||
<div class="alert-items">
|
||||
<div class="alert-item static">
|
||||
<div class="alert-icon-wrapper">
|
||||
<clr-icon class="mt-2" shape="warning-standard"></clr-icon>
|
||||
<cds-icon class="alert-icon" shape="warning-standard"></cds-icon>
|
||||
</div>
|
||||
<div class="alert-text">
|
||||
Data Controller (FREE Tier) - to upgrade contact
|
||||
@ -30,7 +30,7 @@
|
||||
<div class="alert-items">
|
||||
<div class="alert-item static">
|
||||
<div class="alert-icon-wrapper">
|
||||
<clr-icon class="mt-2" shape="warning-standard"></clr-icon>
|
||||
<cds-icon class="alert-icon" shape="warning-standard"></cds-icon>
|
||||
</div>
|
||||
<div class="alert-text">
|
||||
Data Controller (FREE Tier) - Problem with licence
|
||||
@ -55,7 +55,7 @@
|
||||
<div class="alert-items">
|
||||
<div class="alert-item static">
|
||||
<div class="alert-icon-wrapper">
|
||||
<clr-icon class="mt-2" shape="warning-standard"></clr-icon>
|
||||
<cds-icon class="alert-icon" shape="warning-standard"></cds-icon>
|
||||
</div>
|
||||
|
||||
<div class="alert-text">
|
||||
@ -85,7 +85,7 @@
|
||||
<div class="alert-items">
|
||||
<div class="alert-item static">
|
||||
<div class="alert-icon-wrapper">
|
||||
<clr-icon class="mt-2" shape="warning-standard"></clr-icon>
|
||||
<cds-icon class="alert-icon" shape="warning-standard"></cds-icon>
|
||||
</div>
|
||||
|
||||
<div class="alert-text">
|
||||
@ -139,10 +139,15 @@
|
||||
[routerLink]="['/']"
|
||||
class="nav-link"
|
||||
>
|
||||
<img class="without-text d-block d-md-none" src="images/dc-logo.svg" />
|
||||
<img
|
||||
class="without-text d-block d-md-none"
|
||||
src="images/dc-logo.svg"
|
||||
alt="datacontroller logo without text"
|
||||
/>
|
||||
<img
|
||||
class="with-text d-none d-md-block"
|
||||
src="images/datacontroller.svg"
|
||||
alt="datacontroller logo"
|
||||
/>
|
||||
</a>
|
||||
|
||||
@ -168,8 +173,8 @@
|
||||
</button>
|
||||
<clr-dropdown-menu *clrIfOpen clrPosition="bottom-left">
|
||||
<a [routerLink]="['/view']" clrDropdownItem>VIEW</a>
|
||||
<a [routerLink]="['/home']" clrDropdownItem>EDIT</a>
|
||||
<a [routerLink]="['/submitted']" clrDropdownItem>REVIEW</a>
|
||||
<a [routerLink]="['/home']" clrDropdownItem>LOAD</a>
|
||||
<a [routerLink]="['/review/submitted']" clrDropdownItem>REVIEW</a>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
</div>
|
||||
@ -189,10 +194,10 @@
|
||||
router.url.includes('edit-record') ||
|
||||
router.url.includes('home')
|
||||
"
|
||||
>EDIT</a
|
||||
>LOAD</a
|
||||
>
|
||||
<a
|
||||
[routerLink]="['/submitted']"
|
||||
[routerLink]="['/review/submitted']"
|
||||
[class.active]="
|
||||
router.url.includes('submitted') ||
|
||||
router.url.includes('approve') ||
|
||||
@ -204,14 +209,7 @@
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="header-actions">
|
||||
<div class="nav-text">
|
||||
<app-loading-indicator></app-loading-indicator>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
<app-user-nav-dropdown></app-user-nav-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<app-header-actions></app-header-actions>
|
||||
</header>
|
||||
<nav
|
||||
*ngIf="
|
||||
@ -224,7 +222,7 @@
|
||||
<ul class="nav">
|
||||
<li class="nav-item">
|
||||
<a
|
||||
[routerLink]="['/submitted']"
|
||||
[routerLink]="['/review/submitted']"
|
||||
class="nav-link nav-text"
|
||||
routerLinkActive="active"
|
||||
>SUBMIT</a
|
||||
@ -232,15 +230,16 @@
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a
|
||||
[routerLink]="['/approve']"
|
||||
[routerLink]="['/review/approve']"
|
||||
class="nav-link nav-text"
|
||||
[class.active]="router.url.includes('approve')"
|
||||
routerLinkActive="active"
|
||||
>APPROVE</a
|
||||
>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a
|
||||
[routerLink]="['/history']"
|
||||
[routerLink]="['/review/history']"
|
||||
class="nav-link nav-text"
|
||||
routerLinkActive="active"
|
||||
>HISTORY</a
|
||||
@ -251,6 +250,7 @@
|
||||
|
||||
<app-alerts *ngIf="!errTop"></app-alerts>
|
||||
<app-requests-modal [(opened)]="requestsModal"></app-requests-modal>
|
||||
<app-excel-password-modal></app-excel-password-modal>
|
||||
|
||||
<!-- <app-terms *ngIf="showRegistration"></app-terms> -->
|
||||
|
||||
@ -288,7 +288,11 @@
|
||||
|
||||
<!-- App Loading Page -->
|
||||
<div *ngIf="!startupDataLoaded" class="app-loading">
|
||||
<img class="loading-logo" src="images/datacontroller.svg" />
|
||||
<img
|
||||
class="loading-logo"
|
||||
src="images/datacontroller.svg"
|
||||
alt="datacontroller logo"
|
||||
/>
|
||||
|
||||
<div *ngIf="appActive === null" class="slider">
|
||||
<div class="line"></div>
|
||||
|
@ -1,461 +0,0 @@
|
||||
// Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
// This software is released under MIT license.
|
||||
// The full license information can be found in LICENSE in the root directory of this project.
|
||||
app-requests-modal {
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
header.app-header {
|
||||
background: #314351 !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.logo img.without-text {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.logo img.with-text {
|
||||
width: 210px;
|
||||
}
|
||||
|
||||
.header-hamburger-trigger {
|
||||
display: block;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.demo-expired-notice {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100vh !important;
|
||||
width: 100vw !important;
|
||||
z-index: 105;
|
||||
background: rgba(33, 33, 33, .5);
|
||||
|
||||
.expired-details {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 30px;
|
||||
z-index: 110;
|
||||
background: #314351;
|
||||
|
||||
.expired-notice {
|
||||
color: #e0e0e0;
|
||||
font-size: 16px;
|
||||
|
||||
.mailto {
|
||||
color: #8dc53e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main-container .update-key {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: white;
|
||||
padding: 0px 10px;
|
||||
background: #00000026;
|
||||
}
|
||||
|
||||
.alert-icon-wrapper {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.nav-text {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.sidebar-toggle {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
padding-left: 10px;
|
||||
|
||||
clr-icon {
|
||||
cursor: pointer;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
.header-actions {
|
||||
.dropdown {
|
||||
position: unset; //without it, when opening user dropdown scrollbar was displaying without reason
|
||||
}
|
||||
}
|
||||
|
||||
.nav
|
||||
.nav-link {
|
||||
color: #fafafa;
|
||||
opacity: .9;
|
||||
line-height: 1.45rem;
|
||||
}
|
||||
|
||||
.nav .nav-link:hover {
|
||||
box-shadow: inset 0 -3px 0 transparent;
|
||||
transition: box-shadow .2s ease-in;
|
||||
}
|
||||
|
||||
.nav
|
||||
.nav-link:hover {
|
||||
color: #fafafa;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.nav .nav-link.active {
|
||||
background: #61717D;
|
||||
opacity: 1;
|
||||
box-shadow: inset 0 -3px transparent;
|
||||
// padding: 0 1rem 0 1rem;
|
||||
}
|
||||
|
||||
.nav .nav-item {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.notf {
|
||||
background: #16a57a;
|
||||
color: #fffcfc;
|
||||
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 {
|
||||
border-color: #61717D;
|
||||
background-color: #61717D;
|
||||
transition: .15s ease-in;
|
||||
transition-property: border-color,background-color;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
min-height: 100vh !important;
|
||||
}
|
||||
|
||||
.main-container .content-container .content-area {
|
||||
padding: 0rem 1rem 1rem 1rem;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
z-index: 0!important;
|
||||
}
|
||||
|
||||
.navBarResp {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background: #495A67;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@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 {
|
||||
.htInvalid {
|
||||
background: black!important;
|
||||
}
|
||||
|
||||
@media screen and (max-width:480px) {
|
||||
h2 {
|
||||
font-size: .7rem!important;
|
||||
|
||||
}
|
||||
h3 {
|
||||
font-size: .7rem;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
padding: 0rem 1rem 0rem 1rem;
|
||||
}
|
||||
|
||||
.btn-primary .btn, .btn.btn-primary {
|
||||
border-color: #314351;
|
||||
background-color: #314351;
|
||||
color: #fff;
|
||||
}
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
-webkit-appearance: none!important;
|
||||
border-radius: .125rem;
|
||||
border: 1px solid;
|
||||
min-width: 3rem;
|
||||
max-width: 15rem;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
vertical-align: middle;
|
||||
line-height: 1.5rem;
|
||||
letter-spacing: .12em;
|
||||
font-size: .5rem;
|
||||
font-weight: 500;
|
||||
height: 1.5rem;
|
||||
padding: 0 .5rem;
|
||||
border-color: #314351;
|
||||
background-color: transparent;
|
||||
color: #314351;
|
||||
}
|
||||
|
||||
.btn.btn-outline {
|
||||
border-color: #314351;
|
||||
background-color: transparent;
|
||||
color: #314351;
|
||||
}
|
||||
|
||||
.btn.btn-outline:hover {
|
||||
border-color: #314351;
|
||||
background-color: #495A67;
|
||||
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 {
|
||||
|
||||
// }
|
||||
|
||||
.htMobileEditorContainer .inputs textarea {
|
||||
font-size: 13pt;
|
||||
border: 2px solid #485967;
|
||||
border-radius: 4px;
|
||||
-webkit-appearance: none;
|
||||
box-shadow: none;
|
||||
position: absolute;
|
||||
left: 14px;
|
||||
right: 0px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
padding: 7pt;
|
||||
width: 290px;
|
||||
}
|
||||
|
||||
.htMobileEditorContainer .positionControls {
|
||||
width: 333px;
|
||||
position: absolute;
|
||||
right: 5pt;
|
||||
top: 50px;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.htMobileEditorContainer.active {
|
||||
display: block;
|
||||
height: 120px;
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
.handsontable {
|
||||
background-color: #ffffff;
|
||||
// border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.handsontable th {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
/* Left and right */
|
||||
.ht_clone_left th {
|
||||
border-right: 1px solid #ccc;
|
||||
border-left: 1px solid #ccc;
|
||||
}
|
||||
|
||||
/* Column headers */
|
||||
.ht_clone_top th {
|
||||
border-top: 1px solid #ccc;
|
||||
border-right: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.ht_clone_top_left_corner th {
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.ht_master tr:nth-of-type(odd) > td {
|
||||
background-color: #f3f3f3;
|
||||
border: 1px solid rgb(197, 197, 197);
|
||||
border-bottom: 1px solid rgb(236, 235, 235);
|
||||
// padding: 1px 1px;
|
||||
}
|
||||
|
||||
.ht_master tr:nth-of-type(even) > td {
|
||||
background-color: white;
|
||||
border: 1px solid rgb(197, 197, 197);
|
||||
border-bottom: 1px solid rgb(236, 235, 235);
|
||||
// padding: 1px 1px;
|
||||
}
|
||||
|
||||
.wtBorder {
|
||||
background-color: #495A67!important;
|
||||
}
|
||||
|
||||
.handsontable .handsontable.ht_clone_top .wtHider {
|
||||
padding: 0 0 0px 0!important;
|
||||
margin: 0px;
|
||||
border-bottom: 3px solid #d6d3d3;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
background: #F5F6FF;
|
||||
}
|
||||
|
||||
.card {
|
||||
box-shadow: 0 0.125rem 0 0 #d7d7d7;
|
||||
border-radius: .0rem;
|
||||
border: 1px solid transparent;
|
||||
// min-height: calc(100vh - 150px);
|
||||
}
|
||||
|
||||
.datagrid-compact, .datagrid-history{
|
||||
.datagrid {
|
||||
border-collapse: separate;
|
||||
border: 1px solid transparent;
|
||||
border-radius: .125rem;
|
||||
background-color: #fff;
|
||||
color: #565656;
|
||||
margin: 0;
|
||||
margin-top: 1rem;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
padding: 15px 15px 50px 15px;
|
||||
}
|
||||
.datagrid-foot {
|
||||
-webkit-box-pack: end;
|
||||
-ms-flex-pack: end;
|
||||
justify-content: flex-end;
|
||||
height: 1.5rem;
|
||||
padding: 0 .5rem;
|
||||
line-height: calc(1.5rem - 3px);
|
||||
font-size: .45833rem;
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #ccc;
|
||||
border-radius: 0px;
|
||||
// border-radius: 0 0 .125rem .125rem;
|
||||
}
|
||||
.datagrid-footer {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 2px;
|
||||
}
|
||||
.datagrid .datagrid-head {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
margin-top: .083333rem;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
background: #f5f6ff;
|
||||
padding: .5rem 0;
|
||||
border: 1px solid #ccc;
|
||||
box-shadow: 0 1px 0.125rem hsla(0,0%,45%,.25);
|
||||
min-width: 5rem;
|
||||
max-width: 15rem;
|
||||
border-radius: .125rem;
|
||||
visibility: hidden;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.table {
|
||||
border-collapse: separate;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 0px;
|
||||
background-color: #fff;
|
||||
color: #565656;
|
||||
margin: 0;
|
||||
margin-top: 1rem;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.table th {
|
||||
font-size: .45833rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: .03em;
|
||||
background-color: #fff;
|
||||
vertical-align: bottom;
|
||||
border-bottom: 1px solid #ccc;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
border-bottom: 2px solid #e4e4e4;
|
||||
padding: 0 0 .5rem 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.main-container .content-container {
|
||||
min-height: 0px;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.app-loading {
|
||||
.loading-logo {
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
@ -1,4 +1,9 @@
|
||||
import { ChangeDetectorRef, Component, ElementRef } from '@angular/core'
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core'
|
||||
import { Router } from '@angular/router'
|
||||
import { VERSION } from '../environments/version'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
@ -13,11 +18,31 @@ import { InfoModal } from './models/InfoModal'
|
||||
import { DcAdapterSettings } from './models/DcAdapterSettings'
|
||||
import { AppStoreService } from './services/app-store.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({
|
||||
selector: 'my-app',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss']
|
||||
styleUrls: ['./app.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class AppComponent {
|
||||
private dcAdapterSettings: DcAdapterSettings | undefined
|
||||
@ -57,24 +82,15 @@ export class AppComponent {
|
||||
private elementRef: ElementRef
|
||||
) {
|
||||
this.parseDcAdapterSettings()
|
||||
|
||||
/**
|
||||
* Prints app info in the console such as:
|
||||
* - Adapter versions
|
||||
* - App version
|
||||
* - Build timestamp
|
||||
*
|
||||
*/
|
||||
;(window as any).appinfo = () => {
|
||||
const licenseKeyData = this.licenceService.getLicenseKeyData()
|
||||
|
||||
if (licenseKeyData) {
|
||||
const expiry_date = moment(
|
||||
licenseKeyData.valid_until,
|
||||
'YYYY-MM-DD'
|
||||
).startOf('day')
|
||||
const current_date = moment().startOf('day')
|
||||
const daysToExpiry = expiry_date.diff(current_date, 'days')
|
||||
|
||||
licenseKeyData.valid_until += ` (${daysToExpiry} ${
|
||||
daysToExpiry === 1 ? 'day' : 'days'
|
||||
} remaining)`
|
||||
|
||||
if (isNaN(daysToExpiry)) licenseKeyData.valid_until = 'Unlimited'
|
||||
}
|
||||
|
||||
console.table({
|
||||
'Adapter version': VERSION.adapterVersion || 'n/a',
|
||||
'App version': (VERSION.tag || '').replace('v', ''),
|
||||
@ -87,7 +103,12 @@ export class AppComponent {
|
||||
|
||||
this.subscribeToLicenseEvents()
|
||||
|
||||
/**
|
||||
* Fetches git tag ang git hash from `version.ts` file
|
||||
* It's placed in the user drop down.
|
||||
*/
|
||||
this.commitVer = (VERSION.tag || '').replace('v', '') + '.' + VERSION.hash
|
||||
|
||||
router.events.subscribe((val) => {
|
||||
this.routeUrl = this.router.url
|
||||
|
||||
@ -127,8 +148,10 @@ export class AppComponent {
|
||||
this.subscribeToAppActive()
|
||||
this.subscribeToDemoLimitModal()
|
||||
|
||||
/* In Viya streaming apps, content is served within an iframe. This code
|
||||
makes that iframe "full screen" so it looks like a regular window. */
|
||||
/**
|
||||
* In Viya streaming apps, content is served within an iframe. This code
|
||||
* makes that iframe "full screen" so it looks like a regular window.
|
||||
*/
|
||||
if (window.frameElement) {
|
||||
window.frameElement.setAttribute(
|
||||
'style',
|
||||
@ -143,6 +166,9 @@ export class AppComponent {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses adapter settings that are found in the <sasjs> tag inside index.html
|
||||
*/
|
||||
private parseDcAdapterSettings() {
|
||||
const sasjsElement = document.querySelector('sasjs')
|
||||
|
||||
@ -180,9 +206,14 @@ export class AppComponent {
|
||||
this.appService.sasServiceInit()
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens licence page with the active licence problem
|
||||
* Problem details are encoded in the url
|
||||
*/
|
||||
public licenceProblemDetails(url: string) {
|
||||
this.router.navigateByUrl(url)
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on string provided we return true, false or null
|
||||
* True -> Compute API
|
||||
@ -199,6 +230,12 @@ export class AppComponent {
|
||||
return value === 'true' || false
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for an `demo limit` event that will show the `Feature locked modal`
|
||||
* For exmaple when in editor upload feature is not enabled
|
||||
* When user tries to upload the excel, editor component will trgger this event
|
||||
* And stop the execution of file upload code.
|
||||
*/
|
||||
public subscribeToDemoLimitModal() {
|
||||
this.eventService.onDemoLimitModalShow.subscribe((featureName: string) => {
|
||||
this.demoLimitNotice = {
|
||||
@ -208,6 +245,10 @@ export class AppComponent {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for licence events so banner can be displayed.
|
||||
* App is free tier, licence will expire, is expired or is invalid
|
||||
*/
|
||||
public subscribeToLicenseEvents() {
|
||||
this.licenceService.isAppFreeTier.subscribe((isAppFreeTier: boolean) => {
|
||||
this.freeTierBanner = isAppFreeTier
|
||||
@ -227,6 +268,10 @@ export class AppComponent {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for an event that will activate od deactivate full application.
|
||||
* Based on licence key prcoessing result
|
||||
*/
|
||||
public subscribeToAppActive() {
|
||||
this.licenceService.isAppActivated.subscribe((value: any) => {
|
||||
this.appActive = value
|
||||
@ -248,31 +293,51 @@ export class AppComponent {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* When startupservice request is finished with valid response, this event will
|
||||
* make sure loading screen is gone.
|
||||
*/
|
||||
public subscribeToStartupData() {
|
||||
this.eventService.onStartupDataLoaded.subscribe(() => {
|
||||
this.startupDataLoaded = true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens requests modal when requested from event service
|
||||
*/
|
||||
public subscribeToRequestsModal() {
|
||||
this.eventService.onRequestsModalOpen.subscribe((value: boolean) => {
|
||||
this.requestsModal = true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes abort modal with matching ID (there could be multiple abort modals open)
|
||||
*/
|
||||
public closeAbortModal(abortId: number) {
|
||||
let abortIndex = this.sasjsAborts.findIndex((abort) => abort.id === abortId)
|
||||
this.sasjsAborts.splice(abortIndex, 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles sidebar when requested from event service
|
||||
*/
|
||||
public toggleSidebar() {
|
||||
this.eventService.toggleSidebar()
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not current route includes the route from param
|
||||
* @param route route to check
|
||||
*/
|
||||
public isMainRoute(route: string): boolean {
|
||||
return this.router.url.includes(route)
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a page for updating the licence.
|
||||
*/
|
||||
public openLicencingPage() {
|
||||
this.router.navigateByUrl('/licensing/update')
|
||||
}
|
||||
|
2
client/src/app/app.d.ts
vendored
2
client/src/app/app.d.ts
vendored
@ -1,5 +1,5 @@
|
||||
declare module 'save-svg-as-png'
|
||||
|
||||
declare module 'numbro/dist/languages.min'
|
||||
declare interface Navigator {
|
||||
msSaveBlob: (blob: any, defaultName?: string) => boolean
|
||||
}
|
||||
|
@ -11,62 +11,29 @@ import { NotFoundComponent } from './not-found/not-found.component'
|
||||
|
||||
import { SasStoreService } from './services/sas-store.service'
|
||||
import { SharedModule } from './shared/shared.module'
|
||||
// import { EditorComponent } from './editor/editor.component'
|
||||
import { ActionsComponent } from './actions/actions.component'
|
||||
|
||||
import { AppSharedModule } from './app-shared.module'
|
||||
import { ApproveDetailsComponent } from './approve-details/approve-details.component'
|
||||
import { ApproveComponent } from './approve/approve.component'
|
||||
import { DeployComponent } from './deploy/deploy.component'
|
||||
import { AutomaticComponent } from './deploy/sections/automatic/automatic.component'
|
||||
import { ManualComponent } from './deploy/sections/manual/manual.component'
|
||||
import { SasjsConfiguratorComponent } from './deploy/sections/sasjs-configurator/sasjs-configurator.component'
|
||||
import { GroupComponent } from './group/group.component'
|
||||
import { HistoryComponent } from './history/history.component'
|
||||
import { LicensingComponent } from './licensing/licensing.component'
|
||||
import { LineageComponent } from './lineage/lineage.component'
|
||||
import { MetadataComponent } from './metadata/metadata.component'
|
||||
import { PipesModule } from './pipes/pipes.module'
|
||||
import { RoleComponent } from './role/role.component'
|
||||
import { ApproveRouteComponent } from './routes/approve-route/approve-route.component'
|
||||
import { HistoryRouteComponent } from './routes/history-route/history-route.component'
|
||||
import { ReviewRouteComponent } from './routes/review-route/review-route.component'
|
||||
import { LicensingGuard } from './routes/licensing.guard'
|
||||
import { UsernavRouteComponent } from './routes/usernav-route/usernav-route.component'
|
||||
import { AppService } from './services/app.service'
|
||||
import { InfoModalComponent } from './shared/abort-modal/info-modal.component'
|
||||
import { RequestsModalComponent } from './shared/requests-modal/requests-modal.component'
|
||||
import { SubmitterComponent } from './submitter/submitter.component'
|
||||
import { UserComponent } from './user/user.component'
|
||||
import { HomeModule } from './home/home.module'
|
||||
import { SystemComponent } from './system/system.component'
|
||||
import { DirectivesModule } from './directives/directives.module'
|
||||
import { ViyaApiExplorerComponent } from './viya-api-explorer/viya-api-explorer.component'
|
||||
import { NgxJsonViewerModule } from 'ngx-json-viewer'
|
||||
import { AppSettingsService } from './services/app-settings.service'
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
NotFoundComponent,
|
||||
ApproveComponent,
|
||||
ApproveDetailsComponent,
|
||||
ActionsComponent,
|
||||
HistoryComponent,
|
||||
LineageComponent,
|
||||
SubmitterComponent,
|
||||
ApproveRouteComponent,
|
||||
HistoryRouteComponent,
|
||||
MetadataComponent,
|
||||
ReviewRouteComponent,
|
||||
ReviewRouteComponent,
|
||||
UsernavRouteComponent,
|
||||
UserComponent,
|
||||
GroupComponent,
|
||||
RoleComponent,
|
||||
RequestsModalComponent,
|
||||
DeployComponent,
|
||||
InfoModalComponent,
|
||||
LicensingComponent,
|
||||
ManualComponent,
|
||||
AutomaticComponent,
|
||||
SasjsConfiguratorComponent,
|
||||
SystemComponent,
|
||||
ViyaApiExplorerComponent
|
||||
],
|
||||
imports: [
|
||||
@ -79,12 +46,11 @@ import { NgxJsonViewerModule } from 'ngx-json-viewer'
|
||||
SharedModule,
|
||||
ClarityModule,
|
||||
AppSharedModule,
|
||||
HomeModule,
|
||||
PipesModule,
|
||||
DirectivesModule,
|
||||
NgxJsonViewerModule
|
||||
],
|
||||
providers: [AppService, SasStoreService, ApproveComponent, LicensingGuard],
|
||||
providers: [AppService, SasStoreService, LicensingGuard, AppSettingsService],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {}
|
||||
|
@ -4,25 +4,23 @@
|
||||
* The full license information can be found in LICENSE in the root directory of this project.
|
||||
*/
|
||||
import { ModuleWithProviders } from '@angular/core'
|
||||
import { Routes, RouterModule } from '@angular/router'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
|
||||
import { HomeComponent } from './home/home.component'
|
||||
import { ApproveComponent } from './approve/approve.component'
|
||||
import { ApproveDetailsComponent } from './approve-details/approve-details.component'
|
||||
import { ActionsComponent } from './actions/actions.component'
|
||||
import { HistoryComponent } from './history/history.component'
|
||||
import { NotFoundComponent } from './not-found/not-found.component'
|
||||
import { SubmitterComponent } from './submitter/submitter.component'
|
||||
|
||||
import { ApproveRouteComponent } from './routes/approve-route/approve-route.component'
|
||||
import { DeployComponent } from './deploy/deploy.component'
|
||||
import { LicensingComponent } from './licensing/licensing.component'
|
||||
import { LicensingGuard } from './routes/licensing.guard'
|
||||
import { StageModule } from './stage/stage.module'
|
||||
import { DeployModule } from './deploy/deploy.module'
|
||||
import { EditorModule } from './editor/editor.module'
|
||||
import { HomeModule } from './home/home.module'
|
||||
import { LicensingModule } from './licensing/licensing.module'
|
||||
import { ReviewModule } from './review/review.module'
|
||||
import { ReviewRouteComponent } from './routes/review-route/review-route.component'
|
||||
import { StageModule } from './stage/stage.module'
|
||||
import { SystemModule } from './system/system.module'
|
||||
import { ViewerModule } from './viewer/viewer.module'
|
||||
import { SystemComponent } from './system/system.component'
|
||||
|
||||
/**
|
||||
* Defining routes
|
||||
*/
|
||||
export const ROUTES: Routes = [
|
||||
{ path: '', redirectTo: 'home', pathMatch: 'full' },
|
||||
{
|
||||
@ -30,23 +28,31 @@ export const ROUTES: Routes = [
|
||||
loadChildren: () => ViewerModule
|
||||
},
|
||||
{
|
||||
path: 'approve',
|
||||
component: ApproveRouteComponent,
|
||||
/**
|
||||
* Load review module (approve, history, submitted)
|
||||
*/
|
||||
path: 'review',
|
||||
component: ReviewRouteComponent,
|
||||
children: [
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'toapprove' },
|
||||
{ path: 'toapprove', component: ApproveComponent },
|
||||
{ path: 'approveDet/:tableId', component: ApproveDetailsComponent },
|
||||
{ path: 'submitted', component: SubmitterComponent }
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () => ReviewModule
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'licensing/:action',
|
||||
component: LicensingComponent,
|
||||
canActivate: [LicensingGuard],
|
||||
canDeactivate: [LicensingGuard]
|
||||
path: 'licensing',
|
||||
loadChildren: () => LicensingModule
|
||||
},
|
||||
{ path: 'home', component: HomeComponent },
|
||||
{
|
||||
path: 'home',
|
||||
loadChildren: () => HomeModule
|
||||
},
|
||||
{
|
||||
/**
|
||||
* Load editor module with subroutes
|
||||
*/
|
||||
path: 'editor',
|
||||
loadChildren: () => EditorModule
|
||||
},
|
||||
@ -54,16 +60,20 @@ export const ROUTES: Routes = [
|
||||
path: 'stage',
|
||||
loadChildren: () => StageModule
|
||||
},
|
||||
{ path: 'system', component: SystemComponent },
|
||||
{ path: 'actions/:libds/:dsid', component: ActionsComponent },
|
||||
{ path: 'history', component: HistoryComponent },
|
||||
{ path: 'submitted', component: SubmitterComponent },
|
||||
{ path: 'submitted/:tableId', component: SubmitterComponent },
|
||||
{ path: 'deploy', component: DeployComponent },
|
||||
{ path: 'deploy/manualdeploy', component: DeployComponent },
|
||||
{
|
||||
path: 'system',
|
||||
loadChildren: () => SystemModule
|
||||
},
|
||||
{
|
||||
path: 'deploy',
|
||||
loadChildren: () => DeployModule
|
||||
},
|
||||
{ path: '**', component: NotFoundComponent }
|
||||
]
|
||||
|
||||
/**
|
||||
* Exporting routes
|
||||
*/
|
||||
export const ROUTING: ModuleWithProviders<RouterModule> = RouterModule.forRoot(
|
||||
ROUTES,
|
||||
{ useHash: true }
|
||||
|
@ -1,179 +0,0 @@
|
||||
.loader {
|
||||
display:flex;
|
||||
justify-content: center;
|
||||
height:75vh;
|
||||
align-items:center;
|
||||
flex-direction:column
|
||||
}
|
||||
.modalLarge {
|
||||
width: 50rem!important;
|
||||
}
|
||||
|
||||
.addedRow {
|
||||
background: rgb(146, 208, 154);
|
||||
border: 1px solid rgba(9, 77, 117, 0.2);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.deletedRow {
|
||||
background: rgb(230, 179, 179);
|
||||
border: 1px solid rgba(70, 71, 70, 0.2);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.updatedRow {
|
||||
background: #fafda8;
|
||||
border: 1px solid rgba(9, 117, 9, 0.2);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.table {
|
||||
border: 0px solid;
|
||||
}
|
||||
|
||||
.ch {
|
||||
background: rgba(0,0,0,.1);
|
||||
border: 1px solid rgba(104, 100, 0, 0.4);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.ch:hover {
|
||||
background: rgba(252, 135, 120, 0.4);
|
||||
}
|
||||
|
||||
.tooltip .tooltip-content.tooltip-top-right, .tooltip.tooltip-top-right>.tooltip-content, .tooltip>.tooltip-content {
|
||||
font-size: .54167rem;
|
||||
font-weight: 400;
|
||||
letter-spacing: normal;
|
||||
background: #314351;
|
||||
border-radius: .125rem;
|
||||
color: #f0f1ec;;
|
||||
line-height: .75rem;
|
||||
margin: 0;
|
||||
padding: .375rem .5rem;
|
||||
width: 235px;
|
||||
position: absolute;
|
||||
top: auto;
|
||||
bottom: 100%;
|
||||
left: 12px;
|
||||
right: auto;
|
||||
border-bottom-left-radius: 0;
|
||||
margin-bottom: .66667rem;
|
||||
}
|
||||
|
||||
.tooltip .tooltip-content.tooltip-top-right:before, .tooltip.tooltip-top-right>.tooltip-content:before, .tooltip>.tooltip-content:before {
|
||||
position: absolute;
|
||||
bottom: -.375rem;
|
||||
left: 0;
|
||||
top: auto;
|
||||
right: auto;
|
||||
content: "";
|
||||
border-left: .25rem solid #314351;
|
||||
border-top: .20833rem solid #314351;
|
||||
border-right: .25rem solid transparent;
|
||||
border-bottom: .20833rem solid transparent;
|
||||
}
|
||||
|
||||
.table {
|
||||
border: 0px solid;
|
||||
}
|
||||
|
||||
.toggle-switch input[type=checkbox]:checked+label:before {
|
||||
border-color: #314351;
|
||||
background-color: #314351!important;
|
||||
transition: .15s ease-in;
|
||||
transition-property: border-color,background-color;
|
||||
}
|
||||
|
||||
.tableCont {
|
||||
overflow:auto;
|
||||
margin: 15px 10px 10px 10px;
|
||||
|
||||
td {
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.approvalInfo {
|
||||
display: flex;
|
||||
justify-content: flex-end
|
||||
}
|
||||
|
||||
.approvalBack {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
@media screen and (max-width:768px) {
|
||||
.approvalInfo {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 15px;
|
||||
|
||||
}
|
||||
|
||||
.approvalBack {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin-top:0rem!important;
|
||||
min-height: calc(100vh - 0px)!important;
|
||||
}
|
||||
|
||||
.table td.left, .table th.left {
|
||||
text-align: left;
|
||||
width: 150px!important;
|
||||
flex: 0
|
||||
}
|
||||
}
|
||||
|
||||
.table td.left, .table th.left {
|
||||
text-align: left;
|
||||
flex: 1;
|
||||
width: 300px!important;
|
||||
}
|
||||
|
||||
|
||||
.tooll {
|
||||
position: absolute;
|
||||
background: #e6b3b3;
|
||||
color: #314351;
|
||||
top: 0px;
|
||||
height: 36px;
|
||||
width: 100%;
|
||||
left: 0px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#acceptBtn, #rejectBtn {
|
||||
width: 175px
|
||||
}
|
||||
|
||||
.formatted-values-toggle {
|
||||
min-width: 75px
|
||||
}
|
||||
|
||||
clr-modal {
|
||||
::ng-deep {
|
||||
.modal-body-wrapper {
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rows-notice {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 10px;
|
||||
color: #6a6a6a;
|
||||
font-size: 15px;
|
||||
|
||||
clr-icon {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
.column-center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.datagrid .datagrid-column .datagrid-column-title{
|
||||
outline: none!important;
|
||||
}
|
||||
|
||||
.links {
|
||||
font-weight: 700;cursor: pointer;
|
||||
}
|
||||
|
||||
.tooltip.tooltip-bottom-left>.tooltip-content, .tooltip .tooltip-content.tooltip-bottom-left {
|
||||
background: #314351!important;
|
||||
}
|
||||
.tooltip.tooltip-bottom-left>.tooltip-content:before, .tooltip .tooltip-content.tooltip-bottom-left:before {
|
||||
border-right: .25rem solid #314351;
|
||||
border-bottom: .20833rem solid #314351;
|
||||
}
|
||||
|
||||
.noBorder {
|
||||
border-bottom: 1px solid transparent!important;
|
||||
}
|
||||
|
||||
.approvals-list-wrapper {
|
||||
height: 70vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.noapprovals-info-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
height: calc(100vh - 200px);
|
||||
}
|
14
client/src/app/deploy/deploy-routing.module.ts
Normal file
14
client/src/app/deploy/deploy-routing.module.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { DeployComponent } from './deploy.component'
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', component: DeployComponent },
|
||||
{ path: 'manualdeploy', component: DeployComponent }
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class DeployRoutingModule {}
|
@ -5,7 +5,7 @@
|
||||
<div class="card-header">Terms and Conditions</div>
|
||||
<div class="card-block">
|
||||
<div class="card-text">
|
||||
<p>
|
||||
<p class="mt-0">
|
||||
The Demo version of Data Controller is free for EVALUATION purposes
|
||||
only. Before proceeding with configuration, please confirm that you
|
||||
have read, understood, and agreed to the
|
||||
|
@ -1,50 +0,0 @@
|
||||
.card {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.log-wrapper {
|
||||
width: 100%;
|
||||
background: #f0f0f0;
|
||||
border: 1px solid #c9c9c9;
|
||||
padding: 10px;
|
||||
overflow: auto;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
#contexts-btn {
|
||||
padding: 0;
|
||||
min-width: 30px;
|
||||
margin-left: 10px;
|
||||
height: 30px;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
.validation-bar {
|
||||
display: flex;
|
||||
margin-top: 20px;
|
||||
align-items: center;
|
||||
|
||||
clr-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.autodeploy-section {
|
||||
padding: 0px 15px;
|
||||
|
||||
.clr-checkbox-wrapper {
|
||||
margin: 20px 0 20px 0;
|
||||
}
|
||||
|
||||
.btn-autodeploy {
|
||||
display: block;
|
||||
margin: 15px 0 15px 0;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { Component, OnInit } from '@angular/core'
|
||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core'
|
||||
import { SasService } from '../services/sas.service'
|
||||
import { SASjsConfig } from '@sasjs/adapter'
|
||||
import { Router } from '@angular/router'
|
||||
@ -13,7 +13,8 @@ import { DcAdapterSettings } from '../models/DcAdapterSettings'
|
||||
styleUrls: ['./deploy.component.scss'],
|
||||
host: {
|
||||
class: 'content-container'
|
||||
}
|
||||
},
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class DeployComponent implements OnInit {
|
||||
public step: number = 0
|
||||
@ -56,28 +57,12 @@ export class DeployComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.sasJsConfig.serverType === ServerType.SasViya) {
|
||||
fetch('sasbuild/viya.json')
|
||||
.then((res) => res.text())
|
||||
.then((res) => {
|
||||
let initJsonFile: any = null
|
||||
|
||||
try {
|
||||
initJsonFile = JSON.parse(res)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
|
||||
if (initJsonFile) {
|
||||
this.jsonFile = initJsonFile
|
||||
this.loggerService.log(this.jsonFile)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.setDeployDefaults()
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting default values used for deploy request
|
||||
*/
|
||||
public setDeployDefaults() {
|
||||
this.dcPath = this.dcAdapterSettings?.dcPath || ''
|
||||
this.selectedAdminGroup = this.dcAdapterSettings?.adminGroup || ''
|
||||
@ -86,6 +71,9 @@ export class DeployComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepting terms of service shows next screen
|
||||
*/
|
||||
public termsAgreeChange() {
|
||||
if (!this.autodeploy) {
|
||||
this.getAdminGroups()
|
||||
@ -94,6 +82,9 @@ export class DeployComponent implements OnInit {
|
||||
this.step++
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches admin groups from VIYA to be selected for a backend deploy
|
||||
*/
|
||||
public getAdminGroups() {
|
||||
fetch(
|
||||
this.sasJsConfig.serverUrl + '/identities/groups?sortBy=name&limit=5000',
|
||||
|
20
client/src/app/deploy/deploy.module.ts
Normal file
20
client/src/app/deploy/deploy.module.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { DeployComponent } from './deploy.component'
|
||||
import { AutomaticComponent } from './sections/automatic/automatic.component'
|
||||
import { ManualComponent } from './sections/manual/manual.component'
|
||||
import { SasjsConfiguratorComponent } from './sections/sasjs-configurator/sasjs-configurator.component'
|
||||
import { ClarityModule } from '@clr/angular'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { DeployRoutingModule } from './deploy-routing.module'
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
DeployComponent,
|
||||
AutomaticComponent,
|
||||
ManualComponent,
|
||||
SasjsConfiguratorComponent
|
||||
],
|
||||
imports: [CommonModule, FormsModule, ClarityModule, DeployRoutingModule]
|
||||
})
|
||||
export class DeployModule {}
|
@ -9,14 +9,17 @@
|
||||
<p class="m-0 align-self-start">Done</p>
|
||||
<hr class="w-100" />
|
||||
|
||||
<div class="deploy-status-row">
|
||||
<div
|
||||
*ngIf="autoDeployStatus.deployServicePack !== null"
|
||||
class="deploy-status-row"
|
||||
>
|
||||
<clr-icon
|
||||
*ngIf="autoDeployStatus.deployServicePack"
|
||||
*ngIf="autoDeployStatus.deployServicePack === true"
|
||||
class="deploy-success"
|
||||
shape="success-standard"
|
||||
></clr-icon>
|
||||
<clr-icon
|
||||
*ngIf="!autoDeployStatus.deployServicePack"
|
||||
*ngIf="!autoDeployStatus.deployServicePack === false"
|
||||
class="deploy-error"
|
||||
shape="times-circle"
|
||||
></clr-icon>
|
||||
@ -52,7 +55,7 @@
|
||||
class="deploy-error"
|
||||
shape="times-circle"
|
||||
></clr-icon>
|
||||
LAUNCH / CONFIGURE
|
||||
LAUNCH
|
||||
</button>
|
||||
|
||||
<button
|
||||
@ -94,20 +97,72 @@
|
||||
</div>
|
||||
|
||||
<label for="dcloc" class="mt-20 clr-control-label">DC Loc</label>
|
||||
<div class="mb-10 clr-control-container">
|
||||
<div class="clr-input-wrapper">
|
||||
<p class="mt-0">{{ dcPath }}</p>
|
||||
<div class="mb-10 clr-control-container dc-loc-input-wrapper">
|
||||
<div class="clr-input-wrapper small-mt">
|
||||
<input clrInput name="dcloc" [(ngModel)]="dcPath" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label for="dcloc" class="mt-20 clr-control-label">SAS Admin group</label>
|
||||
<div class="mb-10 clr-control-container">
|
||||
<div class="clr-input-wrapper">
|
||||
<p class="mt-0">{{ selectedAdminGroup }}</p>
|
||||
<div class="clr-input-wrapper small-mt">
|
||||
<select
|
||||
*ngIf="!adminGroupsLoading"
|
||||
clrSelect
|
||||
name="options"
|
||||
[(ngModel)]="selectedAdminGroup"
|
||||
>
|
||||
<option *ngFor="let adminGroup of adminGroups" [value]="adminGroup.id">
|
||||
{{ adminGroup.name }}
|
||||
</option>
|
||||
</select>
|
||||
<clr-spinner
|
||||
clrInline
|
||||
class="spinner-sm"
|
||||
*ngIf="adminGroupsLoading"
|
||||
></clr-spinner>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<clr-checkbox-wrapper>
|
||||
<label for="computeContext" class="mt-20 clr-control-label"
|
||||
>Compute Context</label
|
||||
>
|
||||
<div class="mb-10 clr-control-container">
|
||||
<div class="clr-input-wrapper small-mt">
|
||||
<select
|
||||
*ngIf="!computeContextsLoading"
|
||||
clrSelect
|
||||
name="options"
|
||||
(ngModelChange)="onComputeContextChange($event)"
|
||||
[(ngModel)]="selectedComputeContext"
|
||||
>
|
||||
<option
|
||||
*ngFor="let computeContext of computeContexts"
|
||||
[value]="computeContext.id"
|
||||
>
|
||||
{{ computeContext.name }}
|
||||
</option>
|
||||
</select>
|
||||
<clr-spinner
|
||||
clrInline
|
||||
class="spinner-sm"
|
||||
*ngIf="computeContextsLoading"
|
||||
></clr-spinner>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="runningAsUser">
|
||||
<label for="dcloc" class="mt-20 clr-control-label">Running as user:</label>
|
||||
<div class="mb-10 clr-control-container">
|
||||
<div class="clr-input-wrapper">
|
||||
<p class="mt-0">{{ runningAsUser }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<!-- Keeping this for a reference in case future VIYA changes and starts allowing separate backend and frontend) -->
|
||||
|
||||
<!-- <clr-checkbox-wrapper>
|
||||
<input
|
||||
clrCheckbox
|
||||
[(ngModel)]="recreateDatabase"
|
||||
@ -116,19 +171,28 @@
|
||||
checked
|
||||
/>
|
||||
<label>Recreate database</label>
|
||||
</clr-checkbox-wrapper>
|
||||
</clr-checkbox-wrapper> -->
|
||||
|
||||
<hr />
|
||||
|
||||
<button
|
||||
(click)="runAutoDeploy()"
|
||||
class="btn-autodeploy btn btn-primary d-inline-block mr-10"
|
||||
>
|
||||
Deploy
|
||||
</button>
|
||||
|
||||
<!-- Keeping this for a reference in case future VIYA changes and starts allowing separate backend and frontend) -->
|
||||
|
||||
<!-- <button
|
||||
(click)="executeJson()"
|
||||
class="btn-autodeploy btn btn-primary d-inline-block mr-10"
|
||||
[disabled]="!jsonFile"
|
||||
>
|
||||
Deploy {{ !jsonFile ? '(json file is not available)' : '' }}
|
||||
</button>
|
||||
</button> -->
|
||||
|
||||
<button
|
||||
<!-- <button
|
||||
(click)="uploadJsonAuto.click()"
|
||||
class="btn-autodeploy btn btn-primary d-inline-block mr-10"
|
||||
>
|
||||
@ -140,7 +204,7 @@
|
||||
hidden
|
||||
(click)="clearUploadInput($event)"
|
||||
(change)="onJsonFileChange($event)"
|
||||
/>
|
||||
/> -->
|
||||
|
||||
<clr-modal [(clrModalOpen)]="recreateDatabaseModal" [clrModalClosable]="false">
|
||||
<h3 class="modal-title">Warning</h3>
|
||||
|
@ -1,61 +0,0 @@
|
||||
.auto-deploy {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.spinner-box {
|
||||
width: 400px;
|
||||
padding: 20px;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
box-shadow: 1px 1px 8px 0px #00000082;
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.deploy-status-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-self: flex-start;
|
||||
|
||||
p {
|
||||
margin: 0 0 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.deploy-success {
|
||||
color: #6ECF44;
|
||||
}
|
||||
|
||||
.deploy-error {
|
||||
color: #E74C3C;
|
||||
// width: 20px;
|
||||
// height: 20px;
|
||||
}
|
||||
|
||||
.deploy-undeterminated {
|
||||
color: #cacaca;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
border-bottom: 1px solid #00000045;
|
||||
}
|
@ -1,15 +1,34 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core'
|
||||
import SASjs, { SASjsConfig } from '@sasjs/adapter'
|
||||
import { DcAdapterSettings } from 'src/app/models/DcAdapterSettings'
|
||||
import { DeployService } from 'src/app/services/deploy.service'
|
||||
import { EventService } from 'src/app/services/event.service'
|
||||
import { LoggerService } from 'src/app/services/logger.service'
|
||||
import { SasViyaService } from 'src/app/services/sas-viya.service'
|
||||
import { SasService } from 'src/app/services/sas.service'
|
||||
import { ViyaApiCurrentUser } from 'src/app/viya-api-explorer/models/viya-api-current-user.model'
|
||||
import {
|
||||
Item,
|
||||
ViyaApiIdentities
|
||||
} from 'src/app/viya-api-explorer/models/viya-api-identities.model'
|
||||
import { ComputeContextDetails } from 'src/app/viya-api-explorer/models/viya-compute-context-details.model'
|
||||
import {
|
||||
ViyaComputeContexts,
|
||||
Item as ComputeContextItem
|
||||
} from 'src/app/viya-api-explorer/models/viya-compute-contexts.model'
|
||||
|
||||
@Component({
|
||||
selector: 'app-automatic-deploy',
|
||||
templateUrl: './automatic.component.html',
|
||||
styleUrls: ['./automatic.component.scss']
|
||||
styleUrls: ['./automatic.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class AutomaticComponent implements OnInit {
|
||||
@Input() sasJs!: SASjs
|
||||
@ -21,6 +40,7 @@ export class AutomaticComponent implements OnInit {
|
||||
|
||||
@Output() onNavigateToHome: EventEmitter<any> = new EventEmitter<any>()
|
||||
|
||||
public selectedComputeContext: string = ''
|
||||
public makeDataResponse: string = ''
|
||||
public jsonFile: any = null
|
||||
public autodeploying: boolean = false
|
||||
@ -28,8 +48,19 @@ export class AutomaticComponent implements OnInit {
|
||||
public recreateDatabaseModal: boolean = false
|
||||
public isSubmittingJson: boolean = false
|
||||
public isJsonSubmitted: boolean = false
|
||||
public recreateDatabase: boolean = false
|
||||
/**
|
||||
* Default was `false` when deploy was done with frontend and backend separately.
|
||||
* Now we are using only streaming app, so we always want to recreate database (makedata)
|
||||
*/
|
||||
public recreateDatabase: boolean = true
|
||||
public createDatabaseLoading: boolean = false
|
||||
public adminGroupsLoading: boolean = false
|
||||
public currentUserInfoLoading: boolean = false
|
||||
public computeContextsLoading: boolean = false
|
||||
public adminGroups: { id: string; name: string }[] = []
|
||||
public runningAsUser: string | undefined
|
||||
public currentUserInfo: ViyaApiCurrentUser | null = null
|
||||
public computeContexts: ComputeContextItem[] = []
|
||||
|
||||
/** autoDeployStatus
|
||||
* This object presents the status for two steps that we have for deploy.
|
||||
@ -50,13 +81,132 @@ export class AutomaticComponent implements OnInit {
|
||||
private eventService: EventService,
|
||||
private deployService: DeployService,
|
||||
private sasService: SasService,
|
||||
private sasViyaService: SasViyaService,
|
||||
private loggerService: LoggerService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
ngOnInit(): void {
|
||||
this.loadData()
|
||||
}
|
||||
|
||||
public async loadData() {
|
||||
await this.getAdminGroups()
|
||||
await this.getComputeContexts()
|
||||
await this.getCurrentUser()
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.selectedComputeContext) {
|
||||
this.onComputeContextChange(this.selectedComputeContext)
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
public async getComputeContexts() {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.computeContextsLoading = true
|
||||
|
||||
this.sasViyaService.getComputeContexts().subscribe(
|
||||
(res: ViyaComputeContexts) => {
|
||||
this.computeContextsLoading = false
|
||||
|
||||
const defaultContext = res.items.find(
|
||||
(item: ComputeContextItem) =>
|
||||
item.name === 'SAS Job Execution compute context'
|
||||
)
|
||||
|
||||
if (defaultContext) {
|
||||
this.selectedComputeContext = defaultContext.id
|
||||
}
|
||||
|
||||
this.computeContexts = res.items
|
||||
|
||||
resolve()
|
||||
},
|
||||
(err) => {
|
||||
reject(err)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
public async getCurrentUser() {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.currentUserInfoLoading = true
|
||||
|
||||
this.sasViyaService.getCurrentUser().subscribe(
|
||||
(res: ViyaApiCurrentUser) => {
|
||||
this.currentUserInfoLoading = false
|
||||
|
||||
this.currentUserInfo = res
|
||||
|
||||
this.dcPath = `/export/viya/homes/${res.id}`
|
||||
|
||||
resolve()
|
||||
},
|
||||
(err) => {
|
||||
console.error('Error while getting current user', err)
|
||||
reject(err)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
public async getAdminGroups() {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.adminGroupsLoading = true
|
||||
|
||||
this.sasViyaService
|
||||
.getAdminGroups()
|
||||
.subscribe((res: ViyaApiIdentities) => {
|
||||
this.adminGroupsLoading = false
|
||||
// Map admin groups with only needed fields
|
||||
this.adminGroups = res.items.map((item: Item) => {
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name
|
||||
}
|
||||
})
|
||||
|
||||
resolve()
|
||||
}),
|
||||
(err: any) => {
|
||||
this.adminGroupsLoading = false
|
||||
this.loggerService.error('Error while getting admin groups', err)
|
||||
this.eventService.showAbortModal('admin groups', err)
|
||||
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public async onComputeContextChange(computeContextId: string) {
|
||||
this.sasViyaService
|
||||
.getComputeContextById(computeContextId)
|
||||
.subscribe((res: ComputeContextDetails) => {
|
||||
if (res.attributes && res.attributes.runServerAs) {
|
||||
this.runningAsUser = res.attributes.runServerAs
|
||||
} else {
|
||||
this.runningAsUser = this.currentUserInfo?.id || 'unknown'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public getComputeContextName(id: string): string | undefined {
|
||||
return (
|
||||
this.computeContexts.find(
|
||||
(context: ComputeContextItem) => context.id === id
|
||||
)?.name || undefined
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes sas.json file to deploy the backend
|
||||
* Method will first try to run the `auto deploy`
|
||||
* If that fails the rest of the code is ignored.
|
||||
* If request is successfull, method will continue to try
|
||||
* to create database if checkbox is toggled on
|
||||
*/
|
||||
public async executeJson() {
|
||||
this.autodeploying = true
|
||||
this.isSubmittingJson = true
|
||||
|
||||
try {
|
||||
@ -91,6 +241,14 @@ export class AutomaticComponent implements OnInit {
|
||||
}
|
||||
|
||||
this.isSubmittingJson = false
|
||||
}
|
||||
|
||||
public async runAutoDeploy(executeJson: boolean = false) {
|
||||
this.autodeploying = true
|
||||
|
||||
if (executeJson) {
|
||||
this.executeJson()
|
||||
}
|
||||
|
||||
if (this.recreateDatabase) {
|
||||
this.createDatabase()
|
||||
@ -99,6 +257,9 @@ export class AutomaticComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the `makedata` request sending the ADMIN and DCPATH values
|
||||
*/
|
||||
public createDatabase() {
|
||||
let data = {
|
||||
fromjs: [
|
||||
@ -109,14 +270,27 @@ export class AutomaticComponent implements OnInit {
|
||||
]
|
||||
}
|
||||
|
||||
// Get and run service using the selected context name
|
||||
let selectedComputeContextName = this.sasJsConfig.contextName
|
||||
|
||||
if (this.selectedComputeContext.length && this.computeContexts.length) {
|
||||
const computeContextName = this.getComputeContextName(
|
||||
this.selectedComputeContext
|
||||
)
|
||||
|
||||
if (computeContextName) {
|
||||
selectedComputeContextName = computeContextName
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We are overriding default `sasjsConfig` object fields with this object fields.
|
||||
* Here we want to run this request using original WEB method.
|
||||
* contextName: null is the MUST field for it.
|
||||
*/
|
||||
let overrideConfig = {
|
||||
useComputeApi: false,
|
||||
contextName: this.sasJsConfig.contextName,
|
||||
useComputeApi: null,
|
||||
contextName: selectedComputeContextName,
|
||||
debug: true
|
||||
}
|
||||
|
||||
@ -138,8 +312,23 @@ export class AutomaticComponent implements OnInit {
|
||||
} else {
|
||||
this.autoDeployStatus.runMakeData = false
|
||||
}
|
||||
|
||||
if (typeof res.sasjsAbort !== 'undefined') {
|
||||
const abortRes = res
|
||||
const abortMsg = abortRes.sasjsAbort[0].MSG
|
||||
const macMsg = abortRes.sasjsAbort[0].MAC
|
||||
|
||||
this.eventService.showAbortModal('makedata', abortMsg, {
|
||||
SYSWARNINGTEXT: abortRes.SYSWARNINGTEXT,
|
||||
SYSERRORTEXT: abortRes.SYSERRORTEXT,
|
||||
MAC: macMsg
|
||||
})
|
||||
}
|
||||
|
||||
this.updateIndexHtmlComputeContext()
|
||||
})
|
||||
.catch((err: any) => {
|
||||
this.eventService.showAbortModal('makedata', JSON.stringify(err))
|
||||
this.autoDeployStatus.runMakeData = false
|
||||
this.autodeployDone = true
|
||||
|
||||
@ -151,6 +340,50 @@ export class AutomaticComponent implements OnInit {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Only when on Viya, this method will update the `contextname` in the `DataController.html` on the SAS drive
|
||||
* This is needed to ensure that the DC will use the same compute context `makedata` service used to run against.
|
||||
*/
|
||||
public async updateIndexHtmlComputeContext() {
|
||||
const indexHtmlContent = await this.sasService.getFileContent(
|
||||
`${this.appLoc}/services`,
|
||||
'DataController.html'
|
||||
)
|
||||
|
||||
if (!indexHtmlContent) {
|
||||
this.loggerService.error(
|
||||
`Failed to get DataController.html at ${this.appLoc}/services`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const computeContextName = this.getComputeContextName(
|
||||
this.selectedComputeContext
|
||||
)
|
||||
|
||||
if (!computeContextName) {
|
||||
this.loggerService.error(
|
||||
`Compute context name not found for ID: ${this.selectedComputeContext} | List: ${JSON.stringify(this.computeContexts)}`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const updatedContent = indexHtmlContent.replace(
|
||||
/contextname="[^"]*"/g,
|
||||
`contextname="${computeContextName}"`
|
||||
)
|
||||
|
||||
await this.sasService
|
||||
.updateFileContent(
|
||||
`${this.appLoc}/services`,
|
||||
'DataController.html',
|
||||
updatedContent
|
||||
)
|
||||
.catch((err: any) => {
|
||||
this.loggerService.error(`Failed to update DataController.html: ${err}`)
|
||||
})
|
||||
}
|
||||
|
||||
public downloadFile(
|
||||
content: any,
|
||||
filename: string,
|
||||
|
@ -1,4 +0,0 @@
|
||||
.clear-memory-button {
|
||||
right: 10px;
|
||||
top: 2px;
|
||||
}
|
@ -1,6 +1,14 @@
|
||||
import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core'
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
EventEmitter,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core'
|
||||
import SASjs, { SASjsConfig } from '@sasjs/adapter'
|
||||
import { DcAdapterSettings } from 'src/app/models/DcAdapterSettings'
|
||||
import { RequestWrapperResponse } from 'src/app/models/request-wrapper/RequestWrapperResponse'
|
||||
import { DeployService } from 'src/app/services/deploy.service'
|
||||
import { EventService } from 'src/app/services/event.service'
|
||||
import { LoggerService } from 'src/app/services/logger.service'
|
||||
@ -9,7 +17,8 @@ import { SasService } from 'src/app/services/sas.service'
|
||||
@Component({
|
||||
selector: 'app-manual-deploy',
|
||||
templateUrl: './manual.component.html',
|
||||
styleUrls: ['./manual.component.scss']
|
||||
styleUrls: ['./manual.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class ManualComponent implements OnInit {
|
||||
@Input() sasJs!: SASjs
|
||||
@ -52,6 +61,9 @@ export class ManualComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
/**
|
||||
* FIXME: Remove
|
||||
*/
|
||||
public async executableContext() {
|
||||
// getExecutableContexts now need AuthConfig parameter which we don't have on web
|
||||
// this.contextsLoading = true
|
||||
@ -65,10 +77,16 @@ export class ManualComponent implements OnInit {
|
||||
// this.contextsLoading = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes sas.json file attached to the input
|
||||
*/
|
||||
public clearUploadInput(event: Event) {
|
||||
this.deployService.clearUploadInput(event)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads attached SAS file to be sent to sas for execution (backend deploy)
|
||||
*/
|
||||
public onSasFileChange(event: any) {
|
||||
this.preloadedFile = false
|
||||
|
||||
@ -93,12 +111,18 @@ export class ManualComponent implements OnInit {
|
||||
fileReader.readAsText(file)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads attached JSON file to be sent to sas for execution (backend deploy)
|
||||
*/
|
||||
public async onJsonFileChange(event: any) {
|
||||
let file = event.target.files[0]
|
||||
|
||||
this.jsonFile = await this.deployService.readFile(file)
|
||||
}
|
||||
|
||||
/**
|
||||
* Appending precode lines to the attached sas or json file for backend deploy
|
||||
*/
|
||||
public addPrecodeLines() {
|
||||
let headerLines = [
|
||||
`%let context=${this.selectedContext};`,
|
||||
@ -110,6 +134,9 @@ export class ManualComponent implements OnInit {
|
||||
this.linesOfCode.unshift(...headerLines)
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloadng file with precode included
|
||||
*/
|
||||
public downloadSasPrecodeFile() {
|
||||
let linesAsText = this.linesOfCode.join('\n')
|
||||
let filename = this.fileName.split('.')[0]
|
||||
@ -117,6 +144,9 @@ export class ManualComponent implements OnInit {
|
||||
this.downloadFile(linesAsText, filename, 'sas')
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for downloading log and repsonse as a file
|
||||
*/
|
||||
public downloadFile(
|
||||
content: any,
|
||||
filename: string,
|
||||
@ -125,10 +155,17 @@ export class ManualComponent implements OnInit {
|
||||
this.deployService.downloadFile(content, filename, extension)
|
||||
}
|
||||
|
||||
/**
|
||||
* Saving dcpath to localstorage
|
||||
* FIXME: maybe it'snot necessary
|
||||
*/
|
||||
public saveDcPath() {
|
||||
localStorage.setItem('deploy_dc_loc', this.dcPath)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send sas.json to be executed (deploying backend)
|
||||
*/
|
||||
public async executeJson() {
|
||||
this.isSubmittingJson = true
|
||||
|
||||
@ -162,6 +199,9 @@ export class ManualComponent implements OnInit {
|
||||
this.isSubmittingJson = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Send sas file to be executed (deploying backend)
|
||||
*/
|
||||
public async executeSAS() {
|
||||
this.executingScript = true
|
||||
this.jobLog = ''
|
||||
@ -194,6 +234,10 @@ export class ManualComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Running makedata service
|
||||
* @param newTab open and run in new tab
|
||||
*/
|
||||
public createDatabase(newTab: boolean = true) {
|
||||
if (newTab) {
|
||||
let url =
|
||||
@ -230,7 +274,7 @@ export class ManualComponent implements OnInit {
|
||||
* contextName: null is the MUST field for it.
|
||||
*/
|
||||
let overrideConfig = {
|
||||
useComputeApi: false,
|
||||
useComputeApi: null,
|
||||
contextName: this.sasJsConfig.contextName,
|
||||
debug: true
|
||||
}
|
||||
@ -268,10 +312,10 @@ export class ManualComponent implements OnInit {
|
||||
|
||||
this.sasService
|
||||
.request('public/startupservice', null)
|
||||
.then((res: any) => {
|
||||
this.loggerService.log(res)
|
||||
.then((res: RequestWrapperResponse) => {
|
||||
this.loggerService.log(res.adapterResponse)
|
||||
|
||||
if (res.saslibs) {
|
||||
if (res.adapterResponse.saslibs) {
|
||||
this.validationState = 'success'
|
||||
} else {
|
||||
this.validationState = 'error'
|
||||
|
@ -10,11 +10,13 @@
|
||||
</p>
|
||||
|
||||
<p class="m-0 mt-10">
|
||||
Please specify a physical directory below, to which user
|
||||
<strong>{{ SYSUSERID }}</strong> can write, on behalf of Data Controller:
|
||||
Please specify a physical directory (on the
|
||||
<strong> {{ SYSHOSTNAME }}</strong>
|
||||
compute server) below, to which user
|
||||
<strong>{{ SYSUSERID }}</strong> can write, on behalf of Data Controller.
|
||||
</p>
|
||||
|
||||
<label class="mt-20 clr-control-label">DC Directory</label>
|
||||
<label class="mt-20 clr-control-label">DC Staging Directory</label>
|
||||
<div class="mb-10 clr-control-container">
|
||||
<div class="clr-input-wrapper">
|
||||
<input
|
||||
|
@ -1,23 +0,0 @@
|
||||
.clr-control-container {
|
||||
width: 50vw;
|
||||
}
|
||||
|
||||
.clr-input-wrapper {
|
||||
width: 100%;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.thinProgress {
|
||||
left: 0px;
|
||||
right: 0;
|
||||
width: unset;
|
||||
height: 1px;
|
||||
margin-top: 0 !important;
|
||||
|
||||
&::after {
|
||||
top: 0;
|
||||
}
|
||||
}
|
@ -1,18 +1,26 @@
|
||||
import { Location } from '@angular/common'
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core'
|
||||
import SASjs, { SASjsConfig } from '@sasjs/adapter'
|
||||
import { ServerType } from '@sasjs/utils/types/serverType'
|
||||
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 { SASjsApiServerInfo } from 'src/app/models/sasjs-api/SASjsApiServerInfo.model'
|
||||
import { HelperService } from 'src/app/services/helper.service'
|
||||
import { SasService } from 'src/app/services/sas.service'
|
||||
import { SasjsService } from 'src/app/services/sasjs.service'
|
||||
|
||||
@Component({
|
||||
selector: 'app-sasjs-configurator',
|
||||
templateUrl: './sasjs-configurator.component.html',
|
||||
styleUrls: ['./sasjs-configurator.component.scss']
|
||||
styleUrls: ['./sasjs-configurator.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class SasjsConfiguratorComponent implements OnInit {
|
||||
@Input() sasJs!: SASjs
|
||||
@ -51,6 +59,9 @@ export class SasjsConfiguratorComponent implements OnInit {
|
||||
this.getServerInfo()
|
||||
}
|
||||
|
||||
/**
|
||||
* Fethes the sasjs server instance info
|
||||
*/
|
||||
getServerInfo() {
|
||||
this.sasjsService
|
||||
.getServerInfo()
|
||||
@ -59,15 +70,18 @@ export class SasjsConfiguratorComponent implements OnInit {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches user groups from the `usernav/usergroupsbymember` service
|
||||
*/
|
||||
getUserGroups() {
|
||||
this.loading = true
|
||||
|
||||
this.sasService.request('usernav/usergroupsbymember', null).then(
|
||||
(res: any) => {
|
||||
this.METAPERSON = res.MF_GETUSER
|
||||
this.SYSUSERID = res.SYSUSERID
|
||||
this.SYSHOSTNAME = res.SYSHOSTNAME
|
||||
this.SYSVLONG = res.SYSVLONG
|
||||
(res: RequestWrapperResponse) => {
|
||||
this.METAPERSON = res.adapterResponse.MF_GETUSER
|
||||
this.SYSUSERID = res.adapterResponse.SYSUSERID
|
||||
this.SYSHOSTNAME = res.adapterResponse.SYSHOSTNAME
|
||||
this.SYSVLONG = res.adapterResponse.SYSVLONG
|
||||
|
||||
/*
|
||||
We would like to present a default DCPATH (deployment path) to the
|
||||
@ -83,12 +97,14 @@ export class SasjsConfiguratorComponent implements OnInit {
|
||||
*/
|
||||
this.dcDirectory =
|
||||
this.tmpDirectories[
|
||||
['L', 'H', 'A', 'S'].includes(res.SYSSCPL.substring(0, 1))
|
||||
['L', 'H', 'A', 'S'].includes(
|
||||
res.adapterResponse.SYSSCPL.substring(0, 1)
|
||||
)
|
||||
? 'linux'
|
||||
: 'windows'
|
||||
]
|
||||
|
||||
this.dcAdminGroupList = res.groups
|
||||
this.dcAdminGroupList = res.adapterResponse.groups
|
||||
this.dcAdminGroup = this.dcAdminGroupList[0].GROUPNAME
|
||||
|
||||
this.loading = false
|
||||
@ -99,6 +115,9 @@ export class SasjsConfiguratorComponent implements OnInit {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creating database
|
||||
*/
|
||||
makeData() {
|
||||
// const _debug = "&_debug=131"; //debug on
|
||||
const _debug = ' ' //debug off
|
||||
|
@ -4,20 +4,23 @@ import { NgVarDirective } from './ng-var.directive'
|
||||
import { DragNdropDirective } from './drag-ndrop.directive'
|
||||
import { FileDropDirective } from './file-drop.directive'
|
||||
import { FileSelectDirective } from './file-select.directive'
|
||||
import { StealFocusDirective } from './steal-focus.directive'
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
NgVarDirective,
|
||||
DragNdropDirective,
|
||||
FileDropDirective,
|
||||
FileSelectDirective
|
||||
FileSelectDirective,
|
||||
StealFocusDirective
|
||||
],
|
||||
imports: [CommonModule],
|
||||
exports: [
|
||||
NgVarDirective,
|
||||
DragNdropDirective,
|
||||
FileDropDirective,
|
||||
FileSelectDirective
|
||||
FileSelectDirective,
|
||||
StealFocusDirective
|
||||
]
|
||||
})
|
||||
export class DirectivesModule {}
|
||||
|
@ -14,7 +14,9 @@ export class DragNdropDirective {
|
||||
@Output() fileDropped = new EventEmitter<any>()
|
||||
@Output() fileDraggedOver = new EventEmitter<any>()
|
||||
|
||||
// Dragover listener
|
||||
/**
|
||||
* Dragover listener
|
||||
*/
|
||||
@HostListener('dragover', ['$event'])
|
||||
onDragOver(event: any) {
|
||||
event.preventDefault()
|
||||
@ -26,7 +28,9 @@ export class DragNdropDirective {
|
||||
}
|
||||
}
|
||||
|
||||
// Dragleave listener
|
||||
/**
|
||||
* Dragleave listener
|
||||
*/
|
||||
@HostListener('dragleave', ['$event'])
|
||||
public onDragLeave(event: any) {
|
||||
event.preventDefault()
|
||||
@ -34,7 +38,9 @@ export class DragNdropDirective {
|
||||
this.fileOver = false
|
||||
}
|
||||
|
||||
// Drop listener
|
||||
/**
|
||||
* Drop listener
|
||||
*/
|
||||
@HostListener('drop', ['$event'])
|
||||
public ondrop(event: any) {
|
||||
event.preventDefault()
|
||||
@ -48,6 +54,9 @@ export class DragNdropDirective {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks wether dragging element contain files
|
||||
*/
|
||||
private containsFiles(event: any) {
|
||||
if (event && event.dataTransfer && event.dataTransfer.types) {
|
||||
for (let i = 0; i < event.dataTransfer.types.length; i++) {
|
||||
|
@ -22,6 +22,9 @@ export class FileDropDirective {
|
||||
this.element = element
|
||||
}
|
||||
|
||||
/**
|
||||
* Dragging element drop event
|
||||
*/
|
||||
@HostListener('drop', ['$event'])
|
||||
onDrop(event: DragEvent): void {
|
||||
this._preventAndStop(event)
|
||||
@ -39,6 +42,9 @@ export class FileDropDirective {
|
||||
this.fileDrop.emit(fileList)
|
||||
}
|
||||
|
||||
/**
|
||||
* Dragging element drag over event
|
||||
*/
|
||||
@HostListener('dragover', ['$event'])
|
||||
onDragOver(event: DragEvent): void {
|
||||
this._preventAndStop(event)
|
||||
@ -59,6 +65,10 @@ export class FileDropDirective {
|
||||
this.fileOver.emit(false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent propagation trough elements and stop default behavior
|
||||
* For particular event
|
||||
*/
|
||||
protected _preventAndStop(event: MouseEvent): void {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
@ -21,6 +21,9 @@ export class FileSelectDirective {
|
||||
this.element = element
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if files exist in the input after input change
|
||||
*/
|
||||
isEmptyAfterSelection(): boolean {
|
||||
return !!this.element.nativeElement.attributes.multiple
|
||||
}
|
||||
|
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()
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
export interface RowValidation {
|
||||
valid: boolean
|
||||
invalidError: string
|
||||
rowNumber?: number
|
||||
colName?: string
|
||||
value?: string
|
||||
}
|
@ -24,8 +24,8 @@
|
||||
generatedRecordUrl
|
||||
? 'copy to clipboard'
|
||||
: generateEditRecordUrlLoading
|
||||
? 'Generating url...'
|
||||
: 'Link to this record'
|
||||
? 'Generating url...'
|
||||
: 'Link to this record'
|
||||
}}
|
||||
</button>
|
||||
</ng-container>
|
||||
@ -112,7 +112,7 @@
|
||||
|
||||
<div
|
||||
*ngIf="
|
||||
['autocomplete'].includes(
|
||||
['autocomplete', 'autocomplete.custom'].includes(
|
||||
$any(currentRecordValidator?.getRule(col.key)?.editor)
|
||||
)
|
||||
"
|
||||
@ -163,7 +163,7 @@
|
||||
|
||||
<div
|
||||
*ngIf="
|
||||
['autocomplete'].includes(
|
||||
['autocomplete', 'autocomplete.custom'].includes(
|
||||
$any(currentRecordValidator?.getRule(col.key)?.editor)
|
||||
)
|
||||
"
|
||||
@ -277,7 +277,7 @@
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline focusable"
|
||||
class="btn btn-outline focusable mr-5i"
|
||||
(click)="currentRecord!.noLinkOption = false; closeRecordEdit()"
|
||||
>
|
||||
Cancel
|
||||
|
@ -1,241 +0,0 @@
|
||||
.record-edit-modal {
|
||||
.column-entry {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.name-input-row {
|
||||
width: 100%;
|
||||
max-width: 260px;
|
||||
|
||||
.cell-desc {
|
||||
margin-right: 30px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.inputs-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
::ng-deep >*:not(.date-field):not(clr-select-container) {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
::ng-deep {
|
||||
.clr-textarea-wrapper {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.clr-form-control {
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
app-soft-select {
|
||||
display: block;
|
||||
width: 224px;
|
||||
background: #fff;
|
||||
border: 1px solid #999;
|
||||
color: #000;
|
||||
padding: calc(.25rem + 2px) .5rem;
|
||||
border-radius: .125rem;
|
||||
font-size: .541667rem;
|
||||
margin-right: 6px;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
background-color: #fff;
|
||||
|
||||
&:focus {
|
||||
background: none;
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
&::-webkit-outer-spin-button,
|
||||
&::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.date-field {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
textarea {
|
||||
width: 230px;
|
||||
}
|
||||
|
||||
.date-picker {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 4px;
|
||||
|
||||
::ng-deep {
|
||||
// clr-datepicker-view-manager {
|
||||
// transform: unset !important;
|
||||
// left: unset !important;
|
||||
// right: 70px !important;
|
||||
// }
|
||||
.clr-input-group {
|
||||
border: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
::ng-deep {
|
||||
clr-select-container {
|
||||
border: 1px solid #999;
|
||||
color: #000;
|
||||
border-radius: .125rem;
|
||||
margin-right: 5px;
|
||||
|
||||
.clr-select-wrapper {
|
||||
max-height: unset;
|
||||
|
||||
&::after {
|
||||
top: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
height: auto;
|
||||
padding: 10px;
|
||||
padding-right: 20px;
|
||||
border: 0 !important;
|
||||
|
||||
&:focus {
|
||||
background: 0 0 !important;
|
||||
}
|
||||
&:hover {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clr-input-container {
|
||||
width: 224px;
|
||||
background: #fff;
|
||||
border: 1px solid #999;
|
||||
color: #000;
|
||||
padding: calc(.25rem + 2px) .5rem;
|
||||
border-radius: .125rem;
|
||||
font-size: .541667rem;
|
||||
margin-right: 6px;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
|
||||
&:focus {
|
||||
background: none;
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
&::-webkit-outer-spin-button,
|
||||
&::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.invalid-data {
|
||||
border-color: red;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
width: 80vw;
|
||||
}
|
||||
|
||||
.clr-control-container {
|
||||
width: 100%;
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
resize: none;
|
||||
border-color: #999;
|
||||
|
||||
&.invalid-data {
|
||||
border-color: red;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&.not-char {
|
||||
font-family: "Lucida Console", Monaco, monospace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.generate-record-url {
|
||||
right: 40px;
|
||||
top: 40px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.generate-record-url-button {
|
||||
right: 25px;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 0 0 1rem 0;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
// height: 65px;
|
||||
|
||||
.alert {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.prev-next {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 0px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.focusable {
|
||||
&:focus {
|
||||
box-shadow: 0 0 3px 0px #5aa220;
|
||||
}
|
||||
}
|
||||
|
||||
.entry-input-left-offset {
|
||||
left: -30px;
|
||||
}
|
||||
|
||||
.validation-info-alert {
|
||||
width: 310px
|
||||
}
|
@ -1,5 +1,12 @@
|
||||
import { KeyValue } from '@angular/common'
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core'
|
||||
import moment from 'moment'
|
||||
import { ValidateFilterSASResponse } from 'src/app/models/sas/validate-filter.model'
|
||||
import { QueryClause } from 'src/app/models/TableData'
|
||||
@ -16,7 +23,8 @@ import { EditRecordModal } from '../../models/EditRecordModal'
|
||||
@Component({
|
||||
selector: 'app-edit-record',
|
||||
templateUrl: './edit-record.component.html',
|
||||
styleUrls: ['./edit-record.component.scss']
|
||||
styleUrls: ['./edit-record.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class EditRecordComponent implements OnInit {
|
||||
@Input() currentRecord!: EditRecordModal
|
||||
@ -59,6 +67,12 @@ export class EditRecordComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
/**
|
||||
* Runs native HOT validator against cell value
|
||||
* @param cellValidation column rules
|
||||
* @param cellValue value in the cell that is beign validated
|
||||
* @returns Promise boolean - wether valid or invalid
|
||||
*/
|
||||
async validateRecordCol(
|
||||
cellValidation: any,
|
||||
cellValue: any
|
||||
@ -74,6 +88,12 @@ export class EditRecordComponent implements OnInit {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when date field in the record change
|
||||
* Function will parse date and format to string
|
||||
* @param date picker value
|
||||
* @param colKey column name (key)
|
||||
*/
|
||||
recordDateChange(date: Date, colKey: string) {
|
||||
let cellValidation = this.currentRecordValidator?.getRule(colKey)
|
||||
let format = cellValidation ? cellValidation.dateFormat : ''
|
||||
@ -82,24 +102,38 @@ export class EditRecordComponent implements OnInit {
|
||||
this.currentRecord[colKey] = moment(date).format(format)
|
||||
}
|
||||
|
||||
isRecordModalInvalid(): boolean {
|
||||
return this.currentRecordInvalidCols.length > 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Close edit record modal and apply changes by emitting output event
|
||||
*/
|
||||
confirmRecordEdit() {
|
||||
if (this.currentRecordInvalidCols.length < 1) {
|
||||
this.onRecordChange.emit(this.currentRecord)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close edit record modal without applying the changes
|
||||
*/
|
||||
closeRecordEdit() {
|
||||
this.onRecordEditClose.emit()
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitting output event when dropdown (autocomplete) input in any col change
|
||||
* @param colName column name (key)
|
||||
* @param col column index
|
||||
*/
|
||||
onRecordDropdownChange(colName: string, col: number) {
|
||||
this.onRecordDropdownChanged.emit({ colName, col })
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitting output event when input is focused (clicked on) so we can run a `dynamic cell validation`
|
||||
* Since that bit must be run from the parent component (editor.component)
|
||||
* Result is then applied in the `cellValidation` variable and automatically updated in this component.
|
||||
* @param event input event
|
||||
* @param colName column name (key)
|
||||
*/
|
||||
onRecordInputFocus(event: any, colName: number) {
|
||||
this.onRecordInputFocused.emit({ event, colName })
|
||||
}
|
||||
|
@ -1,8 +0,0 @@
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { Component, OnInit } from '@angular/core'
|
||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core'
|
||||
|
||||
/**
|
||||
* Goal of this component is to recieve array of strings where every element is one state
|
||||
@ -10,7 +10,8 @@ import { Component, OnInit } from '@angular/core'
|
||||
@Component({
|
||||
selector: 'app-upload-stater',
|
||||
templateUrl: './upload-stater.component.html',
|
||||
styleUrls: ['./upload-stater.component.scss']
|
||||
styleUrls: ['./upload-stater.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class UploadStaterComponent implements OnInit {
|
||||
public statesList: string[] = [] //States appended to be displayed
|
||||
|
@ -3,7 +3,7 @@
|
||||
appFileDrop
|
||||
(fileOver)="fileOverBase($event)"
|
||||
[uploader]="uploader"
|
||||
(fileDrop)="getFileDesc($event, true)"
|
||||
(fileDrop)="attachFile($event, true)"
|
||||
[clrModalSize]="'xl'"
|
||||
[clrModalStaticBackdrop]="false"
|
||||
[clrModalClosable]="excelUploadState === 'Validating-DQ'"
|
||||
@ -36,7 +36,7 @@
|
||||
<div class="clr-row card-block mt-15 d-flex justify-content-between">
|
||||
<div class="clr-col-md-auto">
|
||||
<div class="encoding-block">
|
||||
<clr-radio-container class="mt-0-i" clrInline>
|
||||
<clr-radio-container class="mt-0" clrInline>
|
||||
<clr-radio-wrapper>
|
||||
<input
|
||||
type="radio"
|
||||
@ -81,7 +81,7 @@
|
||||
type="file"
|
||||
appFileSelect
|
||||
[uploader]="uploader"
|
||||
(change)="getFileDesc($event)"
|
||||
(change)="attachFile($event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -92,7 +92,7 @@
|
||||
<button
|
||||
[disabled]="true"
|
||||
class="btnView btn btn-sm btn-success profile-buttons w-100"
|
||||
(click)="getFile()"
|
||||
(click)="uploadParsedFiles()"
|
||||
>
|
||||
Upload
|
||||
</button>
|
||||
@ -164,19 +164,28 @@
|
||||
<div
|
||||
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">
|
||||
<span class="btn btn-sm" [routerLink]="['/home']">
|
||||
<clr-icon shape="caret" dir="left" size="20"></clr-icon>Back to
|
||||
table selection
|
||||
<div
|
||||
*ngIf="tableTrue"
|
||||
class="clr-col-12 clr-col-md-3 clr-col-lg-4 backBtn"
|
||||
>
|
||||
<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 (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>
|
||||
Viewboxes
|
||||
<span class="text">Viewboxes</span>
|
||||
</span>
|
||||
</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"
|
||||
>
|
||||
<h4
|
||||
@ -184,26 +193,43 @@
|
||||
libName: (libds?.split('.'))![0],
|
||||
tableName: (libds?.split('.'))![1]
|
||||
} as libdsParsed"
|
||||
class="editor-title text-center mt-0-i"
|
||||
class="editor-title text-center mt-0"
|
||||
>
|
||||
<clr-icon
|
||||
(click)="datasetInfo = true"
|
||||
shape="info-circle"
|
||||
class="is-highlight cursor-pointer"
|
||||
size="24"
|
||||
></clr-icon>
|
||||
<clr-tooltip>
|
||||
<clr-icon
|
||||
clrTooltipTrigger
|
||||
(click)="datasetInfo = true"
|
||||
shape="info-circle"
|
||||
aria-label="View dataset meta info"
|
||||
class="is-highlight cursor-pointer"
|
||||
size="24"
|
||||
></clr-icon>
|
||||
|
||||
<clr-icon
|
||||
*ngIf="libdsParsed.tableName.includes('-FC')"
|
||||
shape="bolt"
|
||||
class="color-yellow"
|
||||
></clr-icon>
|
||||
<clr-icon
|
||||
*ngIf="libdsParsed.tableName.includes('-FC')"
|
||||
shape="bolt"
|
||||
class="color-yellow"
|
||||
></clr-icon>
|
||||
|
||||
<span clrTooltipTrigger>
|
||||
{{ libdsParsed.libName }}.<a
|
||||
class="mr-10 view-table"
|
||||
[routerLink]="'/view/data/' + libds!"
|
||||
>{{ libdsParsed.tableName.replace('-FC', '') }}</a
|
||||
>
|
||||
</span>
|
||||
|
||||
<ng-container *ngIf="this.dsNote && this.dsNote.length > 0">
|
||||
<clr-tooltip-content
|
||||
clrPosition="bottom-left"
|
||||
clrSize="lg"
|
||||
*clrIfOpen
|
||||
>
|
||||
{{ this.dsNote }}
|
||||
</clr-tooltip-content>
|
||||
</ng-container>
|
||||
</clr-tooltip>
|
||||
|
||||
{{ libdsParsed.libName }}.<a
|
||||
class="mr-10"
|
||||
[routerLink]="'/view/data/' + libds!"
|
||||
>{{ libdsParsed.tableName.replace('-FC', '') }}</a
|
||||
>
|
||||
<ng-container *ngIf="dataSource">
|
||||
<ng-container *ngIf="!zeroFilterRows">
|
||||
({{ dataSource.length | thousandSeparator: ',' }}
|
||||
@ -215,34 +241,37 @@
|
||||
</ng-container>
|
||||
</h4>
|
||||
</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">
|
||||
<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()"
|
||||
>
|
||||
<clr-icon shape="filter"></clr-icon>
|
||||
<span>Filter</span>
|
||||
<span class="text">Filter</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-primary btn-block"
|
||||
class="btn icon-collapse btn-sm btn-primary btn-block"
|
||||
(click)="editTable()"
|
||||
>
|
||||
<clr-icon shape="note"></clr-icon>
|
||||
<span>Edit</span>
|
||||
<span class="text">Edit</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
*ngIf="!columnLevelSecurityFlag"
|
||||
(click)="onShowUploadModal()"
|
||||
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>
|
||||
<span>Upload</span>
|
||||
<span class="text">Upload</span>
|
||||
</button>
|
||||
</ng-container>
|
||||
|
||||
@ -280,7 +309,7 @@
|
||||
licenceState.value.editor_rows_allowed === 1
|
||||
? 'row'
|
||||
: 'rows'
|
||||
}}, contact support@datacontroller.io</span
|
||||
}}, contact support@datacontroller.io</span
|
||||
>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
@ -346,8 +375,8 @@
|
||||
<ng-container *ngIf="!getdataError">
|
||||
<span class="spinner"> Loading... </span>
|
||||
|
||||
<div>
|
||||
<h3>Loading table</h3>
|
||||
<div class="mt-10">
|
||||
<p cds-text="section">Loading table</p>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@ -356,8 +385,8 @@
|
||||
<clr-icon shape="error-standard" class="error-icon"></clr-icon>
|
||||
</span>
|
||||
|
||||
<div>
|
||||
<h3>Loading table error</h3>
|
||||
<div class="mt-10">
|
||||
<p cds-text="section">Loading table error</p>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
@ -382,10 +411,10 @@
|
||||
hotId="hotInstance"
|
||||
id="hotTable"
|
||||
class="edit-hot"
|
||||
className="htDark"
|
||||
[class.hidden]="hotTable.hidden"
|
||||
[licenseKey]="hotTable.licenseKey"
|
||||
>
|
||||
<!--[licenseKey]=""-->
|
||||
</hot-table>
|
||||
</div>
|
||||
|
||||
@ -418,7 +447,7 @@
|
||||
licenceState.value.editor_rows_allowed === 1
|
||||
? 'row'
|
||||
: 'rows'
|
||||
}}, contact support@datacontroller.io</span
|
||||
}}, contact support@datacontroller.io</span
|
||||
>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
@ -468,14 +497,18 @@
|
||||
: 'rows'
|
||||
}}
|
||||
will be submitted. To remove the restriction, contact
|
||||
support@datacontroller.io</span
|
||||
support@datacontroller.io</span
|
||||
>
|
||||
<div *ngIf="tableTrue" class="clr-offset-md-2 clr-col-md-8">
|
||||
<div class="form-group">
|
||||
<label for="formFields_8">Message</label>
|
||||
<div class="text-area-full-width">
|
||||
<label for="formFields_8" class="mb-5 d-block"
|
||||
>Message</label
|
||||
>
|
||||
<textarea
|
||||
clrTextarea
|
||||
[(ngModel)]="message"
|
||||
[disabled]="!validationDone"
|
||||
tabindex="0"
|
||||
[value]="
|
||||
!validationDone
|
||||
? 'Please wait while we validate ' +
|
||||
@ -483,10 +516,9 @@
|
||||
' cells.'
|
||||
: ''
|
||||
"
|
||||
class="w-100"
|
||||
class="submit-reason"
|
||||
type="text"
|
||||
id="formFields_8"
|
||||
rows="5"
|
||||
></textarea>
|
||||
</div>
|
||||
<!-- TODO:approvers list -->
|
||||
@ -505,6 +537,7 @@
|
||||
[disabled]="!validationDone"
|
||||
type="submit"
|
||||
class="btn btn-sm btn-success-outline m-0"
|
||||
tabindex="0"
|
||||
(click)="saveTable(hotTable.data)"
|
||||
>
|
||||
Submit
|
||||
@ -513,6 +546,7 @@
|
||||
id="cancelSubmitBtn"
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline"
|
||||
tabindex="0"
|
||||
(click)="cancelSubmit(); submit = false; validationDone = 0"
|
||||
>
|
||||
Cancel
|
||||
@ -529,7 +563,7 @@
|
||||
Due to current licence, only
|
||||
{{ licenceState.value.submit_rows_limit }} rows in a file will
|
||||
be submitted. To remove the restriction, contact
|
||||
support@datacontroller.io
|
||||
support@datacontroller.io
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@ -543,7 +577,7 @@
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-primary"
|
||||
(click)="getFile(); submitLimitNotice = false"
|
||||
(click)="uploadParsedFiles(); submitLimitNotice = false"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
@ -597,7 +631,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
<clr-modal [(clrModalOpen)]="pkDups">
|
||||
<clr-modal class="duplicate-keys-modal" [(clrModalOpen)]="pkDups">
|
||||
<h3 class="modal-title">Error</h3>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
@ -831,6 +865,12 @@
|
||||
</div>
|
||||
</clr-modal>
|
||||
|
||||
<app-dataset-info [(open)]="datasetInfo" [dsmeta]="dsmeta"></app-dataset-info>
|
||||
<app-dataset-info
|
||||
[(open)]="datasetInfo"
|
||||
[dsmeta]="dsmeta"
|
||||
[versions]="versions"
|
||||
(rowClicked)="datasetInfoModalRowClicked($event)"
|
||||
>
|
||||
</app-dataset-info>
|
||||
|
||||
<app-viewboxes [(viewboxModal)]="viewboxes"></app-viewboxes>
|
||||
|
@ -1,223 +0,0 @@
|
||||
.card {
|
||||
margin-top: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.buttonBar {
|
||||
padding: 2px 10px 2px 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.testRed {
|
||||
color: white;
|
||||
background: rgba(255,0,0, 0.8) !important;
|
||||
}
|
||||
|
||||
hot-table {
|
||||
::ng-deep {
|
||||
.firstColumnHeaderStyle button.changeType {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.handsontable tbody th.ht__highlight, .handsontable thead th.ht__highlight {
|
||||
&.primaryKeyHeaderStyle {
|
||||
background: #306b00b0;
|
||||
}
|
||||
}
|
||||
|
||||
.primaryKeyHeaderStyle {
|
||||
background: #306b006e;
|
||||
}
|
||||
|
||||
th.readonlyCell {
|
||||
div {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
td.readonlyCell {
|
||||
opacity: 0.5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.infoBar {
|
||||
margin-top:14px;
|
||||
background: #495967;
|
||||
color: white;
|
||||
text-align:center;
|
||||
padding: 3px;
|
||||
font-size: 16px;
|
||||
|
||||
height: 30px;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
span {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
height: unset;
|
||||
white-space: normal;
|
||||
|
||||
span {
|
||||
width: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pkHeader {
|
||||
background: #687682;
|
||||
color: #fff;
|
||||
margin: -1px -1px -1px -1px;
|
||||
}
|
||||
|
||||
.headerBar {
|
||||
// padding: 13px 0px 14px 0px;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
background: #ffffff;
|
||||
background: #f5f6fe;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.btnCtrl {
|
||||
display:flex;
|
||||
justify-content:flex-end;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.my-drop-zone {
|
||||
border: solid 1px lightgray;
|
||||
border-radius: 10px;
|
||||
background: whitesmoke;
|
||||
box-shadow: inset 0px 0px 4px 2px #a7a5a52b;
|
||||
height: 50vh;
|
||||
}
|
||||
.nv-file-over {
|
||||
border: solid 2px green;
|
||||
} /* Default class applied to drop zones on over */
|
||||
|
||||
.file-drop-text{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nv-file-over {
|
||||
border: solid 2px green;
|
||||
} /* Default class applied to drop zones on over */
|
||||
|
||||
.file-drop-text{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
|
||||
.progresStatic {
|
||||
margin-top:9px!important;
|
||||
}
|
||||
|
||||
.progress, .progress-static {
|
||||
width: calc(100% - 14px);
|
||||
}
|
||||
}
|
||||
|
||||
.hotEditor {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.excel-parsing {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
position: relative;
|
||||
|
||||
.details {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
top: -45px;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-record-spinner {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
z-index: 500;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
|
||||
.progresStatic {
|
||||
margin-top:32px!important;
|
||||
}
|
||||
|
||||
.card-block, .card-footer {
|
||||
padding: 10px 0px 0px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.content-area {
|
||||
padding: 0 0.8rem 0.8rem 0.8rem !important;
|
||||
padding-top: 0;
|
||||
|
||||
// .card {
|
||||
// min-height: calc(100vh - 160px);
|
||||
// }
|
||||
}
|
||||
|
||||
.drop-area {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
margin: 1px;
|
||||
|
||||
border: 2px dashed #fff;
|
||||
|
||||
z-index: -1;
|
||||
|
||||
span {
|
||||
font-size: 20px;
|
||||
margin-top: 20px;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
#submitBtn, #cancelSubmitBtn {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// Let's leave it here for a reference if there
|
||||
// is an issue with viewboxes/filter modal overlaying
|
||||
// we will remove it if no issues found
|
||||
// .filter-modal {
|
||||
// z-index: 1210;
|
||||
// }
|
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,6 @@ import { EditRecordComponent } from './components/edit-record/edit-record.compon
|
||||
import { UploadStaterComponent } from './components/upload-stater/upload-stater.component'
|
||||
import { EditorRoutingModule } from './editor-routing.module'
|
||||
import { EditorComponent } from './editor.component'
|
||||
import { HomeModule } from '../home/home.module'
|
||||
import { DcTreeModule } from '../shared/dc-tree/dc-tree.module'
|
||||
import { DragDropModule } from '@angular/cdk/drag-drop'
|
||||
import { ViewboxesModule } from '../shared/viewboxes/viewboxes.module'
|
||||
@ -33,7 +32,6 @@ registerAllModules()
|
||||
AppSharedModule,
|
||||
DirectivesModule,
|
||||
SharedModule,
|
||||
HomeModule,
|
||||
PipesModule,
|
||||
DcTreeModule,
|
||||
DragDropModule,
|
||||
|
@ -1,5 +1,10 @@
|
||||
import { DcValidation } from 'src/app/shared/dc-validator/models/dc-validation.model'
|
||||
|
||||
/**
|
||||
* Wrapper for DC Validation because we need `noLinkOption` property
|
||||
* to be used as a flag to show/hide button that generates link for the
|
||||
* edit record modal
|
||||
*/
|
||||
export interface EditRecordModal extends DcValidation {
|
||||
noLinkOption: boolean
|
||||
[key: string]: any
|
||||
|
@ -1,19 +0,0 @@
|
||||
export interface CellValidation {
|
||||
data: string
|
||||
length: number
|
||||
type?: string
|
||||
source: string[]
|
||||
format?: number
|
||||
validator?: any
|
||||
valid?: boolean
|
||||
renderer?: any
|
||||
dateFormat?: string
|
||||
readOnly?: boolean
|
||||
desc?: string
|
||||
correctFormat?: boolean
|
||||
/**
|
||||
* Key for accessing object fields, any type because it can be
|
||||
* any of the types interface have
|
||||
*/
|
||||
[key: string]: any
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
export enum ColumnType {
|
||||
string = 'string',
|
||||
number = 'number'
|
||||
}
|
||||
|
||||
export interface ColumnInterface {
|
||||
id: number | undefined
|
||||
name: string | undefined
|
||||
type: ColumnType | undefined
|
||||
length: number | undefined
|
||||
}
|
||||
|
||||
export class Column implements ColumnInterface {
|
||||
public id: number | undefined
|
||||
public name: string | undefined
|
||||
public type: ColumnType | undefined
|
||||
public length: number | undefined
|
||||
public static fromPlainObject(obj: object) {
|
||||
return Object.assign(new Column(), obj)
|
||||
}
|
||||
|
||||
constructor(id?: number, name?: string, type?: ColumnType, length?: number) {
|
||||
this.id = id
|
||||
this.name = name
|
||||
this.type = type
|
||||
this.length = length
|
||||
}
|
||||
|
||||
get hsType() {
|
||||
return (
|
||||
(this.type === ColumnType.string && 'text') ||
|
||||
(this.type === ColumnType.number && 'numeric') ||
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
@ -1,3 +1,7 @@
|
||||
/**
|
||||
* Model for the dynamic cell validation in the editor
|
||||
* (sending whole row to the backend service and recieving data for the cell dropdown)
|
||||
*/
|
||||
export interface DynamicExtendedCellValidation {
|
||||
DISPLAY_INDEX: number
|
||||
DISPLAY_TYPE: string
|
||||
|
@ -1,8 +1,14 @@
|
||||
/**
|
||||
* Edit record modal - input has been focused event
|
||||
*/
|
||||
export interface EditRecordInputFocusedEvent {
|
||||
event: any
|
||||
colName: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit record modal - dropdown has been changed event
|
||||
*/
|
||||
export interface EditRecordDropdownChangeEvent {
|
||||
colName: string
|
||||
col: number
|
||||
|
@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Editor restrictions model (based on the licencing)
|
||||
*/
|
||||
export interface EditorRestrictions {
|
||||
restrictEditRecord?: boolean // Feature is locked but edit/add record buttons are visible so when user clicks he gets the `locked feature modal`
|
||||
restrictAddRecord?: boolean // Same as editRecord, but for addRecord
|
||||
|
@ -1,71 +0,0 @@
|
||||
import { Column, ColumnType } from './models/column'
|
||||
|
||||
export enum TableType {
|
||||
INPUT = 'In',
|
||||
OUTPUT = 'Out'
|
||||
}
|
||||
|
||||
export interface TableInterface {
|
||||
id: number | undefined
|
||||
name: string | undefined
|
||||
data: Array<Object>
|
||||
columns: Array<Column>
|
||||
type: TableType | undefined
|
||||
}
|
||||
|
||||
export class Table implements TableInterface {
|
||||
public id: number | undefined
|
||||
public name: string | undefined
|
||||
public data: Array<any>
|
||||
public columns: Array<Column> = []
|
||||
public type: TableType | undefined
|
||||
|
||||
public static fromPlainObject(obj: any) {
|
||||
obj.columns = obj.columns.map((column: object) => {
|
||||
return Column.fromPlainObject(column)
|
||||
})
|
||||
|
||||
return Object.assign(new Table(), obj)
|
||||
}
|
||||
|
||||
constructor(
|
||||
id?: number,
|
||||
name?: string,
|
||||
type?: TableType,
|
||||
data?: Array<Object>,
|
||||
columns?: Array<Column>
|
||||
) {
|
||||
this.id = id
|
||||
this.name = name
|
||||
this.type = type
|
||||
|
||||
this.data = data || [{}]
|
||||
this.columns = columns || []
|
||||
}
|
||||
|
||||
public getNextColumnId(): number {
|
||||
let highestIdColumn: any = this.columns.sort(
|
||||
(cA: any, cB: any) => cA.id - cB.id
|
||||
)[this.columns.length - 1]
|
||||
return (highestIdColumn && highestIdColumn.id + 1) || 0
|
||||
}
|
||||
|
||||
public addColumn(column: Column) {
|
||||
this.columns.push(column)
|
||||
|
||||
this.data.forEach((row: any) => {
|
||||
if (column.name) {
|
||||
row[column.name] = column.type === ColumnType.string ? '' : null
|
||||
}
|
||||
})
|
||||
|
||||
return column
|
||||
}
|
||||
|
||||
public removeColumn(colInd: any) {
|
||||
this.data.forEach((row) => {
|
||||
delete row[this.columns[colInd].name!]
|
||||
})
|
||||
this.columns.splice(colInd, 1)
|
||||
}
|
||||
}
|
@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Converting date object to the UTC time string
|
||||
*/
|
||||
export const dateToUtcTime = (date: Date) => {
|
||||
let timeStr = ('0' + date.getUTCHours()).slice(-2) + ':'
|
||||
timeStr = timeStr + ('0' + date.getUTCMinutes()).slice(-2) + ':'
|
||||
@ -5,6 +8,9 @@ export const dateToUtcTime = (date: Date) => {
|
||||
return timeStr
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts date object to the time string
|
||||
*/
|
||||
export const dateToTime = (date: Date) => {
|
||||
let timeStr = ('0' + date.getHours()).slice(-2) + ':'
|
||||
timeStr = timeStr + ('0' + date.getMinutes()).slice(-2) + ':'
|
||||
@ -12,6 +18,9 @@ export const dateToTime = (date: Date) => {
|
||||
return timeStr
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts date object to the YYYY-MM-DD
|
||||
*/
|
||||
export const dateFormat = (date: Date) => {
|
||||
return (
|
||||
date.getFullYear() +
|
||||
|
@ -1,9 +1,17 @@
|
||||
import { Col } from 'src/app/shared/dc-validator/models/col.model'
|
||||
|
||||
/**
|
||||
* Converts excel date serial number to JS date
|
||||
*/
|
||||
export const excelDateToJSDate = (serial: number) => {
|
||||
return new Date(Math.round((serial - 25569) * 86400 * 1000))
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsing table columns for the HOT in editor
|
||||
* Converts array of objects into array of strings, every string is column name (key)
|
||||
* @param data array of objects (columns data)
|
||||
*/
|
||||
export const parseTableColumns = (data: Col[]): string[] => {
|
||||
const columns: string[] = []
|
||||
|
||||
@ -16,6 +24,12 @@ export const parseTableColumns = (data: Col[]): string[] => {
|
||||
return columns
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures headers that are not found in the current table but is found in the uploaded file data
|
||||
* @param data
|
||||
* @param headers
|
||||
* @returns string array of missing headers
|
||||
*/
|
||||
export const getMissingHeaders = (data: any, headers: any) => {
|
||||
const missingHeaders: string[] = []
|
||||
const remainingHeaders: string[] = []
|
||||
|
@ -1,3 +1,7 @@
|
||||
/**
|
||||
* Custom renderer for HOT cell
|
||||
* Used to show error icon
|
||||
*/
|
||||
export const errorRenderer = (
|
||||
instance: any,
|
||||
td: any,
|
||||
@ -7,13 +11,19 @@ export const errorRenderer = (
|
||||
value: any,
|
||||
cellProperties: any
|
||||
) => {
|
||||
addDarkClass(td)
|
||||
|
||||
td.innerHTML = `${
|
||||
value ? value.toString() : ''
|
||||
} <clr-icon shape="exclamation-circle" status="warning"></clr-icon>`
|
||||
} <cds-icon shape="exclamation-triangle" status="warning"></cds-icon>`
|
||||
|
||||
return td
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom renderer for HOT cell
|
||||
* Used to revert cell back to original state (no spinner, no error)
|
||||
*/
|
||||
export const noSpinnerRenderer = (
|
||||
instance: any,
|
||||
td: any,
|
||||
@ -23,12 +33,18 @@ export const noSpinnerRenderer = (
|
||||
value: any,
|
||||
cellProperties: any
|
||||
) => {
|
||||
addDarkClass(td)
|
||||
|
||||
td.innerHTML = value ? value : ''
|
||||
|
||||
return td
|
||||
}
|
||||
|
||||
// Spinner shown whilst waiting for SAS to respond
|
||||
/**
|
||||
* Custom renderer for HOT cell
|
||||
* Used to show loading spinner in the cell
|
||||
* (Spinner shown whilst waiting for SAS to respond)
|
||||
*/
|
||||
export const spinnerRenderer = (
|
||||
instance: any,
|
||||
td: any,
|
||||
@ -38,9 +54,20 @@ export const spinnerRenderer = (
|
||||
value: any,
|
||||
cellProperties: any
|
||||
) => {
|
||||
addDarkClass(td)
|
||||
|
||||
td.innerHTML = `${
|
||||
value ? value.toString() : ''
|
||||
} <span class="spinner spinner-sm vertical-align-middle"></span>`
|
||||
|
||||
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">
|
||||
<div class="tree-search-wrapper">
|
||||
<input
|
||||
appStealFocus
|
||||
clrInput
|
||||
#searchLibTreeInput
|
||||
placeholder="Filter by Groups"
|
||||
@ -27,7 +28,7 @@
|
||||
<clr-tree-node
|
||||
(click)="groupOnClick(group)"
|
||||
*ngIf="!group['hidden']"
|
||||
[class.table-active]="group.GROUPURI === groupUri"
|
||||
[class.active]="group.GROUPURI === groupUri"
|
||||
>
|
||||
<p class="m-0 cursor-pointer list-padding">
|
||||
<clr-icon shape="users"></clr-icon>
|
||||
|
@ -1,52 +0,0 @@
|
||||
.sidebar-height{
|
||||
height: 100%;
|
||||
}
|
||||
.group-info-text{
|
||||
display: inline;
|
||||
font-size: 20px;
|
||||
}
|
||||
.group-info{
|
||||
background-color: #f9f9f9;
|
||||
border: 1px solid #a7a7a7;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0px 2px 5px #dad7d7;
|
||||
}
|
||||
.group-info td{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.group-data{
|
||||
background-color: #f9f9f9;
|
||||
border: 1px solid #a7a7a7;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0px 2px 5px #dad7d7;
|
||||
}
|
||||
.group-data{
|
||||
min-height: auto;
|
||||
h3, h5{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.member-table{
|
||||
background-color: #f9f9f9;
|
||||
width: 100%;
|
||||
}
|
||||
.member-table thead{
|
||||
background-color: #dadada;
|
||||
}
|
||||
.member-table tbody{
|
||||
tr:hover{
|
||||
background-color: #e6e6e6;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
.table-container{
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px){
|
||||
.group-data{
|
||||
min-height: unset !important;
|
||||
}
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
import { Location } from '@angular/common'
|
||||
import { Component, OnInit } from '@angular/core'
|
||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { SASjsConfig } from '@sasjs/adapter'
|
||||
import { ServerType } from '@sasjs/utils/types/serverType'
|
||||
import { HelperService } from '../services/helper.service'
|
||||
import { SasService } from '../services/sas.service'
|
||||
import { globals } from '../_globals'
|
||||
import { RequestWrapperResponse } from '../models/request-wrapper/RequestWrapperResponse'
|
||||
|
||||
@Component({
|
||||
selector: 'app-group',
|
||||
@ -13,7 +14,8 @@ import { globals } from '../_globals'
|
||||
styleUrls: ['./group.component.scss'],
|
||||
host: {
|
||||
class: 'content-container'
|
||||
}
|
||||
},
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class GroupComponent implements OnInit {
|
||||
public groups: Array<any> | undefined
|
||||
@ -82,11 +84,13 @@ export class GroupComponent implements OnInit {
|
||||
globals.usernav.groupList = groups
|
||||
})
|
||||
} else {
|
||||
this.sasService.request('public/getgroups', null).then((res: any) => {
|
||||
this.loading = false
|
||||
this.groups = res.groups
|
||||
globals.usernav.groupList = res.groups
|
||||
})
|
||||
this.sasService
|
||||
.request('public/getgroups', null)
|
||||
.then((res: RequestWrapperResponse) => {
|
||||
this.loading = false
|
||||
this.groups = res.adapterResponse.groups
|
||||
globals.usernav.groupList = res.adapterResponse.groups
|
||||
})
|
||||
}
|
||||
} else {
|
||||
this.groups = globals.usernav.groupList
|
||||
@ -128,14 +132,15 @@ export class GroupComponent implements OnInit {
|
||||
let data = { iwant: [{ groupid: this.paramURI }] }
|
||||
this.sasService
|
||||
.request('usernav/usermembersbygroup', data)
|
||||
.then((res: any) => {
|
||||
this.groupMembers = res.sasmembers
|
||||
this.groupMemberCount = res.sasmembers.length
|
||||
if (res.sasmembers[0] !== undefined) {
|
||||
.then((res: RequestWrapperResponse) => {
|
||||
this.groupMembers = res.adapterResponse.sasmembers
|
||||
this.groupMemberCount = res.adapterResponse.sasmembers.length
|
||||
if (res.adapterResponse.sasmembers[0] !== undefined) {
|
||||
this.loading = false
|
||||
this.groupUri = res.sasmembers[0].URIMEM || this.paramURI
|
||||
this.groupName = res.sasmembers[0].GROUPNAME
|
||||
this.groupDesc = res.sasmembers[0].GROUPDESC
|
||||
this.groupUri =
|
||||
res.adapterResponse.sasmembers[0].URIMEM || this.paramURI
|
||||
this.groupName = res.adapterResponse.sasmembers[0].GROUPNAME
|
||||
this.groupDesc = res.adapterResponse.sasmembers[0].GROUPDESC
|
||||
|
||||
if (!this.groupName) {
|
||||
this.groupName = this.paramURI
|
||||
@ -202,13 +207,13 @@ export class GroupComponent implements OnInit {
|
||||
|
||||
this.sasService
|
||||
.request('usernav/usermembersbygroup', data)
|
||||
.then((res: any) => {
|
||||
.then((res: RequestWrapperResponse) => {
|
||||
this.loading = false
|
||||
this.groupUri = group.GROUPURI
|
||||
this.groupName = group.GROUPNAME
|
||||
this.groupDesc = group.GROUPDESC
|
||||
this.groupMembers = res.sasmembers
|
||||
this.groupMemberCount = res.sasmembers.length
|
||||
this.groupMembers = res.adapterResponse.sasmembers
|
||||
this.groupMemberCount = res.adapterResponse.sasmembers.length
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +0,0 @@
|
||||
.rejected {
|
||||
color: #f83126;
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
.accepted {
|
||||
color: #3fc424;
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
.hsCell {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
justify-content: center !important;
|
||||
align-items: center !important;
|
||||
padding: 7px;
|
||||
}
|
||||
.btCell {
|
||||
display: flex !important;
|
||||
justify-content: center !important;
|
||||
}
|
||||
|
||||
.verCenter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
input {
|
||||
width: 90px;
|
||||
}
|
||||
}
|
||||
|
||||
#noDataContainer {
|
||||
height: calc(100vh - 200px);
|
||||
}
|
25
client/src/app/home/home-routing.module.ts
Normal file
25
client/src/app/home/home-routing.module.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { HomeRouteComponent } from '../routes/home-route/home-route.component'
|
||||
import { HomeComponent } from './home.component'
|
||||
import { XLMapModule } from '../xlmap/xlmap.module'
|
||||
import { MultiDatasetModule } from '../multi-dataset/multi-dataset.module'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: HomeRouteComponent,
|
||||
children: [
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'tables' },
|
||||
{ path: 'tables', component: HomeComponent },
|
||||
{ path: 'excel-maps', loadChildren: () => XLMapModule },
|
||||
{ path: 'multi-load', loadChildren: () => MultiDatasetModule }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class HomeRoutingModule {}
|
@ -4,6 +4,7 @@
|
||||
<div class="tree-search-wrapper">
|
||||
<input
|
||||
clrInput
|
||||
appStealFocus
|
||||
#searchLibTreeInput
|
||||
placeholder="Libraries"
|
||||
name="input"
|
||||
@ -46,6 +47,7 @@
|
||||
<clr-tree-node *ngIf="library['tables']" class="search-node">
|
||||
<div class="tree-search-wrapper">
|
||||
<input
|
||||
appStealFocus
|
||||
clrInput
|
||||
#searchTreeInput
|
||||
placeholder="Tables"
|
||||
@ -85,7 +87,7 @@
|
||||
(click)="!tableLocked ? onTableClick(libTable, library) : ''"
|
||||
class="clr-treenode-link"
|
||||
[class.dc-locked-control]="tableLocked"
|
||||
[class.table-active]="libTabActive(library.LIBRARYREF, libTable)"
|
||||
[class.active]="libTabActive(library.LIBRARYREF, libTable)"
|
||||
>
|
||||
<ng-container [ngSwitch]="libTable.includes('-FC')">
|
||||
<clr-icon *ngSwitchCase="true" shape="bolt"></clr-icon>
|
||||
@ -94,15 +96,17 @@
|
||||
{{ libTable.replace('-FC', '') }}
|
||||
</button>
|
||||
|
||||
<clr-tooltip-content
|
||||
clrPosition="bottom-right"
|
||||
clrSize="lg"
|
||||
*clrIfOpen
|
||||
>
|
||||
<span *ngIf="tableLocked">
|
||||
To unlock all tables, contact support@datacontroller.io
|
||||
</span>
|
||||
</clr-tooltip-content>
|
||||
<ng-container *ngIf="tableLocked">
|
||||
<clr-tooltip-content
|
||||
clrPosition="bottom-right"
|
||||
clrSize="lg"
|
||||
*clrIfOpen
|
||||
>
|
||||
<span>
|
||||
To unlock all tables, contact support@datacontroller.io
|
||||
</span>
|
||||
</clr-tooltip-content>
|
||||
</ng-container>
|
||||
</clr-tooltip>
|
||||
</clr-tree-node>
|
||||
</clr-tree-node>
|
||||
@ -119,17 +123,25 @@
|
||||
</div>
|
||||
|
||||
<div *ngIf="!loading" class="no-table-selected">
|
||||
<clr-icon
|
||||
shape="warning-standard"
|
||||
size="60"
|
||||
class="is-info icon-dc-fill"
|
||||
></clr-icon>
|
||||
<h3 *ngIf="treeNodeLibraries?.length! > 0" class="text-center color-gray">
|
||||
<img
|
||||
src="images/select-table.png"
|
||||
class="select-table-icon"
|
||||
alt="select table icon"
|
||||
/>
|
||||
<p
|
||||
*ngIf="treeNodeLibraries?.length! > 0"
|
||||
class="text-center color-gray mt-10"
|
||||
cds-text="section"
|
||||
>
|
||||
Please select a table
|
||||
</h3>
|
||||
<h3 *ngIf="treeNodeLibraries?.length! < 1" class="text-center color-gray">
|
||||
</p>
|
||||
<p
|
||||
*ngIf="treeNodeLibraries?.length! < 1"
|
||||
class="text-center color-gray mt-10"
|
||||
cds-text="section"
|
||||
>
|
||||
No Editable Tables Configured
|
||||
</h3>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,32 +0,0 @@
|
||||
clr-tree-node button {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.card-block {
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.no-table-selected {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
::ng-deep {
|
||||
// .badge.badge-info {
|
||||
// background: #6a9235!important;
|
||||
// color: #fff;
|
||||
// }
|
||||
clr-icon.is-blue, clr-icon.is-info {
|
||||
fill: #6a9235;
|
||||
}
|
||||
}
|
||||
|
||||
.spinner-wrapper-fullpage {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user