Skip to content

Commit 04e43cb

Browse files
authored
Merge pull request #3435 from ProgrammeVitam/Story_15526
Story #15526 - [Collect] Adding archives via SIP and ZIP in a transaction.
2 parents e00606a + 76c21b8 commit 04e43cb

File tree

9 files changed

+138
-19
lines changed

9 files changed

+138
-19
lines changed

api/api-collect/collect/src/main/java/fr/gouv/vitamui/collect/server/rest/ProjectController.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
*/
2727
package fr.gouv.vitamui.collect.server.rest;
2828

29+
import fr.gouv.vitam.collect.common.dto.UploadSipResult;
2930
import fr.gouv.vitam.common.exception.VitamClientException;
31+
import fr.gouv.vitam.common.model.RequestResponseOK;
3032
import fr.gouv.vitamui.collect.common.dto.CollectProjectAttachmentsDto;
3133
import fr.gouv.vitamui.collect.common.dto.CollectProjectConfigurationDto;
3234
import fr.gouv.vitamui.collect.common.dto.CollectProjectContextDto;
@@ -208,18 +210,23 @@ public void streamingUpload(
208210
@Secured(ServicesData.ROLE_CREATE_PROJECTS)
209211
@Operation(summary = "Upload and stream SIP")
210212
@PostMapping(value = "/uploadSip", consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE)
211-
public void streamingUploadSip(
213+
public String streamingUploadSip(
212214
InputStream inputStream,
213215
@RequestHeader(value = CommonConstants.X_TRANSACTION_ID_HEADER) final String transactionId
214-
) throws PreconditionFailedException {
216+
) throws PreconditionFailedException, VitamClientException {
215217
ParameterChecker.checkParameter("The transaction ID is a mandatory parameter: ", transactionId);
216218
SanityChecker.checkSecureParameter(transactionId);
217219
LOGGER.debug("[External] upload SIP");
218-
projectService.streamingUploadSip(
220+
var requestResponse = projectService.streamingUploadSip(
219221
inputStream,
220222
transactionId,
221223
externalParametersService.buildVitamContextFromExternalParam()
222224
);
225+
if (!requestResponse.isOk()) {
226+
throw new VitamClientException("Error occurs when uploading SIP to: " + transactionId);
227+
}
228+
var response = (UploadSipResult) ((RequestResponseOK<?>) requestResponse).getFirstResult();
229+
return response.requestId();
223230
}
224231

225232
@Secured(ServicesData.ROLE_UPDATE_PROJECTS_DESCRIPTION)

api/api-collect/collect/src/main/java/fr/gouv/vitamui/collect/server/service/ProjectService.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import fr.gouv.vitam.collect.common.dto.CriteriaProjectDto;
3737
import fr.gouv.vitam.collect.common.dto.ProjectDto;
3838
import fr.gouv.vitam.collect.common.dto.TransactionDto;
39+
import fr.gouv.vitam.collect.common.dto.UploadSipResult;
3940
import fr.gouv.vitam.collect.external.external.exception.CollectExternalClientInvalidRequestException;
4041
import fr.gouv.vitam.common.client.VitamContext;
4142
import fr.gouv.vitam.common.exception.InvalidParseOperationException;
@@ -234,10 +235,14 @@ public void streamingUpload(
234235
}
235236
}
236237

237-
public void streamingUploadSip(InputStream inputStream, String transactionId, VitamContext vitamContext) {
238+
public RequestResponse<UploadSipResult> streamingUploadSip(
239+
InputStream inputStream,
240+
String transactionId,
241+
VitamContext vitamContext
242+
) {
238243
LOGGER.debug("TransactionId: {}", transactionId);
239244
try {
240-
collectService.uploadSipToTransaction(vitamContext, transactionId, inputStream);
245+
return collectService.uploadSipToTransaction(vitamContext, transactionId, inputStream);
241246
} catch (VitamClientException e) {
242247
LOGGER.debug(UNABLE_TO_UPLOAD_SIP_TO_TRANSACTION, e);
243248
throw new BadRequestException(UNABLE_TO_UPLOAD_SIP_TO_TRANSACTION, e);

commons/commons-vitam/src/main/java/fr/gouv/vitamui/commons/vitam/api/collect/CollectService.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import fr.gouv.vitam.collect.common.dto.CriteriaProjectDto;
3434
import fr.gouv.vitam.collect.common.dto.ProjectDto;
3535
import fr.gouv.vitam.collect.common.dto.TransactionDto;
36+
import fr.gouv.vitam.collect.common.dto.UploadSipResult;
3637
import fr.gouv.vitam.collect.external.client.CollectExternalClient;
3738
import fr.gouv.vitam.collect.external.external.exception.CollectExternalClientException;
3839
import fr.gouv.vitam.common.CharsetUtils;
@@ -263,7 +264,7 @@ public RequestResponse uploadProjectZip(
263264
* @return
264265
* @throws VitamClientException
265266
*/
266-
public RequestResponse uploadSipToTransaction(
267+
public RequestResponse<UploadSipResult> uploadSipToTransaction(
267268
final VitamContext vitamContext,
268269
final String transactionId,
269270
final InputStream inputStream

ui/ui-frontend/projects/collect/src/app/collect/archive-search-collect/add-units/add-units.component.html

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,37 @@
1717
<mat-spinner class="vitamui-spinner large"></mat-spinner>
1818
</div>
1919
} @else {
20-
<vitamui-file-selector
21-
[directoryMode]="true"
22-
[maxSizeInBytes]="uploadMaxSizeInBytes"
23-
[multiple]="true"
24-
[formControl]="filesToUploadControl"
25-
>
26-
</vitamui-file-selector>
20+
<div class="main-content">
21+
<div class="gap-6">
22+
<vitamui-select
23+
class="w-100"
24+
[placeholder]="'COLLECT.MODAL.IMPORT_TYPE_PLACEHOLDER' | translate"
25+
[formControl]="importTypeControl"
26+
[options]="[
27+
{ key: 'DIRECTORIES_FILES', label: 'COLLECT.MODAL.IMPORT_TYPE_DIRECTORIES_FILES_V0' | translate },
28+
{ key: 'COMPRESSED', label: 'COLLECT.MODAL.IMPORT_TYPE_COMPRESSED' | translate },
29+
{ key: 'SIP', label: 'COLLECT.MODAL.IMPORT_SIP_ARCHIVES_PACKAGE' | translate },
30+
]"
31+
[enableSearch]="false"
32+
(change)="resetFilesToImportList()"
33+
required
34+
>
35+
</vitamui-select>
36+
@if (importType === ImportType.DIRECTORIES_FILES) {
37+
<vitamui-file-selector
38+
class="w-100"
39+
[directoryMode]="true"
40+
[maxSizeInBytes]="uploadMaxSizeInBytes"
41+
[multiple]="true"
42+
[formControl]="filesToUploadControl"
43+
>
44+
</vitamui-file-selector>
45+
}
46+
@if (importType === ImportType.COMPRESSED || importType === ImportType.SIP) {
47+
<vitamui-file-selector class="w-100" [extensions]="['.zip']" [formControl]="filesToUploadControl"> </vitamui-file-selector>
48+
}
49+
</div>
50+
</div>
2751
}
2852
</mat-dialog-content>
2953

@@ -40,9 +64,13 @@
4064
<mat-dialog-content>
4165
<div class="main-content">
4266
<div class="form-group">
43-
@if (projectUnits) {
67+
@if (projectUnits && importType === ImportType.DIRECTORIES_FILES) {
4468
<vitamui-library-filing-plan [mode]="FilingPlanMode.SOLO" [formControl]="linkParentIdControl" [dataSource]="projectUnits">
4569
</vitamui-library-filing-plan>
70+
} @else if (importType === ImportType.COMPRESSED || importType === ImportType.SIP) {
71+
<span class="text meduim">
72+
{{ 'COLLECT.MODAL.ARCHIVE_POSITION_MSG_FOR_SIP' | translate }}
73+
</span>
4674
}
4775
</div>
4876
</div>

ui/ui-frontend/projects/collect/src/app/collect/archive-search-collect/add-units/add-units.component.spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
4646
import { DecimalPipe } from '@angular/common';
4747
import { ArchiveCollectService } from '../archive-collect.service';
4848
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
49+
import { ActivatedRoute } from '@angular/router';
4950

5051
const translations: any = { TEST: 'Mock translate test' };
5152

@@ -86,6 +87,15 @@ describe('AddUnitsComponent', () => {
8687
totalResults: 0,
8788
}),
8889
};
90+
91+
const activatedRouteMock = {
92+
snapshot: {
93+
params: {
94+
tenantIdentifier: '15',
95+
},
96+
},
97+
};
98+
8999
beforeEach(async () => {
90100
await TestBed.configureTestingModule({
91101
declarations: [AddUnitsComponent],
@@ -104,6 +114,7 @@ describe('AddUnitsComponent', () => {
104114
{ provide: MAT_DIALOG_DATA, useValue: { tenantIdentifier: '15', selectedTransaction } },
105115
{ provide: WINDOW_LOCATION, useValue: window.location },
106116
{ provide: ArchiveCollectService, useValue: archiveCollectServiceMock },
117+
{ provide: ActivatedRoute, useValue: activatedRouteMock },
107118
DecimalPipe,
108119
BytesPipe,
109120
provideHttpClient(withInterceptorsFromDi()),

ui/ui-frontend/projects/collect/src/app/collect/archive-search-collect/add-units/add-units.component.ts

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737

3838
import { Component, Inject, OnInit, TemplateRef, ViewChild } from '@angular/core';
3939
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
40-
import { finalize, from, Observable, switchMap } from 'rxjs';
40+
import { finalize, from, Observable, of, switchMap } from 'rxjs';
4141
import {
4242
CriteriaDataType,
4343
CriteriaOperator,
@@ -50,11 +50,20 @@ import {
5050
Unit,
5151
ZipFile,
5252
ZipFileStatus,
53+
ApplicationId,
54+
SnackBarService,
5355
} from 'vitamui-library';
5456
import { ArchiveCollectService } from '../archive-collect.service';
5557
import { FormControl, Validators } from '@angular/forms';
5658
import { last, tap } from 'rxjs/operators';
5759
import { HttpEventType } from '@angular/common/http';
60+
import { ActivatedRoute } from '@angular/router';
61+
62+
export enum ImportType {
63+
DIRECTORIES_FILES = 'DIRECTORIES_FILES',
64+
COMPRESSED = 'COMPRESSED',
65+
SIP = 'SIP',
66+
}
5867

5968
@Component({
6069
selector: 'app-add-units',
@@ -66,9 +75,11 @@ export class AddUnitsComponent implements OnInit {
6675
protected readonly FilingPlanMode = FilingPlanMode;
6776
protected readonly uploadMaxSizeInBytes = Math.pow(1024, 3); // 1 Gb
6877

78+
uploadedOperationId: string;
6979
isLoading = false;
7080
stepIndex = 0;
7181

82+
importTypeControl: FormControl<string | null> = new FormControl(null);
7283
filesToUploadControl: FormControl<File[]> = new FormControl([], [Validators.required]);
7384
zipFileStatus$: Observable<ZipFileStatus>;
7485
linkParentIdControl = new FormControl({ included: [], excluded: [] });
@@ -82,6 +93,8 @@ export class AddUnitsComponent implements OnInit {
8293
public data: {
8394
transaction: Transaction;
8495
},
96+
private route: ActivatedRoute,
97+
private snackBarService: SnackBarService,
8598
private dialog: MatDialog,
8699
private addUnitsDialogRef: MatDialogRef<AddUnitsComponent>,
87100
private archiveCollectService: ArchiveCollectService,
@@ -128,6 +141,9 @@ export class AddUnitsComponent implements OnInit {
128141
}
129142

130143
close(filesUploaded: boolean) {
144+
if (filesUploaded && this.importType === ImportType.SIP && this.uploadedOperationId) {
145+
this.handleUploadSIPSuccess(this.uploadedOperationId);
146+
}
131147
this.confirmCancelDialog?.close(true);
132148
this.addUnitsDialogRef.close(filesUploaded);
133149
}
@@ -137,13 +153,33 @@ export class AddUnitsComponent implements OnInit {
137153
this.stepIndex++;
138154
const zipFile = new ZipFile(this.data.transaction.id);
139155
this.zipFileStatus$ = zipFile.zipFileStatus$;
140-
from(zipFile.addFiles(this.filesToUploadControl.value).generateZip())
156+
157+
// For COMPRESSED and SIP types, use the file directly without zipping it
158+
let compressedZip: Blob;
159+
if ([ImportType.COMPRESSED, ImportType.SIP].includes(this.importType as ImportType)) {
160+
compressedZip = new Blob(this.filesToUploadControl.value, { type: 'application/zip' });
161+
zipFile.zipFileStatus.size = compressedZip.size;
162+
zipFile.zipFileStatus.currentFileUploadedSize = 100;
163+
}
164+
165+
from(
166+
this.importType === ImportType.DIRECTORIES_FILES
167+
? zipFile.addFiles(this.filesToUploadControl.value).generateZip()
168+
: of(compressedZip).toPromise(),
169+
)
141170
.pipe(
142171
switchMap((content) =>
143-
this.archiveCollectService.uploadZip(content, this.data.transaction.id, this.linkParentIdControl.value.included[0]),
172+
this.importType === ImportType.SIP
173+
? this.archiveCollectService.uploadSip(content, this.data.transaction.id)
174+
: this.archiveCollectService.uploadZip(content, this.data.transaction.id, this.linkParentIdControl.value.included[0]),
144175
),
145176
tap((httpEvent) => zipFile.updateUploadingZipFileStatus(httpEvent)),
146177
last((httpEvent) => httpEvent.type === HttpEventType.Response),
178+
tap((httpEvent) => {
179+
if (this.importType === ImportType.SIP && httpEvent.type === HttpEventType.Response) {
180+
this.uploadedOperationId = httpEvent.body;
181+
}
182+
}),
147183
finalize(() => (this.isLoading = false)),
148184
)
149185
.subscribe({
@@ -152,4 +188,29 @@ export class AddUnitsComponent implements OnInit {
152188
},
153189
});
154190
}
191+
192+
resetFilesToImportList() {
193+
this.filesToUploadControl.setValue([]);
194+
}
195+
196+
private handleUploadSIPSuccess(operationId: string): void {
197+
const tenantId = this.route.snapshot.params.tenantIdentifier;
198+
this.snackBarService.open({
199+
message: 'COLLECT.MODAL.IMPORT_SIP_ARCHIVES_PACKAGE_WITH_SUCCESS',
200+
buttons: [
201+
{
202+
appId: ApplicationId.LOGBOOK_OPERATION_APP,
203+
path: `/tenant/${tenantId}?guid=${operationId}`,
204+
label: 'SNACK_BAR.TO_OPERATION_APP',
205+
},
206+
],
207+
duration: 100_000,
208+
});
209+
}
210+
211+
get importType(): string | null {
212+
return this.importTypeControl.value;
213+
}
214+
215+
protected ImportType = ImportType;
155216
}

ui/ui-frontend/projects/collect/src/app/collect/projects/create-project/create-project.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,7 @@ export class CreateProjectComponent implements OnInit, AfterViewChecked {
564564
}
565565

566566
resetFilesToImportList() {
567-
this.filesToUploadControl.reset();
567+
this.filesToUploadControl.setValue([]);
568568
}
569569

570570
private setDefaultArchivingSystemValue() {

ui/ui-frontend/projects/collect/src/assets/i18n/en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,16 @@
124124
"PROJECT_CREATION_ERROR": "Error while creating attachment project",
125125
"UPLOAD_SUB_TITLE": "Loading data to be poured",
126126
"ARCHIVE_POSITION_SUB_TITLE": "Define fixed attachment position",
127+
"ARCHIVE_POSITION_MSG_FOR_SIP": "For this import, reclassification within the transfer hierarchy will have to be carried out at a later stage, directly via the “Reclassify archival units” button.",
127128
"AUTO_INGEST": "Automatic transaction management:",
128129
"AUTO_INGEST_TRUE": "Yes",
129130
"AUTO_INGEST_FALSE": "No",
130131
"IMPORT_TYPE_PLACEHOLDER": "Select import type",
132+
"IMPORT_TYPE_DIRECTORIES_FILES_V0": "Directories and files import",
131133
"IMPORT_TYPE_DIRECTORIES_FILES": "Directories and files import (attached or not with metadata file)",
132134
"IMPORT_TYPE_COMPRESSED": "Compressed directory import (.zip)",
133135
"IMPORT_SIP_ARCHIVES_PACKAGE": "SIP archives package import (bêta)",
136+
"IMPORT_SIP_ARCHIVES_PACKAGE_WITH_SUCCESS": "The archive addition has been successfully launched",
134137
"DRAG_AND_DROP": "Drag and drop",
135138
"OR": "or",
136139
"BROWSE": "browse",

ui/ui-frontend/projects/collect/src/assets/i18n/fr.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,16 @@
124124
"PROJECT_CREATION_ERROR": "Erreur lors de la création du projet de versement",
125125
"UPLOAD_SUB_TITLE": "Chargement des données à verser",
126126
"ARCHIVE_POSITION_SUB_TITLE": "Définir la position fixe de rattachement",
127+
"ARCHIVE_POSITION_MSG_FOR_SIP": "Pour cet import, le reclassement au sein de l'arborescence du versement devra être réalisé dans un second temps, directement via le bouton \"Reclasser des unités archivistiques\".",
127128
"AUTO_INGEST": "Gestion automatique des transactions :",
128129
"AUTO_INGEST_TRUE": "Oui",
129130
"AUTO_INGEST_FALSE": "Non",
130131
"IMPORT_TYPE_PLACEHOLDER": "Sélectionner le type d'import",
132+
"IMPORT_TYPE_DIRECTORIES_FILES_V0": "Import de dossiers et fichiers",
131133
"IMPORT_TYPE_DIRECTORIES_FILES": "Import de dossiers et fichiers (accompagné ou non d'un fichier metadata)",
132134
"IMPORT_TYPE_COMPRESSED": "Import d'un dossier compressé (.zip)",
133-
"IMPORT_SIP_ARCHIVES_PACKAGE": "Importer le paquet d'archives d'un SIP (bêta)",
135+
"IMPORT_SIP_ARCHIVES_PACKAGE": "Import du paquet d'archives d'un SIP (bêta)",
136+
"IMPORT_SIP_ARCHIVES_PACKAGE_WITH_SUCCESS": "L'ajout d'archives a bien été lancé",
134137
"DRAG_AND_DROP": "Glisser-déposer",
135138
"OR": "ou",
136139
"BROWSE": "parcourir",

0 commit comments

Comments
 (0)