Merge pull request 'feat: Display Previous Versions' (#88) from restore into main
Reviewed-on: #88
This commit is contained in:
commit
f7f59a4b0a
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
|
||||
|
@ -208,6 +208,8 @@
|
||||
>{{ libdsParsed.tableName.replace('-FC', '') }}</a
|
||||
>
|
||||
</span>
|
||||
|
||||
<ng-container *ngIf="this.dsNote && this.dsNote.length > 0">
|
||||
<clr-tooltip-content
|
||||
clrPosition="bottom-left"
|
||||
clrSize="lg"
|
||||
@ -215,6 +217,7 @@
|
||||
>
|
||||
{{ 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>
|
||||
|
@ -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')
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -14,3 +14,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -379,6 +379,8 @@
|
||||
<span clrTooltipTrigger *ngIf="tableTitle && tableTitle.length > 0">
|
||||
{{ tableTitle?.replace('-FC', '') }}
|
||||
</span>
|
||||
|
||||
<ng-container *ngIf="this.dsNote && this.dsNote.length > 0">
|
||||
<clr-tooltip-content
|
||||
clrPosition="bottom-left"
|
||||
clrSize="lg"
|
||||
@ -386,6 +388,7 @@
|
||||
>
|
||||
{{ 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>
|
||||
|
@ -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')
|
||||
|
@ -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' }
|
||||
)
|
||||
|
||||
|
@ -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..;
|
||||
|
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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
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_edit=YES),
|
||||
desc=Checking admin user can restore
|
||||
)
|
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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user