This commit is contained in:
parent
efcdc694dd
commit
fc52e8f41a
@ -47,9 +47,7 @@ import { UploadStaterComponent } from './components/upload-stater/upload-stater.
|
||||
import { DynamicExtendedCellValidation } from './models/dynamicExtendedCellValidation'
|
||||
import { EditRecordInputFocusedEvent } from './models/edit-record/edit-record-events'
|
||||
import { EditorRestrictions } from './models/editor-restrictions.model'
|
||||
import {
|
||||
parseTableColumns
|
||||
} from './utils/grid.utils'
|
||||
import { parseTableColumns } from './utils/grid.utils'
|
||||
import {
|
||||
errorRenderer,
|
||||
noSpinnerRenderer,
|
||||
@ -59,7 +57,10 @@ import { LicenceService } from '../services/licence.service'
|
||||
import * as numbro from 'numbro'
|
||||
import * as languages from 'numbro/dist/languages.min'
|
||||
import { FileUploadEncoding } from '../models/FileUploadEncoding'
|
||||
import { ParseResult, SpreadsheetService } from '../services/spreadsheet.service'
|
||||
import {
|
||||
ParseResult,
|
||||
SpreadsheetService
|
||||
} from '../services/spreadsheet.service'
|
||||
|
||||
@Component({
|
||||
selector: 'app-editor',
|
||||
@ -471,50 +472,58 @@ export class EditorComponent implements OnInit, AfterViewInit {
|
||||
|
||||
this.filename = file.name
|
||||
|
||||
this.spreadsheetService.parseExcelFile({
|
||||
file: file,
|
||||
uploader: this.uploader,
|
||||
dcValidator: this.dcValidator!,
|
||||
headerPks: this.headerPks,
|
||||
headerArray: this.headerArray,
|
||||
headerShow: this.headerShow,
|
||||
timeHeaders: this.timeHeaders,
|
||||
dateHeaders: this.dateHeaders,
|
||||
dateTimeHeaders: this.dateTimeHeaders,
|
||||
xlRules: this.xlRules,
|
||||
encoding: this.encoding
|
||||
}, (uploadState: string) => {
|
||||
this.appendUploadState(uploadState)
|
||||
}, (tableFoundInfo: string) => {
|
||||
this.eventService.showInfoModal('Table Found', tableFoundInfo)
|
||||
}).then((parseResult: ParseResult | undefined) => {
|
||||
if (parseResult) {
|
||||
this.excelFileReady = true
|
||||
|
||||
this.uploader = parseResult.uploader
|
||||
|
||||
if (parseResult.data && parseResult.headerShow) {
|
||||
// If data is returned it means we parsed excel file
|
||||
this.data = parseResult.data
|
||||
this.headerShow = parseResult.headerShow
|
||||
this.getPendingExcelPreview()
|
||||
} else {
|
||||
// otherwise it's csv file, and we send them directly
|
||||
this.uploadParsedFiles()
|
||||
this.spreadsheetService
|
||||
.parseExcelFile(
|
||||
{
|
||||
file: file,
|
||||
uploader: this.uploader,
|
||||
dcValidator: this.dcValidator!,
|
||||
headerPks: this.headerPks,
|
||||
headerArray: this.headerArray,
|
||||
headerShow: this.headerShow,
|
||||
timeHeaders: this.timeHeaders,
|
||||
dateHeaders: this.dateHeaders,
|
||||
dateTimeHeaders: this.dateTimeHeaders,
|
||||
xlRules: this.xlRules,
|
||||
encoding: this.encoding
|
||||
},
|
||||
(uploadState: string) => {
|
||||
this.appendUploadState(uploadState)
|
||||
},
|
||||
(tableFoundInfo: string) => {
|
||||
this.eventService.showInfoModal('Table Found', tableFoundInfo)
|
||||
}
|
||||
}
|
||||
}).catch((error: string) => {
|
||||
this.eventService.showInfoModal('Error', error)
|
||||
)
|
||||
.then((parseResult: ParseResult | undefined) => {
|
||||
if (parseResult) {
|
||||
this.excelFileReady = true
|
||||
|
||||
this.showUploadModal = false
|
||||
this.uploadPreview = false
|
||||
this.uploader = parseResult.uploader
|
||||
|
||||
setTimeout(() => {
|
||||
this.filename = ''
|
||||
if (parseResult.data && parseResult.headerShow) {
|
||||
// If data is returned it means we parsed excel file
|
||||
this.data = parseResult.data
|
||||
this.headerShow = parseResult.headerShow
|
||||
this.getPendingExcelPreview()
|
||||
} else {
|
||||
// otherwise it's csv file, and we send them directly
|
||||
this.uploadParsedFiles()
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((error: string) => {
|
||||
this.eventService.showInfoModal('Error', error)
|
||||
|
||||
this.showUploadModal = false
|
||||
this.uploadPreview = false
|
||||
|
||||
setTimeout(() => {
|
||||
this.filename = ''
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.excelFileParsing = false
|
||||
})
|
||||
}).finally(() => {
|
||||
this.excelFileParsing = false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@ -728,7 +737,6 @@ export class EditorComponent implements OnInit, AfterViewInit {
|
||||
this.sasStoreService.removeClause()
|
||||
}
|
||||
|
||||
|
||||
async sendClause() {
|
||||
this.submitLoading = true
|
||||
let nullVariableArr = []
|
||||
|
@ -13,7 +13,7 @@ const routes: Routes = [
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'tables' },
|
||||
{ path: 'tables', component: HomeComponent },
|
||||
{ path: 'excel-maps', loadChildren: () => XLMapModule },
|
||||
{ path: 'multi-load', loadChildren: () => MultiDatasetModule}
|
||||
{ path: 'multi-load', loadChildren: () => MultiDatasetModule }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -1 +1 @@
|
||||
export type FileUploadEncoding = 'UTF-8' | 'WLATIN1'
|
||||
export type FileUploadEncoding = 'UTF-8' | 'WLATIN1'
|
||||
|
@ -8,9 +8,7 @@ const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: MultiDatasetRouteComponent,
|
||||
children: [
|
||||
{ path: '', component: MultiDatasetComponent }
|
||||
]
|
||||
children: [{ path: '', component: MultiDatasetComponent }]
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -4,15 +4,21 @@
|
||||
</div>
|
||||
|
||||
<div class="text-center mb-10">
|
||||
<button (click)="fileUploadInput.click()" class="btn btn-primary btn-sm" [disabled]="selectedFile !== null">Browse file</button>
|
||||
<button
|
||||
(click)="fileUploadInput.click()"
|
||||
class="btn btn-primary btn-sm"
|
||||
[disabled]="selectedFile !== null"
|
||||
>
|
||||
Browse file
|
||||
</button>
|
||||
<input
|
||||
hidden
|
||||
#fileUploadInput
|
||||
id="file-upload"
|
||||
type="file"
|
||||
(change)="onFileChange($event)"
|
||||
appFileSelect
|
||||
/>
|
||||
hidden
|
||||
#fileUploadInput
|
||||
id="file-upload"
|
||||
type="file"
|
||||
(change)="onFileChange($event)"
|
||||
appFileSelect
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="parsedDatasets.length > 0">
|
||||
@ -24,8 +30,16 @@
|
||||
class="clr-treenode-link"
|
||||
[class.active]="dataset.active"
|
||||
>
|
||||
<cds-icon *ngIf="dataset.status === 'error'" status="danger" shape="exclamation-circle"></cds-icon>
|
||||
<cds-icon *ngIf="dataset.status === 'success'" status="success" shape="check-circle"></cds-icon>
|
||||
<cds-icon
|
||||
*ngIf="dataset.status === 'error'"
|
||||
status="danger"
|
||||
shape="exclamation-circle"
|
||||
></cds-icon>
|
||||
<cds-icon
|
||||
*ngIf="dataset.status === 'success'"
|
||||
status="success"
|
||||
shape="check-circle"
|
||||
></cds-icon>
|
||||
<cds-icon shape="file"></cds-icon>
|
||||
{{ dataset.libds }}
|
||||
</button>
|
||||
@ -39,9 +53,7 @@
|
||||
</app-sidebar>
|
||||
|
||||
<div class="content-area">
|
||||
<div
|
||||
class="card no-borders h-100 d-flex clr-flex-column"
|
||||
>
|
||||
<div class="card no-borders h-100 d-flex clr-flex-column">
|
||||
<div
|
||||
class="header-row clr-row justify-content-between clr-justify-content-center w-100 m-0"
|
||||
>
|
||||
@ -65,24 +77,59 @@
|
||||
<ng-container *ngIf="!parsedDatasets.length">
|
||||
<div class="d-flex clr-justify-content-center mt-15">
|
||||
<div class="dataset-input-wrapper">
|
||||
<p cds-text="secondary regular" class="mb-20">Selected file: <strong>{{ selectedFile?.name }}</strong></p>
|
||||
<p cds-text="secondary regular">Paste or type the list of datasets to upload:</p>
|
||||
<p cds-text="secondary regular" class="mb-20">
|
||||
Selected file: <strong>{{ selectedFile?.name }}</strong>
|
||||
</p>
|
||||
<p cds-text="secondary regular">
|
||||
Paste or type the list of datasets to upload:
|
||||
</p>
|
||||
|
||||
<button (click)="onAutoDetectColumns()" class="mt-15 btn btn-primary-outline btn-sm">Auto detect</button>
|
||||
<button
|
||||
(click)="onAutoDetectColumns()"
|
||||
class="mt-15 btn btn-primary-outline btn-sm"
|
||||
>
|
||||
Auto detect
|
||||
</button>
|
||||
|
||||
<clr-textarea-container class="m-0">
|
||||
<textarea clrTextarea [(ngModel)]="userInputDatasets" (input)="onUserInputDatasetsChange()" class="w-100-i"></textarea>
|
||||
<clr-control-helper>Every row is one dataset. Format: LIBRARY.TABLE</clr-control-helper>
|
||||
<textarea
|
||||
clrTextarea
|
||||
[(ngModel)]="userInputDatasets"
|
||||
(input)="onUserInputDatasetsChange()"
|
||||
class="w-100-i"
|
||||
></textarea>
|
||||
<clr-control-helper
|
||||
>Every row is one dataset. Format:
|
||||
LIBRARY.TABLE</clr-control-helper
|
||||
>
|
||||
</clr-textarea-container>
|
||||
|
||||
<div class="text-right mt-10">
|
||||
<button (click)="onDiscardFile()" class="btn btn-danger btn-sm" [disabled]="uploadLoading">Discard file</button>
|
||||
<button (click)="onUploadFile()" class="btn btn-primary btn-sm" [disabled]="!matchedDatasets.length" [clrLoading]="uploadLoading">Continue</button>
|
||||
<button
|
||||
(click)="onDiscardFile()"
|
||||
class="btn btn-danger btn-sm"
|
||||
[disabled]="uploadLoading"
|
||||
>
|
||||
Discard file
|
||||
</button>
|
||||
<button
|
||||
(click)="onUploadFile()"
|
||||
class="btn btn-primary btn-sm"
|
||||
[disabled]="!matchedDatasets.length"
|
||||
[clrLoading]="uploadLoading"
|
||||
>
|
||||
Continue
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div *ngIf="matchedDatasets.length">
|
||||
<p><strong>Matched datasets:</strong></p>
|
||||
<p *ngFor="let matchedDataset of matchedDatasets" class="m-0 ml-5-i">{{ matchedDataset }}</p>
|
||||
<p
|
||||
*ngFor="let matchedDataset of matchedDatasets"
|
||||
class="m-0 ml-5-i"
|
||||
>
|
||||
{{ matchedDataset }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -105,20 +152,37 @@
|
||||
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<clr-toggle-wrapper>
|
||||
<input type="checkbox" clrToggle name="options" required value="option1"/>
|
||||
<input
|
||||
type="checkbox"
|
||||
clrToggle
|
||||
name="options"
|
||||
required
|
||||
value="option1"
|
||||
/>
|
||||
<label>Include in submission</label>
|
||||
</clr-toggle-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<!--
|
||||
<!--
|
||||
<div *ngIf="!noData && !noDataReqErr && table" class="clr-flex-1">
|
||||
<hot-table
|
||||
hotId="hotInstance"
|
||||
|
@ -1,8 +1,4 @@
|
||||
import {
|
||||
Component,
|
||||
HostBinding,
|
||||
OnInit,
|
||||
} from '@angular/core'
|
||||
import { Component, HostBinding, OnInit } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import {
|
||||
EventService,
|
||||
@ -19,7 +15,10 @@ import { DcValidator } from '../shared/dc-validator/dc-validator'
|
||||
import { ExcelRule } from '../models/TableData'
|
||||
import { HotTableInterface } from '../models/HotTable.interface'
|
||||
import { Col } from '../shared/dc-validator/models/col.model'
|
||||
import { ParseResult, SpreadsheetService } from '../services/spreadsheet.service'
|
||||
import {
|
||||
ParseResult,
|
||||
SpreadsheetService
|
||||
} from '../services/spreadsheet.service'
|
||||
|
||||
@Component({
|
||||
selector: 'app-multi-dataset',
|
||||
@ -81,16 +80,26 @@ export class MultiDatasetComponent implements OnInit {
|
||||
|
||||
onFileChange(event: any) {
|
||||
if (!event?.target?.files[0]) {
|
||||
this.eventService.showAbortModal(null, 'No file found.', null, 'File Upload')
|
||||
this.eventService.showAbortModal(
|
||||
null,
|
||||
'No file found.',
|
||||
null,
|
||||
'File Upload'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const file = event.target.files[0];
|
||||
const fileTitle = file.name;
|
||||
const file = event.target.files[0]
|
||||
const fileTitle = file.name
|
||||
const fileExtension = fileTitle.split('.').pop()
|
||||
|
||||
if (!['xlsx', 'xlsm', 'xlm'].includes(fileExtension)) {
|
||||
this.eventService.showAbortModal(null, 'Only excel extensions are allowed. (xlsx)', null, 'Extension Error')
|
||||
this.eventService.showAbortModal(
|
||||
null,
|
||||
'Only excel extensions are allowed. (xlsx)',
|
||||
null,
|
||||
'Extension Error'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@ -106,19 +115,22 @@ export class MultiDatasetComponent implements OnInit {
|
||||
async onUploadFile() {
|
||||
this.uploadLoading = true
|
||||
|
||||
const datasetFetchingPromises: Promise<EditorsGetDataServiceResponse | undefined>[] = []
|
||||
const datasetFetchingPromises: Promise<
|
||||
EditorsGetDataServiceResponse | undefined
|
||||
>[] = []
|
||||
|
||||
let datasets: EditorsGetDataServiceResponse[] = []
|
||||
|
||||
for (let datasetLibds of this.matchedDatasets) {
|
||||
const promise = this.fetchDataset(datasetLibds)
|
||||
const promise = this.fetchDataset(datasetLibds)
|
||||
|
||||
datasetFetchingPromises.push(promise)
|
||||
datasetFetchingPromises.push(promise)
|
||||
}
|
||||
|
||||
Promise.allSettled(datasetFetchingPromises).then((res) => {
|
||||
res.forEach((promise) => {
|
||||
if (promise.status === 'fulfilled' && promise.value) datasets.push(promise.value)
|
||||
if (promise.status === 'fulfilled' && promise.value)
|
||||
datasets.push(promise.value)
|
||||
})
|
||||
|
||||
this.uploadLoading = false
|
||||
@ -126,28 +138,31 @@ export class MultiDatasetComponent implements OnInit {
|
||||
const datasetObjects = this.buildDatasetsObjects(datasets)
|
||||
|
||||
for (let datasetObject of datasetObjects) {
|
||||
this.spreadsheetService.parseExcelFile({
|
||||
file: this.selectedFile!,
|
||||
dcValidator: datasetObject.dcValidator!,
|
||||
headerPks: datasetObject.headerPks,
|
||||
headerArray: datasetObject.headerArray,
|
||||
headerShow: [],
|
||||
timeHeaders: datasetObject.timeHeaders,
|
||||
dateHeaders: datasetObject.dateHeaders,
|
||||
dateTimeHeaders: datasetObject.dateTimeHeaders,
|
||||
xlRules: datasetObject.xlRules
|
||||
}).then((parseResult: ParseResult | undefined) => {
|
||||
console.log('parseResult', parseResult)
|
||||
this.spreadsheetService
|
||||
.parseExcelFile({
|
||||
file: this.selectedFile!,
|
||||
dcValidator: datasetObject.dcValidator!,
|
||||
headerPks: datasetObject.headerPks,
|
||||
headerArray: datasetObject.headerArray,
|
||||
headerShow: [],
|
||||
timeHeaders: datasetObject.timeHeaders,
|
||||
dateHeaders: datasetObject.dateHeaders,
|
||||
dateTimeHeaders: datasetObject.dateTimeHeaders,
|
||||
xlRules: datasetObject.xlRules
|
||||
})
|
||||
.then((parseResult: ParseResult | undefined) => {
|
||||
console.log('parseResult', parseResult)
|
||||
|
||||
if (parseResult && parseResult.data) {
|
||||
this.parsedDatasets.push({
|
||||
libds: datasetObject.libds,
|
||||
parseResult: parseResult
|
||||
})
|
||||
}
|
||||
}).catch((error: string) => {
|
||||
this.eventService.showInfoModal('Error', error)
|
||||
})
|
||||
if (parseResult && parseResult.data) {
|
||||
this.parsedDatasets.push({
|
||||
libds: datasetObject.libds,
|
||||
parseResult: parseResult
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch((error: string) => {
|
||||
this.eventService.showInfoModal('Error', error)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -158,14 +173,19 @@ export class MultiDatasetComponent implements OnInit {
|
||||
|
||||
this.matchedDatasets = []
|
||||
|
||||
|
||||
inputDatasets.forEach((dataset: string) => {
|
||||
const trimmedDataset = dataset.trim()
|
||||
|
||||
if (this.isValidDatasetFormat(trimmedDataset) && this.isValidDatasetReference(trimmedDataset) && !this.matchedDatasets.includes(trimmedDataset)) {
|
||||
if (
|
||||
this.isValidDatasetFormat(trimmedDataset) &&
|
||||
this.isValidDatasetReference(trimmedDataset) &&
|
||||
!this.matchedDatasets.includes(trimmedDataset)
|
||||
) {
|
||||
this.matchedDatasets.push(trimmedDataset)
|
||||
} else {
|
||||
console.warn(`Sheet name: ${trimmedDataset} is not an actual dataset reference.`)
|
||||
console.warn(
|
||||
`Sheet name: ${trimmedDataset} is not an actual dataset reference.`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@ -186,10 +206,15 @@ export class MultiDatasetComponent implements OnInit {
|
||||
this.userInputDatasets = ''
|
||||
|
||||
sheetNames.forEach((sheetName: string, index: number) => {
|
||||
if (this.isValidDatasetFormat(sheetName) && this.isValidDatasetReference(sheetName)) {
|
||||
if (
|
||||
this.isValidDatasetFormat(sheetName) &&
|
||||
this.isValidDatasetReference(sheetName)
|
||||
) {
|
||||
this.matchedDatasets.push(sheetName)
|
||||
} else {
|
||||
console.warn(`Sheet name: ${sheetName} is not an actual dataset reference.`)
|
||||
console.warn(
|
||||
`Sheet name: ${sheetName} is not an actual dataset reference.`
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -205,13 +230,15 @@ export class MultiDatasetComponent implements OnInit {
|
||||
}
|
||||
|
||||
public get activeParsedDataset(): ParsedDataset | undefined {
|
||||
return this.parsedDatasets.find(dataset => dataset.active)
|
||||
return this.parsedDatasets.find((dataset) => dataset.active)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the table for given datasets params LIBRARY.TABLE
|
||||
*/
|
||||
async fetchDataset(libds: string): Promise<EditorsGetDataServiceResponse | undefined> {
|
||||
* Fetches the table for given datasets params LIBRARY.TABLE
|
||||
*/
|
||||
async fetchDataset(
|
||||
libds: string
|
||||
): Promise<EditorsGetDataServiceResponse | undefined> {
|
||||
let myParams: any = {
|
||||
LIBDS: libds,
|
||||
OUTDEST: 'WEB'
|
||||
@ -235,39 +262,38 @@ export class MultiDatasetComponent implements OnInit {
|
||||
|
||||
private parseExcelSheetNames(): Promise<string[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
const reader = new FileReader()
|
||||
|
||||
if (!this.selectedFile) {
|
||||
console.warn('selectedFile is missing')
|
||||
return resolve([])
|
||||
}
|
||||
|
||||
reader.onload = (event: ProgressEvent<FileReader>) => {
|
||||
if (!event?.target) {
|
||||
console.warn('File reader event.target is missing')
|
||||
return
|
||||
if (!this.selectedFile) {
|
||||
console.warn('selectedFile is missing')
|
||||
return resolve([])
|
||||
}
|
||||
|
||||
const data = event.target.result;
|
||||
const workbook = XLSX.read(data, {
|
||||
type: 'binary'
|
||||
});
|
||||
reader.onload = (event: ProgressEvent<FileReader>) => {
|
||||
if (!event?.target) {
|
||||
console.warn('File reader event.target is missing')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const sheet_names_list = workbook.SheetNames;
|
||||
const data = event.target.result
|
||||
const workbook = XLSX.read(data, {
|
||||
type: 'binary'
|
||||
})
|
||||
|
||||
return resolve(sheet_names_list)
|
||||
try {
|
||||
const sheet_names_list = workbook.SheetNames
|
||||
|
||||
return resolve(sheet_names_list)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e)
|
||||
|
||||
reader.onerror = function (ex) {
|
||||
console.log(ex)
|
||||
}
|
||||
};
|
||||
|
||||
reader.onerror = function (ex) {
|
||||
console.log(ex);
|
||||
};
|
||||
|
||||
reader.readAsBinaryString(this.selectedFile);
|
||||
reader.readAsBinaryString(this.selectedFile)
|
||||
})
|
||||
}
|
||||
|
||||
@ -283,7 +309,7 @@ export class MultiDatasetComponent implements OnInit {
|
||||
* example: LIB123.TABLE_123
|
||||
*/
|
||||
private isValidDatasetFormat(sheetName: string) {
|
||||
const regex = /^\w{1,8}\.\w{1,32}$/gmi
|
||||
const regex = /^\w{1,8}\.\w{1,32}$/gim
|
||||
const correctFormat = regex.test(sheetName)
|
||||
|
||||
return correctFormat
|
||||
@ -336,28 +362,40 @@ export class MultiDatasetComponent implements OnInit {
|
||||
}
|
||||
|
||||
datasetObject.cols = response.data.cols
|
||||
datasetObject.headerColumns = response.data.sasparams[0].COLHEADERS.split(',')
|
||||
datasetObject.headerColumns =
|
||||
response.data.sasparams[0].COLHEADERS.split(',')
|
||||
datasetObject.headerPks = response.data.sasparams[0].PK.split(' ')
|
||||
|
||||
if (datasetObject.headerColumns.indexOf('_____DELETE__THIS__RECORD_____') !== -1) {
|
||||
if (
|
||||
datasetObject.headerColumns.indexOf(
|
||||
'_____DELETE__THIS__RECORD_____'
|
||||
) !== -1
|
||||
) {
|
||||
datasetObject.headerColumns[
|
||||
datasetObject.headerColumns.indexOf('_____DELETE__THIS__RECORD_____')
|
||||
datasetObject.headerColumns.indexOf(
|
||||
'_____DELETE__THIS__RECORD_____'
|
||||
)
|
||||
] = 'Delete?'
|
||||
}
|
||||
|
||||
datasetObject.headerArray = datasetObject.headerColumns.slice(1)
|
||||
|
||||
if (response.data.sasparams[0].DTVARS !== '') {
|
||||
datasetObject.dateHeaders = response.data.sasparams[0].DTVARS.split(' ')
|
||||
datasetObject.dateHeaders =
|
||||
response.data.sasparams[0].DTVARS.split(' ')
|
||||
}
|
||||
if (response.data.sasparams[0].TMVARS !== '') {
|
||||
datasetObject.timeHeaders = response.data.sasparams[0].TMVARS.split(' ')
|
||||
datasetObject.timeHeaders =
|
||||
response.data.sasparams[0].TMVARS.split(' ')
|
||||
}
|
||||
if (response.data.sasparams[0].DTTMVARS !== '') {
|
||||
datasetObject.dateTimeHeaders = response.data.sasparams[0].DTTMVARS.split(' ')
|
||||
datasetObject.dateTimeHeaders =
|
||||
response.data.sasparams[0].DTTMVARS.split(' ')
|
||||
}
|
||||
if (response.data.xl_rules.length > 0) {
|
||||
datasetObject.xlRules = this.helperService.deepClone(response.data.xl_rules)
|
||||
datasetObject.xlRules = this.helperService.deepClone(
|
||||
response.data.xl_rules
|
||||
)
|
||||
}
|
||||
|
||||
datasetObject.dcValidator = new DcValidator(
|
||||
@ -368,7 +406,8 @@ export class MultiDatasetComponent implements OnInit {
|
||||
response.data.dqdata
|
||||
)
|
||||
|
||||
datasetObject.columnHeader = response.data.sasparams[0].COLHEADERS.split(',')
|
||||
datasetObject.columnHeader =
|
||||
response.data.sasparams[0].COLHEADERS.split(',')
|
||||
|
||||
datasetObjects.push(datasetObject)
|
||||
}
|
||||
@ -403,4 +442,4 @@ export interface ParsedDataset {
|
||||
status?: 'success' | 'error'
|
||||
active?: boolean
|
||||
parseResult: ParseResult
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,28 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Injectable } from '@angular/core'
|
||||
import * as XLSX from '@sheet/crypto'
|
||||
import { ExcelPasswordModalService, Result } from '../shared/excel-password-modal/excel-password-modal.service';
|
||||
import { EventService } from './event.service';
|
||||
import { isSpecialMissing } from '@sasjs/utils/input/validators';
|
||||
import { dateFormat, dateToUtcTime, dateToTime } from '../editor/utils/date.utils';
|
||||
import { excelDateToJSDate, getMissingHeaders } from '../editor/utils/grid.utils';
|
||||
import { isStringNumber, isStringDecimal } from '../editor/utils/types.utils';
|
||||
import SheetInfo from '../models/SheetInfo';
|
||||
import { blobToFile } from '../xlmap/utils/file.utils';
|
||||
import { ExcelRule } from '../models/TableData';
|
||||
import { DcValidator } from '../shared/dc-validator/dc-validator';
|
||||
import { LicenceService } from './licence.service';
|
||||
import { FileUploadEncoding } from '../models/FileUploadEncoding';
|
||||
import { FileUploader } from '../models/FileUploader.class';
|
||||
import {
|
||||
ExcelPasswordModalService,
|
||||
Result
|
||||
} from '../shared/excel-password-modal/excel-password-modal.service'
|
||||
import { EventService } from './event.service'
|
||||
import { isSpecialMissing } from '@sasjs/utils/input/validators'
|
||||
import {
|
||||
dateFormat,
|
||||
dateToUtcTime,
|
||||
dateToTime
|
||||
} from '../editor/utils/date.utils'
|
||||
import {
|
||||
excelDateToJSDate,
|
||||
getMissingHeaders
|
||||
} from '../editor/utils/grid.utils'
|
||||
import { isStringNumber, isStringDecimal } from '../editor/utils/types.utils'
|
||||
import SheetInfo from '../models/SheetInfo'
|
||||
import { blobToFile } from '../xlmap/utils/file.utils'
|
||||
import { ExcelRule } from '../models/TableData'
|
||||
import { DcValidator } from '../shared/dc-validator/dc-validator'
|
||||
import { LicenceService } from './licence.service'
|
||||
import { FileUploadEncoding } from '../models/FileUploadEncoding'
|
||||
import { FileUploader } from '../models/FileUploader.class'
|
||||
|
||||
/**
|
||||
* Used in combination with buffer
|
||||
@ -26,7 +36,7 @@ const Buffer = require('buffer/').Buffer
|
||||
type AOA = any[][]
|
||||
|
||||
export interface ParseParams {
|
||||
file: File,
|
||||
file: File
|
||||
dcValidator: DcValidator
|
||||
/**
|
||||
* Parse function will manipulate and return the uploader array which can be provided with files already in the queue
|
||||
@ -53,21 +63,20 @@ export interface ParseResult {
|
||||
*/
|
||||
headerShow?: string[]
|
||||
rangeSheetRes?: SheetInfo
|
||||
uploader: FileUploader,
|
||||
uploader: FileUploader
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SpreadsheetService {
|
||||
|
||||
private licenceState = this.licenceService.licenceState
|
||||
|
||||
constructor(
|
||||
private excelPasswordModalService: ExcelPasswordModalService,
|
||||
private eventService: EventService,
|
||||
private licenceService: LicenceService
|
||||
) { }
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Parses attached file and searches fo the matching data
|
||||
@ -93,7 +102,8 @@ export class SpreadsheetService {
|
||||
|
||||
if (!parseParams.encoding) parseParams.encoding = 'UTF-8'
|
||||
|
||||
if (onParseStateChange) onParseStateChange(`Loading ${filename} into the browser`)
|
||||
if (onParseStateChange)
|
||||
onParseStateChange(`Loading ${filename} into the browser`)
|
||||
|
||||
let foundData = {
|
||||
sheet: ''
|
||||
@ -181,7 +191,10 @@ export class SpreadsheetService {
|
||||
)
|
||||
|
||||
let csvArrayData: any[] = []
|
||||
const rangeSheetRes: SheetInfo = this.getRangeAndSheet(wb, parseParams)
|
||||
const rangeSheetRes: SheetInfo = this.getRangeAndSheet(
|
||||
wb,
|
||||
parseParams
|
||||
)
|
||||
missingHeaders = rangeSheetRes.missingHeaders
|
||||
|
||||
if (rangeSheetRes.foundData) {
|
||||
@ -189,7 +202,10 @@ export class SpreadsheetService {
|
||||
csvArrayHeadersMap = rangeSheetRes.csvArrayHeadersMap
|
||||
const ws: XLSX.WorkSheet = wb.Sheets[rangeSheetRes.sheetName]
|
||||
|
||||
if (onParseStateChange) onParseStateChange(`Table found on sheet ${rangeSheetRes.sheetName} on row ${rangeSheetRes.startRow}`)
|
||||
if (onParseStateChange)
|
||||
onParseStateChange(
|
||||
`Table found on sheet ${rangeSheetRes.sheetName} on row ${rangeSheetRes.startRow}`
|
||||
)
|
||||
|
||||
let startAddress = ''
|
||||
let endAddress = ''
|
||||
@ -231,7 +247,10 @@ export class SpreadsheetService {
|
||||
|
||||
rangeSheetRes.rangeAddress = `${startAddress}:${endAddress}`
|
||||
|
||||
if (onTableFoundEvent) onTableFoundEvent(`Sheet: ${rangeSheetRes.sheetName}\nRange: ${rangeSheetRes.rangeAddress}`)
|
||||
if (onTableFoundEvent)
|
||||
onTableFoundEvent(
|
||||
`Sheet: ${rangeSheetRes.sheetName}\nRange: ${rangeSheetRes.rangeAddress}`
|
||||
)
|
||||
} else {
|
||||
missingHeaders = rangeSheetRes.missingHeaders
|
||||
}
|
||||
@ -266,11 +285,19 @@ export class SpreadsheetService {
|
||||
parseParams.dateHeaders.length > 0 ||
|
||||
parseParams.timeHeaders.length > 0
|
||||
) {
|
||||
csvArrayData = this.updateDateTimeCols(csvArrayHeaders, csvArrayData, parseParams)
|
||||
csvArrayData = this.updateDateTimeCols(
|
||||
csvArrayHeaders,
|
||||
csvArrayData,
|
||||
parseParams
|
||||
)
|
||||
}
|
||||
|
||||
if (parseParams.xlRules.length > 0) {
|
||||
csvArrayData = this.updateXLRuleCols(csvArrayHeaders, csvArrayData, parseParams)
|
||||
csvArrayData = this.updateXLRuleCols(
|
||||
csvArrayHeaders,
|
||||
csvArrayData,
|
||||
parseParams
|
||||
)
|
||||
}
|
||||
|
||||
if (!isComplete) {
|
||||
@ -334,7 +361,8 @@ export class SpreadsheetService {
|
||||
const colRule = parseParams.dcValidator?.getRule(colName)
|
||||
|
||||
if (colRule?.type === 'numeric') {
|
||||
if (isSpecialMissing(col) && !col.includes('.')) col = '.' + col
|
||||
if (isSpecialMissing(col) && !col.includes('.'))
|
||||
col = '.' + col
|
||||
}
|
||||
|
||||
return col
|
||||
@ -366,7 +394,10 @@ export class SpreadsheetService {
|
||||
|
||||
if (parseParams.encoding === 'WLATIN1') {
|
||||
// WLATIN1
|
||||
let encoded = iconv.decode(Buffer.from(csvContentClean), 'CP-1252')
|
||||
let encoded = iconv.decode(
|
||||
Buffer.from(csvContentClean),
|
||||
'CP-1252'
|
||||
)
|
||||
blob = new Blob([encoded], { type: 'application/csv' })
|
||||
} else {
|
||||
// UTF-8
|
||||
@ -378,7 +409,9 @@ export class SpreadsheetService {
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return reject(`Table in the file is empty. Data found on sheet: ${foundData.sheet}`)
|
||||
return reject(
|
||||
`Table in the file is empty. Data found on sheet: ${foundData.sheet}`
|
||||
)
|
||||
}
|
||||
|
||||
return resolve({
|
||||
@ -392,7 +425,9 @@ export class SpreadsheetService {
|
||||
} else if (fileType.toLowerCase() === 'csv') {
|
||||
if (this.licenceState.value.submit_rows_limit !== Infinity) {
|
||||
uploader.queue.pop()
|
||||
return reject('Excel files only. To unlock CSV uploads, please contact support@datacontroller.io')
|
||||
return reject(
|
||||
'Excel files only. To unlock CSV uploads, please contact support@datacontroller.io'
|
||||
)
|
||||
}
|
||||
|
||||
if (parseParams.encoding === 'WLATIN1') {
|
||||
@ -437,7 +472,10 @@ export class SpreadsheetService {
|
||||
* @param wb Excel workbook
|
||||
* @returns {object: SheetInfo} an object which contains necessary information about workbook that which sheet contains required data and what's the range
|
||||
*/
|
||||
private getRangeAndSheet(wb: XLSX.WorkBook, parseParams: ParseParams): SheetInfo {
|
||||
private getRangeAndSheet(
|
||||
wb: XLSX.WorkBook,
|
||||
parseParams: ParseParams
|
||||
): SheetInfo {
|
||||
let data = []
|
||||
|
||||
let rangeStartRow: number = 0
|
||||
@ -757,7 +795,11 @@ export class SpreadsheetService {
|
||||
})
|
||||
}
|
||||
|
||||
private updateDateTimeCols(headers: any, data: any, parseParams: ParseParams) {
|
||||
private updateDateTimeCols(
|
||||
headers: any,
|
||||
data: any,
|
||||
parseParams: ParseParams
|
||||
) {
|
||||
if (parseParams.dateHeaders.length > 0) {
|
||||
let dateCols: number[] = []
|
||||
parseParams.dateHeaders.forEach((element) => {
|
||||
|
@ -1,44 +1,38 @@
|
||||
<ng-container *ngIf="options$ | async as options">
|
||||
<clr-modal
|
||||
[clrModalOpen]="options.open"
|
||||
[clrModalSize]="'md'"
|
||||
[clrModalClosable]="false"
|
||||
>
|
||||
<h3 class="modal-title center text-center color-darker-gray">
|
||||
Password Protected File
|
||||
</h3>
|
||||
<div class="modal-body d-flex clr-justify-content-center">
|
||||
<p class="m-0">Please enter password:</p>
|
||||
<input
|
||||
#filePasswordInput
|
||||
data-lpignore="true"
|
||||
autocomplete="off"
|
||||
id="filePasswordInput"
|
||||
type="text"
|
||||
class="clr-input disable-password-manager"
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<p *ngIf="options.error" class="m-0 color-red">
|
||||
Sorry that didn't work, try again.
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline"
|
||||
(click)="close()"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-success-outline"
|
||||
[disabled]="filePasswordInput.value.length < 1"
|
||||
(click)="
|
||||
close(filePasswordInput.value)
|
||||
"
|
||||
>
|
||||
Unlock
|
||||
</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
</ng-container>
|
||||
[clrModalOpen]="options.open"
|
||||
[clrModalSize]="'md'"
|
||||
[clrModalClosable]="false"
|
||||
>
|
||||
<h3 class="modal-title center text-center color-darker-gray">
|
||||
Password Protected File
|
||||
</h3>
|
||||
<div class="modal-body d-flex clr-justify-content-center">
|
||||
<p class="m-0">Please enter password:</p>
|
||||
<input
|
||||
#filePasswordInput
|
||||
data-lpignore="true"
|
||||
autocomplete="off"
|
||||
id="filePasswordInput"
|
||||
type="text"
|
||||
class="clr-input disable-password-manager"
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<p *ngIf="options.error" class="m-0 color-red">
|
||||
Sorry that didn't work, try again.
|
||||
</p>
|
||||
<button type="button" class="btn btn-sm btn-outline" (click)="close()">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-success-outline"
|
||||
[disabled]="filePasswordInput.value.length < 1"
|
||||
(click)="close(filePasswordInput.value)"
|
||||
>
|
||||
Unlock
|
||||
</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
</ng-container>
|
||||
|
@ -1,6 +1,9 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ExcelPasswordModalService, Options } from './excel-password-modal.service';
|
||||
import { Component } from '@angular/core'
|
||||
import { Observable } from 'rxjs'
|
||||
import {
|
||||
ExcelPasswordModalService,
|
||||
Options
|
||||
} from './excel-password-modal.service'
|
||||
|
||||
@Component({
|
||||
selector: 'app-excel-password-modal',
|
||||
@ -8,15 +11,15 @@ import { ExcelPasswordModalService, Options } from './excel-password-modal.servi
|
||||
styleUrl: './excel-password-modal.component.scss'
|
||||
})
|
||||
export class ExcelPasswordModalComponent {
|
||||
options$: Observable<Options>;
|
||||
options$: Observable<Options>
|
||||
|
||||
fileUnlockError: boolean = false
|
||||
|
||||
constructor(private excelPasswordModalService: ExcelPasswordModalService) {
|
||||
this.options$ = this.excelPasswordModalService.optionsSubject$;
|
||||
this.options$ = this.excelPasswordModalService.optionsSubject$
|
||||
}
|
||||
|
||||
close(password?: string) {
|
||||
this.excelPasswordModalService.close(password);
|
||||
this.excelPasswordModalService.close(password)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Subject, Observable } from 'rxjs';
|
||||
import { Injectable } from '@angular/core'
|
||||
import { Subject, Observable } from 'rxjs'
|
||||
|
||||
export interface Options extends OpenOptions {
|
||||
open: boolean
|
||||
@ -20,28 +20,26 @@ export class ExcelPasswordModalService {
|
||||
public optionsSubject$: Subject<Options> = new Subject()
|
||||
public resultChange$: Subject<Result> = new Subject()
|
||||
|
||||
constructor() {
|
||||
}
|
||||
constructor() {}
|
||||
|
||||
public open(openOptions?: OpenOptions): Observable<Result> {
|
||||
this.optionsSubject$.next({
|
||||
open: true,
|
||||
...openOptions
|
||||
});
|
||||
})
|
||||
|
||||
this.resultChange$ = new Subject<Result>();
|
||||
return this.resultChange$.asObservable();
|
||||
this.resultChange$ = new Subject<Result>()
|
||||
return this.resultChange$.asObservable()
|
||||
}
|
||||
|
||||
|
||||
close(password?: string) {
|
||||
this.optionsSubject$.next({
|
||||
open: false
|
||||
});
|
||||
})
|
||||
|
||||
this.resultChange$.next({
|
||||
password
|
||||
});
|
||||
this.resultChange$.complete();
|
||||
})
|
||||
this.resultChange$.complete()
|
||||
}
|
||||
}
|
||||
|
@ -124,10 +124,16 @@
|
||||
routerLinkActive="active"
|
||||
>Tables</a
|
||||
>
|
||||
<a clrVerticalNavLink routerLink="/home/excel-maps" routerLinkActive="active"
|
||||
<a
|
||||
clrVerticalNavLink
|
||||
routerLink="/home/excel-maps"
|
||||
routerLinkActive="active"
|
||||
>Excel Maps</a
|
||||
>
|
||||
<a clrVerticalNavLink routerLink="/home/multi-load" routerLinkActive="active"
|
||||
<a
|
||||
clrVerticalNavLink
|
||||
routerLink="/home/multi-load"
|
||||
routerLinkActive="active"
|
||||
>Multi Load</a
|
||||
>
|
||||
</clr-dropdown-menu>
|
||||
|
Loading…
Reference in New Issue
Block a user