Commit 4f2b7a57 authored by Florent Poittevin's avatar Florent Poittevin Committed by Nicolas.Rod
Browse files

feat(deposit upload): [DLCM-871] allow to upload datafile with checksum

parent 0a8d5ae0
......@@ -8884,6 +8884,11 @@
}
}
},
"hash-wasm": {
"version": "4.9.0",
"resolved": "https://nexus.unige.ch/repository/npm-all/hash-wasm/-/hash-wasm-4.9.0.tgz",
"integrity": "sha512-7SW7ejyfnRxuOc7ptQHSf4LDoZaWOivfzqw+5rpcQku0nHfmicPKE51ra9BiRLAmT8+gGLestr1XroUkqdjL6w=="
},
"header-case": {
"version": "2.0.4",
"resolved": "https://nexus.unige.ch/repository/npm-all/header-case/-/header-case-2.0.4.tgz",
......
......@@ -71,6 +71,54 @@ export namespace Enums {
}
export namespace DataFile {
export type ChecksumAlgoEnum = "CRC32"
| "MD5"
| "SHA1"
| "SHA256";
export const ChecksumAlgoEnum = {
CRC32: "CRC32" as ChecksumAlgoEnum,
MD5: "MD5" as ChecksumAlgoEnum,
SHA1: "SHA1" as ChecksumAlgoEnum,
SHA256: "SHA256" as ChecksumAlgoEnum,
};
export type ChecksumOriginEnum = "DLCM"
| "DLCM_TOMBSTONE"
| "USER"
| "PORTAL";
export const ChecksumOriginEnum = {
DLCM: "DLCM" as ChecksumOriginEnum,
DLCM_TOMBSTONE: "DLCM_TOMBSTONE" as ChecksumOriginEnum,
USER: "USER" as ChecksumOriginEnum,
PORTAL: "PORTAL" as ChecksumOriginEnum,
};
export const ChecksumOriginEnumTranslate: KeyValue[] = [
{
key: ChecksumOriginEnum.DLCM,
value: MARK_AS_TRANSLATABLE("checksumOriginEnum.dlcm"),
},
{
key: ChecksumOriginEnum.DLCM_TOMBSTONE,
value: MARK_AS_TRANSLATABLE("checksumOriginEnum.dlcmTombstone"),
},
{
key: ChecksumOriginEnum.USER,
value: MARK_AS_TRANSLATABLE("checksumOriginEnum.user"),
},
{
key: ChecksumOriginEnum.PORTAL,
value: MARK_AS_TRANSLATABLE("checksumOriginEnum.portal"),
},
];
export type ChecksumTypeEnum = "COMPLETE"
| "PARTIAL";
export const ChecksumTypeEnum = {
COMPLETE: "COMPLETE" as ChecksumTypeEnum,
PARTIAL: "PARTIAL" as ChecksumTypeEnum,
};
export type DataCategoryEnum = "Primary"
| "Secondary"
| "Package"
......
......@@ -472,6 +472,7 @@ export class DepositFileContainer extends AbstractDetailEditRoutable<Deposit, De
subDirectory: fullFolderPath,
}, {
width: "500px",
disableClose: true,
},
listFilesUploadWrapper => {
if (isNonEmptyArray(listFilesUploadWrapper)) {
......
......@@ -140,6 +140,7 @@ export class DepositUploadContainer extends AbstractDetailEditRoutable<Deposit,
dataCategoryEnum: dataCategoryEnum,
}, {
width: "500px",
disableClose: true,
},
listFilesUploadWrapper => {
if (isNonEmptyArray(listFilesUploadWrapper)) {
......
@import "src/sass/abstracts/abstracts";
@import "abstracts/abstracts";
.hide {
display: none;
......@@ -19,6 +19,12 @@ ul {
}
}
::ng-deep {
.mat-dialog-content {
padding-bottom: 20px;
}
}
.is-in-error {
color: $red;
}
......
......@@ -87,7 +87,7 @@ export abstract class AbstractDepositFileUploadDialog<TData, UResult> extends Sh
return this.form.get(this.formDefinition.dataCategory).value;
}
abstract delete(file: File): void;
abstract delete(file: File, index?: number): void;
}
export class AbstractDepositFileUploadFormComponentFormDefinition extends BaseFormDefinition {
......
import {
ChangeDetectionStrategy,
Component,
ElementRef,
Inject,
OnInit,
ViewChild,
} from "@angular/core";
import {FormBuilder} from "@angular/forms";
import {
......@@ -28,6 +30,9 @@ export class DepositFileUploadArchiveDialog extends AbstractDepositFileUploadDia
archiveToUpload: File;
isFormValid: boolean = true;
@ViewChild("fileInput")
fileInput: ElementRef;
get enumsDataFile(): typeof Enums.DataFile {
return Enums.DataFile;
}
......@@ -56,6 +61,9 @@ export class DepositFileUploadArchiveDialog extends AbstractDepositFileUploadDia
const files: FileList = event.target.files;
this.archiveToUpload = files[0];
}
if (isNotNullNorUndefined(this.fileInput)) {
this.fileInput.nativeElement.value = null;
}
}
onSubmit(): void {
......
......@@ -61,15 +61,20 @@
</form>
<h2>{{labelTranslateEnum.filesToUpload | translate }}</h2>
<mat-accordion>
<mat-expansion-panel *ngFor="let file of filesToUpload">
<mat-accordion [multi]="true">
<mat-expansion-panel *ngFor="let file of filesToUpload; let i = index"
[expanded]="i === 0"
[class.in-error]="formArray.at(i).invalid && formValidationHelper.getFormControl(formArray.at(i),
formDefinitionChecksum.checksumGeneratedIsLoadingCounter)?.value === 0"
class="expansion-panel"
>
<mat-expansion-panel-header>
<mat-panel-title class="file-title-header">
<span class="file-name"
[class.is-in-error]="file.isInError"
>{{file.file.name}}</span>
<button (click)="delete(file.file)"
(onEnter)="delete(file.file)"
<button (click)="delete(file.file, i)"
(onEnter)="delete(file.file, i)"
[matTooltipPosition]="'left'"
[matTooltip]="labelTranslateEnum.delete | translate"
class="trash"
......@@ -95,6 +100,97 @@
<span class="value">{{getDate(file.file.lastModified) | date}}</span>
</li>
</ul>
<ng-container *ngIf="formArray.at(i) as formGroup">
<div class="checksum-header-wrapper">
<mat-checkbox *ngIf="formValidationHelper.getFormControl(formGroup, formDefinitionChecksum.isChecksumUser) as fd"
[formControl]="fd"
color="primary"
class="provide-checksums"
>
{{labelTranslateEnum.provideChecksums | translate}}
</mat-checkbox>
<div *ngIf="formValidationHelper.getFormControl(formGroup, formDefinitionChecksum.checksumGeneratedIsLoadingCounter)?.value > 0 as isLoading"
class="checksum-generated-indicator"
[solidifySpinner]="isLoading"
[solidifySpinnerDiameter]="24"
[solidifySpinnerStrokeWidth]="2"
>
</div>
</div>
<div *ngIf="formValidationHelper.getFormControl(formGroup, formDefinitionChecksum.isChecksumUser)?.value | isTrue"
class="user-checksums"
>
<mat-form-field *ngIf="formValidationHelper.getFormControl(formGroup, formDefinitionChecksum.checksumUserMd5) as fd"
[appearance]="appearanceInputMaterial"
solidifyTooltipOnEllipsis
>
<mat-label>MD5</mat-label>
<input [formControl]="fd"
[required]="formValidationHelper.hasRequiredField(fd)"
[maxLength]="CHECKSUM_LENGTH_MD5"
matInput
>
<mat-error *ngIf="fd.errors?.mismatch && formValidationHelper.getFormControl(formGroup, formDefinitionChecksum.checksumGeneratedMd5).value"
solidifyTooltipOnEllipsis
>
{{labelTranslateEnum.expectedValueX | translate : {value: formValidationHelper.getFormControl(formGroup, formDefinitionChecksum.checksumGeneratedMd5).value} }}</mat-error>
<mat-error *ngIf="fd.errors?.minlength || fd.errors?.maxlength">{{labelTranslateEnum.theFormatIsNotTheOneExpected | translate}}</mat-error>
</mat-form-field>
<mat-form-field *ngIf="formValidationHelper.getFormControl(formGroup, formDefinitionChecksum.checksumUserSha1) as fd"
[appearance]="appearanceInputMaterial"
solidifyTooltipOnEllipsis
>
<mat-label>SHA-1</mat-label>
<input [formControl]="fd"
[required]="formValidationHelper.hasRequiredField(fd)"
[maxLength]="CHECKSUM_LENGTH_SHA1"
matInput
>
<mat-error *ngIf="fd.errors?.mismatch && formValidationHelper.getFormControl(formGroup, formDefinitionChecksum.checksumGeneratedSha1).value"
solidifyTooltipOnEllipsis
>
{{labelTranslateEnum.expectedValueX | translate : {value: formValidationHelper.getFormControl(formGroup, formDefinitionChecksum.checksumGeneratedSha1).value} }}</mat-error>
<mat-error *ngIf="fd.errors?.minlength || fd.errors?.maxlength">{{labelTranslateEnum.theFormatIsNotTheOneExpected | translate}}</mat-error>
</mat-form-field>
<mat-form-field *ngIf="formValidationHelper.getFormControl(formGroup, formDefinitionChecksum.checksumUserSha256) as fd"
[appearance]="appearanceInputMaterial"
solidifyTooltipOnEllipsis
>
<mat-label>SHA-256</mat-label>
<input [formControl]="fd"
[required]="formValidationHelper.hasRequiredField(fd)"
[maxLength]="CHECKSUM_LENGTH_SHA256"
matInput
>
<mat-error *ngIf="fd.errors?.mismatch && formValidationHelper.getFormControl(formGroup, formDefinitionChecksum.checksumGeneratedSha256).value"
solidifyTooltipOnEllipsis
>
{{labelTranslateEnum.expectedValueX | translate : {value: formValidationHelper.getFormControl(formGroup, formDefinitionChecksum.checksumGeneratedSha256).value} }}</mat-error>
<mat-error *ngIf="fd.errors?.minlength || fd.errors?.maxlength">{{labelTranslateEnum.theFormatIsNotTheOneExpected | translate}}</mat-error>
</mat-form-field>
<mat-form-field *ngIf="formValidationHelper.getFormControl(formGroup, formDefinitionChecksum.checksumUserCrc32) as fd"
[appearance]="appearanceInputMaterial"
solidifyTooltipOnEllipsis
>
<mat-label>CRC32</mat-label>
<input [formControl]="fd"
[required]="formValidationHelper.hasRequiredField(fd)"
[maxLength]="CHECKSUM_LENGTH_CRC32"
matInput
>
<mat-error *ngIf="fd.errors?.mismatch && formValidationHelper.getFormControl(formGroup, formDefinitionChecksum.checksumGeneratedCrc32).value"
solidifyTooltipOnEllipsis
>
{{labelTranslateEnum.expectedValueX | translate : {value: formValidationHelper.getFormControl(formGroup, formDefinitionChecksum.checksumGeneratedCrc32).value} }}</mat-error>
<mat-error *ngIf="fd.errors?.minlength || fd.errors?.maxlength">{{labelTranslateEnum.theFormatIsNotTheOneExpected | translate}}</mat-error>
</mat-form-field>
</div>
</ng-container>
</mat-expansion-panel>
</mat-accordion>
......@@ -127,7 +223,7 @@
>{{labelTranslateEnum.close | translate}}
</button>
<button (click)="onSubmit()"
[disabled]="(!form.valid || filesToUpload.length === 0 || isCurrentFileNameInError()) || !isFormValid"
[disabled]="(!form.valid || filesToUpload.length === 0 || isCurrentFileNameInError()) || !isFormValid || formArray.invalid"
color="primary"
id="deposit-upload-confirm"
mat-flat-button
......
@import "../abstract-deposit-file-upload/abstract-deposit-file-upload.dialog";
:host {
.expansion-panel {
&.in-error {
border-left: 2px solid $error;
}
.checksum-header-wrapper {
display: grid;
grid-template-columns: max-content max-content;
justify-content: space-between;
align-items: center;
margin: 10px 0;
.provide-checksums {
}
.checksum-generated-indicator {
width: 24px;
height: 24px;
}
}
.user-checksums {
> * {
width: 100%;
}
}
}
}
export interface Checksum {
checksumAlgo?: string;
checksumType?: string;
checksumOrigin?: string;
checksum?: string;
creationTime?: string;
}
import {Enums} from "@enums";
import {FileUploadWrapper} from "solidify-frontend";
import {
FileUploadWrapper,
MappingObject,
} from "solidify-frontend";
export interface DlcmFileUploadWrapper extends FileUploadWrapper {
subDirectory: string;
dataCategory: Enums.DataFile.DataCategoryEnum;
dataType: Enums.DataFile.DataTypeEnum;
metadataType: string;
checksums: MappingObject<string>; // Enums.DataFile.ChecksumAlgoEnum, string
checksumsOrigin: MappingObject<Enums.DataFile.ChecksumOriginEnum>; // Enums.DataFile.ChecksumAlgoEnum, Enums.DataFile.ChecksumOriginEnum
}
......@@ -11,6 +11,7 @@ import {
DepositUploadAction,
depositUploadActionNameSpace,
} from "@deposit/stores/upload/deposit-upload.action";
import {Enums} from "@enums";
import {DlcmEnvironment} from "@environments/environment.defaults.model";
import {TranslateService} from "@ngx-translate/core";
import {
......@@ -26,6 +27,7 @@ import {
isNullOrUndefined,
LABEL_TRANSLATE,
LabelTranslateInterface,
MappingObjectUtil,
MemoizedUtil,
NotificationService,
StringUtil,
......@@ -49,6 +51,14 @@ export class DepositUploadState extends UploadState<DepositUploadStateModel, Dep
private readonly _CATEGORY_KEY: string = "category";
private readonly _TYPE_KEY: string = "type";
private readonly _FOLDER_KEY: string = "folder";
private readonly _CHECKSUM_MD5: string = "checksumMd5";
private readonly _CHECKSUM_MD5_ORIGIN: string = "checksumMd5Origin";
private readonly _CHECKSUM_SHA1: string = "checksumSha1";
private readonly _CHECKSUM_SHA1_ORIGIN: string = "checksumSha1Origin";
private readonly _CHECKSUM_SHA256: string = "checksumSha256";
private readonly _CHECKSUM_SHA256_ORIGIN: string = "checksumSha256Origin";
private readonly _CHECKSUM_CRC32: string = "checksumCrc32";
private readonly _CHECKSUM_CRC32_ORIGIN: string = "checksumCrc32Origin";
constructor(protected readonly apiService: ApiService,
protected readonly store: Store,
......@@ -78,6 +88,48 @@ export class DepositUploadState extends UploadState<DepositUploadStateModel, Dep
formData.append(this._FILE_KEY, fileUploadWrapper.file, fileUploadWrapper.file.name);
formData.append(this._CATEGORY_KEY, fileUploadWrapper.dataCategory);
formData.append(this._TYPE_KEY, fileUploadWrapper.dataType);
MappingObjectUtil.forEach(fileUploadWrapper.checksums, (checksum, algo: Enums.DataFile.ChecksumAlgoEnum) => {
const algoKey = this._getChecksumKey(algo);
if (isNullOrUndefined(algoKey)) {
return;
}
formData.append(algoKey, checksum);
});
MappingObjectUtil.forEach(fileUploadWrapper.checksumsOrigin, (origin: Enums.DataFile.ChecksumOriginEnum, algo: Enums.DataFile.ChecksumAlgoEnum) => {
const originKey = this._getChecksumOriginKey(algo);
if (isNullOrUndefined(originKey)) {
return;
}
formData.append(originKey, origin);
});
return formData;
}
private _getChecksumKey(checksumAlgo: Enums.DataFile.ChecksumAlgoEnum): string | undefined {
switch (checksumAlgo) {
case Enums.DataFile.ChecksumAlgoEnum.MD5:
return this._CHECKSUM_MD5;
case Enums.DataFile.ChecksumAlgoEnum.SHA1:
return this._CHECKSUM_SHA1;
case Enums.DataFile.ChecksumAlgoEnum.SHA256:
return this._CHECKSUM_SHA256;
case Enums.DataFile.ChecksumAlgoEnum.CRC32:
return this._CHECKSUM_CRC32;
}
return undefined;
}
private _getChecksumOriginKey(checksumAlgo: Enums.DataFile.ChecksumAlgoEnum): string | undefined {
switch (checksumAlgo) {
case Enums.DataFile.ChecksumAlgoEnum.MD5:
return this._CHECKSUM_MD5_ORIGIN;
case Enums.DataFile.ChecksumAlgoEnum.SHA1:
return this._CHECKSUM_SHA1_ORIGIN;
case Enums.DataFile.ChecksumAlgoEnum.SHA256:
return this._CHECKSUM_SHA256_ORIGIN;
case Enums.DataFile.ChecksumAlgoEnum.CRC32:
return this._CHECKSUM_CRC32_ORIGIN;
}
return undefined;
}
}
......@@ -900,4 +900,9 @@ export const icons: IconInfos[] = [
lib: IconLibEnum.materialIcon,
icon: "navigate_next",
},
{
name: IconNameEnum.checksums,
lib: IconLibEnum.materialIcon,
icon: "pin",
},
];
......@@ -126,6 +126,14 @@ export interface ArchiveUserRating extends BaseResource {
export interface ChangeInfo extends ChangeInfoPartial {
}
export interface Checksum {
checksumAlgo?: Enums.DataFile.ChecksumAlgoEnum;
checksumType?: Enums.DataFile.ChecksumTypeEnum;
checksumOrigin?: Enums.DataFile.ChecksumOriginEnum;
checksum?: string;
creationTime?: string;
}
export interface Collection extends CollectionPartial {
}
......
<span class="algo">{{checksumAlgo}}</span>
<span (click)="copy(checksum)"
[matTooltip]="labelTranslateEnum.copyToClipboard | translate"
class="value"
>{{checksum}}<span *ngIf="checksumOrigin"
class="provided"
> ({{labelTranslateEnum.providedByX | translate: {origin: enumUtil.getLabel(checksumOriginEnumTranslate, checksumOrigin) | translate} }})</span>
@import "abstracts/abstracts";
:host {
padding: 2px 0;
display: grid;
grid-template-columns: 50px 1fr;
grid-gap: 10px;
align-items: center;
.algo {
background-color: $extra-light-grey;
border-radius: 4px;
display: inline-block;
padding: 4px 0;
font-size: 10px;
text-align: center;
font-weight: bold;
flex-shrink: 0;
}
.value {
cursor: pointer;
@include truncate-with-ellipsis;
}
.provided {
font-size: 10px;
}
}
import {
ChangeDetectionStrategy,
Component,
Input,
} from "@angular/core";
import {SharedAbstractPresentational} from "@app/shared/components/presentationals/shared-abstract/shared-abstract.presentational";
import {Enums} from "@enums";
import {
ClipboardUtil,
EnumUtil,
MARK_AS_TRANSLATABLE,
NotificationService,
} from "solidify-frontend";
@Component({
selector: "dlcm-shared-checksum-item",
templateUrl: "./shared-checksum-item.presentational.html",
styleUrls: ["./shared-checksum-item.presentational.scss"],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SharedChecksumItemPresentational extends SharedAbstractPresentational {
@Input()
checksumAlgo: Enums.DataFile.ChecksumAlgoEnum;
@Input()
checksumOrigin: Enums.DataFile.ChecksumOriginEnum;
@Input()
checksum: string;
get checksumOriginEnumTranslate(): typeof Enums.DataFile.ChecksumOriginEnumTranslate {
return Enums.DataFile.ChecksumOriginEnumTranslate;
}
get enumUtil(): typeof EnumUtil {
return EnumUtil;
}
constructor(private readonly _notificationService: NotificationService) {
super();
}
copy(checksum: string): void {
ClipboardUtil.copyStringToClipboard(checksum);
this._notificationService.showInformation(MARK_AS_TRANSLATABLE("app.notification.checksumCopy"));
}
}
......@@ -2,10 +2,9 @@
<li *ngFor="let checksum of checksums"
class="checksum"
>
<span class="algo">{{checksum.checksumAlgo}}</span>
<span (click)="copy(checksum)"
[matTooltip]="labelTranslateEnum.copyToClipboard | translate"
class="value"
>{{checksum.checksum}}</span>
<dlcm-shared-checksum-item [checksum]="checksum.checksum"
[checksumAlgo]="checksum.checksumAlgo"
[checksumOrigin]="checksum.checksumOrigin"
></dlcm-shared-checksum-item>
</li>
</ul>
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment