Compare commits

...

15 Commits

Author SHA1 Message Date
sead d66eb5dfc2 fix: remove data:image/svg+xml CSP violation, use class instead changing style directly
Build / Build-and-ng-test (pull_request) Failing after 1m16s
Build / Build-and-test-development (pull_request) Has been skipped
Lighthouse Checks / lighthouse (pull_request) Successful in 18m7s
2026-04-13 10:29:54 +02:00
sead 731b589ed8 chore: override ajv and regenrate lock file 2026-04-13 09:23:13 +02:00
sead fe92d5fc36 chore: bump angular to latest 19 2026-04-13 08:58:54 +02:00
sead a335b400f1 chore: bump @sasjs/adapter 2026-04-13 08:55:48 +02:00
semantic-release-bot f63e507ddf chore(release): 7.6.0 [skip ci]
# [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](52d58036a4))

### Features

* configurable email alerts.  Closes [#217](#217) ([2ccf0d1](2ccf0d1100))
2026-04-03 22:17:14 +00:00
allan 991cc0567d Merge pull request 'feat: configurable email alerts. Closes #217' (#222) from issue217 into main
Release / Build-production-and-ng-test (push) Successful in 3m42s
Release / Build-and-test-development (push) Successful in 10m10s
Release / release (push) Successful in 7m48s
Reviewed-on: #222
2026-04-03 21:09:11 +00:00
sead 52d58036a4 fix: add label and tooltip for libref download, sanitise input
Build / Build-and-ng-test (pull_request) Successful in 4m6s
Build / Build-and-test-development (pull_request) Successful in 10m13s
Lighthouse Checks / lighthouse (pull_request) Successful in 18m37s
2026-04-03 19:55:42 +02:00
allan 26bff85792 chore: fix debug line
Build / Build-and-ng-test (pull_request) Successful in 4m47s
Build / Build-and-test-development (pull_request) Successful in 10m16s
Lighthouse Checks / lighthouse (pull_request) Successful in 19m41s
2026-04-03 18:35:48 +01:00
allan 2ccf0d1100 feat: configurable email alerts. Closes #217
Build / Build-and-ng-test (pull_request) Successful in 4m42s
Build / Build-and-test-development (pull_request) Has been cancelled
Lighthouse Checks / lighthouse (pull_request) Has been cancelled
2026-04-03 18:34:23 +01:00
semantic-release-bot 3be33186bc chore(release): 7.5.0 [skip ci]
# [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](66e98a96cb))
* allow CSV uploads with licence row limit ([5b260e4](5b260e4915)), closes [#213](#213)
* bumping cli and pinning versions in .npmrc ([80039f4](80039f4876))
* guard CSV upload with fileUpload licence flag ([ed40df6](ed40df6295))
* parse embed param from window.location.hash for hash router compatibility ([0269c24](0269c2421d))
* quote CSV char values.  Closes [#215](#215) ([d9980e8](d9980e866d))
* resolve outer promise in parseCsvFile for non-WLATIN1 path ([4ee15e1](4ee15e1b6e))
* use XLSX for CSV row truncation to handle new lines in values ([6d590c0](6d590c050d))

### Features

* add embed URL parameter to hide header and back button ([b0dc441](b0dc441d68)), closes [#214](#214)
* add target libref input to config download ([a89657b](a89657b0b8)), closes [#212](#212)
* export config service to allow dclib swapping.  Closes [#212](#212) ([326c26f](326c26fddf))
2026-04-03 11:06:36 +00:00
allan 1a7f950ae2 Merge pull request 'feat: enabling dclib switching when exporting config' (#220) from issue212 into main
Release / Build-production-and-ng-test (push) Successful in 3m39s
Release / Build-and-test-development (push) Successful in 9m55s
Release / release (push) Successful in 7m46s
Reviewed-on: #220
2026-04-03 10:49:43 +00:00
allan 8924dc8ab1 chore: merge buid.yaml
Build / Build-and-ng-test (pull_request) Successful in 3m58s
Build / Build-and-test-development (pull_request) Successful in 10m3s
Lighthouse Checks / lighthouse (pull_request) Successful in 18m46s
2026-04-03 10:30:05 +00:00
allan 0b0db1c543 chore: run audit check in build.yaml as well as release.yaml
Build / Build-and-ng-test (pull_request) Failing after 1m31s
Build / Build-and-test-development (pull_request) Successful in 10m23s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 19m32s
2026-04-03 01:18:54 +00:00
allan 80039f4876 fix: bumping cli and pinning versions in .npmrc
Build / Build-and-ng-test (pull_request) Successful in 3m51s
Build / Build-and-test-development (pull_request) Successful in 10m9s
Lighthouse Checks / lighthouse (24.5.0) (pull_request) Successful in 19m11s
2026-04-03 02:02:05 +01:00
allan 326c26fddf feat: export config service to allow dclib swapping. Closes #212 2026-04-03 02:01:44 +01:00
22 changed files with 1063 additions and 1308 deletions
+13 -13
View File
@@ -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
+2
View File
@@ -1 +1,3 @@
legacy-peer-deps=true
ignore-scripts=true
save-exact=true
+33
View File
@@ -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)
+747 -1224
View File
File diff suppressed because it is too large Load Diff
+15 -12
View File
@@ -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"
}
}
+2 -1
View File
@@ -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'])
+6
View File
@@ -0,0 +1,6 @@
/**
* Strips characters that could cause SAS macro injection (& % ;).
*/
export function sanitiseForSas(input: string): string {
return input.replace(/[%&;]/g, '')
}
+25 -8
View File
@@ -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)"
+17 -1
View File
@@ -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;
}
-13
View File
@@ -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
View File
@@ -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",
+2
View File
@@ -0,0 +1,2 @@
ignore-scripts=true
save-exact=true
+9 -9
View File
@@ -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
View File
@@ -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';
+46 -5
View File
@@ -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;
+38 -3
View File
@@ -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'
+45 -10
View File
@@ -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");
+2 -1
View File
@@ -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,
+2
View File
@@ -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);