From 5deba44d2b7352866d821b70dbbfbbf54955dc47 Mon Sep 17 00:00:00 2001 From: Mihajlo Medjedovic Date: Tue, 18 Jun 2024 00:37:41 +0200 Subject: [PATCH] feat(multi load): submitting multiple found tables at once --- .../app/models/sas/editors-stagedata.model.ts | 34 ++++ .../multi-dataset.component.html | 181 ++++++++++++------ .../multi-dataset.component.scss | 6 + .../multi-dataset/multi-dataset.component.ts | 179 ++++++++++++++++- client/src/app/services/sas-store.service.ts | 7 +- client/src/styles.scss | 16 +- 6 files changed, 345 insertions(+), 78 deletions(-) create mode 100644 client/src/app/models/sas/editors-stagedata.model.ts diff --git a/client/src/app/models/sas/editors-stagedata.model.ts b/client/src/app/models/sas/editors-stagedata.model.ts new file mode 100644 index 0000000..e930287 --- /dev/null +++ b/client/src/app/models/sas/editors-stagedata.model.ts @@ -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; +} \ No newline at end of file diff --git a/client/src/app/multi-dataset/multi-dataset.component.html b/client/src/app/multi-dataset/multi-dataset.component.html index 69dcaf7..8f87653 100644 --- a/client/src/app/multi-dataset/multi-dataset.component.html +++ b/client/src/app/multi-dataset/multi-dataset.component.html @@ -3,7 +3,7 @@ -
+
- -

Found tables:

+ +
+ + +
+ +

Found tables:

+ + +
+ @@ -146,72 +187,59 @@ class="is-info icon-dc-fill" >

- Please select a dataset on the left + Please select a dataset on the left to {{ !submittedDatasets.length ? 'review data' : 'review submitted results' }}

-
-
-

- Found in range: - "{{ - activeParsedDataset?.parseResult?.rangeSheetRes?.sheetName - }}"!{{ - activeParsedDataset?.parseResult?.rangeSheetRes?.rangeAddress - }} -

-

- Matched with dataset: LIB1.MPE_X_DATA -

+ +
+
+

+ Found in range: + "{{ + activeParsedDataset.parseResult.rangeSheetRes?.sheetName + }}"!{{ + activeParsedDataset.parseResult.rangeSheetRes?.rangeAddress + }} +

+

+ Matched with dataset: {{ activeParsedDataset.libds }} +

+
+ +
+ + + + +
-
- - - - -
-
+ + + - -
+ + + + + + \ No newline at end of file diff --git a/client/src/app/multi-dataset/multi-dataset.component.scss b/client/src/app/multi-dataset/multi-dataset.component.scss index 7127636..495129d 100644 --- a/client/src/app/multi-dataset/multi-dataset.component.scss +++ b/client/src/app/multi-dataset/multi-dataset.component.scss @@ -24,4 +24,10 @@ min-height: 200px; height: 200px; } +} + +.submit-reason { + min-height: 70px; + max-height: 70px; + height: 70px; } \ No newline at end of file diff --git a/client/src/app/multi-dataset/multi-dataset.component.ts b/client/src/app/multi-dataset/multi-dataset.component.ts index 4318f93..6b00660 100644 --- a/client/src/app/multi-dataset/multi-dataset.component.ts +++ b/client/src/app/multi-dataset/multi-dataset.component.ts @@ -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 { 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 +} \ No newline at end of file diff --git a/client/src/app/services/sas-store.service.ts b/client/src/app/services/sas-store.service.ts index 9b75364..a2f1eae 100644 --- a/client/src/app/services/sas-store.service.ts +++ b/client/src/app/services/sas-store.service.ts @@ -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 = new Subject() public changedTable: Subject = new Subject() public details: Subject = new Subject() @@ -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 { // add sp as third argument of createData call let tables: any = { diff --git a/client/src/styles.scss b/client/src/styles.scss index b199cb6..a996b65 100644 --- a/client/src/styles.scss +++ b/client/src/styles.scss @@ -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; }