Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d66eb5dfc2 | |||
| 731b589ed8 | |||
| fe92d5fc36 | |||
| a335b400f1 | |||
| f63e507ddf | |||
| 991cc0567d | |||
| 52d58036a4 | |||
| 26bff85792 | |||
| 2ccf0d1100 | |||
| 3be33186bc | |||
| 1a7f950ae2 | |||
| 8924dc8ab1 | |||
| 0b0db1c543 | |||
| 80039f4876 | |||
| 326c26fddf |
+13
-13
@@ -22,23 +22,11 @@ jobs:
|
||||
apt install -y ./google-chrome*.deb
|
||||
|
||||
- name: Write .npmrc file
|
||||
run: echo "$NPMRC" > client/.npmrc
|
||||
run: echo "$NPMRC" >> client/.npmrc
|
||||
shell: bash
|
||||
env:
|
||||
NPMRC: ${{ secrets.NPMRC}}
|
||||
|
||||
- name: Check audit
|
||||
# Audit should fail and stop the CI if critical vulnerability found
|
||||
run: |
|
||||
npm audit --omit=dev
|
||||
cd ./sas
|
||||
npm audit --omit=dev
|
||||
cd ../client
|
||||
npm audit --audit-level=critical --omit=dev
|
||||
|
||||
- name: Lint check
|
||||
run: npm run lint:check
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd client
|
||||
@@ -46,6 +34,18 @@ jobs:
|
||||
echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg
|
||||
npm ci
|
||||
|
||||
- name: Check audit
|
||||
# Audit should fail and stop the CI if critical vulnerability found
|
||||
run: |
|
||||
npm audit --audit-level=critical --omit=dev
|
||||
cd ./sas
|
||||
npm audit --audit-level=critical --omit=dev
|
||||
cd ../client
|
||||
npm audit --audit-level=critical --omit=dev
|
||||
|
||||
- name: Lint check
|
||||
run: npm run lint:check
|
||||
|
||||
- name: Licence checker
|
||||
run: |
|
||||
cd client
|
||||
|
||||
@@ -1,3 +1,36 @@
|
||||
# [7.6.0](https://git.datacontroller.io/dc/dc/compare/v7.5.0...v7.6.0) (2026-04-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add label and tooltip for libref download, sanitise input ([52d5803](https://git.datacontroller.io/dc/dc/commit/52d58036a40e25847e900f9b04a77dbcc409c12b))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* configurable email alerts. Closes [#217](https://git.datacontroller.io/dc/dc/issues/217) ([2ccf0d1](https://git.datacontroller.io/dc/dc/commit/2ccf0d11000129629a0665421135b7530af9892f))
|
||||
|
||||
# [7.5.0](https://git.datacontroller.io/dc/dc/compare/v7.4.1...v7.5.0) (2026-04-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add workflow audits, update deps ([66e98a9](https://git.datacontroller.io/dc/dc/commit/66e98a96cbd092e762b94a04660f8e17ca003ceb))
|
||||
* allow CSV uploads with licence row limit ([5b260e4](https://git.datacontroller.io/dc/dc/commit/5b260e49153dd85bc0023ad94d8a5f57b8ffa6dc)), closes [#213](https://git.datacontroller.io/dc/dc/issues/213)
|
||||
* bumping cli and pinning versions in .npmrc ([80039f4](https://git.datacontroller.io/dc/dc/commit/80039f4876c8e09dc477678e1eff58329094c9e9))
|
||||
* guard CSV upload with fileUpload licence flag ([ed40df6](https://git.datacontroller.io/dc/dc/commit/ed40df62953c3055770b5cbf50738f4a48b943cd))
|
||||
* parse embed param from window.location.hash for hash router compatibility ([0269c24](https://git.datacontroller.io/dc/dc/commit/0269c2421db245f7f5405678605cb4d4587e2a67))
|
||||
* quote CSV char values. Closes [#215](https://git.datacontroller.io/dc/dc/issues/215) ([d9980e8](https://git.datacontroller.io/dc/dc/commit/d9980e866d1a2fe7a731ff279d73accd35003e67))
|
||||
* resolve outer promise in parseCsvFile for non-WLATIN1 path ([4ee15e1](https://git.datacontroller.io/dc/dc/commit/4ee15e1b6e83f27f279fc345e6998452a8f64d7e))
|
||||
* use XLSX for CSV row truncation to handle new lines in values ([6d590c0](https://git.datacontroller.io/dc/dc/commit/6d590c050dcd593a73464fae5604f774f016b10d))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add embed URL parameter to hide header and back button ([b0dc441](https://git.datacontroller.io/dc/dc/commit/b0dc441d681369e06eee58288dbdbb236f930bdc)), closes [#214](https://git.datacontroller.io/dc/dc/issues/214)
|
||||
* add target libref input to config download ([a89657b](https://git.datacontroller.io/dc/dc/commit/a89657b0b81b9c531f64c0dda2714b4eb16c4bc9)), closes [#212](https://git.datacontroller.io/dc/dc/issues/212)
|
||||
* export config service to allow dclib swapping. Closes [#212](https://git.datacontroller.io/dc/dc/issues/212) ([326c26f](https://git.datacontroller.io/dc/dc/commit/326c26fddfa88a0dc4ca79d3bd0c77c4d807f37c))
|
||||
|
||||
## [7.4.1](https://git.datacontroller.io/dc/dc/compare/v7.4.0...v7.4.1) (2026-03-12)
|
||||
|
||||
|
||||
|
||||
Generated
+747
-1224
File diff suppressed because it is too large
Load Diff
+15
-12
@@ -37,21 +37,21 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^19.2.18",
|
||||
"@angular/animations": "^19.2.20",
|
||||
"@angular/cdk": "^19.2.19",
|
||||
"@angular/common": "^19.2.18",
|
||||
"@angular/compiler": "^19.2.18",
|
||||
"@angular/core": "^19.2.18",
|
||||
"@angular/forms": "^19.2.18",
|
||||
"@angular/platform-browser": "^19.2.18",
|
||||
"@angular/platform-browser-dynamic": "^19.2.18",
|
||||
"@angular/router": "^19.2.18",
|
||||
"@angular/common": "^19.2.20",
|
||||
"@angular/compiler": "^19.2.20",
|
||||
"@angular/core": "^19.2.20",
|
||||
"@angular/forms": "^19.2.20",
|
||||
"@angular/platform-browser": "^19.2.20",
|
||||
"@angular/platform-browser-dynamic": "^19.2.20",
|
||||
"@angular/router": "^19.2.20",
|
||||
"@cds/core": "^6.15.1",
|
||||
"@clr/angular": "file:libraries/clr-angular-17.9.0.tgz",
|
||||
"@clr/icons": "^13.0.2",
|
||||
"@clr/ui": "file:libraries/clr-ui-17.9.0.tgz",
|
||||
"@handsontable/angular-wrapper": "16.0.1",
|
||||
"@sasjs/adapter": "^4.16.3",
|
||||
"@sasjs/adapter": "^4.16.5",
|
||||
"@sasjs/utils": "^3.5.3",
|
||||
"@sheet/crypto": "file:libraries/sheet-crypto.tgz",
|
||||
"@types/d3-graphviz": "^2.6.7",
|
||||
@@ -86,14 +86,14 @@
|
||||
"zone.js": "~0.15.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^19.2.19",
|
||||
"@angular-devkit/build-angular": "^19.2.24",
|
||||
"@angular-eslint/builder": "19.8.1",
|
||||
"@angular-eslint/eslint-plugin": "19.8.1",
|
||||
"@angular-eslint/eslint-plugin-template": "19.8.1",
|
||||
"@angular-eslint/schematics": "19.8.1",
|
||||
"@angular-eslint/template-parser": "19.8.1",
|
||||
"@angular/cli": "^19.2.19",
|
||||
"@angular/compiler-cli": "^19.2.18",
|
||||
"@angular/cli": "^19.2.24",
|
||||
"@angular/compiler-cli": "^19.2.20",
|
||||
"@babel/plugin-proposal-private-methods": "^7.18.6",
|
||||
"@compodoc/compodoc": "^1.1.21",
|
||||
"@cypress/webpack-preprocessor": "^5.17.1",
|
||||
@@ -132,5 +132,8 @@
|
||||
"typescript": "~5.8.3",
|
||||
"wait-on": "^6.0.1",
|
||||
"watch": "^1.0.2"
|
||||
},
|
||||
"overrides": {
|
||||
"ajv": "8.18.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import Handsontable from 'handsontable'
|
||||
import { Subject, Subscription } from 'rxjs'
|
||||
import { sanitiseForSas } from '../shared/utils/sanitise'
|
||||
import { SasStoreService } from '../services/sas-store.service'
|
||||
|
||||
type AOA = any[][]
|
||||
@@ -1669,7 +1670,7 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.submit = true
|
||||
const updateParams: any = {}
|
||||
updateParams.ACTION = 'LOAD'
|
||||
this.message = this.message.replace(/\n/g, '. ')
|
||||
this.message = sanitiseForSas(this.message.replace(/\n/g, '. '))
|
||||
updateParams.MESSAGE = this.message
|
||||
// updateParams.APPROVER = this.approver;
|
||||
updateParams.LIBDS = this.libds
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { sanitiseForSas } from '../../shared/utils/sanitise'
|
||||
import { SasStoreService } from '../../services/sas-store.service'
|
||||
import {
|
||||
Component,
|
||||
@@ -136,7 +137,7 @@ export class ApproveDetailsComponent implements AfterViewInit, OnDestroy {
|
||||
|
||||
public async rejecting() {
|
||||
this.rejectLoading = true
|
||||
this.submitReason = this.submitReason.replace(/\n/g, '. ')
|
||||
this.submitReason = sanitiseForSas(this.submitReason.replace(/\n/g, '. '))
|
||||
|
||||
let rejParams = {
|
||||
STP_ACTION: 'REJECT_TABLE',
|
||||
|
||||
@@ -80,15 +80,13 @@ export class SidebarComponent implements OnInit {
|
||||
public resizeStart() {
|
||||
this.resizing = true
|
||||
|
||||
let body = document.getElementsByTagName('body')[0]
|
||||
body.style.cssText = 'user-select: none'
|
||||
document.body.classList.add('select-none')
|
||||
}
|
||||
|
||||
public resizeEnd() {
|
||||
this.resizing = false
|
||||
|
||||
let body = document.getElementsByTagName('body')[0]
|
||||
body.style.cssText = ''
|
||||
document.body.classList.remove('select-none')
|
||||
}
|
||||
|
||||
@HostListener('document:mousemove', ['$event'])
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Strips characters that could cause SAS macro injection (& % ;).
|
||||
*/
|
||||
export function sanitiseForSas(input: string): string {
|
||||
return input.replace(/[%&;]/g, '')
|
||||
}
|
||||
@@ -236,14 +236,31 @@
|
||||
<div class="admin-action">
|
||||
Download Configuration
|
||||
|
||||
<input
|
||||
type="text"
|
||||
class="clr-input libref-input"
|
||||
maxlength="8"
|
||||
[ngModel]="dcLib"
|
||||
(ngModelChange)="targetLibref = $event.toUpperCase()"
|
||||
placeholder="Target Libref"
|
||||
/>
|
||||
<div class="libref-group">
|
||||
<clr-tooltip class="libref-tooltip">
|
||||
<label clrTooltipTrigger class="libref-label">
|
||||
Target DC Library
|
||||
<cds-icon shape="info-circle" size="16"></cds-icon>
|
||||
</label>
|
||||
<clr-tooltip-content
|
||||
clrPosition="bottom-left"
|
||||
clrSize="md"
|
||||
*clrIfOpen
|
||||
>
|
||||
Enter the target DC library and the downloaded files will
|
||||
contain this, instead of the original.
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
class="clr-input libref-input"
|
||||
maxlength="8"
|
||||
[ngModel]="dcLib"
|
||||
(ngModelChange)="targetLibref = $event.toUpperCase()"
|
||||
placeholder="e.g. MYLIB"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
(click)="downloadConfiguration()"
|
||||
[disabled]="targetLibref !== dcLib && !isValidLibref(targetLibref)"
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
.libref-group {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.libref-label {
|
||||
cursor: pointer;
|
||||
font-size: 0.55rem;
|
||||
font-weight: 600;
|
||||
color: var(--clr-p4-color, #565656);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.libref-input {
|
||||
width: 100px;
|
||||
margin: 0 8px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
@@ -1882,19 +1882,6 @@ app-query {
|
||||
}
|
||||
}
|
||||
|
||||
.clause-row:after {
|
||||
position: relative;
|
||||
content: "";
|
||||
height: .41667rem;
|
||||
width: .41667rem;
|
||||
top: .29167rem;
|
||||
right: .25rem;
|
||||
background-image: url(data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org…%2C9.84%2C3.24a0.68%2C0.68%2C0%2C1%2C1%2C1%2C1Z%22%2F%3E%0A%3C%2Fsvg%3E%0A);
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
vertical-align: middle;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
pre[class*="language-"] {
|
||||
padding: 8px;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dcfrontend",
|
||||
"version": "7.4.1",
|
||||
"version": "7.6.0",
|
||||
"description": "Data Controller",
|
||||
"devDependencies": {
|
||||
"@saithodev/semantic-release-gitea": "^2.1.0",
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ignore-scripts=true
|
||||
save-exact=true
|
||||
Generated
+9
-9
@@ -6,8 +6,8 @@
|
||||
"": {
|
||||
"name": "dc-sas",
|
||||
"dependencies": {
|
||||
"@sasjs/cli": "^4.15.0",
|
||||
"@sasjs/core": "^4.62.0"
|
||||
"@sasjs/cli": "4.15.2",
|
||||
"@sasjs/core": "4.63.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/css-color": {
|
||||
@@ -251,13 +251,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sasjs/cli": {
|
||||
"version": "4.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-4.15.0.tgz",
|
||||
"integrity": "sha512-lVKzm8+4b9VSqbchfLnzyNm53cKDZfqSM8KU7izD/JDBsuATSZtjLo61iNenZaPg9d3WXglb1jf2ASbVKbXxUQ==",
|
||||
"version": "4.15.2",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-4.15.2.tgz",
|
||||
"integrity": "sha512-lY9H+HIquLAPXuhk6ov/xyBooERvefT6oiwNRaQ6DHMMFE4cgPvrUH5s3RRkLI2+lET0M0hPPbuaZ4w9yFIDuA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@sasjs/adapter": "4.16.3",
|
||||
"@sasjs/core": "4.62.0",
|
||||
"@sasjs/core": "4.63.0",
|
||||
"@sasjs/lint": "2.4.3",
|
||||
"@sasjs/utils": "3.5.6",
|
||||
"adm-zip": "0.5.10",
|
||||
@@ -317,9 +317,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sasjs/core": {
|
||||
"version": "4.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.62.0.tgz",
|
||||
"integrity": "sha512-xMWeZbxlvuCP0B9fnSTgSFbSiA0hiKDpTua8wb0ghMUOl+dnh/XF+BYgrHhWhPL9j0+k5d8mJejLmf/l/txpzg==",
|
||||
"version": "4.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.63.0.tgz",
|
||||
"integrity": "sha512-NlIihA4BbP+mveAbb7A/hgnrZEpJKKIkq0v4SSDdYXg8YYdKAdyTK8K+6FNPwp+U6hixQCKVX8oCA1DIUppLqA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@sasjs/lint": {
|
||||
|
||||
+2
-2
@@ -28,7 +28,7 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@sasjs/cli": "^4.15.0",
|
||||
"@sasjs/core": "^4.62.0"
|
||||
"@sasjs/cli": "4.15.2",
|
||||
"@sasjs/core": "4.63.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
@file
|
||||
@brief migration script to move from v7.0 to v7.6 of data controller
|
||||
|
||||
OPTIONAL CHANGE - upload additional data as placeholders for modifying the
|
||||
default email message
|
||||
|
||||
**/
|
||||
|
||||
%let dclib=YOURDCLIB;
|
||||
|
||||
libname &dclib "/YOUR/DATACONTROLLER/LIBRARY/PATH";
|
||||
|
||||
proc sql;
|
||||
insert into &dclib..mpe_config set
|
||||
tx_from=%sysfunc(datetime())
|
||||
,tx_to='31DEC9999:23:59:59'dt
|
||||
,var_scope="DC_EMAIL"
|
||||
,var_name="SUBMITTED_TEMPLATE"
|
||||
,var_value='Dear user,'!!'0A'x!!'Please be advised that a change to table'
|
||||
!!' &alert_lib..&alert_ds has been proposed by &from_user on the '
|
||||
!!'&syshostname SAS server.'!!'0A'x!!'Reason provided: '
|
||||
!!'%superq(SUBMITTED_TXT)'
|
||||
!!'0A'x!!'This is an automated email by Data Controller for SAS. For '
|
||||
!!'documentation, please visit https://docs.datacontroller.io'
|
||||
,var_active=1
|
||||
,var_desc='Template email, sent after submitting a change';
|
||||
insert into &dclib..mpe_config set
|
||||
tx_from=%sysfunc(datetime())
|
||||
,tx_to='31DEC9999:23:59:59'dt
|
||||
,var_scope="DC_EMAIL"
|
||||
,var_name="APPROVED_TEMPLATE"
|
||||
,var_value='Dear user,'!!'0A'x!!'Please be advised that a change to table'
|
||||
!!' &alert_lib..&alert_ds has been approved by &from_user on the '
|
||||
!!'&syshostname SAS server.'!!'0A'x!!'This is an automated email by Data'
|
||||
!!' Controller for SAS. For documentation, please visit '
|
||||
!!'https://docs.datacontroller.io'
|
||||
,var_active=1
|
||||
,var_desc='Template email, sent after approving a change';
|
||||
insert into &dclib..mpe_config set
|
||||
tx_from=%sysfunc(datetime())
|
||||
,tx_to='31DEC9999:23:59:59'dt
|
||||
,var_scope="DC_EMAIL"
|
||||
,var_name="REJECTED_TEMPLATE"
|
||||
,var_value='Dear user,'!!'0A'x!!'Please be advised that a change to table'
|
||||
!!' &alert_lib..&alert_ds has been rejected by &from_user on the '
|
||||
!!'&syshostname SAS server.'!!'0A'x!!'Reason provided: '
|
||||
!!'%superq(REVIEW_REASON_TXT)'
|
||||
!!'0A'x!!'This is an automated email by Data Controller for SAS. For '
|
||||
!!'documentation, please visit https://docs.datacontroller.io'
|
||||
,var_active=1
|
||||
,var_desc='Template email, sent after rejecting a change';
|
||||
@@ -127,6 +127,11 @@ run;
|
||||
filename __out email (&emails)
|
||||
subject="Table &alert_lib..&alert_ds has been &alert_event";
|
||||
|
||||
data work.alertmessage;
|
||||
set &mpelib..mpe_config;
|
||||
where &dc_dttmtfmt. lt tx_to;
|
||||
where also var_scope='DC_EMAIL' and var_name="&alert_event._TEMPLATE";
|
||||
run;
|
||||
%local SUBMITTED_TXT;
|
||||
%if &alert_event=SUBMITTED %then %do;
|
||||
data _null_;
|
||||
@@ -136,30 +141,54 @@ filename __out email (&emails)
|
||||
run;
|
||||
data _null_;
|
||||
File __out lrecl=32000;
|
||||
length txt $2048;
|
||||
%if %mf_getattrn(alertmessage,NLOBS)=0 %then %do;
|
||||
put 'Dear user,';
|
||||
put ' ';
|
||||
put "Please be advised that a change to table &alert_lib..&alert_ds has "
|
||||
"been proposed by &from_user on the '&syshostname' SAS server.";
|
||||
"been proposed by &from_user on the &syshostname SAS server.";
|
||||
put " ";
|
||||
length txt $2048;
|
||||
txt=symget('SUBMITTED_TXT');
|
||||
put "Reason provided: " txt;
|
||||
put " ";
|
||||
put "This is an automated email by Data Controller for SAS. For "
|
||||
"documentation, please visit https://docs.datacontroller.io";
|
||||
%end;
|
||||
%else %do;
|
||||
/* take template from config table */
|
||||
set work.alertmessage;
|
||||
cnt=countw(var_value,'0A'x);
|
||||
do i=1 to cnt;
|
||||
txt=resolve(scan(var_value,i,'0A'x));
|
||||
put txt /;
|
||||
end;
|
||||
%end;
|
||||
run;
|
||||
%end;
|
||||
%else %if &alert_event=APPROVED %then %do;
|
||||
/* there is no approval message */
|
||||
data _null_;
|
||||
File __out lrecl=32000;
|
||||
length txt $2048;
|
||||
%if %mf_getattrn(alertmessage,NLOBS)=0 %then %do;
|
||||
/* fallback message */
|
||||
put 'Dear user,';
|
||||
put ' ';
|
||||
put "Please be advised that a change to table &alert_lib..&alert_ds has "
|
||||
"been approved by &from_user on the '&syshostname' SAS server.";
|
||||
"been approved by &from_user on the &syshostname SAS server.";
|
||||
put " ";
|
||||
put "This is an automated email by Data Controller for SAS. For "
|
||||
"documentation, please visit https://docs.datacontroller.io";
|
||||
%end;
|
||||
%else %do;
|
||||
/* take template from config table */
|
||||
set work.alertmessage;
|
||||
cnt=countw(var_value,'0A'x);
|
||||
do i=1 to cnt;
|
||||
txt=resolve(scan(var_value,i,'0A'x));
|
||||
put txt /;
|
||||
end;
|
||||
%end;
|
||||
run;
|
||||
%end;
|
||||
%else %if &alert_event=REJECTED %then %do;
|
||||
@@ -170,17 +199,29 @@ filename __out email (&emails)
|
||||
run;
|
||||
data _null_;
|
||||
File __out lrecl=32000;
|
||||
length txt $2048;
|
||||
%if %mf_getattrn(alertmessage,NLOBS)=0 %then %do;
|
||||
/* fallback message */
|
||||
put 'Dear user,';
|
||||
put ' ';
|
||||
put "Please be advised that a change to table &alert_lib..&alert_ds has "
|
||||
"been rejected by &from_user on the '&syshostname' SAS server.";
|
||||
"been rejected by &from_user on the &syshostname SAS server.";
|
||||
put " ";
|
||||
length txt $2048;
|
||||
txt=symget('REVIEW_REASON_TXT');
|
||||
put "Reason provided: " txt;
|
||||
put " ";
|
||||
put "This is an automated email by Data Controller for SAS. For "
|
||||
"documentation, please visit https://docs.datacontroller.io";
|
||||
%end;
|
||||
%else %do;
|
||||
/* take template from config table */
|
||||
set work.alertmessage;
|
||||
cnt=countw(var_value,'0A'x);
|
||||
do i=1 to cnt;
|
||||
txt=resolve(scan(var_value,i,'0A'x));
|
||||
put txt /;
|
||||
end;
|
||||
%end;
|
||||
run;
|
||||
%end;
|
||||
|
||||
|
||||
@@ -201,6 +201,44 @@ insert into &lib..mpe_config set
|
||||
,var_value=' '
|
||||
,var_active=1
|
||||
,var_desc='Activation Key';
|
||||
insert into &lib..mpe_config set
|
||||
tx_from=0
|
||||
,tx_to='31DEC9999:23:59:59'dt
|
||||
,var_scope="DC_EMAIL"
|
||||
,var_name="SUBMITTED_TEMPLATE"
|
||||
,var_value='Dear user,'!!'0A'x!!'Please be advised that a change to table'
|
||||
!!' &alert_lib..&alert_ds has been proposed by &from_user on the '
|
||||
!!'&syshostname SAS server.'!!'0A'x!!'Reason provided: '
|
||||
!!'%superq(SUBMITTED_TXT)'
|
||||
!!'0A'x!!'This is an automated email by Data Controller for SAS. For '
|
||||
!!'documentation, please visit https://docs.datacontroller.io'
|
||||
,var_active=1
|
||||
,var_desc='Template email, sent after submitting a change';
|
||||
insert into &lib..mpe_config set
|
||||
tx_from=0
|
||||
,tx_to='31DEC9999:23:59:59'dt
|
||||
,var_scope="DC_EMAIL"
|
||||
,var_name="APPROVED_TEMPLATE"
|
||||
,var_value='Dear user,'!!'0A'x!!'Please be advised that a change to table'
|
||||
!!' &alert_lib..&alert_ds has been approved by &from_user on the '
|
||||
!!'&syshostname SAS server.'!!'0A'x!!'This is an automated email by Data'
|
||||
!!' Controller for SAS. For documentation, please visit '
|
||||
!!'https://docs.datacontroller.io'
|
||||
,var_active=1
|
||||
,var_desc='Template email, sent after approving a change';
|
||||
insert into &lib..mpe_config set
|
||||
tx_from=0
|
||||
,tx_to='31DEC9999:23:59:59'dt
|
||||
,var_scope="DC_EMAIL"
|
||||
,var_name="REJECTED_TEMPLATE"
|
||||
,var_value='Dear user,'!!'0A'x!!'Please be advised that a change to table'
|
||||
!!' &alert_lib..&alert_ds has been rejected by &from_user on the '
|
||||
!!'&syshostname SAS server.'!!'0A'x!!'Reason provided: '
|
||||
!!'%superq(REVIEW_REASON_TXT)'
|
||||
!!'0A'x!!'This is an automated email by Data Controller for SAS. For '
|
||||
!!'documentation, please visit https://docs.datacontroller.io'
|
||||
,var_active=1
|
||||
,var_desc='Template email, sent after rejecting a change';
|
||||
|
||||
|
||||
insert into &lib..mpe_datadictionary set
|
||||
@@ -213,7 +251,6 @@ insert into &lib..mpe_datadictionary set
|
||||
,DD_RESPONSIBLE="&sysuserid"
|
||||
,DD_SENSITIVITY="Low"
|
||||
,tx_to='31DEC5999:23:59:59'dt;
|
||||
|
||||
insert into &lib..mpe_datadictionary set
|
||||
tx_from=0
|
||||
,DD_TYPE='TABLE'
|
||||
@@ -224,7 +261,6 @@ insert into &lib..mpe_datadictionary set
|
||||
,DD_RESPONSIBLE="&sysuserid"
|
||||
,DD_SENSITIVITY="Low"
|
||||
,tx_to='31DEC5999:23:59:59'dt;
|
||||
|
||||
insert into &lib..mpe_datadictionary set
|
||||
tx_from=0
|
||||
,DD_TYPE='COLUMN'
|
||||
@@ -235,7 +271,6 @@ insert into &lib..mpe_datadictionary set
|
||||
,DD_RESPONSIBLE="&sysuserid"
|
||||
,DD_SENSITIVITY="Low"
|
||||
,tx_to='31DEC5999:23:59:59'dt;
|
||||
|
||||
insert into &lib..mpe_datadictionary set
|
||||
tx_from=0
|
||||
,DD_TYPE='DIRECTORY'
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getuser.sas
|
||||
@li mf_nobs.sas
|
||||
@li mp_ds2cards.sas
|
||||
@li mp_abort.sas
|
||||
@li mp_binarycopy.sas
|
||||
@li mp_ds2cards.sas
|
||||
@li mp_ds2csv.sas
|
||||
@li mp_streamfile.sas
|
||||
@li mp_validatecol.sas
|
||||
|
||||
@author 4GL Apps Ltd
|
||||
@copyright 4GL Apps Ltd. This code may only be used within Data Controller
|
||||
@@ -21,23 +23,33 @@
|
||||
|
||||
**/
|
||||
|
||||
%global dclib islib newlib;
|
||||
%mpeinit()
|
||||
|
||||
data _null_;
|
||||
newlib=coalescec(symget('dclib'),"&mpelib");
|
||||
%mp_validatecol(newlib,ISLIB,islib)
|
||||
call symputx('islib',islib);
|
||||
call symputx('newlib',upcase(newlib));
|
||||
putlog (_all_)(=);
|
||||
run;
|
||||
|
||||
%mp_abort(iftrue= (&islib ne 1)
|
||||
,mac=&_program
|
||||
,msg=%nrstr(&newlib is not a valid libref)
|
||||
)
|
||||
|
||||
%let work=%sysfunc(pathname(work));
|
||||
|
||||
/* excel does not work in all envs */
|
||||
%let mime=application/vnd.ms-excel;
|
||||
%let dbms=EXCEL;
|
||||
|
||||
%let mime=application/csv;
|
||||
%let dbms=CSV;
|
||||
%let ext=csv;
|
||||
|
||||
%macro conditional_export(ds);
|
||||
%if %mf_nobs(&ds)>0 %then %do;
|
||||
PROC EXPORT DATA= &ds OUTFILE= "&work/&ds..&ext"
|
||||
DBMS=&dbms REPLACE;
|
||||
RUN;
|
||||
ods package(ProdOutput) add file="&work/&ds..&ext" mimetype="&mime";
|
||||
/* cannot use PROC EXPORT as we need to wrap all csv char values in quotes */
|
||||
/* cannot use excel as it does not work consistently in all SAS envs */
|
||||
%mp_ds2csv(&ds,outfile="&work/&newlib..&ds..csv",headerformat=NAME)
|
||||
ods package(ProdOutput) add file="&work/&newlib..&ds..&ext" mimetype="&mime";
|
||||
%end;
|
||||
%mp_abort(iftrue= (&syscc ne 0)
|
||||
,mac=&_program
|
||||
@@ -52,6 +64,7 @@ data MPE_ALERTS;
|
||||
set &mpelib..MPE_ALERTS;
|
||||
where &dc_dttmtfmt. le tx_to;
|
||||
drop tx_: ;
|
||||
if alert_lib="&mpelib" then alert_lib="&newlib";
|
||||
run;
|
||||
%conditional_export(MPE_ALERTS)
|
||||
|
||||
@@ -61,6 +74,7 @@ data MPE_COLUMN_LEVEL_SECURITY;
|
||||
where &dc_dttmtfmt. le tx_to;
|
||||
where also CLS_LIBREF ne "&mpelib";
|
||||
drop tx_: ;
|
||||
CLS_LIBREF="&newlib";
|
||||
run;
|
||||
%conditional_export(MPE_COLUMN_LEVEL_SECURITY)
|
||||
|
||||
@@ -68,6 +82,7 @@ data MPE_CONFIG;
|
||||
set &mpelib..MPE_CONFIG;
|
||||
where &dc_dttmtfmt. le tx_to;
|
||||
drop tx_: ;
|
||||
if var_name='DC_MACROS' then var_value=tranwrd(var_value,"&mpelib","&newlib");
|
||||
run;
|
||||
%conditional_export(MPE_CONFIG)
|
||||
|
||||
@@ -93,6 +108,7 @@ data MPE_EXCEL_CONFIG;
|
||||
set &mpelib..MPE_EXCEL_CONFIG;
|
||||
where &dc_dttmtfmt. le tx_to;
|
||||
drop tx_: ;
|
||||
if xl_libref="&mpelib" then xl_libref="&newlib";
|
||||
run;
|
||||
%conditional_export(MPE_EXCEL_CONFIG)
|
||||
|
||||
@@ -107,6 +123,7 @@ data MPE_ROW_LEVEL_SECURITY;
|
||||
set &mpelib..MPE_ROW_LEVEL_SECURITY;
|
||||
where &dc_dttmtfmt. le tx_to;
|
||||
drop tx_: ;
|
||||
if rls_libref="&mpelib" then rls_libref="&newlib";
|
||||
run;
|
||||
%conditional_export(MPE_ROW_LEVEL_SECURITY)
|
||||
|
||||
@@ -115,6 +132,7 @@ data MPE_SECURITY;
|
||||
set &mpelib..MPE_SECURITY;
|
||||
where &dc_dttmtfmt. le TX_TO;
|
||||
drop tx_: ;
|
||||
if libref="&mpelib" then libref="&newlib";
|
||||
run;
|
||||
%conditional_export(MPE_SECURITY)
|
||||
|
||||
@@ -142,6 +160,23 @@ data MPE_VALIDATIONS;
|
||||
run;
|
||||
%conditional_export(MPE_VALIDATIONS)
|
||||
|
||||
data MPE_XLMAP_INFO;
|
||||
set &mpelib..MPE_XLMAP_INFO;
|
||||
where &dc_dttmtfmt. le TX_TO;
|
||||
drop tx_: ;
|
||||
if XLMAP_TARGETLIBDS=:"&mpelib.." then
|
||||
XLMAP_TARGETLIBDS=tranwrd(XLMAP_TARGETLIBDS,"&mpelib..","&newlib..");
|
||||
run;
|
||||
%conditional_export(MPE_XLMAP_INFO)
|
||||
|
||||
data MPE_XLMAP_RULES;
|
||||
set &mpelib..MPE_XLMAP_RULES;
|
||||
where &dc_dttmtfmt. le TX_TO;
|
||||
drop tx_: ;
|
||||
run;
|
||||
%conditional_export(MPE_XLMAP_RULES)
|
||||
|
||||
|
||||
/* finish up zip file */
|
||||
ods package(ProdOutput) publish archive properties
|
||||
(archive_name="DCBACKUP.zip" archive_path="&work");
|
||||
|
||||
@@ -84,7 +84,8 @@ data work.reject;
|
||||
REVIEW_STATUS_ID="REJECTED";
|
||||
REVIEWED_BY_NM="&user";
|
||||
REVIEWED_ON_DTTM=&now;
|
||||
REVIEW_REASON_TXT=symget('STP_REASON');
|
||||
/* sanitise message to prevent code injection */
|
||||
REVIEW_REASON_TXT=compress(symget('STP_REASON'), '&%;');
|
||||
run;
|
||||
|
||||
%mp_lockanytable(LOCK,
|
||||
|
||||
@@ -51,6 +51,8 @@
|
||||
data _null_;
|
||||
set work.sascontroltable;
|
||||
call symputx('action',action);
|
||||
/* sanitise message to prevent code injection */
|
||||
message=compress(message, '&%;');
|
||||
call symputx('message',message);
|
||||
libds=upcase(libds);
|
||||
call symputx('orig_libds',libds);
|
||||
|
||||
Reference in New Issue
Block a user