feat(multi load): refactored range find function, unlocking excel with password is reusable #115

Merged
allan merged 15 commits from issue99 into main 2024-06-27 09:40:45 +00:00
4 changed files with 126 additions and 37 deletions
Showing only changes of commit cffeab813d - Show all commits

View File

@ -17,13 +17,13 @@ import '@cds/core/icon/register.js'
import { import {
ClarityIcons, ClarityIcons,
exclamationTriangleIcon, exclamationTriangleIcon,
fileIcon,
moonIcon, moonIcon,
sunIcon, sunIcon,
tableIcon,
trashIcon trashIcon
} from '@cds/core/icon' } from '@cds/core/icon'
ClarityIcons.addIcons(moonIcon, sunIcon, exclamationTriangleIcon, fileIcon, trashIcon) ClarityIcons.addIcons(moonIcon, sunIcon, exclamationTriangleIcon, tableIcon, trashIcon)
@Component({ @Component({
selector: 'my-app', selector: 'my-app',

View File

@ -40,16 +40,11 @@
[class.active]="dataset.active" [class.active]="dataset.active"
> >
<cds-icon <cds-icon
*ngIf="dataset.status === 'error'" *ngIf="!(dataset.datasource && dataset.parseResult)"
status="danger" status="danger"
shape="exclamation-circle" shape="exclamation-circle"
></cds-icon> ></cds-icon>
<cds-icon <cds-icon *ngIf="dataset.datasource && dataset.parseResult" shape="table"></cds-icon>
*ngIf="dataset.status === 'success'"
status="success"
shape="check-circle"
></cds-icon>
<cds-icon shape="file"></cds-icon>
{{ dataset.libds }} {{ dataset.libds }}
</button> </button>
</clr-tree-node> </clr-tree-node>
@ -75,7 +70,7 @@
status="success" status="success"
shape="check-circle" shape="check-circle"
></cds-icon> ></cds-icon>
<cds-icon shape="file"></cds-icon> <cds-icon shape="table"></cds-icon>
{{ dataset.libds }} {{ dataset.libds }}
</button> </button>
</clr-tree-node> </clr-tree-node>
@ -114,14 +109,19 @@
<div class="d-flex clr-justify-content-center mt-15"> <div class="d-flex clr-justify-content-center mt-15">
<div class="dataset-input-wrapper"> <div class="dataset-input-wrapper">
<p cds-text="secondary regular" class="mb-20"> <p cds-text="secondary regular" class="mb-20">
Selected file: <strong>{{ selectedFile?.name }}</strong> Selected file: <strong>{{ selectedFile.name }}</strong>
<cds-icon (click)="onDiscardFile()" shape="trash" status="danger" class="ml-5 cursor-pointer"></cds-icon> <clr-tooltip>
<cds-icon clrTooltipTrigger (click)="onDiscardFile()" shape="trash" status="danger" class="ml-5 cursor-pointer"></cds-icon>
<clr-tooltip-content>
Discard the file
</clr-tooltip-content>
</clr-tooltip>
</p> </p>
<p cds-text="secondary regular" class="mb-15"> <p cds-text="secondary regular" class="mb-15">
Paste or type the list of datasets to upload: Paste or type the list of datasets to upload:
</p> </p>
<clr-control-helper class="mb-5">Each row is one dataset. We automatically detected some tables by the sheetname.</clr-control-helper> <clr-control-helper class="mb-5">Each row is one dataset. We will automatically detect tables by the sheetname and populate if any.</clr-control-helper>
<hot-table <hot-table
hotId="hotInstanceUserDataset" hotId="hotInstanceUserDataset"
@ -173,17 +173,31 @@
<div> <div>
<p cds-text="secondary regular" class="mb-10"> <p cds-text="secondary regular" class="mb-10">
Found in range: Found in range:
<strong
>"{{ <ng-container *ngIf="activeParsedDataset.parseResult">
activeParsedDataset.parseResult.rangeSheetRes?.sheetName <strong
}}"!{{ >"{{
activeParsedDataset.parseResult.rangeSheetRes?.rangeAddress activeParsedDataset.parseResult.rangeSheetRes?.sheetName
}}</strong }}"!{{
> activeParsedDataset.parseResult.rangeSheetRes?.rangeAddress
}}</strong
>
</ng-container>
<ng-container *ngIf="!activeParsedDataset.parseResult">
<strong>No data found</strong>
</ng-container>
</p> </p>
<p cds-text="secondary regular"> <p cds-text="secondary regular">
Matched with dataset: Matched with dataset:
<strong>{{ activeParsedDataset.libds }}</strong> <strong>
<clr-tooltip>
<a clrTooltipTrigger [routerLink]="'/editor/' + activeParsedDataset.libds">{{ activeParsedDataset.libds }}</a>
<clr-tooltip-content [clrPosition]="'top-right'" [clrSize]="'sm'">
Click to edit the table
</clr-tooltip-content>
</clr-tooltip>
</strong>
</p> </p>
</div> </div>
@ -194,6 +208,7 @@
clrToggle clrToggle
[(ngModel)]="activeParsedDataset.includeInSubmission" [(ngModel)]="activeParsedDataset.includeInSubmission"
name="options" name="options"
[disabled]="!(activeParsedDataset.datasource && activeParsedDataset.parseResult)"
required required
value="option1" value="option1"
/> />
@ -238,7 +253,14 @@
</p> </p>
<p cds-text="secondary regular" class="mb-10"> <p cds-text="secondary regular" class="mb-10">
Matched with dataset: Matched with dataset:
<strong>{{ activeSubmittedDataset.libds }}</strong> <strong>
<clr-tooltip>
<a clrTooltipTrigger [routerLink]="'/editor/' + activeSubmittedDataset.libds">{{ activeSubmittedDataset.libds }}</a>
<clr-tooltip-content [clrPosition]="'top-right'" [clrSize]="'sm'">
Click to edit the table
</clr-tooltip-content>
</clr-tooltip>
</strong>
</p> </p>
<p cds-text="secondary regular" class="mb-10"> <p cds-text="secondary regular" class="mb-10">
Status: Status:
@ -291,11 +313,18 @@
</div> </div>
</div> </div>
<clr-modal [(clrModalOpen)]="showSubmitReasonModal"> <clr-modal [(clrModalOpen)]="showSubmitReasonModal" [clrModalClosable]="false">
<h3 class="modal-title"> <h3 class="modal-title">
Submit for approval {{ parsedDatasets.length }} tables Submit {{ tablesToSubmit.length }} {{ tablesToSubmit.length === 1 ? 'table' : 'tables' }} for approval
</h3> </h3>
<div class="modal-body"> <div class="modal-body">
<p *ngIf="licenceState.value.submit_rows_limit !== Infinity" cds-text="body" class="licence-limit-notice mt-0 mb-15">
Due to current licence, only
{{ licenceState.value.submit_rows_limit }} rows in each file will
be submitted. To remove the restriction, contact
support&#64;datacontroller.io.
</p>
<div class="text-area-full-width"> <div class="text-area-full-width">
<label for="formFields_8" class="mb-5 d-block">Message</label> <label for="formFields_8" class="mb-5 d-block">Message</label>
<textarea <textarea

View File

@ -43,4 +43,8 @@
.dataset-selection-actions { .dataset-selection-actions {
border-top: 1px solid #d3d3d3; border-top: 1px solid #d3d3d3;
}
.licence-limit-notice {
color: var(--cds-alias-status-warning-dark);
} }

View File

@ -27,6 +27,7 @@ import { HotTableRegisterer } from '@handsontable/angular'
import { EditorsStageDataSASResponse } from '../models/sas/editors-stagedata.model' import { EditorsStageDataSASResponse } from '../models/sas/editors-stagedata.model'
import { CellChange, ChangeSource } from 'handsontable/common' import { CellChange, ChangeSource } from 'handsontable/common'
import { baseAfterGetColHeader } from '../shared/utils/hot.utils' import { baseAfterGetColHeader } from '../shared/utils/hot.utils'
import { ColumnSettings } from 'handsontable/settings'
@Component({ @Component({
selector: 'app-multi-dataset', selector: 'app-multi-dataset',
@ -37,6 +38,7 @@ export class MultiDatasetComponent implements OnInit {
@HostBinding('class.content-container') contentContainerClass = true @HostBinding('class.content-container') contentContainerClass = true
public licenceState = this.licenceService.licenceState public licenceState = this.licenceService.licenceState
public Infinity = Infinity
public hotTableLicenseKey: string | undefined = undefined public hotTableLicenseKey: string | undefined = undefined
public hotTableMaxRows = public hotTableMaxRows =
@ -83,6 +85,18 @@ export class MultiDatasetComponent implements OnInit {
['', ''], ['', ''],
['', ''] ['', '']
], ],
columns: [
{
type: 'autocomplete',
filter: false,
source: []
},
{
type: 'autocomplete',
filter: false,
source: []
}
],
width: '100%', width: '100%',
height: '305px', height: '305px',
className: ['htDark'], className: ['htDark'],
@ -133,6 +147,12 @@ export class MultiDatasetComponent implements OnInit {
public getFromGlobals() { public getFromGlobals() {
this.libsAndTables = globals.editor.libsAndTables this.libsAndTables = globals.editor.libsAndTables
const libs: string[] = Object.keys(this.libsAndTables)
if (this.hotUserDatasets?.columns) {
(this.hotUserDatasets.columns as ColumnSettings[])[0].source = libs;
}
} }
onFileChange(event: any) { onFileChange(event: any) {
@ -241,13 +261,23 @@ export class MultiDatasetComponent implements OnInit {
}) })
.catch((error: string) => { .catch((error: string) => {
console.warn('Parsing excel file error.', error) console.warn('Parsing excel file error.', error)
this.parsedDatasets.push({
libds: datasetObject.libds,
includeInSubmission: false,
datasetInfo: datasetObject
})
}) })
} }
}) })
} }
onSubmitAll() { onSubmitAll() {
this.showSubmitReasonModal = true if (this.tablesToSubmit.length) {
this.showSubmitReasonModal = true
} else {
this.eventService.showInfoModal('No tables to submit', 'Please include at least one table to proceed.')
}
} }
onDiscard() { onDiscard() {
@ -264,7 +294,7 @@ export class MultiDatasetComponent implements OnInit {
if (this.activeParsedDataset) { if (this.activeParsedDataset) {
this.hotInstance.updateSettings({ this.hotInstance.updateSettings({
data: this.activeParsedDataset.datasource, data: this.activeParsedDataset.datasource || [],
colHeaders: this.activeParsedDataset.datasetInfo.headerColumns, colHeaders: this.activeParsedDataset.datasetInfo.headerColumns,
columns: this.activeParsedDataset.datasetInfo.dcValidator?.getRules(), columns: this.activeParsedDataset.datasetInfo.dcValidator?.getRules(),
readOnly: true, readOnly: true,
@ -301,6 +331,8 @@ export class MultiDatasetComponent implements OnInit {
this.markUnmatchedRows(row) this.markUnmatchedRows(row)
} }
this.dynamicCellValidations()
this.hotInstanceUserDataset.render() this.hotInstanceUserDataset.render()
} }
}) })
@ -315,6 +347,19 @@ export class MultiDatasetComponent implements OnInit {
}) })
} }
dynamicCellValidations() {
const hotData = this.hotInstanceUserDataset.getData()
hotData.forEach((row, rowIndex) => {
const library = row[0]
if (library && library.length) {
const tables = this.libsAndTables[library]
this.hotInstanceUserDataset.setCellMeta(rowIndex, 1, 'source', tables)
}
})
}
markUnmatchedRows(row: number) { markUnmatchedRows(row: number) {
const dataAtRow = this.hotInstanceUserDataset.getDataAtRow(row) as number[] const dataAtRow = this.hotInstanceUserDataset.getDataAtRow(row) as number[]
const dataset = `${dataAtRow[0]}.${dataAtRow[1]}` const dataset = `${dataAtRow[0]}.${dataAtRow[1]}`
@ -381,14 +426,16 @@ export class MultiDatasetComponent implements OnInit {
this.userInputDatasets = '' this.userInputDatasets = ''
sheetNames.forEach((sheetName: string, index: number) => { sheetNames.forEach((sheetName: string, index: number) => {
const trimmedSheetname = sheetName.trim()
if ( if (
this.isValidDatasetFormat(sheetName) && this.isValidDatasetFormat(trimmedSheetname) &&
this.isValidDatasetReference(sheetName) this.isValidDatasetReference(trimmedSheetname)
) { ) {
this.matchedDatasets.push(sheetName) this.matchedDatasets.push(trimmedSheetname)
} else { } else {
console.warn( console.warn(
`Sheet name: ${sheetName} is not an actual dataset reference.` `Sheet name: ${trimmedSheetname} is not an actual dataset reference.`
) )
} }
}) })
@ -417,6 +464,7 @@ export class MultiDatasetComponent implements OnInit {
} }
this.hotInstanceUserDataset.updateData(hotReadyData) this.hotInstanceUserDataset.updateData(hotReadyData)
this.dynamicCellValidations()
} }
onParsedDatasetClick(parsedDataset: ParsedDataset) { onParsedDatasetClick(parsedDataset: ParsedDataset) {
@ -460,6 +508,10 @@ export class MultiDatasetComponent implements OnInit {
} }
} }
public get tablesToSubmit(): ParsedDataset[] {
return this.parsedDatasets.filter(dataset => dataset.datasource && dataset.parseResult && dataset.includeInSubmission)
}
public downloadFile( public downloadFile(
response: any response: any
) { ) {
@ -508,6 +560,8 @@ export class MultiDatasetComponent implements OnInit {
let requestsResults: SubmittedDatasetResult[] = explicitDatasets ? this.submittedDatasets : [] let requestsResults: SubmittedDatasetResult[] = explicitDatasets ? this.submittedDatasets : []
for (let table of this.parsedDatasets) { for (let table of this.parsedDatasets) {
// Skip the table if no data inside
if (!table.parseResult || !table.datasource) continue
// Skip the table if toggle switch is off // Skip the table if toggle switch is off
if (!table.includeInSubmission) continue if (!table.includeInSubmission) continue
// Skip the table if datasets is present and this table not defined in it // Skip the table if datasets is present and this table not defined in it
@ -544,15 +598,13 @@ export class MultiDatasetComponent implements OnInit {
this.licenceState.value.submit_rows_limit this.licenceState.value.submit_rows_limit
) )
console.log('submitData', submitData)
let error let error
let success let success
await this.sasStoreService await this.sasStoreService
.updateTable( .updateTable(
updateParams, updateParams,
table.datasource, submitData,
'SASControlTable', 'SASControlTable',
'editors/stagedata', 'editors/stagedata',
table.datasetInfo.data.$sasdata, table.datasetInfo.data.$sasdata,
@ -579,7 +631,7 @@ export class MultiDatasetComponent implements OnInit {
if (explicitDatasets) { if (explicitDatasets) {
const existingResultIndex = requestsResults.findIndex(result => result.libds === table.libds) const existingResultIndex = requestsResults.findIndex(result => result.libds === table.libds)
if (existingResultIndex) { if (existingResultIndex > -1) {
requestsResults[existingResultIndex] = requestResult requestsResults[existingResultIndex] = requestResult
} else { } else {
requestsResults.push(requestResult) requestsResults.push(requestResult)
@ -598,7 +650,11 @@ export class MultiDatasetComponent implements OnInit {
async reSubmitTable(activeSubmittedDataset: SubmittedDatasetResult) { async reSubmitTable(activeSubmittedDataset: SubmittedDatasetResult) {
// Submit only particular table // Submit only particular table
this.submitTables([activeSubmittedDataset.libds]) await this.submitTables([activeSubmittedDataset.libds])
// Activate new resubmitted table
const newSubmittedDataset = this.submittedDatasets.find(sd => sd.libds === activeSubmittedDataset.libds)
if (newSubmittedDataset) newSubmittedDataset.active = true
} }
/** /**
@ -799,9 +855,9 @@ export interface DatasetsObject extends EditorsGetDataServiceResponse {
export interface ParsedDataset { export interface ParsedDataset {
libds: string libds: string
parseResult: ParseResult parseResult?: ParseResult
datasetInfo: DatasetsObject datasetInfo: DatasetsObject
datasource: any[] datasource?: any[]
includeInSubmission: boolean includeInSubmission: boolean
status?: 'success' | 'error' status?: 'success' | 'error'
active?: boolean active?: boolean