Skip to content

Commit 04ade83

Browse files
committed
Merge branch 'bugfix/446462_upload_large_files' into 'develop'
bugfix/446462_upload_large_files See merge request upm-inesdata/inesdata-connector-interface!39
2 parents 9f85813 + 92cfabb commit 04ade83

29 files changed

+8088
-9324
lines changed

package-lock.json

Lines changed: 7585 additions & 8882 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@
2727
"@angular/router": "^17.1.2",
2828
"@auth0/angular-jwt": "^5.2.0",
2929
"@ckeditor/ckeditor5-angular": "^7.0.1",
30-
"@ckeditor/ckeditor5-build-classic": "^41.4.2",
31-
"@ckeditor/ckeditor5-core": "^41.4.2",
32-
"@ckeditor/ckeditor5-engine": "^41.4.2",
33-
"@ckeditor/ckeditor5-utils": "^41.4.2",
34-
"@ckeditor/ckeditor5-watchdog": "^41.4.2",
30+
"@ckeditor/ckeditor5-build-classic": "^43.1.0",
31+
"@ckeditor/ckeditor5-core": "^43.1.0",
32+
"@ckeditor/ckeditor5-engine": "^43.1.0",
33+
"@ckeditor/ckeditor5-utils": "^43.1.0",
34+
"@ckeditor/ckeditor5-watchdog": "^43.1.0",
3535
"@jsonforms/angular": "^3.2.1",
3636
"@jsonforms/angular-material": "^3.2.1",
3737
"@jsonforms/core": "^3.2.1",

src/app/pages/assets/asset-create/asset-create.component.html

Lines changed: 219 additions & 215 deletions
Large diffs are not rendered by default.

src/app/pages/assets/asset-create/asset-create.component.scss

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,16 @@ mat-card-actions {
5959
:host ::ng-deep .mat-expansion-indicator {
6060
display: none;
6161
}
62+
63+
.toggle {
64+
margin-bottom: 22px;
65+
--mat-mdc-form-field-floating-label-scale: 0.75;
66+
display: inline-flex;
67+
flex-direction: column;
68+
min-width: 0;
69+
text-align: left;
70+
}
71+
72+
:host ::ng-deep .toggle .mdc-form-field{
73+
font-size: 16px;
74+
}

src/app/pages/assets/asset-create/asset-create.component.ts

Lines changed: 73 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Component, Inject, OnInit } from '@angular/core';
2-
import { HttpDataAddress, DataAddress, AssetInput } from '@think-it-labs/edc-connector-client';
3-
import { MatDialogRef } from "@angular/material/dialog";
2+
import { HttpDataAddress, DataAddress } from '@think-it-labs/edc-connector-client';
43
import { JsonDoc } from "../../../shared/models/json-doc";
54
import { StorageType } from "../../../shared/models/storage-type";
65
import { AmazonS3DataAddress } from "../../../shared/models/amazon-s3-data-address";
@@ -89,7 +88,7 @@ export class AssetCreateComponent implements OnInit {
8988
type: 'InesDataStore'
9089
};
9190

92-
assetType:any;
91+
assetType: any;
9392
assetTypes = Object.entries(ASSET_TYPES);
9493
defaultForms: JsonFormData[]
9594
selectedForms: JsonFormData[]
@@ -101,7 +100,7 @@ export class AssetCreateComponent implements OnInit {
101100
config = CKEDITOR_CONFIG
102101
selectedAssetTypeVocabularies: Vocabulary[]
103102

104-
urlPattern: RegExp = /^(file|ftp|http|https|imap|irc|nntp|acap|icap|mtqp|wss):\/\/(localhost|([a-z\d]([a-z\d-]*[a-z\d])*)|(([a-z\d]([a-z\d-]*[a-z\d])*)\.)+[a-z]{2,}|((\d{1,3}\.){3}\d{1,3}))(:\d+)?(\/[-a-z\d%_.~+]*)*(\?[;&a-z\d%_.~+=-]*)?(\#[-a-z\d_]*)?$/i;
103+
urlPattern: RegExp = /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z]{2,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)$/;
105104

106105
private fetch$ = new BehaviorSubject(null);
107106

@@ -122,7 +121,7 @@ export class AssetCreateComponent implements OnInit {
122121
}
123122
if (this.selectedVocabularies?.length > 0) {
124123
this.selectedVocabularies.forEach(s => {
125-
if(this.selectedAssetTypeVocabularies.find(satv=> satv['@id'] === s['@id'])){
124+
if (this.selectedAssetTypeVocabularies.find(satv => satv['@id'] === s['@id'])) {
126125
this.initVocabularyForm(s, false)
127126
}
128127
})
@@ -168,7 +167,7 @@ export class AssetCreateComponent implements OnInit {
168167
const forms: JsonFormData[] = [...this.defaultForms, ...this.selectedForms]
169168

170169
let assetDataProperty: any = {}
171-
forms.forEach(async f=>{
170+
forms.forEach(async f => {
172171
if (f.schema && f.schema.hasOwnProperty("@context")) {
173172
// Add context if it is provided in the Json Schema
174173
const jsonSchema: JsonDoc = f.schema as JsonDoc;
@@ -197,7 +196,7 @@ export class AssetCreateComponent implements OnInit {
197196
dataAddress = this.amazonS3DataAddress;
198197
} else if (this.storageTypeId === DATA_ADDRESS_TYPES.httpData) {
199198
dataAddress = this.httpDataAddress;
200-
} else if (this.storageTypeId === DATA_ADDRESS_TYPES.inesDataStore) {
199+
} else if (this.storageTypeId === DATA_ADDRESS_TYPES.inesDataStore) {
201200
dataAddress = this.inesDataStoreAddress;
202201
} else {
203202
this.notificationService.showError("Incorrect destination value");
@@ -213,6 +212,7 @@ export class AssetCreateComponent implements OnInit {
213212
};
214213

215214
if (this.storageTypeId === DATA_ADDRESS_TYPES.inesDataStore && this.inesDataStoreAddress?.file) {
215+
this.loadingService.showLoading('Processing the file...');
216216
const file = this.inesDataStoreAddress?.file;
217217

218218
const chunkSize = 1024 * 1024;
@@ -229,7 +229,7 @@ export class AssetCreateComponent implements OnInit {
229229
assetInput.blob = new Blob(chunks);
230230
}
231231

232-
this.createAsset(assetInput)
232+
await this.createAsset(assetInput)
233233
}
234234
addInfoProperties(properties: JsonDoc) {
235235
// Add default information
@@ -245,7 +245,7 @@ export class AssetCreateComponent implements OnInit {
245245
this.addKeywords(properties);
246246
}
247247

248-
addKeywords(properties: JsonDoc){
248+
addKeywords(properties: JsonDoc) {
249249
const parsedKeywords: string[] = [];
250250
this.keywords.split(",").forEach(keyword => parsedKeywords.push(keyword.trim()));
251251
properties["dcat:keyword"] = parsedKeywords;
@@ -286,7 +286,7 @@ export class AssetCreateComponent implements OnInit {
286286
if (!this.id || !this.storageTypeId || !this.name || !this.version || !this.description || !this.keywords || !this.shortDescription || !this.assetType) {
287287
return false;
288288
} else {
289-
if (this.storageTypeId === DATA_ADDRESS_TYPES.httpData && (!this.httpDataAddress.name || !this.httpDataAddress.baseUrl || !this.validateUrl())) {
289+
if (this.storageTypeId === DATA_ADDRESS_TYPES.httpData && (!this.httpDataAddress.name || !this.httpDataAddress.baseUrl || !this.validateUrl())) {
290290
return false;
291291
}
292292
if (this.storageTypeId === DATA_ADDRESS_TYPES.amazonS3 && !this.amazonS3DataAddress.region) {
@@ -328,7 +328,7 @@ export class AssetCreateComponent implements OnInit {
328328

329329
if (this.selectedVocabularies.length > 0) {
330330
this.selectedVocabularies.forEach(s => {
331-
if(this.selectedAssetTypeVocabularies.find(satv=> satv['@id'] === s['@id'])){
331+
if (this.selectedAssetTypeVocabularies.find(satv => satv['@id'] === s['@id'])) {
332332
this.initVocabularyForm(s, false)
333333
}
334334
})
@@ -339,24 +339,24 @@ export class AssetCreateComponent implements OnInit {
339339
* Transform to text asset type value
340340
* @returns asset type text
341341
*/
342-
getAssetTypeText(){
343-
return this.assetType?ASSET_TYPES[this.assetType as keyof typeof ASSET_TYPES]:'';
342+
getAssetTypeText() {
343+
return this.assetType ? ASSET_TYPES[this.assetType as keyof typeof ASSET_TYPES] : '';
344344
}
345345

346-
setFiles(event:File[]){
347-
if(event?.length>0){
346+
setFiles(event: File[]) {
347+
if (event?.length > 0) {
348348
this.inesDataStoreAddress.file = event[0]
349-
}else{
349+
} else {
350350
delete this.inesDataStoreAddress.file
351351
}
352352
}
353353

354-
onSelectionChangeVocabulary(){
354+
onSelectionChangeVocabulary() {
355355
this.selectedForms = []
356356

357357
if (this.selectedVocabularies.length > 0) {
358358
this.selectedVocabularies.forEach(s => {
359-
if(this.selectedAssetTypeVocabularies.find(satv=> satv['@id'] === s['@id'])){
359+
if (this.selectedAssetTypeVocabularies.find(satv => satv['@id'] === s['@id'])) {
360360
this.initVocabularyForm(s, false)
361361
}
362362
})
@@ -366,46 +366,76 @@ export class AssetCreateComponent implements OnInit {
366366
validateUrl(): boolean {
367367
const regex = new RegExp(this.urlPattern);
368368
return regex.test(this.httpDataAddress.baseUrl);
369-
}
369+
}
370370

371371

372-
private createAsset(asset: AssetInput){
373-
const newAsset = asset;
374-
if (newAsset) {
375-
if (newAsset.dataAddress.type !== DATA_ADDRESS_TYPES.inesDataStore) {
376-
this.assetService.createAsset(newAsset).subscribe({
377-
next: () => this.fetch$.next(null),
378-
error: err => this.showError(err, "This asset cannot be created"),
379-
complete: () => {
380-
this.navigateToAsset()
381-
this.notificationService.showInfo("Successfully created");
382-
this.loadingService.hideLoading();
383-
}
384-
})
385-
} else {
386-
this.assetService.createStorageAsset(newAsset).subscribe({
387-
next: () => this.fetch$.next(null),
388-
error: err => this.showError(err, "This asset cannot be created"),
389-
complete: () => {
390-
this.navigateToAsset()
391-
this.notificationService.showInfo("Successfully created");
392-
this.loadingService.hideLoading();
372+
async createAsset(assetInput: any) {
373+
if (this.storageTypeId === DATA_ADDRESS_TYPES.inesDataStore && this.inesDataStoreAddress.file) {
374+
const file = this.inesDataStoreAddress.file;
375+
const chunkSize = 50 * 1024 * 1024; // 50 MB
376+
const totalChunks = Math.ceil(file.size / chunkSize);
377+
const fileName = file.name;
378+
const maxRetries = 3;
379+
380+
for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
381+
const start = chunkIndex * chunkSize;
382+
const chunk = file.slice(start, start + chunkSize);
383+
384+
let attempt = 0;
385+
let success = false;
386+
387+
const progressPercentage = Math.floor(((chunkIndex + 1) / totalChunks) * 100);
388+
389+
while (attempt < maxRetries && !success) {
390+
try {
391+
this.loadingService.updateMessage(`Uploading file: ${progressPercentage}% completed`);
392+
393+
await this.assetService.uploadChunk(assetInput, chunk, fileName, chunkIndex, totalChunks);
394+
success = true;
395+
} catch (error) {
396+
attempt++;
397+
if (attempt >= maxRetries) {
398+
this.loadingService.hideLoading();
399+
this.notificationService.showError(`Error uploading chunk ${chunkIndex + 1}. Maximum retries reached.`);
400+
return;
401+
}
393402
}
394-
})
403+
}
395404
}
396405

406+
try {
407+
await this.assetService.finalizeUpload(assetInput, fileName);
408+
this.loadingService.hideLoading();
409+
this.notificationService.showInfo('Asset created successfully');
410+
this.navigateToAsset();
411+
} catch (error: any) {
412+
this.loadingService.hideLoading();
413+
this.notificationService.showError('Error finalizing the asset creation: ' + error.error[0].message);
414+
}
415+
} else {
416+
this.assetService.createAsset(assetInput).subscribe({
417+
next: () => this.fetch$.next(null),
418+
error: (err) => {
419+
this.loadingService.hideLoading();
420+
this.showError(err, "Error creating the asset: " + err.error[0].message);
421+
},
422+
complete: () => {
423+
this.loadingService.hideLoading();
424+
this.notificationService.showInfo('Asset created successfully');
425+
this.navigateToAsset();
426+
},
427+
});
397428
}
398429
}
399430

400431

401-
402432
private showError(error: string, errorMessage: string) {
403433
this.notificationService.showError(errorMessage);
404434
console.error(error);
405435
this.loadingService.hideLoading();
406436
}
407437

408-
navigateToAsset(){
438+
navigateToAsset() {
409439
this.router.navigate(['assets'])
410440
}
411441
}

src/app/pages/assets/asset-viewer/asset-viewer.component.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
import { Component, OnInit } from '@angular/core';
22
import { BehaviorSubject } from 'rxjs';
3-
import { first } from 'rxjs/operators';
43
import { MatDialog } from '@angular/material/dialog';
5-
import { AssetInput, Asset } from "../../../shared/models/edc-connector-entities";
4+
import { Asset } from "../../../shared/models/edc-connector-entities";
65
import { AssetService } from "../../../shared/services/asset.service";
76
import { ConfirmationDialogComponent, ConfirmDialogModel } from "../../../shared/components/confirmation-dialog/confirmation-dialog.component";
87
import { NotificationService } from "../../../shared/services/notification.service";
9-
import { CONTEXTS, DATA_ADDRESS_TYPES } from 'src/app/shared/utils/app.constants';
108
import { PageEvent } from '@angular/material/paginator';
11-
import { EDC_CONTEXT, QuerySpec, DataAddress } from '@think-it-labs/edc-connector-client';
12-
import { ContractOffersViewerComponent } from '../../catalog/contract-offers-viewer/contract-offers-viewer.component';
9+
import { EDC_CONTEXT, QuerySpec } from '@think-it-labs/edc-connector-client';
1310
import { compact } from 'jsonld';
1411
import { Router } from '@angular/router';
1512

@@ -67,7 +64,7 @@ export class AssetViewerComponent implements OnInit {
6764
if (res) {
6865
this.assetService.removeAsset(asset.id).subscribe({
6966
next: () => this.fetch$.next(null),
70-
error: err => this.showError(err, "This asset cannot be deleted"),
67+
error: err => this.showError(err, "This asset cannot be deleted: " + err.error[0].message),
7168
complete: () => {
7269
this.countAssets();
7370
this.loadAssets(this.currentPage);

src/app/pages/catalog/catalog-browser/catalog-browser.component.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
import { Component, Inject, OnInit } from '@angular/core';
1+
import { Component, OnInit } from '@angular/core';
22
import { MatDialog } from '@angular/material/dialog';
33
import { CatalogBrowserService } from "../../../shared/services/catalog-browser.service";
44
import { DataOffer } from 'src/app/shared/models/data-offer';
5-
import { ContractOffersViewerComponent } from '../contract-offers-viewer/contract-offers-viewer.component';
6-
import { Policy } from 'src/app/shared/models/edc-connector-entities';
75
import { PageEvent } from '@angular/material/paginator';
86
import { QuerySpec } from '@think-it-labs/edc-connector-client';
9-
import { ContractOffer } from 'src/app/shared/models/contract-offer';
107
import { Router } from '@angular/router';
118

129

src/app/pages/catalog/contract-offers-viewer/contract-offers-viewer.component.scss

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ mat-list-item:hover .property-title,
2121
.property-value {
2222
font-size: 14px;
2323
color: #d7d7d7;
24+
overflow-wrap: anywhere;
25+
white-space: pre-wrap;
2426
}
2527

2628
.property-icon {
@@ -102,11 +104,6 @@ ul {
102104
padding: 20px;
103105
}
104106

105-
.property-value {
106-
overflow-wrap: anywhere;
107-
white-space: pre-wrap;
108-
}
109-
110107
.grey {
111108
margin-bottom: 15px;
112109
}

src/app/pages/catalog/contract-offers-viewer/contract-offers-viewer.component.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Component, Inject } from '@angular/core';
2-
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
2+
import { MatDialog } from '@angular/material/dialog';
33
import { TransferProcessStates } from "../../../shared/models/transfer-process-states";
44
import { NegotiationResult } from "../../../shared/models/negotiation-result";
55
import { ContractNegotiation, ContractNegotiationRequest, Policy } from "../../../shared/models/edc-connector-entities";
@@ -8,7 +8,6 @@ import { NotificationService } from 'src/app/shared/services/notification.servic
88
import { StorageType } from 'src/app/shared/models/storage-type';
99
import { PolicyCard } from '../../../shared/models/policy/policy-card';
1010
import { DATA_ADDRESS_TYPES } from '../../../shared/utils/app.constants';
11-
import { ContractOffer } from 'src/app/shared/models/contract-offer';
1211
import { PolicyCardBuilder } from 'src/app/shared/models/policy/policy-card-builder';
1312
import { JsonDialogData } from '../../json-dialog/json-dialog/json-dialog.data';
1413
import { JsonDialogComponent } from '../../json-dialog/json-dialog/json-dialog.component'
@@ -218,9 +217,13 @@ export class ContractOffersViewerComponent {
218217
if (finishedNegotiationStates.includes(updatedNegotiation.state)) {
219218
let offerId = negotiation.offerId;
220219
this.runningNegotiations.delete(offerId);
220+
const errorDetail = updatedNegotiation.optionalValue("edc", "errorDetail");
221221
if (updatedNegotiation["state"] === "VERIFIED" || updatedNegotiation["state"] === "FINALIZED") {
222222
this.finishedNegotiations.set(offerId, updatedNegotiation);
223223
this.notificationService.showInfo("Contract Negotiation complete!");
224+
} else if (updatedNegotiation["state"] === "TERMINATED" && typeof errorDetail === 'string' && errorDetail.includes("Contract offer is not valid")) {
225+
this.finishedNegotiations.set(offerId, updatedNegotiation);
226+
this.notificationService.showError("Contract offer is not valid.");
224227
}
225228
}
226229

src/app/pages/contract-definitions/contract-definition-new/contract-definition-new.component.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { Component, Inject, OnInit } from '@angular/core';
2-
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
1+
import { Component, OnInit } from '@angular/core';
32
import { AssetService } from "../../../shared/services/asset.service";
43
import { PolicyService } from "../../../shared/services/policy.service";
54
import { Asset, PolicyDefinition, ContractDefinitionInput } from "../../../shared/models/edc-connector-entities";

0 commit comments

Comments
 (0)