feat(multi load): submitting multiple found tables at once
This commit is contained in:
parent
0a8b1e764c
commit
5deba44d2b
34
client/src/app/models/sas/editors-stagedata.model.ts
Normal file
34
client/src/app/models/sas/editors-stagedata.model.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { BaseSASResponse } from './common/BaseSASResponse'
|
||||
|
||||
export interface EditorsStageDataSASResponse extends BaseSASResponse {
|
||||
SYSDATE: string;
|
||||
SYSTIME: string;
|
||||
sasparams: Sasparam[];
|
||||
_DEBUG: string;
|
||||
_PROGRAM: string;
|
||||
AUTOEXEC: string;
|
||||
MF_GETUSER: string;
|
||||
SYSCC: string;
|
||||
SYSENCODING: string;
|
||||
SYSERRORTEXT: string;
|
||||
SYSHOSTINFOLONG: string;
|
||||
SYSHOSTNAME: string;
|
||||
SYSPROCESSID: string;
|
||||
SYSPROCESSMODE: string;
|
||||
SYSPROCESSNAME: string;
|
||||
SYSJOBID: string;
|
||||
SYSSCPL: string;
|
||||
SYSSITE: string;
|
||||
SYSTCPIPHOSTNAME: string;
|
||||
SYSUSERID: string;
|
||||
SYSVLONG: string;
|
||||
SYSWARNINGTEXT: string;
|
||||
END_DTTM: string;
|
||||
MEMSIZE: string;
|
||||
}
|
||||
|
||||
export interface Sasparam {
|
||||
STATUS: string | 'SUCCESS';
|
||||
DSID: string;
|
||||
URL: string;
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
<clr-spinner clrMedium></clr-spinner>
|
||||
</div>
|
||||
|
||||
<div class="text-center mb-10">
|
||||
<div *ngIf="!parsedDatasets.length" class="text-center mb-10">
|
||||
<button
|
||||
(click)="fileUploadInput.click()"
|
||||
class="btn btn-primary btn-sm"
|
||||
@ -21,13 +21,28 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="parsedDatasets.length > 0">
|
||||
<p cds-text="caption" class="ml-10">Found tables:</p>
|
||||
<ng-container *ngIf="parsedDatasets.length && !submittedDatasets.length">
|
||||
<div class="text-center mb-10">
|
||||
<button
|
||||
(click)="onDiscard()"
|
||||
class="btn btn-danger btn-sm mr-10"
|
||||
>
|
||||
Discard
|
||||
</button>
|
||||
<button
|
||||
(click)="onSubmitAll()"
|
||||
class="btn btn-primary btn-sm"
|
||||
>
|
||||
Submit All
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p cds-text="caption" class="ml-10 mb-10">Found tables:</p>
|
||||
<clr-tree>
|
||||
<clr-tree-node *ngFor="let dataset of parsedDatasets">
|
||||
<button
|
||||
(click)="onParsedDatasetClick(dataset)"
|
||||
class="clr-treenode-link"
|
||||
class="clr-treenode-link whitespace-nowrap"
|
||||
[class.active]="dataset.active"
|
||||
>
|
||||
<cds-icon
|
||||
@ -47,6 +62,32 @@
|
||||
</clr-tree>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="submittedDatasets.length">
|
||||
<p cds-text="caption" class="ml-10 mb-10 mt-10">Submitted tables:</p>
|
||||
<clr-tree>
|
||||
<clr-tree-node *ngFor="let dataset of submittedDatasets">
|
||||
<button
|
||||
(click)="onSubmittedDatasetClick(dataset)"
|
||||
class="clr-treenode-link whitespace-nowrap"
|
||||
[class.active]="dataset.active"
|
||||
>
|
||||
<cds-icon
|
||||
*ngIf="dataset.error"
|
||||
status="danger"
|
||||
shape="exclamation-circle"
|
||||
></cds-icon>
|
||||
<cds-icon
|
||||
*ngIf="dataset.success"
|
||||
status="success"
|
||||
shape="check-circle"
|
||||
></cds-icon>
|
||||
<cds-icon shape="file"></cds-icon>
|
||||
{{ dataset.libds }}
|
||||
</button>
|
||||
</clr-tree-node>
|
||||
</clr-tree>
|
||||
</ng-container>
|
||||
|
||||
<!-- <div *ngIf="librariesPaging" class="w-100 text-center">
|
||||
<span class="spinner spinner-sm"> Loading... </span>
|
||||
</div> -->
|
||||
@ -146,72 +187,59 @@
|
||||
class="is-info icon-dc-fill"
|
||||
></clr-icon>
|
||||
<p class="text-center color-gray mt-10" cds-text="section">
|
||||
Please select a dataset on the left
|
||||
Please select a dataset on the left to {{ !submittedDatasets.length ? 'review data' : 'review submitted results' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="d-flex clr-justify-content-between p-10">
|
||||
<div>
|
||||
<p cds-text="secondary regular" class="mb-10">
|
||||
Found in range:
|
||||
<strong
|
||||
>"{{
|
||||
activeParsedDataset?.parseResult?.rangeSheetRes?.sheetName
|
||||
}}"!{{
|
||||
activeParsedDataset?.parseResult?.rangeSheetRes?.rangeAddress
|
||||
}}</strong
|
||||
>
|
||||
</p>
|
||||
<p cds-text="secondary regular">
|
||||
Matched with dataset: <strong>LIB1.MPE_X_DATA</strong>
|
||||
</p>
|
||||
<ng-container *ngIf="activeParsedDataset">
|
||||
<div class="d-flex clr-justify-content-between p-10">
|
||||
<div>
|
||||
<p cds-text="secondary regular" class="mb-10">
|
||||
Found in range:
|
||||
<strong
|
||||
>"{{
|
||||
activeParsedDataset.parseResult.rangeSheetRes?.sheetName
|
||||
}}"!{{
|
||||
activeParsedDataset.parseResult.rangeSheetRes?.rangeAddress
|
||||
}}</strong
|
||||
>
|
||||
</p>
|
||||
<p cds-text="secondary regular">
|
||||
Matched with dataset: <strong>{{ activeParsedDataset.libds }}</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<clr-toggle-wrapper>
|
||||
<input
|
||||
type="checkbox"
|
||||
clrToggle
|
||||
[(ngModel)]="activeParsedDataset.includeInSubmission"
|
||||
name="options"
|
||||
required
|
||||
value="option1"
|
||||
/>
|
||||
<label>Include in submission</label>
|
||||
</clr-toggle-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<clr-toggle-wrapper>
|
||||
<input
|
||||
type="checkbox"
|
||||
clrToggle
|
||||
name="options"
|
||||
required
|
||||
value="option1"
|
||||
/>
|
||||
<label>Include in submission</label>
|
||||
</clr-toggle-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
<hot-table
|
||||
hotId="hotInstance"
|
||||
id="hotTable"
|
||||
class="mt-15"
|
||||
className="htDark"
|
||||
[licenseKey]="hotTableLicenseKey"
|
||||
[multiColumnSorting]="true"
|
||||
[viewportRowRenderingOffset]="50"
|
||||
[manualColumnResize]="true"
|
||||
[filters]="true"
|
||||
stretchH="all"
|
||||
>
|
||||
</hot-table>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<!--
|
||||
<div *ngIf="!noData && !noDataReqErr && table" class="clr-flex-1">
|
||||
<hot-table
|
||||
hotId="hotInstance"
|
||||
id="hotTable"
|
||||
className="htDark"
|
||||
[multiColumnSorting]="true"
|
||||
[viewportRowRenderingOffset]="50"
|
||||
[data]="hotTable.data"
|
||||
[colHeaders]="hotTable.colHeaders"
|
||||
[columns]="hotTable.columns"
|
||||
[copyPaste]="hotTable.copyPaste"
|
||||
[contextMenu]="hotTable.contextMenu"
|
||||
[filters]="true"
|
||||
[dropdownMenu]="hotTable.dropdownMenu"
|
||||
[height]="hotTable.height"
|
||||
stretchH="all"
|
||||
[modifyColWidth]="maxWidthCheker"
|
||||
[cells]="hotTable.cells"
|
||||
[maxRows]="hotTable.maxRows"
|
||||
[manualColumnResize]="true"
|
||||
[afterGetColHeader]="hotTable.afterGetColHeader"
|
||||
[rowHeaders]="hotTable.rowHeaders"
|
||||
[rowHeaderWidth]="hotTable.rowHeaderWidth"
|
||||
[rowHeights]="hotTable.rowHeights"
|
||||
[licenseKey]="hotTable.licenseKey"
|
||||
>
|
||||
</hot-table>
|
||||
</div> -->
|
||||
|
||||
<!-- <div>
|
||||
<p
|
||||
*ngIf="
|
||||
@ -227,3 +255,30 @@
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<clr-modal [(clrModalOpen)]="showSubmitReasonModal">
|
||||
<h3 class="modal-title">Submit for approval {{parsedDatasets.length}} tables</h3>
|
||||
<div class="modal-body">
|
||||
<div class="text-area-full-width">
|
||||
<label for="formFields_8" class="mb-5 d-block"
|
||||
>Message</label
|
||||
>
|
||||
<textarea
|
||||
clrTextarea
|
||||
[(ngModel)]="submitReasonMessage"
|
||||
tabindex="0"
|
||||
class="submit-reason"
|
||||
type="text"
|
||||
id="formFields_8"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<p cds-text="caption_clean" class="mt-10">
|
||||
Sheets which did not match any dataset will be ignored. Tables will be sent sequentially, logs will be available after all tables are submitted.
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline" [disabled]="submitLoading" (click)="showSubmitReasonModal = false">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" [clrLoading]="submitLoading" (click)="submitTables()">Submit</button>
|
||||
</div>
|
||||
</clr-modal>
|
@ -24,4 +24,10 @@
|
||||
min-height: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.submit-reason {
|
||||
min-height: 70px;
|
||||
max-height: 70px;
|
||||
height: 70px;
|
||||
}
|
@ -1,11 +1,8 @@
|
||||
import { Component, HostBinding, OnInit } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { ChangeDetectorRef, Component, HostBinding, OnInit, SimpleChanges } from '@angular/core'
|
||||
import {
|
||||
EventService,
|
||||
HelperService,
|
||||
LicenceService,
|
||||
LoggerService,
|
||||
SasService,
|
||||
SasStoreService
|
||||
} from '../services'
|
||||
import * as XLSX from '@sheet/crypto'
|
||||
@ -19,6 +16,10 @@ import {
|
||||
ParseResult,
|
||||
SpreadsheetService
|
||||
} from '../services/spreadsheet.service'
|
||||
import Handsontable from 'handsontable'
|
||||
import { HotTableRegisterer } from '@handsontable/angular'
|
||||
import { Router } from '@angular/router'
|
||||
import { EditorsStageDataSASResponse } from '../models/sas/editors-stagedata.model'
|
||||
|
||||
@Component({
|
||||
selector: 'app-multi-dataset',
|
||||
@ -36,25 +37,35 @@ export class MultiDatasetComponent implements OnInit {
|
||||
|
||||
public selectedFile: File | null = null
|
||||
public parsedDatasets: ParsedDataset[] = []
|
||||
public submittedDatasets: SubmittedDatasetResult[] = []
|
||||
|
||||
public datasetsLoading: boolean = false
|
||||
public uploadLoading: boolean = false
|
||||
public submitLoading: boolean = false
|
||||
|
||||
public matchedDatasets: string[] = []
|
||||
public userInputDatasets: string = ''
|
||||
|
||||
public uploadLoading: boolean = false
|
||||
|
||||
public libsAndTables: {
|
||||
[key: string]: string[]
|
||||
} = {}
|
||||
|
||||
public hotInstance!: Handsontable
|
||||
private hotRegisterer: HotTableRegisterer
|
||||
|
||||
public showSubmitReasonModal: boolean = false
|
||||
public submitReasonMessage: string = ''
|
||||
|
||||
constructor(
|
||||
private eventService: EventService,
|
||||
private licenceService: LicenceService,
|
||||
private helperService: HelperService,
|
||||
private sasStoreService: SasStoreService,
|
||||
private spreadsheetService: SpreadsheetService
|
||||
) {}
|
||||
private spreadsheetService: SpreadsheetService,
|
||||
private cdr: ChangeDetectorRef
|
||||
) {
|
||||
this.hotRegisterer = new HotTableRegisterer()
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.licenceService.hot_license_key.subscribe(
|
||||
@ -154,9 +165,28 @@ export class MultiDatasetComponent implements OnInit {
|
||||
console.log('parseResult', parseResult)
|
||||
|
||||
if (parseResult && parseResult.data) {
|
||||
let datasource: any[] = []
|
||||
|
||||
parseResult.data.map((item) => {
|
||||
let itemObject: any = {}
|
||||
|
||||
parseResult.headerShow!.map((header: any, index: number) => {
|
||||
itemObject[header] = item[index]
|
||||
})
|
||||
|
||||
// If Delete? column is not set in the file, we set it to NO
|
||||
if (!itemObject['_____DELETE__THIS__RECORD_____'])
|
||||
itemObject['_____DELETE__THIS__RECORD_____'] = 'No'
|
||||
|
||||
datasource.push(itemObject)
|
||||
})
|
||||
|
||||
this.parsedDatasets.push({
|
||||
libds: datasetObject.libds,
|
||||
parseResult: parseResult
|
||||
parseResult: parseResult,
|
||||
includeInSubmission: true,
|
||||
datasetInfo: datasetObject,
|
||||
datasource: datasource
|
||||
})
|
||||
}
|
||||
})
|
||||
@ -167,6 +197,34 @@ export class MultiDatasetComponent implements OnInit {
|
||||
})
|
||||
}
|
||||
|
||||
onSubmitAll() {
|
||||
this.showSubmitReasonModal = true
|
||||
}
|
||||
|
||||
onDiscard() {
|
||||
this.parsedDatasets = []
|
||||
this.matchedDatasets = []
|
||||
this.selectedFile = null
|
||||
this.userInputDatasets = ''
|
||||
this.submitReasonMessage = ''
|
||||
}
|
||||
|
||||
initHot() {
|
||||
setTimeout(() => {
|
||||
if (!this.hotInstance) this.hotInstance = this.hotRegisterer.getInstance('hotInstance')
|
||||
|
||||
if (this.activeParsedDataset) {
|
||||
this.hotInstance.updateSettings({
|
||||
data: this.activeParsedDataset.datasetInfo.data.sasdata,
|
||||
colHeaders: this.activeParsedDataset.datasetInfo.headerColumns,
|
||||
columns: this.activeParsedDataset.datasetInfo.dcValidator?.getRules(),
|
||||
readOnly: true,
|
||||
height: '300px',
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onUserInputDatasetsChange() {
|
||||
this.helperService.debounceCall(500, () => {
|
||||
const inputDatasets = this.userInputDatasets.split('\n')
|
||||
@ -227,6 +285,14 @@ export class MultiDatasetComponent implements OnInit {
|
||||
this.deselectAllParsedDatasets()
|
||||
|
||||
parsedDataset.active = true
|
||||
|
||||
this.cdr.detectChanges()
|
||||
|
||||
this.initHot()
|
||||
}
|
||||
|
||||
onSubmittedDatasetClick(submittedDataset: SubmittedDatasetResult) {
|
||||
|
||||
}
|
||||
|
||||
public get activeParsedDataset(): ParsedDataset | undefined {
|
||||
@ -260,6 +326,89 @@ export class MultiDatasetComponent implements OnInit {
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends tables to the SAS sequentially
|
||||
*/
|
||||
async submitTables() {
|
||||
console.info('Submitting multiple tables', this.parsedDatasets)
|
||||
|
||||
this.submitLoading = true
|
||||
|
||||
let requestsResults: SubmittedDatasetResult[] = []
|
||||
|
||||
for (let table of this.parsedDatasets) {
|
||||
// Skip the table if toggle switch is off
|
||||
if (!table.includeInSubmission) continue
|
||||
|
||||
let updateParams: any = {}
|
||||
|
||||
this.submitReasonMessage = this.submitReasonMessage.replace(/\n/g, '. ')
|
||||
updateParams.ACTION = 'LOAD'
|
||||
updateParams.MESSAGE = this.submitReasonMessage
|
||||
updateParams.LIBDS = table.libds
|
||||
|
||||
let data = table.datasource
|
||||
|
||||
if (data) {
|
||||
data = data.map((row: any) => {
|
||||
let deleteColValue = row['_____DELETE__THIS__RECORD_____']
|
||||
|
||||
delete row['_____DELETE__THIS__RECORD_____']
|
||||
row['_____DELETE__THIS__RECORD_____'] = deleteColValue
|
||||
|
||||
// If cell is numeric and value is dot `.` we change it to `null`
|
||||
Object.keys(row).map((key: string) => {
|
||||
const colRule = table.datasetInfo.dcValidator?.getRule(key)
|
||||
|
||||
if (colRule?.type === 'numeric' && row[key] === '.') row[key] = null
|
||||
})
|
||||
|
||||
return row
|
||||
})
|
||||
|
||||
const submitData = data.slice(
|
||||
0,
|
||||
this.licenceState.value.submit_rows_limit
|
||||
)
|
||||
|
||||
console.log('submitData', submitData)
|
||||
|
||||
let error
|
||||
let success
|
||||
|
||||
await this.sasStoreService
|
||||
.updateTable(
|
||||
updateParams,
|
||||
table.datasource,
|
||||
'SASControlTable',
|
||||
'editors/stagedata',
|
||||
table.datasetInfo.data.$sasdata
|
||||
)
|
||||
.then((res: EditorsStageDataSASResponse) => {
|
||||
success = res
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.error('err', err)
|
||||
|
||||
error = err
|
||||
})
|
||||
|
||||
requestsResults.push({
|
||||
success,
|
||||
error,
|
||||
libds: table.libds
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
console.log('requestsResults', requestsResults)
|
||||
|
||||
this.submittedDatasets = requestsResults
|
||||
this.showSubmitReasonModal = false
|
||||
this.submitLoading = false
|
||||
this.deselectAllParsedDatasets()
|
||||
}
|
||||
|
||||
private parseExcelSheetNames(): Promise<string[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
@ -439,7 +588,17 @@ export interface DatasetsObject extends EditorsGetDataServiceResponse {
|
||||
|
||||
export interface ParsedDataset {
|
||||
libds: string
|
||||
parseResult: ParseResult,
|
||||
datasetInfo: DatasetsObject,
|
||||
datasource: any[],
|
||||
includeInSubmission: boolean
|
||||
status?: 'success' | 'error'
|
||||
active?: boolean
|
||||
parseResult: ParseResult
|
||||
}
|
||||
|
||||
export interface SubmittedDatasetResult {
|
||||
libds: string,
|
||||
success: EditorsStageDataSASResponse | undefined,
|
||||
error: any,
|
||||
active?: boolean
|
||||
}
|
@ -17,10 +17,10 @@ import { LoggerService } from './logger.service'
|
||||
import { isSpecialMissing } from '@sasjs/utils/input/validators'
|
||||
import { Col } from '../shared/dc-validator/models/col.model'
|
||||
import { get } from 'lodash-es'
|
||||
import { EditorsStageDataSASResponse } from '../models/sas/editors-stagedata.model'
|
||||
|
||||
@Injectable()
|
||||
export class SasStoreService {
|
||||
public libds!: string
|
||||
public response: Subject<any> = new Subject<any>()
|
||||
public changedTable: Subject<any> = new Subject<any>()
|
||||
public details: Subject<any> = new Subject<any>()
|
||||
@ -56,7 +56,6 @@ export class SasStoreService {
|
||||
program: string,
|
||||
libds: string
|
||||
) {
|
||||
this.libds = libds
|
||||
const tables: any = {}
|
||||
tables[tableName] = [tableData]
|
||||
const res: EditorsGetDataSASResponse = await this.sasService.request(
|
||||
@ -65,7 +64,7 @@ export class SasStoreService {
|
||||
)
|
||||
const response: EditorsGetDataServiceResponse = {
|
||||
data: res,
|
||||
libds: this.libds
|
||||
libds: libds
|
||||
}
|
||||
return response
|
||||
}
|
||||
@ -85,7 +84,7 @@ export class SasStoreService {
|
||||
tableName: string,
|
||||
program: string,
|
||||
$dataFormats: $DataFormats | null
|
||||
) {
|
||||
): Promise<EditorsStageDataSASResponse> {
|
||||
// add sp as third argument of createData call
|
||||
|
||||
let tables: any = {
|
||||
|
@ -84,6 +84,17 @@ body[cds-theme="light"] {
|
||||
line-height: 1.8 !important;
|
||||
}
|
||||
|
||||
[cds-text=caption_clean] {
|
||||
font-size: var(--cds-global-typography-caption-font-size);
|
||||
font-weight: var(--cds-global-typography-caption-font-weight);
|
||||
line-height: var(--cds-global-typography-caption-line-height);
|
||||
letter-spacing: var(--cds-global-typography-caption-letter-spacing);
|
||||
|
||||
&::after, &::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Custom loading spinner
|
||||
.slider {
|
||||
position: absolute;
|
||||
@ -466,6 +477,10 @@ body[cds-theme="light"] {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.whitespace-nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
@ -1048,7 +1063,6 @@ clr-tree-node {
|
||||
padding: 0px 8px 0px 8px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user