Compare commits
	
		
			84 Commits
		
	
	
		
			v6.6.0
			...
			d77f2eb674
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					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 | 
@@ -10,7 +10,7 @@ jobs:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: actions/setup-node@v3
 | 
			
		||||
        with:
 | 
			
		||||
          node-version: 18
 | 
			
		||||
          node-version: 20
 | 
			
		||||
 | 
			
		||||
      - name: Write .npmrc file
 | 
			
		||||
        run: echo "$NPMRC" > client/.npmrc
 | 
			
		||||
@@ -21,8 +21,21 @@ jobs:
 | 
			
		||||
      - name: Lint check
 | 
			
		||||
        run: npm run lint:check
 | 
			
		||||
 | 
			
		||||
      - name: Licence checker
 | 
			
		||||
      - name: Install dependencies
 | 
			
		||||
        run: |
 | 
			
		||||
          cd client
 | 
			
		||||
          npm ci
 | 
			
		||||
          npm run license-checker
 | 
			
		||||
          # Decrypt and Install sheet
 | 
			
		||||
          echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg
 | 
			
		||||
          npm i ./libraries/sheet-crypto.tgz
 | 
			
		||||
          # End
 | 
			
		||||
 | 
			
		||||
      - name: Licence checker
 | 
			
		||||
        run: |
 | 
			
		||||
          cd client
 | 
			
		||||
          npm run license-checker
 | 
			
		||||
 | 
			
		||||
      - name: Angular Tests
 | 
			
		||||
        run: |
 | 
			
		||||
          cd client
 | 
			
		||||
          npm run test:headless
 | 
			
		||||
@@ -13,7 +13,7 @@ jobs:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: actions/setup-node@v3
 | 
			
		||||
        with:
 | 
			
		||||
          node-version: 18
 | 
			
		||||
          node-version: 20
 | 
			
		||||
 | 
			
		||||
      - name: Write .npmrc file
 | 
			
		||||
        run: |
 | 
			
		||||
@@ -34,7 +34,13 @@ jobs:
 | 
			
		||||
          CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }}
 | 
			
		||||
 | 
			
		||||
      - name: Install dependencies
 | 
			
		||||
        run: npm ci
 | 
			
		||||
        run: |
 | 
			
		||||
          cd client
 | 
			
		||||
          npm ci
 | 
			
		||||
          # Decrypt and Install sheet
 | 
			
		||||
          echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg
 | 
			
		||||
          npm i ./libraries/sheet-crypto.tgz
 | 
			
		||||
          # End
 | 
			
		||||
 | 
			
		||||
      - name: Check audit
 | 
			
		||||
      # Audit should fail and stop the CI if critical vulnerability found
 | 
			
		||||
@@ -48,7 +54,7 @@ jobs:
 | 
			
		||||
      - name: Angular Tests
 | 
			
		||||
        run: |
 | 
			
		||||
          cd client
 | 
			
		||||
          npm test -- --no-watch --no-progress --browsers=ChromeHeadlessCI
 | 
			
		||||
          npm run test:headless
 | 
			
		||||
 | 
			
		||||
      - name: Angular Production Build
 | 
			
		||||
        run: |
 | 
			
		||||
@@ -64,7 +70,7 @@ jobs:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: actions/setup-node@v3
 | 
			
		||||
        with:
 | 
			
		||||
          node-version: 18
 | 
			
		||||
          node-version: 20
 | 
			
		||||
 | 
			
		||||
      - name: Write .npmrc file
 | 
			
		||||
        run: |
 | 
			
		||||
@@ -86,7 +92,13 @@ jobs:
 | 
			
		||||
          CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }}
 | 
			
		||||
 | 
			
		||||
      - name: Install dependencies
 | 
			
		||||
        run: npm ci
 | 
			
		||||
        run: |
 | 
			
		||||
          cd client
 | 
			
		||||
          npm ci
 | 
			
		||||
          # Decrypt and Install sheet
 | 
			
		||||
          echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg
 | 
			
		||||
          npm i ./libraries/sheet-crypto.tgz
 | 
			
		||||
          # End
 | 
			
		||||
 | 
			
		||||
      # Install pm2 and prepare SASJS server
 | 
			
		||||
      - run: npm i -g pm2
 | 
			
		||||
@@ -185,6 +197,10 @@ jobs:
 | 
			
		||||
        run: |
 | 
			
		||||
          cd client
 | 
			
		||||
          npm ci
 | 
			
		||||
          # Decrypt and Install sheet
 | 
			
		||||
          echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg
 | 
			
		||||
          npm i ./libraries/sheet-crypto.tgz
 | 
			
		||||
          # End
 | 
			
		||||
          npm run build
 | 
			
		||||
 | 
			
		||||
      - name: Build SAS9 EBI Release
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -11,6 +11,7 @@ client/cypress/screenshots
 | 
			
		||||
client/cypress/results
 | 
			
		||||
client/cypress/videos
 | 
			
		||||
client/documentation
 | 
			
		||||
client/sheet-crypto*
 | 
			
		||||
cypress.env.json
 | 
			
		||||
sasjsbuild
 | 
			
		||||
sasjsresults
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										37
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,3 +1,40 @@
 | 
			
		||||
# [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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -27,4 +27,6 @@ For more information:
 | 
			
		||||
 | 
			
		||||
* Main site:  https://datacontroller.io
 | 
			
		||||
* Docs:  https://docs.datacontroller.io
 | 
			
		||||
* Code: https://code.datacontroller.io
 | 
			
		||||
* Code: https://code.datacontroller.io
 | 
			
		||||
 | 
			
		||||
For support, contact support@4gl.io or reach out on [Matrix](https://matrix.to/#/#dc:4gl.io)!
 | 
			
		||||
@@ -45,6 +45,7 @@
 | 
			
		||||
              "numbro",
 | 
			
		||||
              "@clr/icons",
 | 
			
		||||
              "@sasjs/adapter",
 | 
			
		||||
              "@sasjs/utils/types/serverType",
 | 
			
		||||
              "@sasjs/utils/input/validators",
 | 
			
		||||
              "@sasjs/utils/utils/bytesToSize",
 | 
			
		||||
              "base64-arraybuffer",
 | 
			
		||||
@@ -67,7 +68,6 @@
 | 
			
		||||
              "src/styles.scss"
 | 
			
		||||
            ],
 | 
			
		||||
            "scripts": [
 | 
			
		||||
              "node_modules/@clr/icons/clr-icons.min.js",
 | 
			
		||||
              "node_modules/marked/marked.min.js"
 | 
			
		||||
            ]
 | 
			
		||||
          },
 | 
			
		||||
@@ -116,10 +116,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 +127,27 @@
 | 
			
		||||
        "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": []
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "lint": {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,8 @@ export default defineConfig({
 | 
			
		||||
    html: true,
 | 
			
		||||
    json: false,
 | 
			
		||||
  },
 | 
			
		||||
  viewportHeight: 900,
 | 
			
		||||
  viewportWidth: 1600,
 | 
			
		||||
 | 
			
		||||
  chromeWebSecurity: false,
 | 
			
		||||
  defaultCommandTimeout: 30000,
 | 
			
		||||
 
 | 
			
		||||
@@ -221,13 +221,13 @@ 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')
 | 
			
		||||
            .includes('approve')
 | 
			
		||||
        ) {
 | 
			
		||||
          approvalButton.click()
 | 
			
		||||
          break
 | 
			
		||||
 
 | 
			
		||||
@@ -405,13 +405,13 @@ 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')
 | 
			
		||||
            .includes('approve')
 | 
			
		||||
        ) {
 | 
			
		||||
          approvalButton.click()
 | 
			
		||||
          break
 | 
			
		||||
@@ -438,13 +438,13 @@ 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')
 | 
			
		||||
            .includes('approve')
 | 
			
		||||
        ) {
 | 
			
		||||
          approvalButton.click()
 | 
			
		||||
          break
 | 
			
		||||
 
 | 
			
		||||
@@ -699,13 +699,13 @@ 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')
 | 
			
		||||
            .includes('approve')
 | 
			
		||||
        ) {
 | 
			
		||||
          approvalButton.click()
 | 
			
		||||
          break
 | 
			
		||||
 
 | 
			
		||||
@@ -125,13 +125,13 @@ 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')
 | 
			
		||||
            .includes('approve')
 | 
			
		||||
        ) {
 | 
			
		||||
          approvalButton.click()
 | 
			
		||||
          break
 | 
			
		||||
 
 | 
			
		||||
@@ -221,14 +221,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
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -407,14 +407,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 +436,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
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -699,14 +699,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
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -125,14 +125,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
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,45 +0,0 @@
 | 
			
		||||
// Karma configuration file, see link for more information
 | 
			
		||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
 | 
			
		||||
 | 
			
		||||
module.exports = function (config) {
 | 
			
		||||
  config.set({
 | 
			
		||||
    basePath: '',
 | 
			
		||||
    frameworks: ['jasmine', '@angular-devkit/build-angular'],
 | 
			
		||||
    plugins: [
 | 
			
		||||
      require('karma-jasmine'),
 | 
			
		||||
      require('karma-chrome-launcher'),
 | 
			
		||||
      require('karma-jasmine-html-reporter'),
 | 
			
		||||
      require('karma-coverage'),
 | 
			
		||||
      require('@angular-devkit/build-angular/plugins/karma')
 | 
			
		||||
    ],
 | 
			
		||||
    client: {
 | 
			
		||||
      jasmine: {
 | 
			
		||||
        // you can add configuration options for Jasmine here
 | 
			
		||||
        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
 | 
			
		||||
        // for example, you can disable the random execution with `random: false`
 | 
			
		||||
        // or set a specific seed with `seed: 4321`
 | 
			
		||||
      },
 | 
			
		||||
      clearContext: false // leave Jasmine Spec Runner output visible in browser
 | 
			
		||||
    },
 | 
			
		||||
    jasmineHtmlReporter: {
 | 
			
		||||
      suppressAll: true // removes the duplicated traces
 | 
			
		||||
    },
 | 
			
		||||
    coverageReporter: {
 | 
			
		||||
      dir: require('path').join(__dirname, './coverage/datacontroller'),
 | 
			
		||||
      subdir: '.',
 | 
			
		||||
      reporters: [
 | 
			
		||||
        { type: 'html' },
 | 
			
		||||
        { type: 'text-summary' }
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    reporters: ['progress', 'kjhtml'],
 | 
			
		||||
    browsers: ['Chrome', 'ChromeHeadless', 'ChromeHeadlessCI'],
 | 
			
		||||
    restartOnFileChange: true,
 | 
			
		||||
    customLaunchers: {
 | 
			
		||||
      ChromeHeadlessCI: {
 | 
			
		||||
        base: 'ChromeHeadless',
 | 
			
		||||
        flags: ['--no-sandbox']
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										
											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.1.0;handsontable@13.1.0;hyperformula@2.5.0;jackspeak@2.2.0;path-scurry@1.7.0'
 | 
			
		||||
          '@cds/city@1.1.0;@handsontable/angular@13.1.0;handsontable@13.1.0;hyperformula@2.7.0;jackspeak@2.2.0;path-scurry@1.7.0'
 | 
			
		||||
      },
 | 
			
		||||
      (error, json) => {
 | 
			
		||||
        if (error) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										25590
									
								
								client/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										25590
									
								
								client/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -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 ChromeHeadless",
 | 
			
		||||
    "watch": "ng test watch=true",
 | 
			
		||||
    "pree2e": "webdriver-manager update",
 | 
			
		||||
    "e2e": "protractor protractor.config.js",
 | 
			
		||||
@@ -35,23 +35,22 @@
 | 
			
		||||
  },
 | 
			
		||||
  "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.10.0",
 | 
			
		||||
    "@clr/angular": "^17.0.1",
 | 
			
		||||
    "@clr/icons": "^13.0.2",
 | 
			
		||||
    "@clr/ui": "^13.17.0",
 | 
			
		||||
    "@clr/ui": "^17.0.1",
 | 
			
		||||
    "@handsontable/angular": "^13.1.0",
 | 
			
		||||
    "@sasjs/adapter": "4.10.1",
 | 
			
		||||
    "@sasjs/adapter": "4.10.2",
 | 
			
		||||
    "@sasjs/utils": "^3.4.0",
 | 
			
		||||
    "@sheet/crypto": "1.20211122.1",
 | 
			
		||||
    "@types/d3-graphviz": "^2.6.7",
 | 
			
		||||
    "@types/text-encoding": "0.0.35",
 | 
			
		||||
    "base64-arraybuffer": "^0.2.0",
 | 
			
		||||
@@ -78,24 +77,25 @@
 | 
			
		||||
    "stream-http": "3.2.0",
 | 
			
		||||
    "text-encoding": "^0.7.0",
 | 
			
		||||
    "tslib": "^2.3.0",
 | 
			
		||||
    "zone.js": "~0.13.0"
 | 
			
		||||
    "vm": "^0.1.0",
 | 
			
		||||
    "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",
 | 
			
		||||
    "@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.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",
 | 
			
		||||
@@ -109,12 +109,12 @@
 | 
			
		||||
    "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",
 | 
			
		||||
@@ -123,9 +123,7 @@
 | 
			
		||||
    "rimraf": "3.0.2",
 | 
			
		||||
    "ts-loader": "^9.2.8",
 | 
			
		||||
    "ts-node": "^3.3.0",
 | 
			
		||||
    "typedoc": "^0.24.8",
 | 
			
		||||
    "typedoc-plugin-external-module-name": "^4.0.6",
 | 
			
		||||
    "typescript": "~4.9.4",
 | 
			
		||||
    "typescript": "~5.4.4",
 | 
			
		||||
    "wait-on": "^6.0.1",
 | 
			
		||||
    "watch": "^1.0.2"
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -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">
 | 
			
		||||
@@ -204,14 +204,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="
 | 
			
		||||
 
 | 
			
		||||
@@ -91,33 +91,12 @@ header {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .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 {
 | 
			
		||||
  .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;
 | 
			
		||||
  .nav-link.active {
 | 
			
		||||
    background: #61717D;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,6 @@ import { NgxJsonViewerModule } from 'ngx-json-viewer'
 | 
			
		||||
    SharedModule,
 | 
			
		||||
    ClarityModule,
 | 
			
		||||
    AppSharedModule,
 | 
			
		||||
    HomeModule,
 | 
			
		||||
    PipesModule,
 | 
			
		||||
    DirectivesModule,
 | 
			
		||||
    NgxJsonViewerModule
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,10 @@ export const ROUTES: Routes = [
 | 
			
		||||
    path: 'licensing',
 | 
			
		||||
    loadChildren: () => LicensingModule
 | 
			
		||||
  },
 | 
			
		||||
  { path: 'home', loadChildren: () => HomeModule },
 | 
			
		||||
  {
 | 
			
		||||
    path: 'home',
 | 
			
		||||
    loadChildren: () => HomeModule
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    /**
 | 
			
		||||
     * Load editor module with subroutes
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
                    )
 | 
			
		||||
                  "
 | 
			
		||||
 
 | 
			
		||||
@@ -203,18 +203,21 @@
 | 
			
		||||
 | 
			
		||||
              <span clrTooltipTrigger>
 | 
			
		||||
                {{ libdsParsed.libName }}.<a
 | 
			
		||||
                  class="mr-10"
 | 
			
		||||
                  class="mr-10 view-table"
 | 
			
		||||
                  [routerLink]="'/view/data/' + libds!"
 | 
			
		||||
                  >{{ libdsParsed.tableName.replace('-FC', '') }}</a
 | 
			
		||||
                >
 | 
			
		||||
              </span>
 | 
			
		||||
              <clr-tooltip-content
 | 
			
		||||
                clrPosition="bottom-left"
 | 
			
		||||
                clrSize="lg"
 | 
			
		||||
                *clrIfOpen
 | 
			
		||||
              >
 | 
			
		||||
                {{ this.dsNote }}
 | 
			
		||||
              </clr-tooltip-content>
 | 
			
		||||
 | 
			
		||||
              <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>
 | 
			
		||||
 | 
			
		||||
            <ng-container *ngIf="dataSource">
 | 
			
		||||
@@ -843,6 +846,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>
 | 
			
		||||
 
 | 
			
		||||
@@ -214,6 +214,10 @@ hot-table {
 | 
			
		||||
  width: 150px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.view-table {
 | 
			
		||||
  font-size: inherit !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FIXME
 | 
			
		||||
// Let's leave it here for a reference if there
 | 
			
		||||
// is an issue with viewboxes/filter modal overlaying
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,8 @@ import { HotTableInterface } from '../models/HotTable.interface'
 | 
			
		||||
import {
 | 
			
		||||
  $DataFormats,
 | 
			
		||||
  DSMeta,
 | 
			
		||||
  EditorsGetDataServiceResponse
 | 
			
		||||
  EditorsGetDataServiceResponse,
 | 
			
		||||
  Version
 | 
			
		||||
} from '../models/sas/editors-getdata.model'
 | 
			
		||||
import { DataFormat } from '../models/sas/common/DateFormat'
 | 
			
		||||
import SheetInfo from '../models/SheetInfo'
 | 
			
		||||
@@ -121,6 +122,7 @@ export class EditorComponent implements OnInit, AfterViewInit {
 | 
			
		||||
 | 
			
		||||
  datasetInfo: boolean = false
 | 
			
		||||
  dsmeta: DSMeta[] = []
 | 
			
		||||
  versions: Version[] = []
 | 
			
		||||
  dsNote = ''
 | 
			
		||||
 | 
			
		||||
  viewboxes: boolean = false
 | 
			
		||||
@@ -940,13 +942,30 @@ export class EditorComponent implements OnInit, AfterViewInit {
 | 
			
		||||
            return row.map((col: any, index: number) => {
 | 
			
		||||
              if (!col && col !== 0) col = ''
 | 
			
		||||
 | 
			
		||||
              if (isNaN(col)) {
 | 
			
		||||
                col = col.replace(/"/g, '""')
 | 
			
		||||
              /**
 | 
			
		||||
               * Keeping this for the reference
 | 
			
		||||
               * Code below used to convert JSON to CSV
 | 
			
		||||
               * now the XLSX is converting to CSV
 | 
			
		||||
               */
 | 
			
		||||
              // if (isNaN(col)) {
 | 
			
		||||
              //   // Match and replace the double quotes, ignore the first and last char
 | 
			
		||||
              //   // in case they are double quotes already
 | 
			
		||||
              //   col = col.replace(/(?<!^)"(?!$)/g, '""')
 | 
			
		||||
 | 
			
		||||
                if (col.search(/,/g) > -1) {
 | 
			
		||||
                  col = '"' + col + '"'
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
              //   if (col.search(/,/g) > -1 ||
 | 
			
		||||
              //       col.search(/\r|\n/g) > -1
 | 
			
		||||
              //   ) {
 | 
			
		||||
              //     // Missing quotes at the end
 | 
			
		||||
              //     if (col.search(/"$/g) < 0) {
 | 
			
		||||
              //       col = col + '"' // So we add them
 | 
			
		||||
              //     }
 | 
			
		||||
 | 
			
		||||
              //     // Missing quotes at the start
 | 
			
		||||
              //     if (col.search(/^"/g) < 0) {
 | 
			
		||||
              //       col = '"' + col // So we add them
 | 
			
		||||
              //     }
 | 
			
		||||
              //   }
 | 
			
		||||
              // }
 | 
			
		||||
 | 
			
		||||
              const colName = this.headerShow[index]
 | 
			
		||||
              const colRule = this.dcValidator?.getRule(colName)
 | 
			
		||||
@@ -961,20 +980,30 @@ export class EditorComponent implements OnInit, AfterViewInit {
 | 
			
		||||
 | 
			
		||||
          this.data = csvArrayData
 | 
			
		||||
 | 
			
		||||
          let csvContent = csvArrayHeaders.join(',') + '\n'
 | 
			
		||||
          // Apply licence rows limitation if exists
 | 
			
		||||
          csvContent += csvArrayData
 | 
			
		||||
            .slice(0, this.licenceState.value.submit_rows_limit)
 | 
			
		||||
            .map((e) => e.join(','))
 | 
			
		||||
            .join('\n')
 | 
			
		||||
          // Apply licence rows limitation if exists, it is only affecting data
 | 
			
		||||
          // which will be send to SAS
 | 
			
		||||
          const strippedCsvArrayData = csvArrayData.slice(
 | 
			
		||||
            0,
 | 
			
		||||
            this.licenceState.value.submit_rows_limit
 | 
			
		||||
          )
 | 
			
		||||
          // To submit to sas service, we need clean version of CSV of file
 | 
			
		||||
          // attached. XLSX will do the parsing and heavy lifting
 | 
			
		||||
          // First we create worksheet of json (data we extracted)
 | 
			
		||||
          let ws = XLSX.utils.json_to_sheet(strippedCsvArrayData, {
 | 
			
		||||
            skipHeader: true
 | 
			
		||||
          })
 | 
			
		||||
          // create CSV to be uploaded from worksheet
 | 
			
		||||
          let csvContentClean = XLSX.utils.sheet_to_csv(ws)
 | 
			
		||||
          // Prepend headers
 | 
			
		||||
          csvContentClean = csvArrayHeaders.join(',') + '\n' + csvContentClean
 | 
			
		||||
 | 
			
		||||
          if (this.encoding === 'WLATIN1') {
 | 
			
		||||
            let encoded = iconv.decode(Buffer.from(csvContent), 'CP-1252')
 | 
			
		||||
            let encoded = iconv.decode(Buffer.from(csvContentClean), 'CP-1252')
 | 
			
		||||
            let blob = new Blob([encoded], { type: 'application/csv' })
 | 
			
		||||
            let newCSVFile: File = this.blobToFile(blob, this.filename + '.csv')
 | 
			
		||||
            this.uploader.addToQueue([newCSVFile])
 | 
			
		||||
          } else {
 | 
			
		||||
            let blob = new Blob([csvContent], { type: 'application/csv' })
 | 
			
		||||
            let blob = new Blob([csvContentClean], { type: 'application/csv' })
 | 
			
		||||
            let newCSVFile: File = this.blobToFile(blob, this.filename + '.csv')
 | 
			
		||||
            this.uploader.addToQueue([newCSVFile])
 | 
			
		||||
          }
 | 
			
		||||
@@ -1929,13 +1958,13 @@ export class EditorComponent implements OnInit, AfterViewInit {
 | 
			
		||||
 | 
			
		||||
        if (entry.values.length > 0) {
 | 
			
		||||
          hot.setCellMeta(entry.row, entry.col, 'renderer', 'autocomplete')
 | 
			
		||||
          hot.setCellMeta(entry.row, entry.col, 'editor', 'autocomplete')
 | 
			
		||||
          hot.setCellMeta(entry.row, entry.col, 'editor', 'autocomplete.custom')
 | 
			
		||||
          hot.setCellMeta(entry.row, entry.col, 'strict', entry.strict)
 | 
			
		||||
          hot.setCellMeta(entry.row, entry.col, 'filter', false)
 | 
			
		||||
 | 
			
		||||
          this.currentEditRecordValidator?.updateRule(entry.col, {
 | 
			
		||||
            renderer: 'autocomplete',
 | 
			
		||||
            editor: 'autocomplete',
 | 
			
		||||
            editor: 'autocomplete.custom',
 | 
			
		||||
            strict: entry.strict,
 | 
			
		||||
            filter: false
 | 
			
		||||
          })
 | 
			
		||||
@@ -2030,13 +2059,13 @@ export class EditorComponent implements OnInit, AfterViewInit {
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      hot.setCellMeta(row, cellCol, 'renderer', 'autocomplete')
 | 
			
		||||
      hot.setCellMeta(row, cellCol, 'editor', 'autocomplete')
 | 
			
		||||
      hot.setCellMeta(row, cellCol, 'editor', 'autocomplete.custom')
 | 
			
		||||
      hot.setCellMeta(row, cellCol, 'strict', cellValidationEntry.strict)
 | 
			
		||||
      hot.setCellMeta(row, cellCol, 'filter', false)
 | 
			
		||||
 | 
			
		||||
      this.currentEditRecordValidator?.updateRule(cellCol, {
 | 
			
		||||
        renderer: 'autocomplete',
 | 
			
		||||
        editor: 'autocomplete',
 | 
			
		||||
        editor: 'autocomplete.custom',
 | 
			
		||||
        strict: cellValidationEntry.strict,
 | 
			
		||||
        filter: false
 | 
			
		||||
      })
 | 
			
		||||
@@ -2234,8 +2263,8 @@ export class EditorComponent implements OnInit, AfterViewInit {
 | 
			
		||||
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        let txt: any = document.getElementById('formFields_8')
 | 
			
		||||
        txt.focus()
 | 
			
		||||
      })
 | 
			
		||||
        if (txt) txt.focus()
 | 
			
		||||
      }, 200)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    // let cnt = 0;
 | 
			
		||||
@@ -2679,13 +2708,13 @@ export class EditorComponent implements OnInit, AfterViewInit {
 | 
			
		||||
          const strict = this.cellValidationSource[validationSourceIndex].strict
 | 
			
		||||
 | 
			
		||||
          hot.setCellMeta(row, column, 'renderer', 'autocomplete')
 | 
			
		||||
          hot.setCellMeta(row, column, 'editor', 'autocomplete')
 | 
			
		||||
          hot.setCellMeta(row, column, 'editor', 'autocomplete.custom')
 | 
			
		||||
          hot.setCellMeta(row, column, 'strict', strict)
 | 
			
		||||
          hot.setCellMeta(row, column, 'filter', false)
 | 
			
		||||
 | 
			
		||||
          this.currentEditRecordValidator?.updateRule(column, {
 | 
			
		||||
            renderer: 'autocomplete',
 | 
			
		||||
            editor: 'autocomplete',
 | 
			
		||||
            editor: 'autocomplete.custom',
 | 
			
		||||
            strict: strict,
 | 
			
		||||
            filter: false
 | 
			
		||||
          })
 | 
			
		||||
@@ -2907,6 +2936,16 @@ export class EditorComponent implements OnInit, AfterViewInit {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  datasetInfoModalRowClicked(value: Version | DSMeta) {
 | 
			
		||||
    if ((<Version>value).LOAD_REF !== undefined) {
 | 
			
		||||
      // Type is Version
 | 
			
		||||
      const row = value as Version
 | 
			
		||||
      const url = `/stage/${row.LOAD_REF}`
 | 
			
		||||
 | 
			
		||||
      this.router.navigate([url])
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  viewboxManager() {
 | 
			
		||||
    this.viewboxes = true
 | 
			
		||||
  }
 | 
			
		||||
@@ -2919,6 +2958,37 @@ export class EditorComponent implements OnInit, AfterViewInit {
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Function checks if selected hot cell is solo cell selected
 | 
			
		||||
   * and if it is, set the `filter` property based on filter param.
 | 
			
		||||
   *
 | 
			
		||||
   * @param filter
 | 
			
		||||
   */
 | 
			
		||||
  private setCellFilter(filter: boolean) {
 | 
			
		||||
    const hotSelected = this.hotInstance.getSelected()
 | 
			
		||||
    const selection = hotSelected ? hotSelected[0] : hotSelected
 | 
			
		||||
 | 
			
		||||
    // When we open a dropdown we want filter disabled so value in cell
 | 
			
		||||
    // don't filter out items, since we want to see them all.
 | 
			
		||||
    // But when we start typing we want to be able to start filtering values
 | 
			
		||||
    // again
 | 
			
		||||
    if (selection) {
 | 
			
		||||
      const startRow = selection[0]
 | 
			
		||||
      const endRow = selection[2]
 | 
			
		||||
      const startCell = selection[1]
 | 
			
		||||
      const endCell = selection[3]
 | 
			
		||||
 | 
			
		||||
      if (startRow === endRow && startCell === endCell) {
 | 
			
		||||
        const cellMeta = this.hotInstance.getCellMeta(startRow, startCell)
 | 
			
		||||
 | 
			
		||||
        // If filter is not already set at the value in the param, set it
 | 
			
		||||
        if (cellMeta && cellMeta.filter === !filter) {
 | 
			
		||||
          this.hotInstance.setCellMeta(startRow, startCell, 'filter', filter)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async ngOnInit() {
 | 
			
		||||
    this.licenceService.hot_license_key.subscribe(
 | 
			
		||||
      (hot_license_key: string | undefined) => {
 | 
			
		||||
@@ -2986,6 +3056,7 @@ export class EditorComponent implements OnInit, AfterViewInit {
 | 
			
		||||
 | 
			
		||||
    this.cols = response.data.cols
 | 
			
		||||
    this.dsmeta = response.data.dsmeta
 | 
			
		||||
    this.versions = response.data.versions || []
 | 
			
		||||
 | 
			
		||||
    const notes = this.dsmeta.find((item) => item.NAME === 'NOTES')
 | 
			
		||||
    const longDesc = this.dsmeta.find((item) => item.NAME === 'DD_LONGDESC')
 | 
			
		||||
@@ -3274,28 +3345,16 @@ export class EditorComponent implements OnInit, AfterViewInit {
 | 
			
		||||
      }
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    hot.addHook('beforeKeyDown', (e: any) => {
 | 
			
		||||
      const hotSelected = this.hotInstance.getSelected()
 | 
			
		||||
      const selection = hotSelected ? hotSelected[0] : hotSelected
 | 
			
		||||
 | 
			
		||||
    hot.addHook('afterBeginEditing', () => {
 | 
			
		||||
      // When we open a dropdown we want filter disabled so value in cell
 | 
			
		||||
      // don't filter out items, since we want to see them all.
 | 
			
		||||
      this.setCellFilter(false)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    hot.addHook('beforeKeyDown', () => {
 | 
			
		||||
      // When we start typing, we are enabling the filter since we want to find
 | 
			
		||||
      // values faster.
 | 
			
		||||
      if (selection) {
 | 
			
		||||
        const startRow = selection[0]
 | 
			
		||||
        const endRow = selection[2]
 | 
			
		||||
        const startCell = selection[1]
 | 
			
		||||
        const endCell = selection[3]
 | 
			
		||||
 | 
			
		||||
        if (startRow === endRow && startCell === endCell) {
 | 
			
		||||
          const cellMeta = this.hotInstance.getCellMeta(startRow, startCell)
 | 
			
		||||
 | 
			
		||||
          if (cellMeta && cellMeta.filter === false) {
 | 
			
		||||
            this.hotInstance.setCellMeta(startRow, startCell, 'filter', true)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      this.setCellFilter(true)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    hot.addHook('afterChange', (source: any, change: any) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -94,15 +94,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>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,147 +2,152 @@
 | 
			
		||||
  <div class="card-header">Licencing</div>
 | 
			
		||||
 | 
			
		||||
  <div [ngSwitch]="action" class="card-block">
 | 
			
		||||
    <ng-container *ngSwitchCase="'key'">
 | 
			
		||||
      <p class="key-error" *ngIf="!keyError">
 | 
			
		||||
        Licence key is invalid. We can't provide you more details at the moment
 | 
			
		||||
    <div class="card-text">
 | 
			
		||||
      <ng-container *ngSwitchCase="'key'">
 | 
			
		||||
        <p class="key-error" *ngIf="!keyError">
 | 
			
		||||
          Licence key is invalid. We can't provide you more details at the
 | 
			
		||||
          moment
 | 
			
		||||
        </p>
 | 
			
		||||
 | 
			
		||||
        <p
 | 
			
		||||
          class="key-error"
 | 
			
		||||
          *ngIf="keyError"
 | 
			
		||||
          [innerHTML]="licenseErrors[keyError]"
 | 
			
		||||
        ></p>
 | 
			
		||||
 | 
			
		||||
        <p *ngIf="errorDetails"><strong>Details:</strong> {{ errorDetails }}</p>
 | 
			
		||||
      </ng-container>
 | 
			
		||||
 | 
			
		||||
      <ng-container *ngSwitchCase="'limit'">
 | 
			
		||||
        <p class="key-error">
 | 
			
		||||
          The registered number of users reached the limit specified for your
 | 
			
		||||
          licence. Please contact
 | 
			
		||||
          <contact-link classes="color-green" />
 | 
			
		||||
          or your reseller to arrange additional licences for this product.
 | 
			
		||||
        </p>
 | 
			
		||||
      </ng-container>
 | 
			
		||||
 | 
			
		||||
      <ng-container *ngSwitchCase="'update'">
 | 
			
		||||
        <p class="key-error">
 | 
			
		||||
          Update the license key by uploading the licence file or by pasting a
 | 
			
		||||
          license key and activation key in the inputs below.
 | 
			
		||||
        </p>
 | 
			
		||||
      </ng-container>
 | 
			
		||||
 | 
			
		||||
      <p>
 | 
			
		||||
        <strong>SYSSITE:</strong>
 | 
			
		||||
        <span
 | 
			
		||||
          *ngFor="let id of syssite.value; let i = index"
 | 
			
		||||
          [class.misskey]="missmatchedKey && missmatchedKey === id"
 | 
			
		||||
        >
 | 
			
		||||
          {{ id }}{{ i === syssite.value?.length! - 1 ? '' : ',' }}
 | 
			
		||||
        </span>
 | 
			
		||||
 | 
			
		||||
        <a
 | 
			
		||||
          class="tooltip tooltip-md tooltip-top-right"
 | 
			
		||||
          (click)="copySyssite(copyIcon, copyTooltip, syssite.value || [])"
 | 
			
		||||
        >
 | 
			
		||||
          <clr-icon
 | 
			
		||||
            #copyIcon
 | 
			
		||||
            class="cursor-pointer"
 | 
			
		||||
            shape="copy"
 | 
			
		||||
            size="15"
 | 
			
		||||
          ></clr-icon>
 | 
			
		||||
          <span #copyTooltip class="tooltip-content">Copy to clipboard</span>
 | 
			
		||||
        </a>
 | 
			
		||||
      </p>
 | 
			
		||||
 | 
			
		||||
      <p
 | 
			
		||||
        class="key-error"
 | 
			
		||||
        *ngIf="keyError"
 | 
			
		||||
        [innerHTML]="licenseErrors[keyError]"
 | 
			
		||||
      ></p>
 | 
			
		||||
 | 
			
		||||
      <p *ngIf="errorDetails"><strong>Details:</strong> {{ errorDetails }}</p>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
 | 
			
		||||
    <ng-container *ngSwitchCase="'limit'">
 | 
			
		||||
      <p class="key-error">
 | 
			
		||||
        The registered number of users reached the limit specified for your
 | 
			
		||||
        licence. Please contact
 | 
			
		||||
        <contact-link classes="color-green" />
 | 
			
		||||
        or your reseller to arrange additional licences for this product.
 | 
			
		||||
      <p *ngIf="licenseKeyData && userCountLimitation" class="m-0">
 | 
			
		||||
        <strong>Allowed users:</strong>
 | 
			
		||||
        {{ licenseKeyData.users_allowed }}
 | 
			
		||||
      </p>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
 | 
			
		||||
    <ng-container *ngSwitchCase="'update'">
 | 
			
		||||
      <p class="key-error">
 | 
			
		||||
        Update the license key by uploading the licence file or by pasting a
 | 
			
		||||
        license key and activation key in the inputs below.
 | 
			
		||||
      </p>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      <strong>SYSSITE:</strong>
 | 
			
		||||
      <span
 | 
			
		||||
        *ngFor="let id of syssite.value; let i = index"
 | 
			
		||||
        [class.misskey]="missmatchedKey && missmatchedKey === id"
 | 
			
		||||
      >
 | 
			
		||||
        {{ id }}{{ i === syssite.value?.length! - 1 ? '' : ',' }}
 | 
			
		||||
      </span>
 | 
			
		||||
 | 
			
		||||
      <a
 | 
			
		||||
        class="tooltip tooltip-md tooltip-top-right"
 | 
			
		||||
        (click)="copySyssite(copyIcon, copyTooltip, syssite.value || [])"
 | 
			
		||||
      >
 | 
			
		||||
        <clr-icon
 | 
			
		||||
          #copyIcon
 | 
			
		||||
          class="cursor-pointer"
 | 
			
		||||
          shape="copy"
 | 
			
		||||
          size="15"
 | 
			
		||||
        ></clr-icon>
 | 
			
		||||
        <span #copyTooltip class="tooltip-content">Copy to clipboard</span>
 | 
			
		||||
      </a>
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <p *ngIf="licenseKeyData && userCountLimitation" class="m-0">
 | 
			
		||||
      <strong>Allowed users:</strong>
 | 
			
		||||
      {{ licenseKeyData.users_allowed }}
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <clr-tabs>
 | 
			
		||||
      <clr-tab>
 | 
			
		||||
        <button clrTabLink>Upload licence</button>
 | 
			
		||||
        <clr-tab-content>
 | 
			
		||||
          <input
 | 
			
		||||
            #licenceFile
 | 
			
		||||
            (change)="onFileCapture($event)"
 | 
			
		||||
            type="file"
 | 
			
		||||
            hidden
 | 
			
		||||
          />
 | 
			
		||||
          <div
 | 
			
		||||
            (click)="licenceFile.click()"
 | 
			
		||||
            appFileDrop
 | 
			
		||||
            (fileDrop)="onFileCapture($event, true)"
 | 
			
		||||
            class="drop-area"
 | 
			
		||||
          >
 | 
			
		||||
            <clr-spinner
 | 
			
		||||
              class="spinner-sm"
 | 
			
		||||
              *ngIf="licenceFileLoading"
 | 
			
		||||
            ></clr-spinner>
 | 
			
		||||
            <ng-container *ngIf="!licenceFileLoading">
 | 
			
		||||
              <div *ngIf="licencefile.filename === ''">
 | 
			
		||||
                Drop / Browse licence file
 | 
			
		||||
              </div>
 | 
			
		||||
              <div *ngIf="licencefile.filename !== ''">
 | 
			
		||||
                Selected file: <strong>{{ licencefile.filename }}</strong>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div *ngIf="licenceFileError">
 | 
			
		||||
                <strong>{{ licenceFileError }}</strong>
 | 
			
		||||
              </div>
 | 
			
		||||
            </ng-container>
 | 
			
		||||
          </div>
 | 
			
		||||
        </clr-tab-content>
 | 
			
		||||
      </clr-tab>
 | 
			
		||||
 | 
			
		||||
      <clr-tab>
 | 
			
		||||
        <button clrTabLink>Paste licence</button>
 | 
			
		||||
        <clr-tab-content>
 | 
			
		||||
          <form class="clr-form license-key-form">
 | 
			
		||||
            <p>Licence key:</p>
 | 
			
		||||
            <div class="clr-control-container">
 | 
			
		||||
              <textarea
 | 
			
		||||
                [(ngModel)]="licenceKeyValue"
 | 
			
		||||
                (mouseleave)="trimKeys()"
 | 
			
		||||
                name="license-key-area"
 | 
			
		||||
                placeholder="Paste licence key here"
 | 
			
		||||
                class="clr-textarea"
 | 
			
		||||
              ></textarea>
 | 
			
		||||
      <clr-tabs>
 | 
			
		||||
        <clr-tab>
 | 
			
		||||
          <button clrTabLink>Upload licence</button>
 | 
			
		||||
          <clr-tab-content>
 | 
			
		||||
            <input
 | 
			
		||||
              #licenceFile
 | 
			
		||||
              (change)="onFileCapture($event)"
 | 
			
		||||
              type="file"
 | 
			
		||||
              hidden
 | 
			
		||||
            />
 | 
			
		||||
            <div
 | 
			
		||||
              (click)="licenceFile.click()"
 | 
			
		||||
              appFileDrop
 | 
			
		||||
              (fileDrop)="onFileCapture($event, true)"
 | 
			
		||||
              class="drop-area"
 | 
			
		||||
            >
 | 
			
		||||
              <clr-spinner
 | 
			
		||||
                class="spinner-sm"
 | 
			
		||||
                *ngIf="licenceFileLoading"
 | 
			
		||||
              ></clr-spinner>
 | 
			
		||||
              <ng-container *ngIf="!licenceFileLoading">
 | 
			
		||||
                <div *ngIf="licencefile.filename === ''">
 | 
			
		||||
                  Drop / Browse licence file
 | 
			
		||||
                </div>
 | 
			
		||||
                <div *ngIf="licencefile.filename !== ''">
 | 
			
		||||
                  Selected file: <strong>{{ licencefile.filename }}</strong>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div *ngIf="licenceFileError">
 | 
			
		||||
                  <strong>{{ licenceFileError }}</strong>
 | 
			
		||||
                </div>
 | 
			
		||||
              </ng-container>
 | 
			
		||||
            </div>
 | 
			
		||||
          </form>
 | 
			
		||||
          </clr-tab-content>
 | 
			
		||||
        </clr-tab>
 | 
			
		||||
 | 
			
		||||
          <form class="clr-form activation-key-form">
 | 
			
		||||
            <p>Activation key:</p>
 | 
			
		||||
            <div class="clr-control-container">
 | 
			
		||||
              <textarea
 | 
			
		||||
                [(ngModel)]="activationKeyValue"
 | 
			
		||||
                (mouseleave)="trimKeys()"
 | 
			
		||||
                name="activation-key-area"
 | 
			
		||||
                placeholder="Paste activation key here"
 | 
			
		||||
                class="clr-textarea"
 | 
			
		||||
              ></textarea>
 | 
			
		||||
            </div>
 | 
			
		||||
          </form>
 | 
			
		||||
        </clr-tab-content>
 | 
			
		||||
      </clr-tab>
 | 
			
		||||
    </clr-tabs>
 | 
			
		||||
        <clr-tab>
 | 
			
		||||
          <button clrTabLink>Paste licence</button>
 | 
			
		||||
          <clr-tab-content>
 | 
			
		||||
            <form class="clr-form license-key-form">
 | 
			
		||||
              <p>Licence key:</p>
 | 
			
		||||
              <div class="clr-control-container">
 | 
			
		||||
                <textarea
 | 
			
		||||
                  [(ngModel)]="licenceKeyValue"
 | 
			
		||||
                  (mouseleave)="trimKeys()"
 | 
			
		||||
                  name="license-key-area"
 | 
			
		||||
                  placeholder="Paste licence key here"
 | 
			
		||||
                  class="clr-textarea"
 | 
			
		||||
                ></textarea>
 | 
			
		||||
              </div>
 | 
			
		||||
            </form>
 | 
			
		||||
 | 
			
		||||
    <button
 | 
			
		||||
      (click)="applyKeys()"
 | 
			
		||||
      class="btn btn-primary apply-keys"
 | 
			
		||||
      [clrLoading]="applyingKeys"
 | 
			
		||||
      [disabled]="disableApplyButton"
 | 
			
		||||
    >
 | 
			
		||||
      Apply licence keys
 | 
			
		||||
    </button>
 | 
			
		||||
            <form class="clr-form activation-key-form">
 | 
			
		||||
              <p>Activation key:</p>
 | 
			
		||||
              <div class="clr-control-container">
 | 
			
		||||
                <textarea
 | 
			
		||||
                  [(ngModel)]="activationKeyValue"
 | 
			
		||||
                  (mouseleave)="trimKeys()"
 | 
			
		||||
                  name="activation-key-area"
 | 
			
		||||
                  placeholder="Paste activation key here"
 | 
			
		||||
                  class="clr-textarea"
 | 
			
		||||
                ></textarea>
 | 
			
		||||
              </div>
 | 
			
		||||
            </form>
 | 
			
		||||
          </clr-tab-content>
 | 
			
		||||
        </clr-tab>
 | 
			
		||||
      </clr-tabs>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <button
 | 
			
		||||
      *ngIf="isAppFreeTier.value"
 | 
			
		||||
      routerLink="/"
 | 
			
		||||
      class="btn btn-sm btn-link"
 | 
			
		||||
    >
 | 
			
		||||
      Continue with free tier
 | 
			
		||||
    </button>
 | 
			
		||||
    <div class="card-footer d-flex clr-align-items-center">
 | 
			
		||||
      <button
 | 
			
		||||
        (click)="applyKeys()"
 | 
			
		||||
        class="btn btn-primary apply-keys"
 | 
			
		||||
        [clrLoading]="applyingKeys"
 | 
			
		||||
        [disabled]="disableApplyButton"
 | 
			
		||||
      >
 | 
			
		||||
        Apply licence keys
 | 
			
		||||
      </button>
 | 
			
		||||
 | 
			
		||||
      <button
 | 
			
		||||
        *ngIf="isAppFreeTier.value"
 | 
			
		||||
        routerLink="/"
 | 
			
		||||
        class="btn btn-sm btn-link"
 | 
			
		||||
      >
 | 
			
		||||
        Continue with free tier
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,6 @@
 | 
			
		||||
 | 
			
		||||
.apply-keys {
 | 
			
		||||
  height: 40px;
 | 
			
		||||
  width: 200px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.drop-area {
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ export interface EditorsGetDataSASResponse extends BaseSASResponse {
 | 
			
		||||
  dqrules: DQRule[]
 | 
			
		||||
  dsmeta: DSMeta[]
 | 
			
		||||
  dqdata: DQData[]
 | 
			
		||||
  versions: Version[]
 | 
			
		||||
  cols: Col[]
 | 
			
		||||
  maxvarlengths: Maxvarlength[]
 | 
			
		||||
  xl_rules: any[]
 | 
			
		||||
@@ -27,6 +28,18 @@ export interface DSMeta {
 | 
			
		||||
  ODS_TABLE: string
 | 
			
		||||
  NAME: string
 | 
			
		||||
  VALUE: string
 | 
			
		||||
  [key: string]: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Version {
 | 
			
		||||
  LOAD_REF: string
 | 
			
		||||
  USER_NM: string
 | 
			
		||||
  VERSION_DTTM: string
 | 
			
		||||
  VERSION_DESC: string
 | 
			
		||||
  CHANGED_RECORDS: number
 | 
			
		||||
  NEW_RECORDS: number
 | 
			
		||||
  DELETED_RECORDS: number
 | 
			
		||||
  [key: string]: string | number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Sasdata {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								client/src/app/models/sas/editors-restore.model.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								client/src/app/models/sas/editors-restore.model.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
import { BaseSASResponse } from './common/BaseSASResponse'
 | 
			
		||||
 | 
			
		||||
export interface EditorsRestoreServiceResponse extends BaseSASResponse {
 | 
			
		||||
  restore_out: RestoreOut[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface RestoreOut {
 | 
			
		||||
  LOADREF: string
 | 
			
		||||
}
 | 
			
		||||
@@ -143,7 +143,6 @@
 | 
			
		||||
                  (ngModelChange)="
 | 
			
		||||
                    setVariableOperator(queryIndex, query.operator, clauseIndex)
 | 
			
		||||
                  "
 | 
			
		||||
                  class="mt-2"
 | 
			
		||||
                  clrSelect
 | 
			
		||||
                >
 | 
			
		||||
                  <option *ngFor="let opr of query.operators">{{ opr }}</option>
 | 
			
		||||
 
 | 
			
		||||
@@ -878,17 +878,25 @@ export class QueryComponent
 | 
			
		||||
   */
 | 
			
		||||
  public hasInvalidCluase(clauses: any): boolean {
 | 
			
		||||
    for (let clause of clauses) {
 | 
			
		||||
      clause['invalidClause'] = false
 | 
			
		||||
 | 
			
		||||
      if (
 | 
			
		||||
        clause.variable === null ||
 | 
			
		||||
        clause.operator === null ||
 | 
			
		||||
        clause.value === null ||
 | 
			
		||||
        clause.value === ''
 | 
			
		||||
        clause.value === '' &&
 | 
			
		||||
        !(clause.operator === 'NE' || clause.operator === 'CONTAINS')
 | 
			
		||||
      ) {
 | 
			
		||||
        clause['invalidClause'] = true
 | 
			
		||||
 | 
			
		||||
        return true
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (
 | 
			
		||||
        clause.variable === null ||
 | 
			
		||||
        clause.operator === null ||
 | 
			
		||||
        clause.value === null
 | 
			
		||||
      ) {
 | 
			
		||||
        clause['invalidClause'] = true
 | 
			
		||||
 | 
			
		||||
        return true
 | 
			
		||||
      } else {
 | 
			
		||||
        clause['invalidClause'] = false
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -139,10 +139,7 @@
 | 
			
		||||
        <div class="card-header p-0">
 | 
			
		||||
          <div class="clr-row">
 | 
			
		||||
            <div class="clr-col-md-4 approvalBack">
 | 
			
		||||
              <span
 | 
			
		||||
                class="btn btn-sm btn-outline m-0"
 | 
			
		||||
                (click)="goToApprovalsList()"
 | 
			
		||||
              >
 | 
			
		||||
              <span class="btn btn-outline m-0" (click)="goToApprovalsList()">
 | 
			
		||||
                <clr-icon shape="caret" dir="left" size="20"></clr-icon>Back to
 | 
			
		||||
                approvals list
 | 
			
		||||
              </span>
 | 
			
		||||
@@ -209,22 +206,22 @@
 | 
			
		||||
                  <div class="d-flex justify-content-center mt-0">
 | 
			
		||||
                    <div class="clr-row clr-gap-5 clr-gap-sm-0">
 | 
			
		||||
                      <button
 | 
			
		||||
                        class="btn btn-sm btn-outline text-center mt-5"
 | 
			
		||||
                        class="btn btn-sm btn-outline text-center mt-5 mr-5i"
 | 
			
		||||
                        (click)="goToBase(jsParams?.TABLE_NM)"
 | 
			
		||||
                      >
 | 
			
		||||
                        Go to base table screen
 | 
			
		||||
                        View base table
 | 
			
		||||
                      </button>
 | 
			
		||||
                      <button
 | 
			
		||||
                        class="btn btn-sm btn-success-outline text-center mt-5"
 | 
			
		||||
                        class="btn btn-sm btn-success-outline text-center mt-5 mr-5i"
 | 
			
		||||
                        (click)="getTable(tableId)"
 | 
			
		||||
                      >
 | 
			
		||||
                        Go to edited screen
 | 
			
		||||
                        View staged data
 | 
			
		||||
                      </button>
 | 
			
		||||
                      <button
 | 
			
		||||
                        class="btn btn-sm btn-info-outline text-center mt-5"
 | 
			
		||||
                        (click)="goBack(jsParams?.TABLE_NM)"
 | 
			
		||||
                      >
 | 
			
		||||
                        Go back to editor
 | 
			
		||||
                        Edit base table
 | 
			
		||||
                      </button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
@@ -236,7 +233,7 @@
 | 
			
		||||
                    id="acceptBtn"
 | 
			
		||||
                    [clrLoading]="acceptLoading"
 | 
			
		||||
                    type="submit"
 | 
			
		||||
                    class="btn btn-sm btn-success"
 | 
			
		||||
                    class="btn btn-sm btn-success mr-5i"
 | 
			
		||||
                    (click)="approveTable()"
 | 
			
		||||
                    [disabled]="
 | 
			
		||||
                      !loadingTable || params?.ISAPPROVER === 'NO' || noChanges
 | 
			
		||||
@@ -246,7 +243,7 @@
 | 
			
		||||
                  </button>
 | 
			
		||||
                  <button
 | 
			
		||||
                    id="rejectBtn"
 | 
			
		||||
                    class="btn btn-sm btn btn-danger mr-0"
 | 
			
		||||
                    class="btn btn-sm btn btn-danger mr-5i"
 | 
			
		||||
                    (click)="rejectOpen = true"
 | 
			
		||||
                    [disabled]="
 | 
			
		||||
                      !loadingTable || params?.ISAPPROVER === 'NO' || noChanges
 | 
			
		||||
@@ -394,9 +391,9 @@
 | 
			
		||||
        <div class="card-header">
 | 
			
		||||
          <div class="clr-row">
 | 
			
		||||
            <div class="clr-col-md-4 approvalBack">
 | 
			
		||||
              <span class="btn btn-sm btn-outline" (click)="goToSubmitList()">
 | 
			
		||||
                <clr-icon shape="caret" dir="left" size="20"></clr-icon>Back to
 | 
			
		||||
                submitted list
 | 
			
		||||
              <span class="btn btn-outline" (click)="goToSubmitList()">
 | 
			
		||||
                <cds-icon shape="angle" direction="left" size="20"></cds-icon
 | 
			
		||||
                >Back to submitted list
 | 
			
		||||
              </span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="clr-col-md-4">
 | 
			
		||||
@@ -443,22 +440,22 @@
 | 
			
		||||
              <div class="d-flex justify-content-center mt-0">
 | 
			
		||||
                <div class="clr-row clr-gap-5 clr-gap-sm-0">
 | 
			
		||||
                  <button
 | 
			
		||||
                    class="btn btn-sm btn-outline text-center mt-5"
 | 
			
		||||
                    class="btn btn-sm btn-outline text-center mt-5 mr-5i"
 | 
			
		||||
                    (click)="goToBase(subObj.base)"
 | 
			
		||||
                  >
 | 
			
		||||
                    Go to base table screen
 | 
			
		||||
                    View base table
 | 
			
		||||
                  </button>
 | 
			
		||||
                  <button
 | 
			
		||||
                    class="btn btn-sm btn-success-outline text-center mt-5"
 | 
			
		||||
                    class="btn btn-sm btn-success-outline text-center mt-5 mr-5i"
 | 
			
		||||
                    (click)="getTable(subObj.tableId)"
 | 
			
		||||
                  >
 | 
			
		||||
                    Go to edited screen
 | 
			
		||||
                    View staged data
 | 
			
		||||
                  </button>
 | 
			
		||||
                  <button
 | 
			
		||||
                    class="btn btn-sm btn-info-outline text-center mt-5"
 | 
			
		||||
                    (click)="goBack(subObj.base)"
 | 
			
		||||
                  >
 | 
			
		||||
                    Go back to editor
 | 
			
		||||
                    Edit base table
 | 
			
		||||
                  </button>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -6,25 +6,31 @@
 | 
			
		||||
>
 | 
			
		||||
  <h3 class="modal-title center text-center color-darker-gray">Dataset Meta</h3>
 | 
			
		||||
  <div class="modal-body">
 | 
			
		||||
    <p *ngIf="dsmetaGroupped.length < 1" class="text-center">
 | 
			
		||||
    <p *ngIf="dsmetaTabs.length < 1" class="text-center">
 | 
			
		||||
      No dataset meta to show.
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <clr-tabs clrLayout="vertical">
 | 
			
		||||
      <clr-tab *ngFor="let dsmeta of dsmetaGroupped; let index = index">
 | 
			
		||||
        <button clrTabLink id="link1">{{ dsmeta.group }}</button>
 | 
			
		||||
      <clr-tab *ngFor="let tab of tabs; let index = index">
 | 
			
		||||
        <button clrTabLink id="link1">{{ tab.name }}</button>
 | 
			
		||||
        <clr-tab-content
 | 
			
		||||
          id="content1"
 | 
			
		||||
          *clrIfActive="index === 0"
 | 
			
		||||
          class="d-flex clr-justify-content-center w-100"
 | 
			
		||||
        >
 | 
			
		||||
          <clr-datagrid>
 | 
			
		||||
            <clr-dg-column>Name</clr-dg-column>
 | 
			
		||||
            <clr-dg-column>Value</clr-dg-column>
 | 
			
		||||
            <ng-container *ngFor="let col of tab.colsToDisplay">
 | 
			
		||||
              <clr-dg-column>{{ col.colName || col.colKey }}</clr-dg-column>
 | 
			
		||||
            </ng-container>
 | 
			
		||||
 | 
			
		||||
            <clr-dg-row *ngFor="let info of dsmeta.dsmeta">
 | 
			
		||||
              <clr-dg-cell>{{ info.NAME }}</clr-dg-cell>
 | 
			
		||||
              <clr-dg-cell>{{ info.VALUE }}</clr-dg-cell>
 | 
			
		||||
            <clr-dg-row
 | 
			
		||||
              (click)="tab.onRowClick ? tab.onRowClick(info) : ''"
 | 
			
		||||
              class="clickable-row"
 | 
			
		||||
              *ngFor="let info of tab.meta"
 | 
			
		||||
            >
 | 
			
		||||
              <ng-container *ngFor="let col of tab.colsToDisplay">
 | 
			
		||||
                <clr-dg-cell>{{ info[col.colKey] }}</clr-dg-cell>
 | 
			
		||||
              </ng-container>
 | 
			
		||||
            </clr-dg-row>
 | 
			
		||||
          </clr-datagrid>
 | 
			
		||||
        </clr-tab-content>
 | 
			
		||||
 
 | 
			
		||||
@@ -13,4 +13,23 @@
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
clr-modal {
 | 
			
		||||
  ::ng-deep {
 | 
			
		||||
    .modal-dialog {
 | 
			
		||||
      height: 100%;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.clickable-row {
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
::ng-deep {
 | 
			
		||||
  .datagrid-table .datagrid-cell:focus {
 | 
			
		||||
    outline: none;
 | 
			
		||||
    outline-offset: 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -7,8 +7,8 @@ import {
 | 
			
		||||
  Output,
 | 
			
		||||
  SimpleChanges
 | 
			
		||||
} from '@angular/core'
 | 
			
		||||
import { DSMeta } from 'src/app/models/sas/editors-getdata.model'
 | 
			
		||||
import { DSMetaGroupped } from './models/dsmeta-groupped.model'
 | 
			
		||||
import { DSMeta, Version } from 'src/app/models/sas/editors-getdata.model'
 | 
			
		||||
import { Tab } from './models/dsmeta-groupped.model'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-dataset-info',
 | 
			
		||||
@@ -18,10 +18,15 @@ import { DSMetaGroupped } from './models/dsmeta-groupped.model'
 | 
			
		||||
export class DatasetInfoComponent implements OnInit, OnChanges {
 | 
			
		||||
  @Input() open: boolean = false
 | 
			
		||||
  @Input() dsmeta: DSMeta[] = []
 | 
			
		||||
  @Input() versions: Version[] = []
 | 
			
		||||
 | 
			
		||||
  @Output() openChange = new EventEmitter<boolean>()
 | 
			
		||||
  @Output() rowClicked = new EventEmitter<Version | DSMeta>()
 | 
			
		||||
 | 
			
		||||
  dsmetaGroupped: DSMetaGroupped[] = []
 | 
			
		||||
  dsmetaTabs: Tab<DSMeta>[] = []
 | 
			
		||||
  versionsTabs: Tab<Version>[] = []
 | 
			
		||||
 | 
			
		||||
  tabs: Tab<DSMeta | Version>[] = []
 | 
			
		||||
 | 
			
		||||
  constructor() {}
 | 
			
		||||
 | 
			
		||||
@@ -30,28 +35,58 @@ export class DatasetInfoComponent implements OnInit, OnChanges {
 | 
			
		||||
  ngOnChanges(changes: SimpleChanges): void {
 | 
			
		||||
    if (changes.dsmeta?.currentValue?.length > 0) {
 | 
			
		||||
      this.parseDSMeta()
 | 
			
		||||
      this.parseVersions()
 | 
			
		||||
 | 
			
		||||
      this.tabs = [...[...this.dsmetaTabs], ...[...this.versionsTabs]]
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  parseDSMeta() {
 | 
			
		||||
    this.dsmetaGroupped = []
 | 
			
		||||
    this.dsmetaTabs = []
 | 
			
		||||
 | 
			
		||||
    for (let info of this.dsmeta) {
 | 
			
		||||
      let groupIndex = this.dsmetaGroupped.findIndex(
 | 
			
		||||
        (x) => x.group === info.ODS_TABLE
 | 
			
		||||
      let groupIndex = this.dsmetaTabs.findIndex(
 | 
			
		||||
        (x) => x.name === info.ODS_TABLE
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
      if (groupIndex < 0)
 | 
			
		||||
        groupIndex =
 | 
			
		||||
          this.dsmetaGroupped.push({
 | 
			
		||||
            group: info.ODS_TABLE,
 | 
			
		||||
            dsmeta: []
 | 
			
		||||
          this.dsmetaTabs.push({
 | 
			
		||||
            name: info.ODS_TABLE,
 | 
			
		||||
            title: 'Dataset Meta',
 | 
			
		||||
            colsToDisplay: [{ colKey: 'NAME' }, { colKey: 'VALUE' }],
 | 
			
		||||
            meta: [],
 | 
			
		||||
            onRowClick: (value: DSMeta) => {
 | 
			
		||||
              this.rowClicked.emit(value)
 | 
			
		||||
            }
 | 
			
		||||
          }) - 1
 | 
			
		||||
 | 
			
		||||
      this.dsmetaGroupped[groupIndex].dsmeta.push(info)
 | 
			
		||||
      this.dsmetaTabs[groupIndex].meta.push(info)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  parseVersions() {
 | 
			
		||||
    this.versionsTabs = [
 | 
			
		||||
      {
 | 
			
		||||
        name: 'VERSIONS',
 | 
			
		||||
        title: 'Dataset Meta',
 | 
			
		||||
        colsToDisplay: [
 | 
			
		||||
          { colKey: 'LOAD_REF' },
 | 
			
		||||
          { colKey: 'USER_NM' },
 | 
			
		||||
          { colKey: 'VERSION_DTTM' },
 | 
			
		||||
          { colKey: 'NEW_RECORDS', colName: 'ADD' },
 | 
			
		||||
          { colKey: 'CHANGED_RECORDS', colName: 'MOD' },
 | 
			
		||||
          { colKey: 'DELETED_RECORDS', colName: 'DEL' },
 | 
			
		||||
          { colKey: 'VERSION_DESC' }
 | 
			
		||||
        ],
 | 
			
		||||
        meta: this.versions,
 | 
			
		||||
        onRowClick: (value: Version) => {
 | 
			
		||||
          this.rowClicked.emit(value)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onOpenChange(open: boolean) {
 | 
			
		||||
    this.open = open
 | 
			
		||||
    this.openChange.emit(open)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,14 @@
 | 
			
		||||
import { DSMeta } from 'src/app/models/sas/editors-getdata.model'
 | 
			
		||||
 | 
			
		||||
export interface DSMetaGroupped {
 | 
			
		||||
  group: string
 | 
			
		||||
  dsmeta: DSMeta[]
 | 
			
		||||
export interface Tab<T> {
 | 
			
		||||
  name: string
 | 
			
		||||
  title: string
 | 
			
		||||
  /**
 | 
			
		||||
   * Columns to be displayed in the the grid
 | 
			
		||||
   * If empty, all columns will be displayed
 | 
			
		||||
   */
 | 
			
		||||
  colsToDisplay: {
 | 
			
		||||
    colKey: string
 | 
			
		||||
    colName?: string
 | 
			
		||||
  }[]
 | 
			
		||||
  meta: T[]
 | 
			
		||||
  onRowClick?: (value: any) => void
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ import { parseColType } from './utils/parseColType'
 | 
			
		||||
import { dqValidate } from './validations/dq-validation'
 | 
			
		||||
import { specialMissingNumericValidator } from './validations/hot-custom-validators'
 | 
			
		||||
import { applyNumericFormats } from './utils/applyNumericFormats'
 | 
			
		||||
import { CustomAutocompleteEditor } from './editors/numericAutocomplete'
 | 
			
		||||
 | 
			
		||||
export class DcValidator {
 | 
			
		||||
  private rules: DcValidation[] = []
 | 
			
		||||
@@ -38,6 +39,8 @@ export class DcValidator {
 | 
			
		||||
    dqData: DQData[],
 | 
			
		||||
    hotInstance?: Handsontable
 | 
			
		||||
  ) {
 | 
			
		||||
    this.registerCustomEditors()
 | 
			
		||||
 | 
			
		||||
    this.sasparams = sasparams
 | 
			
		||||
    this.hotInstance = hotInstance
 | 
			
		||||
    this.rules = parseColType(sasparams.COLTYPE)
 | 
			
		||||
@@ -51,6 +54,13 @@ export class DcValidator {
 | 
			
		||||
    this.setupValidations()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  registerCustomEditors() {
 | 
			
		||||
    Handsontable.editors.registerEditor(
 | 
			
		||||
      'autocomplete.custom',
 | 
			
		||||
      CustomAutocompleteEditor
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getRules(): DcValidation[] {
 | 
			
		||||
    return this.rules
 | 
			
		||||
  }
 | 
			
		||||
@@ -262,6 +272,7 @@ export class DcValidator {
 | 
			
		||||
        if (source.length > 0) {
 | 
			
		||||
          this.rules[i].source = source
 | 
			
		||||
          this.rules[i].type = 'autocomplete'
 | 
			
		||||
          this.rules[i].editor = 'autocomplete.custom'
 | 
			
		||||
          this.rules[i].filter = false
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -315,7 +326,10 @@ export class DcValidator {
 | 
			
		||||
 | 
			
		||||
      // Because of dynamic cell validation, that will change the type of cell to dropdown
 | 
			
		||||
      // `rules[i].colType` could be different type (eg. numeric). So we check if current cell is dropdown, to call HOT native dropdown validator
 | 
			
		||||
      if (this.editor === 'autocomplete') {
 | 
			
		||||
      if (
 | 
			
		||||
        this.editor === 'autocomplete' ||
 | 
			
		||||
        this.editor === 'autocomplete.custom'
 | 
			
		||||
      ) {
 | 
			
		||||
        self
 | 
			
		||||
          .getHandsontableValidator('autocomplete')
 | 
			
		||||
          .call(this, value, (valid: boolean) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
import Handsontable from 'handsontable'
 | 
			
		||||
import Core from 'handsontable/core'
 | 
			
		||||
 | 
			
		||||
export class CustomAutocompleteEditor extends Handsontable.editors
 | 
			
		||||
  .AutocompleteEditor {
 | 
			
		||||
  constructor(instance: Core) {
 | 
			
		||||
    super(instance)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  createElements() {
 | 
			
		||||
    super.createElements()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Listbox open
 | 
			
		||||
  open(event?: Event | undefined): void {
 | 
			
		||||
    super.open(event)
 | 
			
		||||
 | 
			
		||||
    if (this.isCellNumeric()) {
 | 
			
		||||
      this.htContainer.classList.add('numericListbox')
 | 
			
		||||
    } else {
 | 
			
		||||
      this.htContainer.classList.remove('numericListbox')
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isCellNumeric() {
 | 
			
		||||
    return this.cellProperties?.className?.includes('htNumeric')
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -11,6 +11,8 @@ $clr-green: #60b515;
 | 
			
		||||
  height: $clr-header-height;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  margin-right: 10px;
 | 
			
		||||
 | 
			
		||||
  .spinner {
 | 
			
		||||
    vertical-align: middle;
 | 
			
		||||
 
 | 
			
		||||
@@ -16,8 +16,6 @@
 | 
			
		||||
        *ngFor="let programLog of sasjsRequests; let i = index"
 | 
			
		||||
        [id]="'request_' + i"
 | 
			
		||||
        [clrStackViewLevel]="1"
 | 
			
		||||
        [clrStackViewSetsize]="3"
 | 
			
		||||
        [clrStackViewPosinset]="3"
 | 
			
		||||
      >
 | 
			
		||||
        <clr-stack-label>
 | 
			
		||||
          {{ programLog.serviceLink }}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ import { LoadingIndicatorComponent } from './loading-indicator/loading-indicator
 | 
			
		||||
import { LoginComponent } from './login/login.component'
 | 
			
		||||
import { UserService } from './user.service'
 | 
			
		||||
import { AlertsService } from './alerts/alerts.service'
 | 
			
		||||
import { UserNavDropdownComponent } from './user-nav-dropdown/user-nav-dropdown.component'
 | 
			
		||||
import { HeaderActions } from './user-nav-dropdown/header-actions.component'
 | 
			
		||||
import { AlertsComponent } from './alerts/alerts.component'
 | 
			
		||||
import { TermsComponent } from './terms/terms.component'
 | 
			
		||||
import { DirectivesModule } from '../directives/directives.module'
 | 
			
		||||
@@ -26,7 +26,7 @@ import { ContactLinkComponent } from './contact-link/contact-link.component'
 | 
			
		||||
  declarations: [
 | 
			
		||||
    LoadingIndicatorComponent,
 | 
			
		||||
    LoginComponent,
 | 
			
		||||
    UserNavDropdownComponent,
 | 
			
		||||
    HeaderActions,
 | 
			
		||||
    AlertsComponent,
 | 
			
		||||
    TermsComponent,
 | 
			
		||||
    DatasetInfoComponent,
 | 
			
		||||
@@ -35,7 +35,7 @@ import { ContactLinkComponent } from './contact-link/contact-link.component'
 | 
			
		||||
  exports: [
 | 
			
		||||
    LoadingIndicatorComponent,
 | 
			
		||||
    LoginComponent,
 | 
			
		||||
    UserNavDropdownComponent,
 | 
			
		||||
    HeaderActions,
 | 
			
		||||
    AlertsComponent,
 | 
			
		||||
    TermsComponent,
 | 
			
		||||
    DatasetInfoComponent,
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,69 @@
 | 
			
		||||
<div class="header-actions">
 | 
			
		||||
  <app-loading-indicator></app-loading-indicator>
 | 
			
		||||
 | 
			
		||||
  <clr-dropdown class="app-nav-dropdown">
 | 
			
		||||
    <button class="nav-text color-white" clrDropdownToggle>
 | 
			
		||||
      <span>{{ userName }}</span>
 | 
			
		||||
      <span *ngIf="userName !== 'Not logged in' && isViya"
 | 
			
		||||
        ><img class="avatar-img" src="{{ getPictureUrl() }}" alt=""
 | 
			
		||||
      /></span>
 | 
			
		||||
      <span
 | 
			
		||||
        class="badge badge-danger"
 | 
			
		||||
        *ngIf="!sasjsConfig.debug"
 | 
			
		||||
        [class.hidden]="failedReqs.length === 0"
 | 
			
		||||
        >{{ failedReqs.length }}</span
 | 
			
		||||
      >
 | 
			
		||||
      <span
 | 
			
		||||
        class="badge badge-info"
 | 
			
		||||
        *ngIf="sasjsConfig.debug"
 | 
			
		||||
        [class.hidden]="debugLogs.length === 0"
 | 
			
		||||
        >{{ debugLogs.length }}</span
 | 
			
		||||
      >
 | 
			
		||||
      <clr-icon *ngIf="!isViya" shape="caret down"></clr-icon>
 | 
			
		||||
    </button>
 | 
			
		||||
    <clr-dropdown-menu clrPosition="bottom-right" *clrIfOpen>
 | 
			
		||||
      <div #dropdownItemDebug class="debug-switch-item" clrDropdownItem>
 | 
			
		||||
        <clr-toggle-container
 | 
			
		||||
          class="toggle-switch"
 | 
			
		||||
          (click)="onDebugRowClick($event, dropdownItemDebug)"
 | 
			
		||||
        >
 | 
			
		||||
          <clr-toggle-wrapper>
 | 
			
		||||
            <input
 | 
			
		||||
              id="debug-toggle1"
 | 
			
		||||
              type="checkbox"
 | 
			
		||||
              [(ngModel)]="sasjsConfig.debug"
 | 
			
		||||
              (ngModelChange)="onDebugModeChange()"
 | 
			
		||||
              clrToggle
 | 
			
		||||
            />
 | 
			
		||||
            <label>Debug Mode</label>
 | 
			
		||||
          </clr-toggle-wrapper>
 | 
			
		||||
        </clr-toggle-container>
 | 
			
		||||
      </div>
 | 
			
		||||
      <a (click)="openRequestsModal()" clrDropdownItem>
 | 
			
		||||
        <span>SAS Requests</span>
 | 
			
		||||
      </a>
 | 
			
		||||
 | 
			
		||||
      <ng-container *ngIf="!isDeployPage">
 | 
			
		||||
        <a
 | 
			
		||||
          target="_blank"
 | 
			
		||||
          href="https://docs.datacontroller.io"
 | 
			
		||||
          clrDropdownItem
 | 
			
		||||
        >
 | 
			
		||||
          <span class="dropdown-text">Documentation</span>
 | 
			
		||||
        </a>
 | 
			
		||||
      </ng-container>
 | 
			
		||||
 | 
			
		||||
      <div class="separator"></div>
 | 
			
		||||
      <a href="..." routerLink="/system" clrDropdownItem>
 | 
			
		||||
        <span>System</span>
 | 
			
		||||
      </a>
 | 
			
		||||
      <a href="..." (click)="logout($event)" clrDropdownItem>
 | 
			
		||||
        <span>Log Out</span>
 | 
			
		||||
        <clr-icon class="clr-logout" shape="logout"></clr-icon>
 | 
			
		||||
      </a>
 | 
			
		||||
      <div class="copyRight">
 | 
			
		||||
        <span>v{{ commitVer }}</span>
 | 
			
		||||
      </div>
 | 
			
		||||
    </clr-dropdown-menu>
 | 
			
		||||
  </clr-dropdown>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -1,17 +1,12 @@
 | 
			
		||||
// it must be a better way to read clarity variables...
 | 
			
		||||
//@import '../../../../node_modules/@clr/ui/src/utils/helpers.clarity';
 | 
			
		||||
 | 
			
		||||
//@import '../../../../node_modules/@clr/ui/src/color/utils/colors.clarity';
 | 
			
		||||
//@import '../../../../node_modules/@clr/ui/src/color/utils/contrast-cache.clarity';
 | 
			
		||||
//@import '../../../../node_modules/@clr/ui/src/color/utils/helpers.clarity';
 | 
			
		||||
 | 
			
		||||
//@import '../../../../node_modules/@clr/ui/src/utils/variables.clarity';
 | 
			
		||||
 | 
			
		||||
$clr-header-height: 3rem;
 | 
			
		||||
$clr-near-white: #fafafa;
 | 
			
		||||
$clr-dark-gray: #565656;
 | 
			
		||||
$clr-light-gray: #eee;
 | 
			
		||||
 | 
			
		||||
:host {
 | 
			
		||||
  display: contents;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.copyRight {
 | 
			
		||||
  margin-top: 10px;
 | 
			
		||||
 | 
			
		||||
@@ -8,11 +8,11 @@ import { EventService } from '../../services/event.service'
 | 
			
		||||
import { Router } from '@angular/router'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-user-nav-dropdown',
 | 
			
		||||
  templateUrl: './user-nav-dropdown.component.html',
 | 
			
		||||
  styleUrls: ['./user-nav-dropdown.component.scss']
 | 
			
		||||
  selector: 'app-header-actions',
 | 
			
		||||
  templateUrl: './header-actions.component.html',
 | 
			
		||||
  styleUrls: ['./header-actions.component.scss']
 | 
			
		||||
})
 | 
			
		||||
export class UserNavDropdownComponent implements OnInit, OnDestroy {
 | 
			
		||||
export class HeaderActions implements OnInit, OnDestroy {
 | 
			
		||||
  public userName: string = 'Not logged in'
 | 
			
		||||
  private reqSub: Subscription = new Subscription()
 | 
			
		||||
  private userSub: Subscription = new Subscription()
 | 
			
		||||
@@ -1,121 +0,0 @@
 | 
			
		||||
<clr-dropdown class="app-nav-dropdown d-md-block">
 | 
			
		||||
  <button class="nav-text color-white" clrDropdownToggle>
 | 
			
		||||
    <span>{{ userName }}</span>
 | 
			
		||||
    <span *ngIf="userName !== 'Not logged in' && isViya"
 | 
			
		||||
      ><img class="avatar-img" src="{{ getPictureUrl() }}" alt=""
 | 
			
		||||
    /></span>
 | 
			
		||||
    <span
 | 
			
		||||
      class="badge badge-danger"
 | 
			
		||||
      *ngIf="!sasjsConfig.debug"
 | 
			
		||||
      [class.hidden]="failedReqs.length === 0"
 | 
			
		||||
      >{{ failedReqs.length }}</span
 | 
			
		||||
    >
 | 
			
		||||
    <span
 | 
			
		||||
      class="badge badge-info"
 | 
			
		||||
      *ngIf="sasjsConfig.debug"
 | 
			
		||||
      [class.hidden]="debugLogs.length === 0"
 | 
			
		||||
      >{{ debugLogs.length }}</span
 | 
			
		||||
    >
 | 
			
		||||
    <clr-icon *ngIf="!isViya" shape="caret down"></clr-icon>
 | 
			
		||||
  </button>
 | 
			
		||||
  <clr-dropdown-menu clrPosition="bottom-right" *clrIfOpen>
 | 
			
		||||
    <div #dropdownItemDebug class="debug-switch-item" clrDropdownItem>
 | 
			
		||||
      <clr-toggle-container
 | 
			
		||||
        class="toggle-switch"
 | 
			
		||||
        (click)="onDebugRowClick($event, dropdownItemDebug)"
 | 
			
		||||
      >
 | 
			
		||||
        <clr-toggle-wrapper>
 | 
			
		||||
          <input
 | 
			
		||||
            id="debug-toggle1"
 | 
			
		||||
            type="checkbox"
 | 
			
		||||
            [(ngModel)]="sasjsConfig.debug"
 | 
			
		||||
            (ngModelChange)="onDebugModeChange()"
 | 
			
		||||
            clrToggle
 | 
			
		||||
          />
 | 
			
		||||
          <label>Debug Mode</label>
 | 
			
		||||
        </clr-toggle-wrapper>
 | 
			
		||||
      </clr-toggle-container>
 | 
			
		||||
    </div>
 | 
			
		||||
    <a (click)="openRequestsModal()" clrDropdownItem>
 | 
			
		||||
      <span>SAS Requests</span>
 | 
			
		||||
    </a>
 | 
			
		||||
 | 
			
		||||
    <ng-container *ngIf="!isDeployPage">
 | 
			
		||||
      <a target="_blank" href="https://docs.datacontroller.io" clrDropdownItem>
 | 
			
		||||
        <span class="dropdown-text">Documentation</span>
 | 
			
		||||
      </a>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
 | 
			
		||||
    <div class="separator"></div>
 | 
			
		||||
    <a href="..." routerLink="/system" clrDropdownItem>
 | 
			
		||||
      <span>System</span>
 | 
			
		||||
    </a>
 | 
			
		||||
    <a href="..." (click)="logout($event)" clrDropdownItem>
 | 
			
		||||
      <span>Log Out</span>
 | 
			
		||||
      <clr-icon class="clr-logout" shape="logout"></clr-icon>
 | 
			
		||||
    </a>
 | 
			
		||||
    <div class="copyRight">
 | 
			
		||||
      <span>v{{ commitVer }}</span>
 | 
			
		||||
    </div>
 | 
			
		||||
  </clr-dropdown-menu>
 | 
			
		||||
</clr-dropdown>
 | 
			
		||||
<div class="content-container h-auto">
 | 
			
		||||
  <nav class="sidenav d-block d-md-none" [clr-nav-level]="2">
 | 
			
		||||
    <section class="sidenav-content">
 | 
			
		||||
      <a href="..." class="nav-link active">
 | 
			
		||||
        {{ userName }}
 | 
			
		||||
      </a>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <form>
 | 
			
		||||
          <div class="toggle-switch">
 | 
			
		||||
            <input
 | 
			
		||||
              id="debug-toggle2"
 | 
			
		||||
              type="checkbox"
 | 
			
		||||
              [(ngModel)]="sasjsConfig.debug"
 | 
			
		||||
              (ngModelChange)="onDebugModeChange()"
 | 
			
		||||
              [ngModelOptions]="{ standalone: true }"
 | 
			
		||||
            />
 | 
			
		||||
            <label
 | 
			
		||||
              for="debug-toggle2"
 | 
			
		||||
              class="debug-toggle-label color-dark-gray"
 | 
			
		||||
              >Debug Mode</label
 | 
			
		||||
            >
 | 
			
		||||
          </div>
 | 
			
		||||
        </form>
 | 
			
		||||
      </div>
 | 
			
		||||
      <!-- <a href="..." class="nav-link d-block" [routerLink]="['/application-logs']">
 | 
			
		||||
        <span>Application Logs</span>
 | 
			
		||||
        <span class="badge" *ngIf="appLogs.length > 0">{{appLogs.length}}</span>
 | 
			
		||||
      </a>
 | 
			
		||||
      <a *ngIf="debugMode" class="nav-link d-block" href="..." [routerLink]="['/debug-logs']">
 | 
			
		||||
        <span>Debug Logs</span>
 | 
			
		||||
        <span class="badge badge-info" *ngIf="debugLogs.length > 0">{{debugLogs.length}}</span>
 | 
			
		||||
      </a>
 | 
			
		||||
      <a *ngIf="!debugMode" class="nav-link d-block" href="..." [routerLink]="['/failed-requests']">
 | 
			
		||||
        <span>Failed Requests</span>
 | 
			
		||||
        <span class="badge badge-danger" *ngIf="failedReqs.length > 0">{{failedReqs.length}}</span>
 | 
			
		||||
      </a>
 | 
			
		||||
      <a href="..." class="nav-link d-block" [routerLink]="['/errors']">
 | 
			
		||||
        <span>Errors</span>
 | 
			
		||||
        <span class="badge badge-warning" *ngIf="sasErrors.length > 0">{{sasErrors.length}}</span>
 | 
			
		||||
      </a> -->
 | 
			
		||||
      <a
 | 
			
		||||
        class="nav-link d-block"
 | 
			
		||||
        target="_blank"
 | 
			
		||||
        href="https://docs.datacontroller.io"
 | 
			
		||||
      >
 | 
			
		||||
        <span>Documentation</span>
 | 
			
		||||
      </a>
 | 
			
		||||
      <div class="separator"></div>
 | 
			
		||||
      <a routerLink="/system" class="nav-link d-block">
 | 
			
		||||
        <span>System</span>
 | 
			
		||||
        <clr-icon shape="logout"></clr-icon>
 | 
			
		||||
      </a>
 | 
			
		||||
      <a href="..." class="nav-link d-block" (click)="logout($event)">
 | 
			
		||||
        <span>Log Out</span>
 | 
			
		||||
        <clr-icon shape="logout"></clr-icon>
 | 
			
		||||
      </a>
 | 
			
		||||
    </section>
 | 
			
		||||
  </nav>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -58,34 +58,57 @@
 | 
			
		||||
          <div class="mt-20">
 | 
			
		||||
            <div class="row">
 | 
			
		||||
              <button
 | 
			
		||||
                class="btn btn-sm btn-outline text-center mt-20"
 | 
			
		||||
                class="btn btn-sm btn-outline text-center mr-5i"
 | 
			
		||||
                (click)="viewerTableScreen()"
 | 
			
		||||
                [disabled]="revertingChanges"
 | 
			
		||||
              >
 | 
			
		||||
                Go to base table screen
 | 
			
		||||
                View base table
 | 
			
		||||
              </button>
 | 
			
		||||
              <button
 | 
			
		||||
                id="approval-btn"
 | 
			
		||||
                class="btn btn-sm btn-success-outline text-center mt-20"
 | 
			
		||||
                class="btn btn-sm btn-success-outline text-center mr-5i"
 | 
			
		||||
                [disabled]="
 | 
			
		||||
                  tableDetails?.REVIEW_STATUS_ID === 'APPROVED' ||
 | 
			
		||||
                  tableDetails?.REVIEW_STATUS_ID === 'REJECTED'
 | 
			
		||||
                "
 | 
			
		||||
                (click)="approveTableScreen()"
 | 
			
		||||
                [disabled]="revertingChanges"
 | 
			
		||||
              >
 | 
			
		||||
                Go to approvals screen
 | 
			
		||||
                Approve
 | 
			
		||||
              </button>
 | 
			
		||||
              <button
 | 
			
		||||
                class="btn btn-sm btn-info-outline text-center mt-20"
 | 
			
		||||
                class="btn btn-sm btn-info-outline text-center mr-5i"
 | 
			
		||||
                (click)="goBack()"
 | 
			
		||||
                [disabled]="revertingChanges"
 | 
			
		||||
              >
 | 
			
		||||
                Go back to editor
 | 
			
		||||
                Edit base table
 | 
			
		||||
              </button>
 | 
			
		||||
              <button
 | 
			
		||||
                class="btn btn-sm btn-success text-center mt-20 min-w-0"
 | 
			
		||||
                class="btn btn-sm btn-success text-center mr-5i min-w-0"
 | 
			
		||||
                (click)="download(tableDetails?.TABLE_ID)"
 | 
			
		||||
              >
 | 
			
		||||
                <clr-icon shape="download"></clr-icon>
 | 
			
		||||
              </button>
 | 
			
		||||
 | 
			
		||||
              <clr-tooltip>
 | 
			
		||||
                <button
 | 
			
		||||
                  *ngIf="tableDetails?.['ALLOW_RESTORE'] === 'YES'"
 | 
			
		||||
                  (click)="revertChanges()"
 | 
			
		||||
                  clrTooltipTrigger
 | 
			
		||||
                  [clrLoading]="revertingChanges"
 | 
			
		||||
                  class="btn btn-sm btn-danger text-center mt-20"
 | 
			
		||||
                >
 | 
			
		||||
                  REVERT
 | 
			
		||||
 | 
			
		||||
                  <clr-tooltip-content
 | 
			
		||||
                    clrPosition="bottom-left"
 | 
			
		||||
                    clrSize="lg"
 | 
			
		||||
                    *clrIfOpen
 | 
			
		||||
                  >
 | 
			
		||||
                    <span> Revert this and all subsequent changes </span>
 | 
			
		||||
                  </clr-tooltip-content>
 | 
			
		||||
                </button>
 | 
			
		||||
              </clr-tooltip>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,10 +4,10 @@ import { Router } from '@angular/router'
 | 
			
		||||
import { ActivatedRoute } from '@angular/router'
 | 
			
		||||
import { SasService } from '../services/sas.service'
 | 
			
		||||
import { EventService } from '../services/event.service'
 | 
			
		||||
import { AppService } from '../services/app.service'
 | 
			
		||||
import { HotTableInterface } from '../models/HotTable.interface'
 | 
			
		||||
import { LicenceService } from '../services/licence.service'
 | 
			
		||||
import { globals } from '../_globals'
 | 
			
		||||
import { EditorsRestoreServiceResponse } from '../models/sas/editors-restore.model'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-stage',
 | 
			
		||||
@@ -23,6 +23,7 @@ export class StageComponent implements OnInit {
 | 
			
		||||
  public keysArray: any
 | 
			
		||||
  public tableDetails: any
 | 
			
		||||
  public loaded: boolean = false
 | 
			
		||||
  public revertingChanges: boolean = false
 | 
			
		||||
  public licenceState = this.licenceService.licenceState
 | 
			
		||||
  public hotTable: HotTableInterface = {
 | 
			
		||||
    data: [],
 | 
			
		||||
@@ -162,6 +163,31 @@ export class StageComponent implements OnInit {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  revertChanges() {
 | 
			
		||||
    this.revertingChanges = true
 | 
			
		||||
 | 
			
		||||
    const data = {
 | 
			
		||||
      restore_in: [
 | 
			
		||||
        {
 | 
			
		||||
          load_ref: this.table_id
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.sasService
 | 
			
		||||
      .request('editors/restore', data)
 | 
			
		||||
      .then((res: EditorsRestoreServiceResponse) => {
 | 
			
		||||
        if (res.restore_out) {
 | 
			
		||||
          this.route.navigate([`/stage`]).then(() => {
 | 
			
		||||
            this.route.navigate([`/stage/${res.restore_out[0].LOADREF}`])
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
      .finally(() => {
 | 
			
		||||
        this.revertingChanges = false
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private setFocus() {
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      let approvalBtn: any = window.document.getElementById('approval-btn')
 | 
			
		||||
 
 | 
			
		||||
@@ -9,43 +9,45 @@
 | 
			
		||||
        class="sys-info d-flex clr-justify-content-center clr-flex-column clr-flex-lg-row"
 | 
			
		||||
      >
 | 
			
		||||
        <div>
 | 
			
		||||
          <h6 class="m-0">Environment Details <span class="dark"></span></h6>
 | 
			
		||||
          <p class="m-0">
 | 
			
		||||
          <h6 cds-text="subsection" class="mb-10">
 | 
			
		||||
            Environment Details <span class="dark"></span>
 | 
			
		||||
          </h6>
 | 
			
		||||
          <p cds-text="label" class="m-0">
 | 
			
		||||
            SYSSITE: <span class="dark">{{ environmentInfo?.SYSSITE }}</span>
 | 
			
		||||
          </p>
 | 
			
		||||
          <p class="m-0">
 | 
			
		||||
          <p cds-text="label" class="m-0">
 | 
			
		||||
            SYSSCPL: <span class="dark">{{ environmentInfo?.SYSSCPL }}</span>
 | 
			
		||||
          </p>
 | 
			
		||||
          <p class="m-0">
 | 
			
		||||
          <p cds-text="label" class="m-0">
 | 
			
		||||
            SYSTCPIPHOSTNAME:
 | 
			
		||||
            <span class="dark">{{ environmentInfo?.SYSTCPIPHOSTNAME }}</span>
 | 
			
		||||
          </p>
 | 
			
		||||
          <p class="m-0">
 | 
			
		||||
          <p cds-text="label" class="m-0">
 | 
			
		||||
            SYSVLONG: <span class="dark">{{ environmentInfo?.SYSVLONG }}</span>
 | 
			
		||||
          </p>
 | 
			
		||||
          <p class="m-0">
 | 
			
		||||
          <p cds-text="label" class="m-0">
 | 
			
		||||
            MEMSIZE: <span class="dark">{{ environmentInfo?.MEMSIZE }}</span>
 | 
			
		||||
          </p>
 | 
			
		||||
          <p class="m-0">
 | 
			
		||||
          <p cds-text="label" class="m-0">
 | 
			
		||||
            SYSPROCESSMODE:
 | 
			
		||||
            <span class="dark">{{ environmentInfo?.SYSPROCESSMODE }}</span>
 | 
			
		||||
          </p>
 | 
			
		||||
          <p class="m-0">
 | 
			
		||||
          <p cds-text="label" class="m-0">
 | 
			
		||||
            SYSHOSTNAME:
 | 
			
		||||
            <span class="dark">{{ environmentInfo?.SYSHOSTNAME }}</span>
 | 
			
		||||
          </p>
 | 
			
		||||
          <p class="m-0">
 | 
			
		||||
          <p cds-text="label" class="m-0">
 | 
			
		||||
            SYSHOSTINFOLONG:
 | 
			
		||||
            <span class="dark">{{ environmentInfo?.SYSHOSTINFOLONG }}</span>
 | 
			
		||||
          </p>
 | 
			
		||||
          <p class="m-0">
 | 
			
		||||
          <p cds-text="label" class="m-0">
 | 
			
		||||
            SYSENCODING:
 | 
			
		||||
            <span class="dark">{{ environmentInfo?.SYSENCODING }}</span>
 | 
			
		||||
          </p>
 | 
			
		||||
          <p class="m-0">
 | 
			
		||||
          <p cds-text="label" class="m-0">
 | 
			
		||||
            AUTOEXEC: <span class="dark">{{ environmentInfo?.AUTOEXEC }}</span>
 | 
			
		||||
          </p>
 | 
			
		||||
          <p class="m-0">
 | 
			
		||||
          <p cds-text="label" class="m-0">
 | 
			
		||||
            DC ADMIN GROUP:
 | 
			
		||||
            <span class="dark">{{ environmentInfo?.DC_ADMIN_GROUP }}</span>
 | 
			
		||||
          </p>
 | 
			
		||||
@@ -53,42 +55,44 @@
 | 
			
		||||
 | 
			
		||||
        <div class="d-flex clr-justify-content-lg-center">
 | 
			
		||||
          <div>
 | 
			
		||||
            <h6 class="m-0">
 | 
			
		||||
            <h6 cds-text="subsection" class="mb-10">
 | 
			
		||||
              Data Controller Details <span class="dark"></span>
 | 
			
		||||
            </h6>
 | 
			
		||||
            <p class="m-0">
 | 
			
		||||
            <p cds-text="label" class="m-0">
 | 
			
		||||
              Application version:
 | 
			
		||||
              <span class="dark">{{ appInfo.appVersion }}</span>
 | 
			
		||||
            </p>
 | 
			
		||||
            <p class="m-0">
 | 
			
		||||
            <p cds-text="label" class="m-0">
 | 
			
		||||
              Build timestamp:
 | 
			
		||||
              <span class="dark">{{ appInfo.buildTimestamp }}</span>
 | 
			
		||||
            </p>
 | 
			
		||||
            <p class="m-0">
 | 
			
		||||
            <p cds-text="label" class="m-0">
 | 
			
		||||
              Adapter version:
 | 
			
		||||
              <span class="dark">{{ appInfo.adapterVersion }}</span>
 | 
			
		||||
            </p>
 | 
			
		||||
            <p class="m-0">
 | 
			
		||||
            <p cds-text="label" class="m-0">
 | 
			
		||||
              HTTP: <span class="dark">{{ http ? 'YES' : 'NO' }}</span>
 | 
			
		||||
            </p>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div>
 | 
			
		||||
          <h6 class="m-0">Licence details <span class="dark"></span></h6>
 | 
			
		||||
          <p class="m-0">
 | 
			
		||||
          <h6 cds-text="subsection" class="mb-10">
 | 
			
		||||
            Licence details <span class="dark"></span>
 | 
			
		||||
          </h6>
 | 
			
		||||
          <p cds-text="label" class="m-0">
 | 
			
		||||
            Valid until:
 | 
			
		||||
            <span class="dark">{{ licenceInfo?.valid_until }}</span>
 | 
			
		||||
          </p>
 | 
			
		||||
          <p class="m-0">
 | 
			
		||||
          <p cds-text="label" class="m-0">
 | 
			
		||||
            Users allowed:
 | 
			
		||||
            <span class="dark">{{ licenceInfo?.users_allowed }}</span>
 | 
			
		||||
          </p>
 | 
			
		||||
          <p class="m-0">
 | 
			
		||||
          <p cds-text="label" class="m-0">
 | 
			
		||||
            Site IDs:
 | 
			
		||||
            <span class="dark">{{ licenceInfo?.site_id_multiple }}</span>
 | 
			
		||||
          </p>
 | 
			
		||||
          <p class="m-0">
 | 
			
		||||
          <p cds-text="label" class="m-0">
 | 
			
		||||
            Free Tier:
 | 
			
		||||
            <span class="dark">{{ licenceInfo?.demo ? 'YES' : 'NO' }}</span>
 | 
			
		||||
          </p>
 | 
			
		||||
@@ -157,25 +161,25 @@
 | 
			
		||||
              licenceState.value.lineage_daily_limit
 | 
			
		||||
            }}</span>
 | 
			
		||||
          </p>
 | 
			
		||||
          <p class="m-0">
 | 
			
		||||
          <p cds-text="label" class="m-0">
 | 
			
		||||
            Viewboxes:
 | 
			
		||||
            <span class="dark">{{
 | 
			
		||||
              licenceState.value.viewbox ? 'YES' : 'NO'
 | 
			
		||||
            }}</span>
 | 
			
		||||
          </p>
 | 
			
		||||
          <p class="m-0">
 | 
			
		||||
          <p cds-text="label" class="m-0">
 | 
			
		||||
            File Upload:
 | 
			
		||||
            <span class="dark">{{
 | 
			
		||||
              licenceState.value.fileUpload ? 'YES' : 'NO'
 | 
			
		||||
            }}</span>
 | 
			
		||||
          </p>
 | 
			
		||||
          <p class="m-0">
 | 
			
		||||
          <p cds-text="label" class="m-0">
 | 
			
		||||
            Edit record:
 | 
			
		||||
            <span class="dark">{{
 | 
			
		||||
              licenceState.value.editRecord ? 'YES' : 'NO'
 | 
			
		||||
            }}</span>
 | 
			
		||||
          </p>
 | 
			
		||||
          <p class="m-0">
 | 
			
		||||
          <p cds-text="label" class="m-0">
 | 
			
		||||
            Add record:
 | 
			
		||||
            <span class="dark">{{
 | 
			
		||||
              licenceState.value.addRecord ? 'YES' : 'NO'
 | 
			
		||||
 
 | 
			
		||||
@@ -99,15 +99,18 @@
 | 
			
		||||
              </ng-container>
 | 
			
		||||
              {{ 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>
 | 
			
		||||
@@ -361,7 +364,7 @@
 | 
			
		||||
        <h3
 | 
			
		||||
          class="viewerTitle clr-flex-column d-flex clr-flex-sm-row clr-align-items-center clr-justify-content-center"
 | 
			
		||||
        >
 | 
			
		||||
          <clr-tooltip class="d-flex">
 | 
			
		||||
          <clr-tooltip class="d-flex clr-align-items-center">
 | 
			
		||||
            <clr-icon
 | 
			
		||||
              clrTooltipTrigger
 | 
			
		||||
              (click)="datasetInfo = true"
 | 
			
		||||
@@ -373,19 +376,22 @@
 | 
			
		||||
            <clr-icon
 | 
			
		||||
              *ngIf="tableTitle?.includes('-FC')"
 | 
			
		||||
              shape="bolt"
 | 
			
		||||
              class="color-yellow mt-5 mr-5"
 | 
			
		||||
              class="color-yellow mr-5"
 | 
			
		||||
            ></clr-icon>
 | 
			
		||||
 | 
			
		||||
            <span clrTooltipTrigger *ngIf="tableTitle && tableTitle.length > 0">
 | 
			
		||||
              {{ tableTitle?.replace('-FC', '') }}
 | 
			
		||||
            </span>
 | 
			
		||||
            <clr-tooltip-content
 | 
			
		||||
              clrPosition="bottom-left"
 | 
			
		||||
              clrSize="lg"
 | 
			
		||||
              *clrIfOpen
 | 
			
		||||
            >
 | 
			
		||||
              {{ this.dsNote }}
 | 
			
		||||
            </clr-tooltip-content>
 | 
			
		||||
 | 
			
		||||
            <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>
 | 
			
		||||
 | 
			
		||||
          <ng-container *ngIf="tableTitle && tableTitle.length > 0">
 | 
			
		||||
@@ -419,62 +425,34 @@
 | 
			
		||||
            options
 | 
			
		||||
          </button>
 | 
			
		||||
          <clr-dropdown-menu clrPosition="bottom-right" *clrIfOpen>
 | 
			
		||||
            <button
 | 
			
		||||
              type="button"
 | 
			
		||||
              class="btn btn-sm btn-success-outline"
 | 
			
		||||
              (click)="newViewbox()"
 | 
			
		||||
              clrDropdownItem
 | 
			
		||||
            >
 | 
			
		||||
            <div (click)="newViewbox()" clrDropdownItem>
 | 
			
		||||
              <clr-icon shape="view-cards"></clr-icon>
 | 
			
		||||
              <span>Viewboxes</span>
 | 
			
		||||
            </button>
 | 
			
		||||
            <button
 | 
			
		||||
              type="button"
 | 
			
		||||
              class="btn btn-sm btn-success-outline"
 | 
			
		||||
            </div>
 | 
			
		||||
            <div
 | 
			
		||||
              *ngIf="tableEditExists()"
 | 
			
		||||
              (click)="editTable()"
 | 
			
		||||
              clrDropdownItem
 | 
			
		||||
            >
 | 
			
		||||
              <clr-icon shape="pencil"></clr-icon>
 | 
			
		||||
              <span>Edit</span>
 | 
			
		||||
            </button>
 | 
			
		||||
            <button
 | 
			
		||||
              type="button"
 | 
			
		||||
              class="btn btn-sm btn-success-outline"
 | 
			
		||||
              *ngIf="tableuri"
 | 
			
		||||
              (click)="goToLineage()"
 | 
			
		||||
              clrDropdownItem
 | 
			
		||||
            >
 | 
			
		||||
            </div>
 | 
			
		||||
            <div *ngIf="tableuri" (click)="goToLineage()" clrDropdownItem>
 | 
			
		||||
              <clr-icon shape="switch"></clr-icon>
 | 
			
		||||
              <span>Lineage</span>
 | 
			
		||||
            </button>
 | 
			
		||||
            <button
 | 
			
		||||
              type="button"
 | 
			
		||||
              class="btn btn-sm btn-outline btn-block"
 | 
			
		||||
              (click)="openQb()"
 | 
			
		||||
              clrDropdownItem
 | 
			
		||||
            >
 | 
			
		||||
            </div>
 | 
			
		||||
            <div (click)="openQb()" clrDropdownItem>
 | 
			
		||||
              <clr-icon shape="filter"></clr-icon>
 | 
			
		||||
              <span>Filter</span>
 | 
			
		||||
            </button>
 | 
			
		||||
            <button
 | 
			
		||||
              type="button"
 | 
			
		||||
              class="btn btn-sm btn-success-outline"
 | 
			
		||||
              (click)="openDownload = true"
 | 
			
		||||
              clrDropdownItem
 | 
			
		||||
            >
 | 
			
		||||
            </div>
 | 
			
		||||
            <div (click)="openDownload = true" clrDropdownItem>
 | 
			
		||||
              <clr-icon shape="download"></clr-icon>
 | 
			
		||||
              <span>Download</span>
 | 
			
		||||
            </button>
 | 
			
		||||
            <button
 | 
			
		||||
              type="button"
 | 
			
		||||
              class="btn btn-sm btn-success-outline"
 | 
			
		||||
              (click)="showWebQuery()"
 | 
			
		||||
              clrDropdownItem
 | 
			
		||||
            >
 | 
			
		||||
            </div>
 | 
			
		||||
            <div (click)="showWebQuery()" clrDropdownItem>
 | 
			
		||||
              <clr-icon shape="download-cloud"></clr-icon>
 | 
			
		||||
              <span>Web Query URL</span>
 | 
			
		||||
            </button>
 | 
			
		||||
            </div>
 | 
			
		||||
          </clr-dropdown-menu>
 | 
			
		||||
        </clr-dropdown>
 | 
			
		||||
      </div>
 | 
			
		||||
@@ -667,6 +645,12 @@
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<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)]="viewboxOpen"></app-viewboxes>
 | 
			
		||||
 
 | 
			
		||||
@@ -57,6 +57,7 @@ clr-tree-node button {
 | 
			
		||||
.viewerTitle {
 | 
			
		||||
  text-align:center;
 | 
			
		||||
  margin-bottom: 15px;
 | 
			
		||||
  margin-top: 16px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dropdown-menu {
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,11 @@ import { FilterGroup, FilterQuery } from '../models/FilterQuery'
 | 
			
		||||
import { HotTableInterface } from '../models/HotTable.interface'
 | 
			
		||||
import { LoggerService } from '../services/logger.service'
 | 
			
		||||
import Handsontable from 'handsontable'
 | 
			
		||||
import { $DataFormats, DSMeta } from '../models/sas/editors-getdata.model'
 | 
			
		||||
import {
 | 
			
		||||
  $DataFormats,
 | 
			
		||||
  DSMeta,
 | 
			
		||||
  Version
 | 
			
		||||
} from '../models/sas/editors-getdata.model'
 | 
			
		||||
import { mergeColsRules } from '../shared/dc-validator/utils/mergeColsRules'
 | 
			
		||||
import { PublicViewtablesServiceResponse } from '../models/sas/public-viewtables.model'
 | 
			
		||||
import { PublicViewlibsServiceResponse } from '../models/sas/public-viewlibs.model'
 | 
			
		||||
@@ -95,6 +99,7 @@ export class ViewerComponent implements AfterContentInit, AfterViewInit {
 | 
			
		||||
  public $dataFormats: $DataFormats | null = null
 | 
			
		||||
  public datasetInfo: boolean = false
 | 
			
		||||
  public dsmeta: DSMeta[] = []
 | 
			
		||||
  public versions: Version[] = []
 | 
			
		||||
  public dsNote = ''
 | 
			
		||||
 | 
			
		||||
  public licenceState = this.licenceService.licenceState
 | 
			
		||||
@@ -247,6 +252,7 @@ export class ViewerComponent implements AfterContentInit, AfterViewInit {
 | 
			
		||||
        this.hotTable.data = res.viewdata
 | 
			
		||||
        this.$dataFormats = res.$viewdata
 | 
			
		||||
        this.dsmeta = res.dsmeta
 | 
			
		||||
        this.versions = res.versions || []
 | 
			
		||||
        this.setDSNote()
 | 
			
		||||
        this.numberOfRows = res.sasparams[0].NOBS
 | 
			
		||||
        this.queryText = res.sasparams[0].FILTER_TEXT
 | 
			
		||||
@@ -805,6 +811,7 @@ export class ViewerComponent implements AfterContentInit, AfterViewInit {
 | 
			
		||||
          this.hotTable.data = res.viewdata
 | 
			
		||||
          this.$dataFormats = res.$viewdata
 | 
			
		||||
          this.dsmeta = res.dsmeta
 | 
			
		||||
          this.versions = res.versions || []
 | 
			
		||||
          this.setDSNote()
 | 
			
		||||
          this.queryText = res.sasparams[0].FILTER_TEXT
 | 
			
		||||
          let columns: any[] = []
 | 
			
		||||
@@ -1019,6 +1026,16 @@ export class ViewerComponent implements AfterContentInit, AfterViewInit {
 | 
			
		||||
    this.sasStoreService.removeClause()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public datasetInfoModalRowClicked(value: Version | DSMeta) {
 | 
			
		||||
    if ((<Version>value).LOAD_REF !== undefined) {
 | 
			
		||||
      // Type is Version
 | 
			
		||||
      const row = value as Version
 | 
			
		||||
      const url = `/stage/${row.LOAD_REF}`
 | 
			
		||||
 | 
			
		||||
      this.router.navigate([url])
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private setDSNote() {
 | 
			
		||||
    const notes = this.dsmeta.find((item) => item.NAME === 'NOTES')
 | 
			
		||||
    const longDesc = this.dsmeta.find((item) => item.NAME === 'DD_LONGDESC')
 | 
			
		||||
 
 | 
			
		||||
@@ -94,13 +94,21 @@
 | 
			
		||||
      }}</i>
 | 
			
		||||
      <h5 class="d-flex clr-col-12 clr-justify-content-center mt-5-i">
 | 
			
		||||
        Rules Source:
 | 
			
		||||
        <a class="ml-10" [routerLink]="'/view/data/' + rulesSource">
 | 
			
		||||
        <a
 | 
			
		||||
          cds-text="labelLink"
 | 
			
		||||
          class="ml-10"
 | 
			
		||||
          [routerLink]="'/view/data/' + rulesSource"
 | 
			
		||||
        >
 | 
			
		||||
          {{ rulesSource }}
 | 
			
		||||
        </a>
 | 
			
		||||
      </h5>
 | 
			
		||||
      <h5 class="d-flex clr-col-12 clr-justify-content-center mt-5-i">
 | 
			
		||||
        Target dataset:
 | 
			
		||||
        <a class="ml-10" [routerLink]="'/view/data/' + selectedXLMap.targetDS">
 | 
			
		||||
        <a
 | 
			
		||||
          cds-text="labelLink"
 | 
			
		||||
          class="ml-10"
 | 
			
		||||
          [routerLink]="'/view/data/' + selectedXLMap.targetDS"
 | 
			
		||||
        >
 | 
			
		||||
          {{ selectedXLMap.targetDS }}
 | 
			
		||||
        </a>
 | 
			
		||||
      </h5>
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ try {
 | 
			
		||||
 | 
			
		||||
  writeFileSync(
 | 
			
		||||
    file,
 | 
			
		||||
    `//IMPORTANT: THIS FILE IS AUTO GENERATED BASED ON LICENCE.MD FILE!\nexport const EULA = \`\n${licence}\n\``,
 | 
			
		||||
    `//IMPORTANT: THIS FILE IS AUTO GENERATED BASED ON LICENCE.MD FILE!\nexport const EULA = \`\n${licence}\n\`\n`,
 | 
			
		||||
    { encoding: 'utf-8' }
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -57,7 +57,7 @@
 | 
			
		||||
  >
 | 
			
		||||
  </sasjs>
 | 
			
		||||
 | 
			
		||||
  <body class="m-0">
 | 
			
		||||
  <body cds-theme="light" class="m-0">
 | 
			
		||||
    <my-app></my-app>
 | 
			
		||||
  </body>
 | 
			
		||||
</html>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,12 @@
 | 
			
		||||
/* You can add global styles to this file, and also import other style files */
 | 
			
		||||
@import '~handsontable/dist/handsontable.full.css';
 | 
			
		||||
 | 
			
		||||
@import '~@clr/ui/clr-ui.min.css';
 | 
			
		||||
@import '~@clr/icons/clr-icons.min.css';
 | 
			
		||||
 | 
			
		||||
@import '@cds/core/global.min.css';
 | 
			
		||||
@import '@cds/core/styles/theme.dark.min.css';
 | 
			
		||||
@import '@clr/ui/clr-ui.min.css';
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: text-security-disc;
 | 
			
		||||
  src: url('https://raw.githubusercontent.com/noppa/text-security/master/dist/text-security-disc.woff');
 | 
			
		||||
@@ -29,6 +32,14 @@ button {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[cds-text=label] {
 | 
			
		||||
  color: var(--cds-global-typography-color-200);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[cds-text=labelLink] {
 | 
			
		||||
  line-height: 1.8 !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Custom loading spinner
 | 
			
		||||
.slider {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
@@ -261,6 +272,10 @@ button {
 | 
			
		||||
  margin-right: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mr-5i {
 | 
			
		||||
  margin-right: 5px !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mr-10 {
 | 
			
		||||
  margin-right: 10px;
 | 
			
		||||
}
 | 
			
		||||
@@ -660,6 +675,10 @@ clr-icon.is-info {
 | 
			
		||||
  -webkit-box-direction: normal;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn .clr-loading-btn-content {
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn.btn-danger,
 | 
			
		||||
.btn.btn-warning {
 | 
			
		||||
  border-color: #ef4f2e;
 | 
			
		||||
@@ -667,6 +686,11 @@ clr-icon.is-info {
 | 
			
		||||
  color: #fff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Vertical align fix for small buttons with icons
 | 
			
		||||
.btn.btn-sm:has(clr-icon) {
 | 
			
		||||
  line-height: 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.d-none {
 | 
			
		||||
  display: none !important;
 | 
			
		||||
}
 | 
			
		||||
@@ -713,6 +737,11 @@ clr-icon.is-info {
 | 
			
		||||
  border: 1px solid red !important;
 | 
			
		||||
  color: #ffffff !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.handsontable .numericListbox {
 | 
			
		||||
  text-align: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-top-20 {
 | 
			
		||||
  margin-top: 20px;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9226
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										9226
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "dcfrontend",
 | 
			
		||||
  "version": "6.6.0",
 | 
			
		||||
  "version": "6.7.0",
 | 
			
		||||
  "description": "Data Controller",
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@saithodev/semantic-release-gitea": "^2.1.0",
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,10 @@ _webout = `{"SYSDATE" : "26SEP22"
 | 
			
		||||
"APPROVER": "sasdemo"
 | 
			
		||||
}
 | 
			
		||||
]
 | 
			
		||||
, "histparams":
 | 
			
		||||
[
 | 
			
		||||
{"HIST":100 ,"STARTROW":1 ,"NOBS":3 }
 | 
			
		||||
]
 | 
			
		||||
,"_DEBUG" : ""
 | 
			
		||||
,"_METAUSER": "sasdemo@SAS"
 | 
			
		||||
,"_METAPERSON": "sasdemo"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										29
									
								
								sas/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										29
									
								
								sas/package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -7,7 +7,7 @@
 | 
			
		||||
      "name": "dc-sas",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@sasjs/cli": "^4.11.1",
 | 
			
		||||
        "@sasjs/core": "^4.49.0"
 | 
			
		||||
        "@sasjs/core": "^4.52.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@coolaj86/urequest": {
 | 
			
		||||
@@ -116,9 +116,9 @@
 | 
			
		||||
      "integrity": "sha512-Grwydm5GxBsYk238PZw41XPjXVVQ9vWcvfZ06L2P0bQbvK0sGn7l69JA7H5MGr3QcaLpiD4Kg70cAh7PgE+JOw=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@sasjs/core": {
 | 
			
		||||
      "version": "4.49.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.49.0.tgz",
 | 
			
		||||
      "integrity": "sha512-hp3Hb4DkT6FmowyNHTOvSlgmSObW9WeuTJj+TQlwPgnBo59mAB4XFUnUaYSA+7ghvsHqUZf1OP2eSYqmnN5swQ=="
 | 
			
		||||
      "version": "4.52.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.52.0.tgz",
 | 
			
		||||
      "integrity": "sha512-r+yQYSTYdvNPJ82n2xW/rbJd/cJpk3HuFhCa49RTDlDWjXMXJlymvGB0ltv3/L8rNk4+TwoyMzb7cJYnILi6ag=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@sasjs/lint": {
 | 
			
		||||
      "version": "2.3.1",
 | 
			
		||||
@@ -229,12 +229,6 @@
 | 
			
		||||
        "@types/node": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/tough-cookie": {
 | 
			
		||||
      "version": "4.0.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
 | 
			
		||||
      "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
 | 
			
		||||
      "peer": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/abab": {
 | 
			
		||||
      "version": "2.0.6",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
 | 
			
		||||
@@ -1834,9 +1828,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@sasjs/core": {
 | 
			
		||||
      "version": "4.49.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.49.0.tgz",
 | 
			
		||||
      "integrity": "sha512-hp3Hb4DkT6FmowyNHTOvSlgmSObW9WeuTJj+TQlwPgnBo59mAB4XFUnUaYSA+7ghvsHqUZf1OP2eSYqmnN5swQ=="
 | 
			
		||||
      "version": "4.52.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.52.0.tgz",
 | 
			
		||||
      "integrity": "sha512-r+yQYSTYdvNPJ82n2xW/rbJd/cJpk3HuFhCa49RTDlDWjXMXJlymvGB0ltv3/L8rNk4+TwoyMzb7cJYnILi6ag=="
 | 
			
		||||
    },
 | 
			
		||||
    "@sasjs/lint": {
 | 
			
		||||
      "version": "2.3.1",
 | 
			
		||||
@@ -1933,12 +1927,6 @@
 | 
			
		||||
        "@types/node": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@types/tough-cookie": {
 | 
			
		||||
      "version": "4.0.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
 | 
			
		||||
      "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
 | 
			
		||||
      "peer": true
 | 
			
		||||
    },
 | 
			
		||||
    "abab": {
 | 
			
		||||
      "version": "2.0.6",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
 | 
			
		||||
@@ -2965,8 +2953,7 @@
 | 
			
		||||
    "ws": {
 | 
			
		||||
      "version": "8.13.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
 | 
			
		||||
      "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
 | 
			
		||||
      "requires": {}
 | 
			
		||||
      "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA=="
 | 
			
		||||
    },
 | 
			
		||||
    "xml": {
 | 
			
		||||
      "version": "1.0.1",
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,6 @@
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@sasjs/cli": "^4.11.1",
 | 
			
		||||
    "@sasjs/core": "^4.49.0"
 | 
			
		||||
    "@sasjs/core": "^4.52.0"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -75,7 +75,9 @@ data basetable;
 | 
			
		||||
run;
 | 
			
		||||
/* create results table */
 | 
			
		||||
data results;
 | 
			
		||||
  format test $7. result $4. reason $50.; stop;
 | 
			
		||||
  format test $7. result $4. reason $50.;
 | 
			
		||||
  call missing(of _all_);
 | 
			
		||||
  stop;
 | 
			
		||||
run;
 | 
			
		||||
 | 
			
		||||
%put assigning lib..;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										135
									
								
								sas/sasjs/macros/mpe_checkrestore.sas
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								sas/sasjs/macros/mpe_checkrestore.sas
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,135 @@
 | 
			
		||||
/**
 | 
			
		||||
  @file
 | 
			
		||||
  @brief Checks if a user is able to restore a LOAD_REF
 | 
			
		||||
  @details Not all LOAD_REFs can be restored - maybe the user does not have
 | 
			
		||||
    permission, maybe the load was never loaded, or maybe the load was not
 | 
			
		||||
    tracked.
 | 
			
		||||
 | 
			
		||||
    The macro creates two output (global) macro variables.
 | 
			
		||||
 | 
			
		||||
  @param [in] LOAD_REF The Load Reference to check
 | 
			
		||||
  @param [out] outresult= (ALLOW_RESTORE) Output macro variable NAME.  Will be
 | 
			
		||||
    given the value of YES or NO depending on whether the user is allowed to
 | 
			
		||||
    restore the load ref.
 | 
			
		||||
  @param [out] outreason= (REASON) Output macro variable NAME.
 | 
			
		||||
    Will be populated with the reason for which the restore decision was made.
 | 
			
		||||
 | 
			
		||||
  <h4> SAS Macros </h4>
 | 
			
		||||
  @li mf_nobs.sas
 | 
			
		||||
  @li mf_getuser.sas
 | 
			
		||||
  @li mpe_accesscheck.sas
 | 
			
		||||
  @li mpe_getgroups.sas
 | 
			
		||||
 | 
			
		||||
  <h4> Related Macros </h4>
 | 
			
		||||
  @li mpe_checkrestore.test.sas
 | 
			
		||||
 | 
			
		||||
  @version 9.2
 | 
			
		||||
  @author 4GL Apps Ltd
 | 
			
		||||
  @copyright 4GL Apps Ltd.  This code may only be used within Data Controller
 | 
			
		||||
    and may not be re-distributed or re-sold without the express permission of
 | 
			
		||||
    4GL Apps Ltd.
 | 
			
		||||
**/
 | 
			
		||||
 | 
			
		||||
%macro mpe_checkrestore(load_ref,
 | 
			
		||||
  outresult=ALLOW_RESTORE,
 | 
			
		||||
  outreason=REASON
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
  %global &outresult &outreason;
 | 
			
		||||
  %let &outresult=NO;
 | 
			
		||||
  %let &outreason=NOTFOUND;
 | 
			
		||||
 | 
			
		||||
  /* check if there is actually a version to restore */
 | 
			
		||||
  %local chk;
 | 
			
		||||
  %let chk=0;
 | 
			
		||||
  proc sql noprint;
 | 
			
		||||
  select count(*) into: chk from &dc_libref..mpe_audit
 | 
			
		||||
    where load_ref="&load_ref";
 | 
			
		||||
  %if &chk=0 %then %do;
 | 
			
		||||
    %let allow_restore=NO;
 | 
			
		||||
    %let reason=No entry for &load_ref in MPE_AUDIT;
 | 
			
		||||
    %return;
 | 
			
		||||
  %end;
 | 
			
		||||
 | 
			
		||||
  /* grab user groups */
 | 
			
		||||
  %local user;
 | 
			
		||||
  %let user=%mf_getuser();
 | 
			
		||||
  %mpe_getgroups(user=&user,outds=work.groups)
 | 
			
		||||
 | 
			
		||||
  /* check if user is admin */
 | 
			
		||||
  %local is_admin;
 | 
			
		||||
  %let is_admin=0;
 | 
			
		||||
  proc sql;
 | 
			
		||||
  select count(*) into: is_admin from work.groups
 | 
			
		||||
    where groupname="&dc_admin_group";
 | 
			
		||||
 | 
			
		||||
  %if &is_admin>0 %then %do;
 | 
			
		||||
    %let allow_restore=YES;
 | 
			
		||||
    %let reason=IS ADMIN;
 | 
			
		||||
    %return;
 | 
			
		||||
  %end;
 | 
			
		||||
 | 
			
		||||
  /* check if user has basic access */
 | 
			
		||||
  %local libds;
 | 
			
		||||
  proc sql noprint;
 | 
			
		||||
  select cats(base_lib,'.',base_ds) into: libds
 | 
			
		||||
    from &mpelib..mpe_submit
 | 
			
		||||
    where TABLE_ID="&load_ref";
 | 
			
		||||
  %mpe_accesscheck(&libds,outds=work.access_check
 | 
			
		||||
    ,user=&user
 | 
			
		||||
    ,access_level=EDIT
 | 
			
		||||
  )
 | 
			
		||||
  %if %mf_nobs(access_check)=0 %then %do;
 | 
			
		||||
    %let allow_restore=NO;
 | 
			
		||||
    %let reason=No access in MPE_TABLES;
 | 
			
		||||
    %return;
 | 
			
		||||
  %end;
 | 
			
		||||
 | 
			
		||||
  /* check if user has column level security rules */
 | 
			
		||||
  proc sql;
 | 
			
		||||
  create table work.cls_rules as
 | 
			
		||||
    select *
 | 
			
		||||
    from &mpelib..mpe_column_level_security
 | 
			
		||||
    where &dc_dttmtfmt. lt tx_to
 | 
			
		||||
      and CLS_SCOPE in ("EDIT",'ALL')
 | 
			
		||||
      and CLS_ACTIVE=1
 | 
			
		||||
      and upcase(CLS_GROUP) in (select upcase(groupname) from work.groups)
 | 
			
		||||
      and CLS_LIBREF="%upcase(&base_lib)"
 | 
			
		||||
      and CLS_TABLE="%upcase(&base_ds)";
 | 
			
		||||
  %if %mf_nobs(work.cls_rules)>0 %then %do;
 | 
			
		||||
    %let allow_restore=NO;
 | 
			
		||||
    %let reason=User has restrictions in MPE_COLUMN_LEVEL_SECURITY;
 | 
			
		||||
    data _null_;
 | 
			
		||||
      set work.cls_rules;
 | 
			
		||||
      putlog (_all_)(=);
 | 
			
		||||
      if _n_>5 then stop;
 | 
			
		||||
    run;
 | 
			
		||||
    %return;
 | 
			
		||||
  %end;
 | 
			
		||||
 | 
			
		||||
  /* check if user has row level security rules */
 | 
			
		||||
  proc sql;
 | 
			
		||||
  create table work.rls_rules as
 | 
			
		||||
    select *
 | 
			
		||||
    from &mpelib..mpe_row_level_security
 | 
			
		||||
    where &dc_dttmtfmt. lt tx_to
 | 
			
		||||
      and rls_scope in ("EDIT",'ALL')
 | 
			
		||||
      and upcase(rls_group) in (select upcase(groupname) from work.groups)
 | 
			
		||||
      and rls_libref="&base_lib"
 | 
			
		||||
      and rls_table="&base_ds"
 | 
			
		||||
      and rls_active=1;
 | 
			
		||||
  %if %mf_nobs(work.rls_rules)>0 %then %do;
 | 
			
		||||
    %let allow_restore=NO;
 | 
			
		||||
    %let reason=User has restrictions in MPE_ROW_LEVEL_SECURITY;
 | 
			
		||||
    data _null_;
 | 
			
		||||
      set work.rls_rules;
 | 
			
		||||
      putlog (_all_)(=);
 | 
			
		||||
      if _n_>5 then stop;
 | 
			
		||||
    run;
 | 
			
		||||
    %return;
 | 
			
		||||
  %end;
 | 
			
		||||
  %else %do;
 | 
			
		||||
    %let allow_restore=YES;
 | 
			
		||||
    %let reason=CHECKS PASSED;
 | 
			
		||||
  %end;
 | 
			
		||||
%mend mpe_checkrestore;
 | 
			
		||||
							
								
								
									
										59
									
								
								sas/sasjs/macros/mpe_checkrestore.test.sas
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								sas/sasjs/macros/mpe_checkrestore.test.sas
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
			
		||||
/**
 | 
			
		||||
  @file
 | 
			
		||||
  @brief Testing mpe_checkrestore macro
 | 
			
		||||
  @details Checking functionality of mpe_checkrestore.sas macro
 | 
			
		||||
 | 
			
		||||
  <h4> SAS Macros </h4>
 | 
			
		||||
  @li dc_getsettings.sas
 | 
			
		||||
  @li mpe_checkrestore.sas
 | 
			
		||||
  @li mp_assert.sas
 | 
			
		||||
  @li mp_assertscope.sas
 | 
			
		||||
  @li mp_testservice.sas
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  @author 4GL Apps Ltd
 | 
			
		||||
  @copyright 4GL Apps Ltd.  This code may only be used within Data Controller
 | 
			
		||||
    and may not be re-distributed or re-sold without the express permission of
 | 
			
		||||
    4GL Apps Ltd.
 | 
			
		||||
 | 
			
		||||
**/
 | 
			
		||||
 | 
			
		||||
/* first, run a data update */
 | 
			
		||||
%mp_testservice(&appLoc/tests/services/auditors/postdata.test.1,
 | 
			
		||||
  viyacontext=&defaultcontext
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/* now grab the latest update */
 | 
			
		||||
%let loadref=0;
 | 
			
		||||
data APPROVE1;
 | 
			
		||||
  set &mpelib..mpe_submit end=last;
 | 
			
		||||
  if last then call symputx('loadref',table_id);
 | 
			
		||||
run;
 | 
			
		||||
 | 
			
		||||
%global dc_repo_users dc_macros dc_libref dc_staging_area dc_admin_group
 | 
			
		||||
  mpelib dc_dttmtfmt;
 | 
			
		||||
%dc_getsettings()
 | 
			
		||||
 | 
			
		||||
%put checking it is restorable;
 | 
			
		||||
%global allow_restore reason;
 | 
			
		||||
%mp_assertscope(SNAPSHOT)
 | 
			
		||||
%mpe_checkrestore(&loadref,outresult=ALLOW_RESTORE,outreason=REASON)
 | 
			
		||||
%mp_assertscope(COMPARE,
 | 
			
		||||
  desc=Checking macro variables against previous snapshot,
 | 
			
		||||
  ignorelist=ALLOW_RESTORE REASON
 | 
			
		||||
    MCLIB0_JADP1LEN MCLIB0_JADP2LEN MCLIB0_JADPNUM MCLIB0_JADVLEN
 | 
			
		||||
    MCLIB2_JADP1LEN MCLIB2_JADP2LEN MCLIB2_JADPNUM MCLIB2_JADVLEN
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
%mp_assert(
 | 
			
		||||
  iftrue=(&syscc=0),
 | 
			
		||||
  desc=Checking successful submission
 | 
			
		||||
)
 | 
			
		||||
%mp_assert(
 | 
			
		||||
  iftrue=(&ALLOW_RESTORE=YES),
 | 
			
		||||
  desc=Checking restoring is possible
 | 
			
		||||
)
 | 
			
		||||
%mp_assert(
 | 
			
		||||
  iftrue=(&REASON=IS ADMIN),
 | 
			
		||||
  desc=Checking reason code returned
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										34
									
								
								sas/sasjs/macros/mpe_getversions.sas
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								sas/sasjs/macros/mpe_getversions.sas
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
/**
 | 
			
		||||
  @file
 | 
			
		||||
  @brief Get previous versions of a table
 | 
			
		||||
  @details  Used to fetch version data for a particular table
 | 
			
		||||
  Delivered as part of this issue: https://git.datacontroller.io/dc/dc/issues/84
 | 
			
		||||
 | 
			
		||||
  @param [in] dclib The DC libref
 | 
			
		||||
  @param [in] lib The library of the dataset for which to fetch versions
 | 
			
		||||
  @param [in] ds The dataset to fetch versions for
 | 
			
		||||
  @param [out] outds= (work.mpe_getversions) the DS to create
 | 
			
		||||
 | 
			
		||||
**/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
%macro mpe_getversions(dclib,libref,ds,outds=work.mpe_getversions);
 | 
			
		||||
 | 
			
		||||
  proc sql;
 | 
			
		||||
  create table &outds as
 | 
			
		||||
    select a.table_id as LOAD_REF
 | 
			
		||||
      ,a.reviewed_by_nm as user_nm
 | 
			
		||||
      ,a.reviewed_on_dttm as version_dttm_num
 | 
			
		||||
      ,put(a.reviewed_on_dttm,datetime19.) as VERSION_DTTM
 | 
			
		||||
      ,a.submitted_reason_txt as VERSION_DESC
 | 
			
		||||
      ,b.changed_records
 | 
			
		||||
      ,b.new_records
 | 
			
		||||
      ,b.deleted_records
 | 
			
		||||
    from &dclib..mpe_submit a
 | 
			
		||||
    left join &dclib..MPE_DATALOADS(where=(libref="&libref" & dsn="&ds")) b
 | 
			
		||||
    on a.table_id=b.etlsource
 | 
			
		||||
    where a.base_lib="&libref" and a.base_ds="&ds"
 | 
			
		||||
      and a.submit_status_cd='APPROVED' and a.num_of_approvals_remaining=0
 | 
			
		||||
    order by a.reviewed_on_dttm desc;
 | 
			
		||||
 | 
			
		||||
%mend mpe_getversions;
 | 
			
		||||
							
								
								
									
										84
									
								
								sas/sasjs/macros/mpe_getversions.test.sas
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								sas/sasjs/macros/mpe_getversions.test.sas
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,84 @@
 | 
			
		||||
/**
 | 
			
		||||
  @file
 | 
			
		||||
  @brief Testing mpe_getversions macro
 | 
			
		||||
  @details Checking functionality of mpe_getversions.sas macro
 | 
			
		||||
 | 
			
		||||
  <h4> SAS Macros </h4>
 | 
			
		||||
  @li mf_nobs.sas
 | 
			
		||||
  @li mp_assert.sas
 | 
			
		||||
  @li mp_assertscope.sas
 | 
			
		||||
  @li mpe_getversions.sas
 | 
			
		||||
  @li mpe_targetloader.sas
 | 
			
		||||
 | 
			
		||||
**/
 | 
			
		||||
 | 
			
		||||
/* run the macro*/
 | 
			
		||||
%mp_assertscope(SNAPSHOT)
 | 
			
		||||
%mpe_getversions(&mpelib,&mpelib,MPE_DATADICTIONARY, outds=ds0)
 | 
			
		||||
%mp_assertscope(COMPARE,
 | 
			
		||||
  desc=Checking macro variables against previous snapshot
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* now stage some data */
 | 
			
		||||
 | 
			
		||||
%let f1=%mf_getuniquefileref();
 | 
			
		||||
data _null_;
 | 
			
		||||
  file &f1 termstr=crlf;
 | 
			
		||||
  put 'ACTION:$char4. MESSAGE:$char40. LIBDS:$char38.';
 | 
			
		||||
  put "LOAD,staging some data,&dclib..MPE_DATADICTIONARY";
 | 
			
		||||
run;
 | 
			
		||||
data work.jsdata;
 | 
			
		||||
  set &mpelib..MPE_DATADICTIONARY;
 | 
			
		||||
  _____DELETE__THIS__RECORD_____='No';
 | 
			
		||||
  dd_source=cats(ranuni(0));
 | 
			
		||||
  output;
 | 
			
		||||
  stop;
 | 
			
		||||
run;
 | 
			
		||||
%mx_testservice(&appLoc/services/editors/stagedata,
 | 
			
		||||
  viyacontext=&defaultcontext,
 | 
			
		||||
  inputfiles=&f1:sascontroltable,
 | 
			
		||||
  inputdatasets=jsdata,
 | 
			
		||||
  outlib=web1,
 | 
			
		||||
  mdebug=&sasjs_mdebug
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
%let status=0;
 | 
			
		||||
data work.sasparams;
 | 
			
		||||
  set web1.sasparams;
 | 
			
		||||
  putlog (_all_)(=);
 | 
			
		||||
  if status='SUCCESS' then call symputx('status',1);
 | 
			
		||||
  call symputx('dsid',dsid);
 | 
			
		||||
run;
 | 
			
		||||
%mp_assert(
 | 
			
		||||
  iftrue=(&status=1),
 | 
			
		||||
  desc=Checking staged data component,
 | 
			
		||||
  outds=work.test_results
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/* now approve the data so the change is applied */
 | 
			
		||||
data work.sascontroltable;
 | 
			
		||||
  ACTION='APPROVE_TABLE';
 | 
			
		||||
  TABLE="&dsid";
 | 
			
		||||
  DIFFTIME="%sysfunc(datetime(),datetime19.)";
 | 
			
		||||
  output;
 | 
			
		||||
  stop;
 | 
			
		||||
run;
 | 
			
		||||
%mx_testservice(&appLoc/services/auditors/postdata,
 | 
			
		||||
  viyacontext=&defaultcontext,
 | 
			
		||||
  inputdatasets=work.sascontroltable,
 | 
			
		||||
  outlib=web2,
 | 
			
		||||
  outref=wbout,
 | 
			
		||||
  mdebug=&sasjs_mdebug
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* finally - check that we have an extra version! */
 | 
			
		||||
 | 
			
		||||
%mpe_getversions(&mpelib,&mpelib,MPE_DATADICTIONARY, outds=ds1)
 | 
			
		||||
 | 
			
		||||
%mp_assert(
 | 
			
		||||
  iftrue=(%mf_nobs(ds0) = %mf_nobs(ds1)-1),
 | 
			
		||||
  desc=Checking one extra version was created
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -121,7 +121,8 @@ insert into &mpelib..mpe_loads
 | 
			
		||||
  set USER_NM="&user"
 | 
			
		||||
    ,STATUS='IN PROGRESS'
 | 
			
		||||
    ,CSV_dir="&mperef"
 | 
			
		||||
    ,PROCESSED_DTTM=&now;
 | 
			
		||||
    ,PROCESSED_DTTM=&now
 | 
			
		||||
    ,reason_txt = symget('submitted_reason_txt');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* import CSV */
 | 
			
		||||
@@ -300,28 +301,6 @@ create table vars_csv2 as
 | 
			
		||||
  on a.name=b.name
 | 
			
		||||
  order by a.varnum;
 | 
			
		||||
 | 
			
		||||
  /* make sure that the variables we are importing, actually
 | 
			
		||||
        exist on the target table */
 | 
			
		||||
 | 
			
		||||
  /** edit - extra variables are now simply ignored
 | 
			
		||||
  %local very_bad_vars;
 | 
			
		||||
  select name into: very_bad_vars separated by ' '
 | 
			
		||||
    from vars_csv1
 | 
			
		||||
    where name not in (select name from vars)
 | 
			
		||||
    and name ne "_____DELETE__THIS__RECORD_____";
 | 
			
		||||
  %if %length(&very_bad_vars) > 0 %then %do;
 | 
			
		||||
    %let msg=%str(WARNING: The following vars are not defined in %trim(
 | 
			
		||||
      )&libref..&ds, yet they exist in &csv_dir/&ds..csv: &very_bad_vars);
 | 
			
		||||
    %mpe_loadfail(
 | 
			
		||||
      status=FAILED
 | 
			
		||||
      ,now=&now
 | 
			
		||||
      ,mperef=&mperef
 | 
			
		||||
      ,reason_txt=%quote(&msg)
 | 
			
		||||
      ,dc_dttmtfmt=&dc_dttmtfmt.
 | 
			
		||||
    )
 | 
			
		||||
    %return;
 | 
			
		||||
  %end;
 | 
			
		||||
  **/
 | 
			
		||||
 | 
			
		||||
/* now build input statement */
 | 
			
		||||
data final_check;
 | 
			
		||||
@@ -408,7 +387,7 @@ run;
 | 
			
		||||
  /* get log back */
 | 
			
		||||
  proc printto log=&logloc;run;
 | 
			
		||||
  data _null_; infile tmp; input; putlog _infile_;run;
 | 
			
		||||
  /* scan log for invalid data warning */
 | 
			
		||||
  /* scan log for invalid data warnings */
 | 
			
		||||
  data _null_;
 | 
			
		||||
    infile tmp;
 | 
			
		||||
    input;
 | 
			
		||||
 
 | 
			
		||||
@@ -26,14 +26,6 @@
 | 
			
		||||
%end;
 | 
			
		||||
 | 
			
		||||
proc sql;
 | 
			
		||||
insert into &lib..mpe_alerts set
 | 
			
		||||
    tx_from=0
 | 
			
		||||
    ,tx_to='31DEC9999:23:59:59'dt
 | 
			
		||||
    ,alert_event='*ALL*'
 | 
			
		||||
    ,alert_lib='*ALL*'
 | 
			
		||||
    ,alert_ds='*ALL*'
 | 
			
		||||
    ,alert_user="&sysuserid";
 | 
			
		||||
 | 
			
		||||
insert into &lib..mpe_column_level_security set
 | 
			
		||||
    tx_from=0
 | 
			
		||||
    ,tx_to='31DEC9999:23:59:59'dt
 | 
			
		||||
@@ -1874,6 +1866,16 @@ insert into &lib..MPE_VALIDATIONS set
 | 
			
		||||
    ,rule_value="services/validations/columns_in_libds"
 | 
			
		||||
    ,rule_active=1
 | 
			
		||||
    ,tx_to='31DEC5999:23:59:59'dt;
 | 
			
		||||
/* test softselect on numeric var (should be ordered numerically) */
 | 
			
		||||
insert into &lib..MPE_VALIDATIONS set
 | 
			
		||||
    tx_from=0
 | 
			
		||||
    ,base_lib="&lib"
 | 
			
		||||
    ,base_ds="MPE_X_TEST"
 | 
			
		||||
    ,base_col="SOME_BESTNUM"
 | 
			
		||||
    ,rule_type='SOFTSELECT'
 | 
			
		||||
    ,rule_value="&lib..MPE_X_TEST.SOME_BESTNUM"
 | 
			
		||||
    ,rule_active=1
 | 
			
		||||
    ,tx_to='31DEC5999:23:59:59'dt;
 | 
			
		||||
insert into &lib..MPE_VALIDATIONS set
 | 
			
		||||
    tx_from=0
 | 
			
		||||
    ,base_lib="&lib"
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@
 | 
			
		||||
    ,CLOSE_VARS=                /* provide close vars to override defaults */
 | 
			
		||||
    ,dclib=NOTPROVIDED
 | 
			
		||||
    ,mdebug=0
 | 
			
		||||
    ,dc_dttmtfmt=E8601DT26.6
 | 
			
		||||
    ,dc_dttmtfmt=%sysfunc(datetime())
 | 
			
		||||
  );
 | 
			
		||||
%local lib ds nobs;
 | 
			
		||||
 | 
			
		||||
@@ -219,7 +219,7 @@ run;
 | 
			
		||||
  )
 | 
			
		||||
%end;
 | 
			
		||||
%else %do;
 | 
			
		||||
  %put WARNING: LOADTYPE &LOADTYPE not supported;
 | 
			
		||||
  %put %str(WARN)ING: LOADTYPE &LOADTYPE not supported;
 | 
			
		||||
  %let syscc=4;
 | 
			
		||||
  %mp_abort(msg=LOADTYPE &LOADTYPE not supported,mac=mpe_targetloader.sas)
 | 
			
		||||
%end;
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,8 @@
 | 
			
		||||
        dc_activation_key /* extracted in dc_getsettings */
 | 
			
		||||
        dc_locale /* extracted in dc_getsettings */
 | 
			
		||||
        dc_dttmtfmt /* can be overridden in dc_getsettings */
 | 
			
		||||
        _debug
 | 
			
		||||
        _debug /* automatic variable when provided in URL */
 | 
			
		||||
        sasjs_mdebug /* used to show extra info when _debug is enabled */
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
%if &mpeinit=1 %then %return;
 | 
			
		||||
@@ -110,4 +111,9 @@ run;
 | 
			
		||||
  ,msg=%str(Problem during compilation or with STP precode (&syswarningtext))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
%if "&_debug"="2477" or "&_debug"="fields,log,trace" or "&_debug"="131"
 | 
			
		||||
%then %do;
 | 
			
		||||
  %let sasjs_mdebug=1;
 | 
			
		||||
%end;
 | 
			
		||||
 | 
			
		||||
%mend mpeinit;
 | 
			
		||||
 
 | 
			
		||||
@@ -233,7 +233,7 @@
 | 
			
		||||
        "streamWeb": true,
 | 
			
		||||
        "streamWebFolder": "web",
 | 
			
		||||
        "webSourcePath": "../client/dist",
 | 
			
		||||
        "streamServiceName": "DataController",
 | 
			
		||||
        "streamServiceName": "DataController2",
 | 
			
		||||
        "streamLogo": "favicon.ico",
 | 
			
		||||
        "assetPaths": []
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -59,6 +59,7 @@ data _null_;
 | 
			
		||||
  call symputx('LOAD_REF',TABLE);
 | 
			
		||||
  /* DIFFTIME is when the DIFF was generated on the frontend */
 | 
			
		||||
  call symputx('DIFFTIME',DIFFTIME);
 | 
			
		||||
  putlog (_all_)(=);
 | 
			
		||||
run;
 | 
			
		||||
 | 
			
		||||
%global action is_err err_msg msg;
 | 
			
		||||
@@ -190,7 +191,7 @@ run;
 | 
			
		||||
    and REVIEW_STATUS_ID ne "SUBMITTED";
 | 
			
		||||
  %let authcheck=%mf_getattrn(work.authAPP,NLOBS);
 | 
			
		||||
  %if &authcheck=0 or &prev_upload_check=1 %then %do;
 | 
			
		||||
    %put WARNING: authcheck=&authcheck prev_upload_check=&prev_upload_check;
 | 
			
		||||
    %put %str(WARN)ING: &=authcheck &=prev_upload_check;
 | 
			
		||||
    data apPARAMS;
 | 
			
		||||
      AUTHORISED=&authcheck;
 | 
			
		||||
      PREV_UPLOAD_CHECK=&prev_upload_check;
 | 
			
		||||
 
 | 
			
		||||
@@ -34,12 +34,17 @@ data work.jsdata;
 | 
			
		||||
  SOME_DATETIME=put(dttm2,datetime19.);
 | 
			
		||||
  some_time=put(tm2,time.);
 | 
			
		||||
  drop dt2 dttm2 tm2;
 | 
			
		||||
  _____DELETE__THIS__RECORD_____='No';
 | 
			
		||||
  if _n_=1 then do;
 | 
			
		||||
    primary_key_field=sum(&maxpk,1);
 | 
			
		||||
    some_char='   leadingblanks';
 | 
			
		||||
    some_num=._;
 | 
			
		||||
    _____DELETE__THIS__RECORD_____='Yes';
 | 
			
		||||
    output;
 | 
			
		||||
    _____DELETE__THIS__RECORD_____='No';
 | 
			
		||||
    some_char='   leadingblanks';
 | 
			
		||||
    some_bestnum=._;
 | 
			
		||||
    /* modify 1 record and add 4 more */
 | 
			
		||||
    do primary_key_field=&maxpk to (&maxpk+5);
 | 
			
		||||
      some_num=ranuni(0);
 | 
			
		||||
      output;
 | 
			
		||||
    end;
 | 
			
		||||
  end;
 | 
			
		||||
  else stop;
 | 
			
		||||
run;
 | 
			
		||||
@@ -52,7 +57,7 @@ run;
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
%let status=0;
 | 
			
		||||
data work.sasparams;
 | 
			
		||||
data _null_;
 | 
			
		||||
  set web1.sasparams;
 | 
			
		||||
  putlog (_all_)(=);
 | 
			
		||||
  if status='SUCCESS' then call symputx('status',1);
 | 
			
		||||
@@ -91,7 +96,7 @@ data _null_;
 | 
			
		||||
  /* the JSON libname engine removes leading blanks!!!! */
 | 
			
		||||
  if index(_infile_,'   leadingblanks') then call symputx('leadcheck',1);
 | 
			
		||||
  /* there is no clean way to send a special missing - so is sent as string */
 | 
			
		||||
  if index(_infile_,',"SOME_NUM":"_"') then call symputx('speshcheck',1);
 | 
			
		||||
  if index(_infile_,',"SOME_BESTNUM":"_"') then call symputx('speshcheck',1);
 | 
			
		||||
run;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -103,3 +108,32 @@ run;
 | 
			
		||||
  iftrue=(&leadcheck=1),
 | 
			
		||||
  desc=Checking special characters were applied
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/* Now actually approve the table */
 | 
			
		||||
data work.sascontroltable;
 | 
			
		||||
  ACTION='APPROVE_TABLE';
 | 
			
		||||
  TABLE="&dsid";
 | 
			
		||||
  /* difftime is numeric for approve action */
 | 
			
		||||
  DIFFTIME="%sysfunc(datetime())";
 | 
			
		||||
  output;
 | 
			
		||||
  stop;
 | 
			
		||||
run;
 | 
			
		||||
%mx_testservice(&appLoc/services/auditors/postdata,
 | 
			
		||||
  viyacontext=&defaultcontext,
 | 
			
		||||
  inputdatasets=work.sascontroltable,
 | 
			
		||||
  outlib=web3,
 | 
			
		||||
  outref=wb3,
 | 
			
		||||
  mdebug=&sasjs_mdebug
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
%let status=0;
 | 
			
		||||
data _null_;
 | 
			
		||||
  set web3.apparams;
 | 
			
		||||
  putlog (_all_)(=);
 | 
			
		||||
  if response='SUCCESS!' then call symputx('status',1);
 | 
			
		||||
run;
 | 
			
		||||
 | 
			
		||||
%mp_assert(
 | 
			
		||||
  iftrue=(&status=1 and &syscc=0),
 | 
			
		||||
  desc=Checking successful submission
 | 
			
		||||
)
 | 
			
		||||
@@ -41,6 +41,9 @@
 | 
			
		||||
  <h5> xl_rules </h5>
 | 
			
		||||
  <h5> query </h5>
 | 
			
		||||
 | 
			
		||||
  <h5> versions </h5>
 | 
			
		||||
  history of DC versions for this particular table
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  <h4> SAS Macros </h4>
 | 
			
		||||
  @li dc_assignlib.sas
 | 
			
		||||
@@ -63,6 +66,7 @@
 | 
			
		||||
  @li mpe_columnlevelsecurity.sas
 | 
			
		||||
  @li mpe_dsmeta.sas
 | 
			
		||||
  @li mpe_getlabels.sas
 | 
			
		||||
  @li mpe_getversions.sas
 | 
			
		||||
  @li mpe_filtermaster.sas
 | 
			
		||||
  @li mpe_runhook.sas
 | 
			
		||||
 | 
			
		||||
@@ -631,9 +635,14 @@ create table dqdata as
 | 
			
		||||
      select distinct "&&base_col&x" as base_col length=32
 | 
			
		||||
        ,"&source" as rule_value length=74
 | 
			
		||||
        ,cats(&col) as rule_data length=1000
 | 
			
		||||
        ,0 as selectbox_order
 | 
			
		||||
        ,&col as tmp_order
 | 
			
		||||
      from &lib..&ds
 | 
			
		||||
      order by 1;
 | 
			
		||||
      order by tmp_order;
 | 
			
		||||
    /* ensure both numerics and char vals are ordered correctly */
 | 
			
		||||
    data work.dqdata&x (drop=tmp_order);
 | 
			
		||||
      set work.dqdata&x;
 | 
			
		||||
      selectbox_order=_n_;
 | 
			
		||||
    run;
 | 
			
		||||
    %mp_abort(iftrue= (&syscc ne 0)
 | 
			
		||||
      ,mac=&_program
 | 
			
		||||
      ,msg=%str(syscc=&syscc when selecting &&base_col&x from &orig_libds)
 | 
			
		||||
@@ -667,6 +676,13 @@ run;
 | 
			
		||||
 | 
			
		||||
%mpe_dsmeta(&libds, outds=dsmeta)
 | 
			
		||||
 | 
			
		||||
%mpe_getversions(&mpelib,
 | 
			
		||||
  %scan(&orig_libds,1,.),
 | 
			
		||||
  %scan(&orig_libds,2,.),
 | 
			
		||||
  outds=versions
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* send to the client */
 | 
			
		||||
%webout(OPEN)
 | 
			
		||||
%webout(OBJ,approvers)
 | 
			
		||||
@@ -678,6 +694,7 @@ run;
 | 
			
		||||
%webout(OBJ,query)
 | 
			
		||||
%webout(OBJ,sasdata1,fmt=N,missing=STRING,showmeta=YES,dslabel=sasdata)
 | 
			
		||||
%webout(OBJ,sasparams)
 | 
			
		||||
%webout(OBJ,versions)
 | 
			
		||||
%webout(OBJ,xl_rules)
 | 
			
		||||
%webout(CLOSE)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										157
									
								
								sas/sasjs/services/editors/restore.sas
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								sas/sasjs/services/editors/restore.sas
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,157 @@
 | 
			
		||||
/**
 | 
			
		||||
  @file restore.sas
 | 
			
		||||
  @brief Restores a data version
 | 
			
		||||
  @details Only applies if the history is stored in the audit table
 | 
			
		||||
 | 
			
		||||
  <h4> SAS Macros </h4>
 | 
			
		||||
  @li dc_assignlib.sas
 | 
			
		||||
  @li mf_nobs.sas
 | 
			
		||||
  @li mp_abort.sas
 | 
			
		||||
  @li mp_ds2csv.sas
 | 
			
		||||
  @li mp_stripdiffs.sas
 | 
			
		||||
  @li mpeinit.sas
 | 
			
		||||
  @li mpe_checkrestore.sas
 | 
			
		||||
  @li mpe_loader.sas
 | 
			
		||||
 | 
			
		||||
  <h4> Service Inputs </h4>
 | 
			
		||||
  <h5> restore_in </h5>
 | 
			
		||||
 | 
			
		||||
  |LOAD_REF:$32|
 | 
			
		||||
  |---|
 | 
			
		||||
  |DCXXXXXX|
 | 
			
		||||
 | 
			
		||||
  @version 9.2
 | 
			
		||||
  @author 4GL Apps Ltd
 | 
			
		||||
  @copyright 4GL Apps Ltd.  This code may only be used within Data Controller
 | 
			
		||||
    and may not be re-distributed or re-sold without the express permission of
 | 
			
		||||
    4GL Apps Ltd.
 | 
			
		||||
 | 
			
		||||
**/
 | 
			
		||||
 | 
			
		||||
%mpeinit()
 | 
			
		||||
 | 
			
		||||
%let loadref=;
 | 
			
		||||
data _null_;
 | 
			
		||||
  set work.restore_in;
 | 
			
		||||
  call symputx('loadref',load_ref);
 | 
			
		||||
run;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
  * Check if user has basic access permission to RESTORE the table
 | 
			
		||||
  */
 | 
			
		||||
%put checking access;
 | 
			
		||||
%global allow_restore reason;
 | 
			
		||||
%mpe_checkrestore(&loadref,outresult=ALLOW_RESTORE,outreason=REASON)
 | 
			
		||||
 | 
			
		||||
%mp_abort(iftrue= (&ALLOW_RESTORE ne YES)
 | 
			
		||||
  ,mac=&_program..sas
 | 
			
		||||
  ,msg=%str(Cannot restore because: &reason)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/* grab the base DS */
 | 
			
		||||
proc sql noprint;
 | 
			
		||||
select cats(base_lib,'.',base_ds) into: tgtds
 | 
			
		||||
  from &mpelib..mpe_submit
 | 
			
		||||
  where TABLE_ID="&loadref";
 | 
			
		||||
 | 
			
		||||
/* find the audit table */
 | 
			
		||||
select coalescec(audit_libds,"&mpelib..MPE_AUDIT"), loadtype, var_txto
 | 
			
		||||
  into: difftable, :loadtype, :txto
 | 
			
		||||
  from &mpelib..MPE_TABLES
 | 
			
		||||
  where libref="%scan(&tgtds,1,.)"
 | 
			
		||||
        & dsn="%scan(&tgtds,2,.)"
 | 
			
		||||
        & &dc_dttmtfmt<tx_to;
 | 
			
		||||
%mp_abort(iftrue= ("&difftable"="0")
 | 
			
		||||
  ,mac=&_program..sas
 | 
			
		||||
  ,msg=%str(No audit table configured for &tgtds)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/* assign the library */
 | 
			
		||||
%dc_assignlib(READ,%scan(&tgtds,1,.))
 | 
			
		||||
 | 
			
		||||
/* if the target is an SCD2 table, create a view to get current snapshot */
 | 
			
		||||
%let fltr=%sysfunc(ifc(&loadtype=TXTEMPORAL,&dc_dttmtfmt<&txto,1=1));
 | 
			
		||||
 | 
			
		||||
/* extract the differences to be applied */
 | 
			
		||||
%mp_stripdiffs(&tgtds,&loadref,&difftable
 | 
			
		||||
  ,outds=work.mp_stripdiffs
 | 
			
		||||
  ,filtervar=fltr
 | 
			
		||||
  ,mdebug=&sasjs_mdebug
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/* abort if any issues */
 | 
			
		||||
%mp_abort(iftrue= (&syscc>0)
 | 
			
		||||
  ,mac=&_program..sas
 | 
			
		||||
  ,msg=%str(syscc=&syscc after stripdiffs)
 | 
			
		||||
)
 | 
			
		||||
%mp_abort(iftrue= (%mf_nobs(work.mp_stripdiffs)=0)
 | 
			
		||||
  ,mac=&_program..sas
 | 
			
		||||
  ,msg=%str(THERE ARE NO DIFFERENCES TO APPLY)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/* create a new load ref */
 | 
			
		||||
%let mperef=DC%left(%sysfunc(datetime(),B8601DT19.3))_%substr(
 | 
			
		||||
  %sysfunc(ranuni(0)),3,6)_%substr(%str(&sysjobid    ),1,4);
 | 
			
		||||
 | 
			
		||||
/* Create package folder */
 | 
			
		||||
%let dir=&mpelocapprovals/&mperef;
 | 
			
		||||
%mf_mkdir(&dir)
 | 
			
		||||
options notes mprint;
 | 
			
		||||
libname approve "&dir";
 | 
			
		||||
 | 
			
		||||
/* take copy of macvars */
 | 
			
		||||
data _null_;
 | 
			
		||||
  file "&dir/macvars.sas";
 | 
			
		||||
  set sashelp.vmacro;
 | 
			
		||||
  where scope='GLOBAL';
 | 
			
		||||
  put '%let ' name '=' value ';';
 | 
			
		||||
run;
 | 
			
		||||
 | 
			
		||||
/* copy the diffs dataset */
 | 
			
		||||
data approve.jsdset;
 | 
			
		||||
  length _____DELETE__THIS__RECORD_____ $3;
 | 
			
		||||
  if 0 then call missing(_____DELETE__THIS__RECORD_____);
 | 
			
		||||
  set work.mp_stripdiffs;
 | 
			
		||||
run;
 | 
			
		||||
 | 
			
		||||
/* export to csv */
 | 
			
		||||
%mp_ds2csv(approve.jsdset
 | 
			
		||||
  ,dlm=COMMA
 | 
			
		||||
  ,outfile="&dir/%trim(&tgtds).csv"
 | 
			
		||||
  ,outencoding="UTF-8"
 | 
			
		||||
  ,headerformat=NAME
 | 
			
		||||
  ,termstr=CRLF
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
%mp_abort(iftrue= (&syscc ne 0)
 | 
			
		||||
  ,mac=&_program
 | 
			
		||||
  ,msg=%str(syscc=&syscc when writing the CSV)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
%mpe_loader(mperef=&mperef
 | 
			
		||||
  ,submitted_reason_txt=Restoring &loadref
 | 
			
		||||
  ,dc_dttmtfmt=&dc_dttmtfmt
 | 
			
		||||
)
 | 
			
		||||
%mp_abort(mode=INCLUDE)
 | 
			
		||||
 | 
			
		||||
%mp_abort(
 | 
			
		||||
  iftrue=(%sysfunc(fileexist(%sysfunc(pathname(work))/mf_abort.error))=1)
 | 
			
		||||
  ,mac=&_program..sas
 | 
			
		||||
  ,msg=%str(mf_abort.error=1)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
%mp_abort(iftrue= (&syscc ne 0)
 | 
			
		||||
  ,mac=&_program..sas
 | 
			
		||||
  ,msg=%str(syscc=&syscc)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/* send relevant SUCCESS values */
 | 
			
		||||
data work.restore_out;
 | 
			
		||||
  loadref="&mperef";
 | 
			
		||||
run;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
%webout(OPEN)
 | 
			
		||||
%webout(OBJ,restore_out)
 | 
			
		||||
%webout(CLOSE)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										109
									
								
								sas/sasjs/services/editors/restore.test.sas
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								sas/sasjs/services/editors/restore.test.sas
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
			
		||||
/**
 | 
			
		||||
  @file
 | 
			
		||||
  @brief testing restore process
 | 
			
		||||
 | 
			
		||||
  <h4> SAS Macros </h4>
 | 
			
		||||
  @li mf_getuniquefileref.sas
 | 
			
		||||
  @li mx_testservice.sas
 | 
			
		||||
  @li mp_assert.sas
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
**/
 | 
			
		||||
 | 
			
		||||
%let _program=&appLoc/services/editors/restore;
 | 
			
		||||
 | 
			
		||||
/* take a snapshot of the table for later comparison */
 | 
			
		||||
proc sort data=&mpelib..mpe_x_test out=work.origds;
 | 
			
		||||
by primary_key_field;
 | 
			
		||||
run;
 | 
			
		||||
 | 
			
		||||
/* run an update */
 | 
			
		||||
%mx_testservice(&appLoc/tests/services/auditors/postdata.test.1,
 | 
			
		||||
  viyacontext=&defaultcontext
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/* grab the loadref for later reversion */
 | 
			
		||||
%let loadref=0;
 | 
			
		||||
data APPROVE1;
 | 
			
		||||
  set &mpelib..mpe_submit end=last;
 | 
			
		||||
  if last then call symputx('loadref',table_id);
 | 
			
		||||
run;
 | 
			
		||||
 | 
			
		||||
/* run another update for good measure */
 | 
			
		||||
%mx_testservice(&appLoc/tests/services/auditors/postdata.test.1,
 | 
			
		||||
  viyacontext=&defaultcontext
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/* now we are ready to revert */
 | 
			
		||||
data work.restore_in;
 | 
			
		||||
  load_ref="&loadref";
 | 
			
		||||
  output;
 | 
			
		||||
  stop;
 | 
			
		||||
run;
 | 
			
		||||
%mx_testservice(&_program,
 | 
			
		||||
  viyacontext=&defaultcontext,
 | 
			
		||||
  inputdatasets=work.restore_in,
 | 
			
		||||
  outlib=web1,
 | 
			
		||||
  mdebug=&sasjs_mdebug
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/* check for success */
 | 
			
		||||
%let loadref=0;
 | 
			
		||||
data work.restore_out;
 | 
			
		||||
  set web1.restore_out;
 | 
			
		||||
  putlog (_all_)(=);
 | 
			
		||||
  call symputx('newref',loadref);
 | 
			
		||||
run;
 | 
			
		||||
%mp_assert(
 | 
			
		||||
  iftrue=(&newref ne 0),
 | 
			
		||||
  desc=Checking successful submission of a reversion,
 | 
			
		||||
  outds=work.test_results
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/* approve the reversion */
 | 
			
		||||
data work.sascontroltable;
 | 
			
		||||
  ACTION='APPROVE_TABLE';
 | 
			
		||||
  TABLE="&newref";
 | 
			
		||||
  /* difftime is numeric for approve action */
 | 
			
		||||
  DIFFTIME="%sysfunc(datetime())";
 | 
			
		||||
  output;
 | 
			
		||||
  stop;
 | 
			
		||||
run;
 | 
			
		||||
%mx_testservice(&appLoc/services/auditors/postdata,
 | 
			
		||||
  viyacontext=&defaultcontext,
 | 
			
		||||
  inputdatasets=work.sascontroltable,
 | 
			
		||||
  outlib=web3,
 | 
			
		||||
  outref=wb3,
 | 
			
		||||
  mdebug=&sasjs_mdebug
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
%let status=0;
 | 
			
		||||
data _null_;
 | 
			
		||||
  set web3.apparams;
 | 
			
		||||
  putlog (_all_)(=);
 | 
			
		||||
  if response='SUCCESS!' then call symputx('status',1);
 | 
			
		||||
run;
 | 
			
		||||
 | 
			
		||||
%mp_assert(
 | 
			
		||||
  iftrue=(&status=1 and &syscc=0),
 | 
			
		||||
  desc=Checking successful submission of reversion
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/* compare snapshot with latest data */
 | 
			
		||||
proc sort data=&mpelib..mpe_x_test out=work.compareds;
 | 
			
		||||
by primary_key_field;
 | 
			
		||||
run;
 | 
			
		||||
 | 
			
		||||
proc compare base=work.origds compare=work.compareds
 | 
			
		||||
  out=work.resultds outnoequal;
 | 
			
		||||
run;
 | 
			
		||||
data _null_;
 | 
			
		||||
  set work.resultds;
 | 
			
		||||
  if &sasjs_mdebug=1 then putlog (_all_)(=);
 | 
			
		||||
  if _n_>10 then stop;
 | 
			
		||||
run;
 | 
			
		||||
%mp_assert(
 | 
			
		||||
  iftrue=(&sysinfo le 41),
 | 
			
		||||
  desc=Checking compare of MPE_X_TEST,
 | 
			
		||||
  outds=work.test_results
 | 
			
		||||
)
 | 
			
		||||
@@ -5,8 +5,27 @@
 | 
			
		||||
 | 
			
		||||
  <h4> SAS Macros </h4>
 | 
			
		||||
  @li mf_getengine.sas
 | 
			
		||||
  @li dc_assignlib.sas
 | 
			
		||||
  @li mp_abort.sas
 | 
			
		||||
  @li mpe_checkrestore.sas
 | 
			
		||||
 | 
			
		||||
  <h4> Service Inputs </h4>
 | 
			
		||||
  <h5> sascontroltable </h5>
 | 
			
		||||
  @li table table ID or LOAD_REF used to uniquely identify a staged change
 | 
			
		||||
 | 
			
		||||
  <h4> Service Outputs </h4>
 | 
			
		||||
 | 
			
		||||
  <h5> work.jsparams </h5>
 | 
			
		||||
  Mainly sourced from MPE_SUBMIT plus some extra cols:
 | 
			
		||||
 | 
			
		||||
  @li LIB_ENGINE Library engine
 | 
			
		||||
  @li allow_restore YES if a user can restore, else NO
 | 
			
		||||
  @li REASON reason why a restore is / is no possible
 | 
			
		||||
 | 
			
		||||
  <h4> Data Inputs </h4>
 | 
			
		||||
  @li MPE_AUDIT
 | 
			
		||||
  @li MPE_COLUMN_LEVEL_SECURITY
 | 
			
		||||
  @li MPE_ROW_LEVEL_SECURITY
 | 
			
		||||
  @li MPE_SUBMIT
 | 
			
		||||
 | 
			
		||||
  @version 9.2
 | 
			
		||||
  @author 4GL Apps Ltd
 | 
			
		||||
@@ -24,10 +43,6 @@ data _null_;
 | 
			
		||||
  call symputx('table',table);
 | 
			
		||||
run;
 | 
			
		||||
 | 
			
		||||
%dc_assignlib(WRITE,%scan(&table,1,.))
 | 
			
		||||
 | 
			
		||||
%let max_ver_dttm=0;
 | 
			
		||||
 | 
			
		||||
data APPROVE1;
 | 
			
		||||
  set &mpelib..mpe_submit
 | 
			
		||||
    (rename=(SUBMITTED_ON_DTTM=submitted_on REVIEWED_ON_DTTM=REVIEWED_ON));
 | 
			
		||||
@@ -39,9 +54,18 @@ data APPROVE1;
 | 
			
		||||
  SUBMITTED_ON_DTTM=put(submitted_on,datetime19.);
 | 
			
		||||
run;
 | 
			
		||||
 | 
			
		||||
data jsParams;
 | 
			
		||||
/**
 | 
			
		||||
  * Check if user has basic access permission to RESTORE the table
 | 
			
		||||
  */
 | 
			
		||||
%put checking access;
 | 
			
		||||
%global allow_restore reason;
 | 
			
		||||
%mpe_checkrestore(&table,outresult=ALLOW_RESTORE,outreason=REASON)
 | 
			
		||||
 | 
			
		||||
data work.jsParams;
 | 
			
		||||
  set approve1;
 | 
			
		||||
  LIB_ENGINE="%mf_getEngine(&base_lib)";
 | 
			
		||||
  allow_restore="&allow_restore";
 | 
			
		||||
  REASON="&reason";
 | 
			
		||||
run;
 | 
			
		||||
 | 
			
		||||
%mp_abort(iftrue= (&syscc ne 0)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										97
									
								
								sas/sasjs/services/public/getchangeinfo.test.sas
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								sas/sasjs/services/public/getchangeinfo.test.sas
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
			
		||||
/**
 | 
			
		||||
  @file
 | 
			
		||||
  @brief testing getchangeinfo service
 | 
			
		||||
 | 
			
		||||
  <h4> SAS Macros </h4>
 | 
			
		||||
  @li mp_assertcolvals.sas
 | 
			
		||||
  @li mf_getuniquefileref.sas
 | 
			
		||||
 | 
			
		||||
**/
 | 
			
		||||
 | 
			
		||||
%let _program=&appLoc/services/public/getchangeinfo;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
  * First part - stage some data (for diffing)
 | 
			
		||||
  */
 | 
			
		||||
data work.sascontroltable;
 | 
			
		||||
  action='LOAD';
 | 
			
		||||
  message="getdiffs prep";
 | 
			
		||||
  libds="&dclib..MPE_X_TEST";
 | 
			
		||||
  output;
 | 
			
		||||
  stop;
 | 
			
		||||
run;
 | 
			
		||||
proc sql noprint;
 | 
			
		||||
select max(primary_key_field) into: maxpk from &dclib..mpe_x_test;
 | 
			
		||||
data work.jsdata;
 | 
			
		||||
  set &dclib..mpe_x_test(rename=(
 | 
			
		||||
    some_date=dt2 SOME_DATETIME=dttm2 SOME_TIME=tm2)
 | 
			
		||||
  );
 | 
			
		||||
  /* for now, the adapter sends these as strings */
 | 
			
		||||
  some_date=put(dt2,date9.);
 | 
			
		||||
  SOME_DATETIME=put(dttm2,datetime19.);
 | 
			
		||||
  some_time=put(tm2,time.);
 | 
			
		||||
  drop dt2 dttm2 tm2;
 | 
			
		||||
  _____DELETE__THIS__RECORD_____='No';
 | 
			
		||||
  if _n_=1 then do;
 | 
			
		||||
    primary_key_field=sum(&maxpk,1);
 | 
			
		||||
    some_char='   leadingblanks';
 | 
			
		||||
    some_num=._;
 | 
			
		||||
    output;
 | 
			
		||||
  end;
 | 
			
		||||
  else if _n_<3 then do;
 | 
			
		||||
    SOME_NUM=ranuni(0);
 | 
			
		||||
  end;
 | 
			
		||||
  else stop;
 | 
			
		||||
run;
 | 
			
		||||
 | 
			
		||||
%mx_testservice(&appLoc/services/editors/stagedata,
 | 
			
		||||
  viyacontext=&defaultcontext,
 | 
			
		||||
  inputdatasets=work.jsdata work.sascontroltable,
 | 
			
		||||
  outlib=web1,
 | 
			
		||||
  mdebug=&sasjs_mdebug
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
%let status=0;
 | 
			
		||||
data work.sasparams;
 | 
			
		||||
  set web1.sasparams;
 | 
			
		||||
  putlog (_all_)(=);
 | 
			
		||||
  if status='SUCCESS' then call symputx('status',1);
 | 
			
		||||
  call symputx('dsid',dsid);
 | 
			
		||||
run;
 | 
			
		||||
%mp_assert(
 | 
			
		||||
  iftrue=(&status=1 and &syscc=0),
 | 
			
		||||
  desc=Checking successful submission
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* now call getchangeinfo */
 | 
			
		||||
%let f3=%mf_getuniquefileref();
 | 
			
		||||
data _null_;
 | 
			
		||||
  file &f3 termstr=crlf;
 | 
			
		||||
  put 'TABLE:$43.';
 | 
			
		||||
  put "&dsid";
 | 
			
		||||
run;
 | 
			
		||||
%mp_testservice(&_program,
 | 
			
		||||
  viyacontext=&defaultcontext,
 | 
			
		||||
  inputfiles=&f3:sascontroltable,
 | 
			
		||||
  outlib=web3,
 | 
			
		||||
  mdebug=&sasjs_mdebug
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
data work.jsparams;
 | 
			
		||||
  set web3.jsparams;
 | 
			
		||||
  putlog (_all_)(=);
 | 
			
		||||
  call symputx('ALLOW_RESTORE',ALLOW_RESTORE);
 | 
			
		||||
run;
 | 
			
		||||
%mp_assert(
 | 
			
		||||
  iftrue=(&syscc=0),
 | 
			
		||||
  desc=Checking successful execution
 | 
			
		||||
)
 | 
			
		||||
%mp_assert(
 | 
			
		||||
  iftrue=(%mf_nobs(work.jsparams)=1),
 | 
			
		||||
  desc=Checking data was returned
 | 
			
		||||
)
 | 
			
		||||
%mp_assert(
 | 
			
		||||
  iftrue=(&ALLOW_RESTORE=NO),
 | 
			
		||||
  desc=Checking admin user cannot restore - as table was not approved
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										48
									
								
								sas/sasjs/services/public/getversion.sas
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								sas/sasjs/services/public/getversion.sas
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
/**
 | 
			
		||||
  @file getversion.sas
 | 
			
		||||
  @brief get a specific (previous) version of a particular table
 | 
			
		||||
  @details Used to fetch a version of a table as at a previous point in time
 | 
			
		||||
  Delivered as part of this issue: https://git.datacontroller.io/dc/dc/issues/84
 | 
			
		||||
 | 
			
		||||
  <h4> Service Inputs </h4>
 | 
			
		||||
  <h5> getversion_input </h5>
 | 
			
		||||
 | 
			
		||||
  |LIBREF:$char8.|DS:$char32.|TS: 8.|
 | 
			
		||||
  |---|---|---|
 | 
			
		||||
  |SOMELIB|SOMEDS|1341344.804|
 | 
			
		||||
 | 
			
		||||
  <h4> Service Outputs </h4>
 | 
			
		||||
  <h5> work.getversion_output </h5>
 | 
			
		||||
 | 
			
		||||
  The data for a particular version
 | 
			
		||||
 | 
			
		||||
  <h4> SAS Macros </h4>
 | 
			
		||||
  @li mf_getuser.sas
 | 
			
		||||
  @li mpeinit.sas
 | 
			
		||||
  @li mpe_getvars.sas
 | 
			
		||||
 | 
			
		||||
  @version 9.2
 | 
			
		||||
  @author 4GL Apps Ltd
 | 
			
		||||
 | 
			
		||||
**/
 | 
			
		||||
 | 
			
		||||
%mpeinit()
 | 
			
		||||
 | 
			
		||||
%global LIBREF DS;
 | 
			
		||||
 | 
			
		||||
/* load parameters */
 | 
			
		||||
%mpe_getvars(getversion_input, getversion_input)
 | 
			
		||||
 | 
			
		||||
%mp_abort(iftrue= (&syscc ne 0 )
 | 
			
		||||
  ,mac=&_program
 | 
			
		||||
  ,msg=%str(Issue on startup)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/* todo */
 | 
			
		||||
 | 
			
		||||
%webout(OPEN)
 | 
			
		||||
%webout(OBJ,getversion_output)
 | 
			
		||||
%webout(CLOSE)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
%mpeterm()
 | 
			
		||||
@@ -31,6 +31,9 @@
 | 
			
		||||
  @li TABLEURI
 | 
			
		||||
  @li VARS
 | 
			
		||||
 | 
			
		||||
  <h5> versions </h5>
 | 
			
		||||
  history of DC versions for this particular table
 | 
			
		||||
 | 
			
		||||
  <h5> viewdata </h5>
 | 
			
		||||
  The raw data from the target table.
 | 
			
		||||
 | 
			
		||||
@@ -51,6 +54,7 @@
 | 
			
		||||
  @li mp_validatecol.sas
 | 
			
		||||
  @li mpe_columnlevelsecurity.sas
 | 
			
		||||
  @li mpe_dsmeta.sas
 | 
			
		||||
  @li mpe_getversions.sas
 | 
			
		||||
  @li mpe_filtermaster.sas
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -81,7 +85,7 @@
 | 
			
		||||
data work.intest;
 | 
			
		||||
  length libds $41 filter_rk 8. searchval $100 searchtype $4;
 | 
			
		||||
  set work.SASCONTROLTABLE;
 | 
			
		||||
 | 
			
		||||
  call symputx('orig_libds',libds);
 | 
			
		||||
  /* validate filter_rk */
 | 
			
		||||
  if filter_rk le 0 then filter_rk=-1;
 | 
			
		||||
 | 
			
		||||
@@ -353,12 +357,19 @@ run;
 | 
			
		||||
 | 
			
		||||
%mpe_dsmeta(&libds, outds=dsmeta)
 | 
			
		||||
 | 
			
		||||
%mpe_getversions(&mpelib,
 | 
			
		||||
  %scan(&orig_libds,1,.),
 | 
			
		||||
  %scan(&orig_libds,2,.),
 | 
			
		||||
  outds=versions
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
%webout(OPEN)
 | 
			
		||||
%webout(OBJ,cls_rules)
 | 
			
		||||
%webout(OBJ,cols)
 | 
			
		||||
%webout(OBJ,dsmeta)
 | 
			
		||||
%webout(OBJ,query)
 | 
			
		||||
%webout(OBJ,sasparams)
 | 
			
		||||
%webout(OBJ,versions)
 | 
			
		||||
%webout(OBJ,viewData2,fmt=Y,missing=STRING,showmeta=YES,dslabel=viewdata)
 | 
			
		||||
%webout(CLOSE)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ data groups
 | 
			
		||||
  a=1;
 | 
			
		||||
  grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);
 | 
			
		||||
  if grpassn in (-3,-4) then do;
 | 
			
		||||
    putlog "WARNING: No groups found for ";
 | 
			
		||||
    putlog "%str(WARN)ING: No groups found for ";
 | 
			
		||||
  end;
 | 
			
		||||
  else do while (grpassn > 0);
 | 
			
		||||
    rc=metadata_getattr(groupuri, "Name", groupname);
 | 
			
		||||
 
 | 
			
		||||
@@ -9,12 +9,15 @@
 | 
			
		||||
  @li mf_getplatform.sas
 | 
			
		||||
  @li mpeinit2.sas
 | 
			
		||||
  @li mp_abort.sas
 | 
			
		||||
  @li mp_init.sas
 | 
			
		||||
  @li mp_testservice.sas
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
REMOVE THAT LAST MACRO
 | 
			
		||||
**/
 | 
			
		||||
 | 
			
		||||
%mp_init()
 | 
			
		||||
 | 
			
		||||
%let syscc=0;
 | 
			
		||||
%global apploc _program dclib defaultcontext _debug sasjs_mdebug dc_dttmtfmt;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user