feat(multi load): refactored range find function, unlocking excel with password is reusable #115
@ -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',
|
||||||
|
@ -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,6 +173,8 @@
|
|||||||
<div>
|
<div>
|
||||||
<p cds-text="secondary regular" class="mb-10">
|
<p cds-text="secondary regular" class="mb-10">
|
||||||
Found in range:
|
Found in range:
|
||||||
|
|
||||||
|
<ng-container *ngIf="activeParsedDataset.parseResult">
|
||||||
<strong
|
<strong
|
||||||
>"{{
|
>"{{
|
||||||
activeParsedDataset.parseResult.rangeSheetRes?.sheetName
|
activeParsedDataset.parseResult.rangeSheetRes?.sheetName
|
||||||
@ -180,10 +182,22 @@
|
|||||||
activeParsedDataset.parseResult.rangeSheetRes?.rangeAddress
|
activeParsedDataset.parseResult.rangeSheetRes?.rangeAddress
|
||||||
}}</strong
|
}}</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@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
|
||||||
|
@ -44,3 +44,7 @@
|
|||||||
.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);
|
||||||
|
}
|
@ -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() {
|
||||||
|
if (this.tablesToSubmit.length) {
|
||||||
this.showSubmitReasonModal = true
|
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
|
||||||
|
Loading…
Reference in New Issue
Block a user