feat: Display Previous Versions #88

Merged
allan merged 17 commits from restore into main 2024-04-04 13:24:26 +00:00
26 changed files with 611 additions and 82 deletions

1
.gitignore vendored
View File

@ -11,6 +11,7 @@ client/cypress/screenshots
client/cypress/results
client/cypress/videos
client/documentation
client/sheet-crypto*
cypress.env.json
sasjsbuild
sasjsresults

View File

@ -208,13 +208,16 @@
>{{ 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>

View File

@ -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
@ -2934,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
}
@ -3044,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')

View File

@ -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 {

View File

@ -212,19 +212,19 @@
class="btn btn-sm btn-outline text-center mt-5"
(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"
(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>
@ -446,19 +446,19 @@
class="btn btn-sm btn-outline text-center mt-5"
(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"
(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>

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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)

View File

@ -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
}

View File

@ -61,7 +61,7 @@
class="btn btn-sm btn-outline text-center mt-20"
(click)="viewerTableScreen()"
>
Go to base table screen
View base table
</button>
<button
id="approval-btn"
@ -72,13 +72,13 @@
"
(click)="approveTableScreen()"
>
Go to approvals screen
Approve
</button>
<button
class="btn btn-sm btn-info-outline text-center mt-20"
(click)="goBack()"
>
Go back to editor
Edit base table
</button>
<button
class="btn btn-sm btn-success text-center mt-20 min-w-0"
@ -86,6 +86,24 @@
>
<clr-icon shape="download"></clr-icon>
</button>
<clr-tooltip>
<button
*ngIf="tableDetails?.['ALLOW_RESTORE'] === 'YES'"
clrTooltipTrigger
class="btn btn-sm btn-danger text-center mt-20"
>
REVERT (Coming Soon)
<clr-tooltip-content
clrPosition="bottom-right"
clrSize="lg"
*clrIfOpen
>
<span> Revert this and all subsequent changes </span>
</clr-tooltip-content>
</button>
</clr-tooltip>
</div>
</div>
</div>

View File

@ -379,13 +379,16 @@
<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">
@ -667,6 +670,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>

View File

@ -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')

View File

@ -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' }
)

View File

@ -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..;

View 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;

View 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
)

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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
@ -672,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)
@ -683,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)

View File

@ -4,9 +4,26 @@
@details
<h4> SAS Macros </h4>
@li mf_getengine.sas
@li dc_assignlib.sas
@li mf_getengine.sas
@li mf_getuser.sas
@li mf_nobs.sas
@li mp_abort.sas
@li mpe_accesscheck.sas
@li mpe_getgroups.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
@version 9.2
@author 4GL Apps Ltd
@ -35,13 +52,106 @@ data APPROVE1;
TABLE_NM=cats(base_lib,'.',base_ds);
BASE_TABLE=table_nm;
call symputx('base_lib',base_lib);
call symputx('base_ds',base_ds);
REVIEWED_ON_DTTM=put(reviewed_on,datetime19.);
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;
%let allow_restore=NO;
%let reason=NOTFOUND;
%macro access_check();
/* grab user groups */
%let user=%mf_getuser();
%mpe_getgroups(user=&user,outds=work.groups)
/* check if user is admin */
%let is_admin=0;
proc sql;
select count(*) into: is_admin from work.groups where groupname="&MPEADMINS";
%if &is_admin>0 %then %do;
%let allow_restore=YES;
%let reason=IS ADMIN;
%return;
%end;
/* check if user has basic access */
%mpe_accesscheck(&base_lib..&base_ds,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;
%return;
%end;
%mend access_check;
%access_check();
data work.jsParams;
set approve1;
LIB_ENGINE="%mf_getEngine(&base_lib)";
allow_restore="&allow_restore";
REASON="&reason";
run;
%mp_abort(iftrue= (&syscc ne 0)

View 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_edit=YES),
desc=Checking admin user can restore
)

View 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()

View File

@ -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)

View File

@ -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);

View File

@ -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;