From 1e40f5205720edd7fe210e334da7ae8ef1a7df43 Mon Sep 17 00:00:00 2001 From: martintrajanovski Date: Mon, 26 May 2025 16:46:52 +0200 Subject: [PATCH 01/21] refactor: published data review and refactor according to the new requirements and workflow --- .../datasets/publish/publish.component.html | 11 ++-- src/app/datasets/publish/publish.component.ts | 51 ++++++++++--------- .../publisheddata-edit.component.html | 29 +++++++---- .../publisheddata-edit.component.ts | 42 +++++++-------- src/styles.scss | 8 +++ 5 files changed, 81 insertions(+), 60 deletions(-) diff --git a/src/app/datasets/publish/publish.component.html b/src/app/datasets/publish/publish.component.html index c919d3ee0..033c92f06 100644 --- a/src/app/datasets/publish/publish.component.html +++ b/src/app/datasets/publish/publish.component.html @@ -88,7 +88,7 @@ > - + Related publications @@ -109,7 +109,6 @@ cancel - Publish + Create data publication -
+
diff --git a/src/app/datasets/publish/publish.component.ts b/src/app/datasets/publish/publish.component.ts index f004549a8..b0583799f 100644 --- a/src/app/datasets/publish/publish.component.ts +++ b/src/app/datasets/publish/publish.component.ts @@ -46,14 +46,14 @@ export class PublishComponent implements OnInit, OnDestroy { resourceType: "", description: "", abstract: "", - pidArray: [], + datasetPids: [], publicationYear: null, url: "", - dataDescription: "", - thumbnail: "", - numberOfFiles: null, - sizeOfArchive: null, - downloadLink: "", + // dataDescription: "", + // thumbnail: "", + // numberOfFiles: null, + // sizeOfArchive: null, + // downloadLink: "", relatedPublications: [], }; @@ -132,12 +132,12 @@ export class PublishComponent implements OnInit, OnDestroy { (item, i) => creator.indexOf(item) === i, ); this.form.creators = unique; - this.form.pidArray = datasets.map((dataset) => dataset.pid); - let size = 0; - datasets.forEach((dataset) => { - size += dataset.size; - }); - this.form.sizeOfArchive = size; + this.form.datasetPids = datasets.map((dataset) => dataset.pid); + // let size = 0; + // datasets.forEach((dataset) => { + // size += dataset.size; + // }); + // this.form.sizeOfArchive = size; } }), ) @@ -150,13 +150,13 @@ export class PublishComponent implements OnInit, OnDestroy { }); this.publishedDataApi - .publishedDataControllerFormPopulateV3(this.form.pidArray[0]) + .publishedDataControllerFormPopulateV3(this.form.datasetPids[0]) .subscribe((result) => { this.form.abstract = result.abstract; this.form.title = result.title; this.form.description = result.description; this.form.resourceType = "raw"; - this.form.thumbnail = result.thumbnail ?? ""; + // this.form.thumbnail = result.thumbnail ?? ""; }); this.actionSubjectSubscription = this.actionsSubj.subscribe((data) => { @@ -184,30 +184,31 @@ export class PublishComponent implements OnInit, OnDestroy { description, creators, resourceType, - pidArray, + datasetPids, publisher, url, - thumbnail, - numberOfFiles, - sizeOfArchive, - downloadLink, + // thumbnail, + // numberOfFiles, + // sizeOfArchive, + // downloadLink, relatedPublications, } = this.form; - const publishedData: CreatePublishedDataDto = { + // TODO: Fix the types here + const publishedData: any = { title: title, abstract: abstract, dataDescription: description, creator: creators, resourceType: resourceType, - pidArray: pidArray, + datasetPids: datasetPids, publisher: publisher, publicationYear: parseInt(formatDate(this.today, "yyyy", "en_GB"), 10), url: url, - thumbnail: thumbnail, - numberOfFiles: numberOfFiles, - sizeOfArchive: sizeOfArchive, - downloadLink: downloadLink, + // thumbnail: thumbnail, + // numberOfFiles: numberOfFiles, + // sizeOfArchive: sizeOfArchive, + // downloadLink: downloadLink, relatedPublications: relatedPublications, }; diff --git a/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.html b/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.html index d45f489a3..3d3490e5d 100644 --- a/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.html +++ b/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.html @@ -28,8 +28,8 @@ {{ item }} @@ -58,6 +58,17 @@ + + + + - + - + @@ -136,7 +147,7 @@ -
+
diff --git a/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.ts b/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.ts index d5371c070..195f57717 100644 --- a/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.ts +++ b/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.ts @@ -30,19 +30,21 @@ export class PublisheddataEditComponent implements OnInit, OnDestroy { form: FormGroup = this.formBuilder.group({ doi: [""], title: ["", Validators.required], - creator: [[""], Validators.minLength(1)], + creators: [[""], Validators.minLength(1)], publisher: ["", Validators.required], resourceType: ["", Validators.required], abstract: ["", Validators.required], - pidArray: [[""], Validators.minLength(1)], + datasetPids: [[""], Validators.minLength(1)], publicationYear: [0, Validators.required], url: [""], - dataDescription: ["", Validators.required], - thumbnail: [""], - numberOfFiles: [0], - sizeOfArchive: [0], - downloadLink: [""], + contributors: [[""]], + // dataDescription: ["", Validators.required], + // thumbnail: [""], + // numberOfFiles: [0], + // sizeOfArchive: [0], + // downloadLink: [""], relatedPublications: [[]], + keywords: [[""]], }); public separatorKeysCodes: number[] = [ENTER, COMMA]; @@ -57,7 +59,7 @@ export class PublisheddataEditComponent implements OnInit, OnDestroy { addCreator(event: MatChipInputEvent) { const value = (event.value || "").trim(); if (value) { - this.creator!.value.push(value); + this.creators!.value.push(value); } if (event.chipInput && event.chipInput.inputElement.value) { @@ -67,7 +69,7 @@ export class PublisheddataEditComponent implements OnInit, OnDestroy { removeCreator(index: number) { if (index >= 0) { - this.creator!.value.splice(index, 1); + this.creators!.value.splice(index, 1); } } @@ -107,25 +109,25 @@ export class PublisheddataEditComponent implements OnInit, OnDestroy { } } - onFileUploaderFilePicked(file: PickedFile) { - this.form.get("thumbnail")!.setValue(file.content); - } + // onFileUploaderFilePicked(file: PickedFile) { + // this.form.get("thumbnail")!.setValue(file.content); + // } - deleteAttachment(attachmentId: string) { - this.form.get("thumbnail")!.setValue(""); - } + // deleteAttachment(attachmentId: string) { + // this.form.get("thumbnail")!.setValue(""); + // } - get creator() { - return this.form.get("creator"); + get creators() { + return this.form.get("creators"); } get relatedPublications() { return this.form.get("relatedPublications"); } - get thumbnail() { - return this.form.get("thumbail"); - } + // get thumbnail() { + // return this.form.get("thumbail"); + // } ngOnInit() { this.routeSubscription = this.route.params.subscribe(({ id }) => diff --git a/src/styles.scss b/src/styles.scss index 890086281..5df7e9c71 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -332,6 +332,14 @@ a:hover { input.mat-mdc-chip-input { margin-left: 0; } + + .mdc-evolution-chip-set__chips { + margin-left: 0 !important; + + input.mat-mdc-chip-input { + margin-left: 8px; + } + } } .mat-mdc-form-field { From f3d9cbb9d440b608d2310e2abda55dbf7114af33 Mon Sep 17 00:00:00 2001 From: martintrajanovski Date: Thu, 5 Jun 2025 09:23:57 +0200 Subject: [PATCH 02/21] install jsonforms --- package-lock.json | 90 +++++++++++++++++++++++++++++++++++++++++------ package.json | 3 ++ 2 files changed, 82 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 90faa7342..aad20bc38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,9 @@ "@angular/platform-server": "^19.2.8", "@angular/router": "^19.2.8", "@angular/service-worker": "^19.2.8", + "@jsonforms/angular": "^3.5.1", + "@jsonforms/angular-material": "^3.5.1", + "@jsonforms/core": "^3.5.1", "@ngbracket/ngx-layout": "^16.0.0", "@ngrx/effects": "^19.1.0", "@ngrx/operators": "^19.1.0", @@ -4099,6 +4102,70 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsonforms/angular": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@jsonforms/angular/-/angular-3.5.1.tgz", + "integrity": "sha512-qbMblz/G/kWOol1n6iMYUA81ndzQe80p788VyMsm6dJ5edTrqpvWVK8vjLkzWZtH5kbFrg/nkYaI/f6XsP1Chw==", + "dependencies": { + "lodash": "^4.17.21" + }, + "peerDependencies": { + "@angular/core": "^18.0.0 || ^19.0.0", + "@angular/forms": "^18.0.0 || ^19.0.0", + "@jsonforms/core": "3.5.1", + "rxjs": "^6.6.0 || ^7.4.0" + } + }, + "node_modules/@jsonforms/angular-material": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@jsonforms/angular-material/-/angular-material-3.5.1.tgz", + "integrity": "sha512-x2j3B3XG1uL3aU8gzo3iRSSRPuRBzceI4Do1itXn016h3S1JT5sDtI7NWvZ+K6TQN4YGuWypaXuBm2y686DKpw==", + "dependencies": { + "hammerjs": "2.0.8", + "lodash": "^4.17.21" + }, + "peerDependencies": { + "@angular/animations": "^18.0.0 || ^19.0.0", + "@angular/cdk": "^18.0.0 || ^19.0.0", + "@angular/common": "^18.0.0 || ^19.0.0", + "@angular/core": "^18.0.0 || ^19.0.0", + "@angular/forms": "^18.0.0 || ^19.0.0", + "@angular/material": "^18.0.0 || ^19.0.0", + "@angular/platform-browser": "^18.0.0 || ^19.0.0", + "@angular/router": "^18.0.0 || ^19.0.0", + "@jsonforms/angular": "3.5.1", + "@jsonforms/core": "3.5.1", + "dayjs": "^1.11.10", + "rxjs": "^6.6.0 || ^7.4.0" + } + }, + "node_modules/@jsonforms/core": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@jsonforms/core/-/core-3.5.1.tgz", + "integrity": "sha512-Jrq/UcfvKsAprLJ+9TMFa8pKsfdyv3dAw85XstSNRcjDT19LreBlhVqIvTvtgZidg8Iet3yqy5xlNnB+XyrvrQ==", + "dependencies": { + "@types/json-schema": "^7.0.3", + "ajv": "^8.6.1", + "ajv-formats": "^2.1.0", + "lodash": "^4.17.21" + } + }, + "node_modules/@jsonforms/core/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/@jsonjoy.com/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", @@ -6112,8 +6179,7 @@ "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "node_modules/@types/lodash": { "version": "4.17.16", @@ -7166,7 +7232,6 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -10565,8 +10630,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-diff": { "version": "1.3.0", @@ -10624,7 +10688,6 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", - "dev": true, "funding": [ { "type": "github", @@ -11279,6 +11342,14 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/hammerjs": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", + "integrity": "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -12623,8 +12694,7 @@ "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -13489,8 +13559,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash-es": { "version": "4.17.21", @@ -16088,7 +16157,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, "engines": { "node": ">=0.10.0" } diff --git a/package.json b/package.json index f023331d0..3e0d4d25d 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,9 @@ "@angular/platform-server": "^19.2.8", "@angular/router": "^19.2.8", "@angular/service-worker": "^19.2.8", + "@jsonforms/angular": "^3.5.1", + "@jsonforms/angular-material": "^3.5.1", + "@jsonforms/core": "^3.5.1", "@ngbracket/ngx-layout": "^16.0.0", "@ngrx/effects": "^19.1.0", "@ngrx/operators": "^19.1.0", From 92d9c6d53cca09c3d3b4a3896f54120ff90cadc0 Mon Sep 17 00:00:00 2001 From: martintrajanovski Date: Tue, 10 Jun 2025 13:03:01 +0200 Subject: [PATCH 03/21] refactored published data creation form --- cypress/e2e/datasets/datasets-publish.cy.js | 2 +- .../datasets.routing.module.ts | 2 + src/app/datasets/datasets.module.ts | 7 + .../datasets/publish/publish.component.html | 119 +++--------- .../datasets/publish/publish.component.scss | 4 + src/app/datasets/publish/publish.component.ts | 170 +++++++----------- .../publisheddata-details.component.html | 26 +-- .../actions/published-data.actions.ts | 11 ++ .../effects/published-data.effects.ts | 18 ++ .../reducers/published-data.reducer.ts | 8 + .../selectors/published-data.selectors.ts | 5 + .../state/published-data.store.ts | 4 + 12 files changed, 168 insertions(+), 208 deletions(-) diff --git a/cypress/e2e/datasets/datasets-publish.cy.js b/cypress/e2e/datasets/datasets-publish.cy.js index d55a09442..b2a0a115a 100644 --- a/cypress/e2e/datasets/datasets-publish.cy.js +++ b/cypress/e2e/datasets/datasets-publish.cy.js @@ -37,7 +37,7 @@ describe("Datasets", () => { cy.get("#abstractInput").type("some abstract text"); - cy.get("#publishButton").click(); + cy.get("#createDataPublicationButton").click(); cy.get("#doiRow").should("exist"); }); diff --git a/src/app/app-routing/lazy/datasets-routing/datasets.routing.module.ts b/src/app/app-routing/lazy/datasets-routing/datasets.routing.module.ts index d1637dbd5..494952e4a 100644 --- a/src/app/app-routing/lazy/datasets-routing/datasets.routing.module.ts +++ b/src/app/app-routing/lazy/datasets-routing/datasets.routing.module.ts @@ -1,6 +1,7 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { AuthGuard } from "app-routing/auth.guard"; +import { leavingPageGuard } from 'app-routing/pending-changes.guard'; import { BatchViewComponent } from "datasets/batch-view/batch-view.component"; import { DashboardComponent } from "datasets/dashboard/dashboard.component"; import { DatablocksComponent } from "datasets/datablocks-table/datablocks-table.component"; @@ -21,6 +22,7 @@ const routes: Routes = [ path: "batch/publish", component: PublishComponent, canActivate: [AuthGuard], + canDeactivate: [leavingPageGuard], }, { path: ":id", diff --git a/src/app/datasets/datasets.module.ts b/src/app/datasets/datasets.module.ts index b350c0fcb..cf9afeeb7 100644 --- a/src/app/datasets/datasets.module.ts +++ b/src/app/datasets/datasets.module.ts @@ -86,6 +86,10 @@ import { DatasetDetailDynamicComponent } from "./dataset-detail/dataset-detail-d import { DatasetDetailWrapperComponent } from "./dataset-detail/dataset-detail-wrapper.component"; import { JsonHeadPipe } from "shared/pipes/json-head.pipe"; import { ThumbnailPipe } from "shared/pipes/thumbnail.pipe"; +import { JsonFormsModule } from "@jsonforms/angular"; +import { JsonFormsAngularMaterialModule } from "@jsonforms/angular-material"; +import { MatExpansionModule } from "@angular/material/expansion"; + @NgModule({ imports: [ CommonModule, @@ -147,6 +151,9 @@ import { ThumbnailPipe } from "shared/pipes/thumbnail.pipe"; CdkDrag, CdkDragHandle, FiltersModule, + JsonFormsModule, + JsonFormsAngularMaterialModule, + MatExpansionModule, ], declarations: [ BatchViewComponent, diff --git a/src/app/datasets/publish/publish.component.html b/src/app/datasets/publish/publish.component.html index 033c92f06..3d2be7bcf 100644 --- a/src/app/datasets/publish/publish.component.html +++ b/src/app/datasets/publish/publish.component.html @@ -15,66 +15,10 @@ matInput autocomplete="off" [(ngModel)]="form.title" + (change)="onFormFieldChange()" name="title" />
- - - Creators - - - {{ name }} - cancel - - - - - - - Publisher - - - - - Resource Type - - raw - derived - - - - - Description - - - Abstract - - - - Related publications - - - {{ relatedPublication }} - cancel - - - - + + + Metadata + + Here you can add metadata to your published data. + + + + @@ -130,9 +68,4 @@ - diff --git a/src/app/datasets/publish/publish.component.scss b/src/app/datasets/publish/publish.component.scss index 4e5337fbf..c0edda138 100644 --- a/src/app/datasets/publish/publish.component.scss +++ b/src/app/datasets/publish/publish.component.scss @@ -9,3 +9,7 @@ mat-form-field { mat-card { margin: 1em; } + +mat-expansion-panel { + margin: 1em 0; +} diff --git a/src/app/datasets/publish/publish.component.ts b/src/app/datasets/publish/publish.component.ts index b0583799f..d6724e45f 100644 --- a/src/app/datasets/publish/publish.component.ts +++ b/src/app/datasets/publish/publish.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, OnDestroy } from "@angular/core"; +import { Component, OnInit, OnDestroy, Output, signal } from "@angular/core"; import { COMMA, ENTER } from "@angular/cdk/keycodes"; import { Store, ActionsSubject } from "@ngrx/store"; @@ -9,18 +9,23 @@ import { prefillBatchAction } from "state-management/actions/datasets.actions"; import { publishDatasetAction, fetchPublishedDataCompleteAction, + fetchPublishedDataConfigAction, } from "state-management/actions/published-data.actions"; import { - CreatePublishedDataDto, + OutputDatasetObsoleteDto, PublishedDataService, } from "@scicatproject/scicat-sdk-ts-angular"; -import { formatDate } from "@angular/common"; import { Router } from "@angular/router"; -import { selectCurrentPublishedData } from "state-management/selectors/published-data.selectors"; -import { Subscription } from "rxjs"; -import { selectCurrentUserName } from "state-management/selectors/user.selectors"; +import { + selectCurrentPublishedData, + selectPublishedDataConfig, +} from "state-management/selectors/published-data.selectors"; +import { fromEvent, Subscription } from "rxjs"; import { AppConfigService } from "app-config.service"; +import { angularMaterialRenderers } from "@jsonforms/angular-material"; +import { isEmpty } from "lodash-es"; +import { EditableComponent } from "app-routing/pending-changes.guard"; @Component({ selector: "publish", @@ -28,33 +33,30 @@ import { AppConfigService } from "app-config.service"; styleUrls: ["./publish.component.scss"], standalone: false, }) -export class PublishComponent implements OnInit, OnDestroy { +export class PublishComponent implements OnInit, OnDestroy, EditableComponent { + private _hasUnsavedChanges = false; + renderers = angularMaterialRenderers; + schema: any = {}; + metadataData: any = {}; private datasets$ = this.store.select(selectDatasetsInBatch); - private userName$ = this.store.select(selectCurrentUserName); + private publishedDataConfig$ = this.store.select(selectPublishedDataConfig); private countSubscription: Subscription; + private publishedDataSubscription: Subscription; + private beforeUnloadSubscription: Subscription; + readonly panelOpenState = signal(false); appConfig = this.appConfigService.getConfig(); public separatorKeysCodes: number[] = [ENTER, COMMA]; public datasetCount: number; + public datasets: OutputDatasetObsoleteDto[] = []; today: number = Date.now(); + public metadataFormErrors = []; public form = { - title: "", - creators: [], - publisher: this.appConfig.facility, - resourceType: "", - description: "", - abstract: "", + title: undefined, + abstract: undefined, datasetPids: [], - publicationYear: null, - url: "", - // dataDescription: "", - // thumbnail: "", - // numberOfFiles: null, - // sizeOfArchive: null, - // downloadLink: "", - relatedPublications: [], }; public formData = null; @@ -68,84 +70,60 @@ export class PublishComponent implements OnInit, OnDestroy { private router: Router, ) {} - addCreator(event) { - if ((event.value || "").trim()) { - this.form.creators.push(event.value); - } - - if (event.input) { - event.input.value = ""; + public formIsValid() { + if (!Object.values(this.form).includes(undefined)) { + return this.form.title.length > 0 && this.form.abstract.length > 0; + } else { + return false; } } - removeCreator(creator) { - const index = this.form.creators.indexOf(creator); - - if (index >= 0) { - this.form.creators.splice(index, 1); - } + public metadataDataIsValid() { + return this.metadataFormErrors.length === 0; } - addRelatedPublication(event) { - if ((event.value || "").trim()) { - this.form.relatedPublications.push(event.value); - } - - if (event.input) { - event.input.value = ""; - } + onErrors(errors) { + this.metadataFormErrors = errors; } - removeRelatedPublication(relatedPublication) { - const index = this.form.relatedPublications.indexOf(relatedPublication); - - if (index >= 0) { - this.form.relatedPublications.splice(index, 1); + onMetadataChange(data: any) { + this.metadataData = data; + if (JSON.stringify(data) !== "{}") { + this._hasUnsavedChanges = true; } } - public formIsValid() { - if (!Object.values(this.form).includes(undefined)) { - return ( - this.form.title.length > 0 && - this.form.resourceType.length > 0 && - this.form.creators.length > 0 && - this.form.publisher.length > 0 && - this.form.description.length > 0 && - this.form.abstract.length > 0 - ); - } else { - return false; - } + onFormFieldChange() { + this._hasUnsavedChanges = true; } ngOnInit() { this.store.dispatch(prefillBatchAction()); + this.store.dispatch(fetchPublishedDataConfigAction()); this.datasets$ .pipe( first(), tap((datasets) => { if (datasets) { - const creator = datasets.map((dataset) => dataset.owner); - const unique = creator.filter( - (item, i) => creator.indexOf(item) === i, - ); - this.form.creators = unique; this.form.datasetPids = datasets.map((dataset) => dataset.pid); - // let size = 0; - // datasets.forEach((dataset) => { - // size += dataset.size; - // }); - // this.form.sizeOfArchive = size; } }), ) .subscribe(); + this.publishedDataSubscription = this.publishedDataConfig$.subscribe( + (publishedDataConfig) => { + if (!isEmpty(publishedDataConfig)) { + this.schema = publishedDataConfig.metadataSchema; + } + }, + ); + this.countSubscription = this.datasets$.subscribe((datasets) => { if (datasets) { this.datasetCount = datasets.length; + this.datasets = datasets; } }); @@ -154,9 +132,6 @@ export class PublishComponent implements OnInit, OnDestroy { .subscribe((result) => { this.form.abstract = result.abstract; this.form.title = result.title; - this.form.description = result.description; - this.form.resourceType = "raw"; - // this.form.thumbnail = result.thumbnail ?? ""; }); this.actionSubjectSubscription = this.actionsSubj.subscribe((data) => { @@ -170,48 +145,41 @@ export class PublishComponent implements OnInit, OnDestroy { .unsubscribe(); } }); + + // Prevent user from reloading page if there are unsave changes + this.beforeUnloadSubscription = fromEvent(window, "beforeunload").subscribe( + (event) => { + if (this.hasUnsavedChanges()) { + event.preventDefault(); + } + }, + ); } ngOnDestroy() { this.actionSubjectSubscription.unsubscribe(); this.countSubscription.unsubscribe(); + this.publishedDataSubscription.unsubscribe(); + this.beforeUnloadSubscription.unsubscribe(); } - public onPublish() { - const { - title, - abstract, - description, - creators, - resourceType, - datasetPids, - publisher, - url, - // thumbnail, - // numberOfFiles, - // sizeOfArchive, - // downloadLink, - relatedPublications, - } = this.form; + public onCreateDataPublication() { + const { title, abstract, datasetPids } = this.form; // TODO: Fix the types here const publishedData: any = { title: title, abstract: abstract, - dataDescription: description, - creator: creators, - resourceType: resourceType, datasetPids: datasetPids, - publisher: publisher, - publicationYear: parseInt(formatDate(this.today, "yyyy", "en_GB"), 10), - url: url, - // thumbnail: thumbnail, - // numberOfFiles: numberOfFiles, - // sizeOfArchive: sizeOfArchive, - // downloadLink: downloadLink, - relatedPublications: relatedPublications, + metadata: this.metadataData, }; + this._hasUnsavedChanges = false; + this.store.dispatch(publishDatasetAction({ data: publishedData })); } + + hasUnsavedChanges() { + return this._hasUnsavedChanges; + } } diff --git a/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html b/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html index 03c53a276..021941205 100644 --- a/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html +++ b/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html @@ -63,7 +63,7 @@ URL - + Publication Year {{ value }} @@ -81,19 +81,19 @@ - + - + - + - + @@ -111,7 +111,7 @@
Creator {{ value }}
Authors {{ value }}
Affiliation {{ value }}
Publisher {{ value }}
- + @@ -123,11 +123,11 @@ - + - +
Download Link {{ value }}
Number of Files {{ value }}
Resource Type {{ value }}
Data Description @@ -154,14 +154,14 @@ - + - + - + @@ -81,7 +89,7 @@
Related Publications{{ publishedData.relatedPublications }}{{ publishedData.metadata.relatedPublications }}
Dataset IDs @@ -212,7 +212,7 @@
- + diff --git a/src/app/state-management/actions/published-data.actions.ts b/src/app/state-management/actions/published-data.actions.ts index e0b363aa1..6521bb34c 100644 --- a/src/app/state-management/actions/published-data.actions.ts +++ b/src/app/state-management/actions/published-data.actions.ts @@ -16,6 +16,17 @@ export const fetchAllPublishedDataFailedAction = createAction( "[PublishedData] Fetch All Published Datas Failed", ); +export const fetchPublishedDataConfigAction = createAction( + "[PublishedData] Fetch Published Data Config", +); +export const fetchPublishedDataConfigCompleteAction = createAction( + "[PublishedData] Fetch Published Data Config Complete", + props<{ publishedDataConfig: any }>(), +); +export const fetchPublishedDataConfigFailedAction = createAction( + "[PublishedData] Fetch Published Data Config Failed", +); + export const fetchCountAction = createAction("[PublishedData] Fetch Count"); export const fetchCountCompleteAction = createAction( "[PublishedData] Fetch Count Complete", diff --git a/src/app/state-management/effects/published-data.effects.ts b/src/app/state-management/effects/published-data.effects.ts index d4cf8cba6..7ce7e374d 100644 --- a/src/app/state-management/effects/published-data.effects.ts +++ b/src/app/state-management/effects/published-data.effects.ts @@ -87,6 +87,24 @@ export class PublishedDataEffects { ); }); + fetchPublishedDataConfig$ = createEffect(() => { + return this.actions$.pipe( + ofType(fromActions.fetchPublishedDataConfigAction), + switchMap(() => + this.publishedDataService.publishedDataControllerGetConfigV3().pipe( + map((publishedDataConfig) => + fromActions.fetchPublishedDataConfigCompleteAction({ + publishedDataConfig, + }), + ), + catchError(() => + of(fromActions.fetchPublishedDataConfigFailedAction()), + ), + ), + ), + ); + }); + navigateToResyncedPublishedData$ = createEffect( () => { return this.actions$.pipe( diff --git a/src/app/state-management/reducers/published-data.reducer.ts b/src/app/state-management/reducers/published-data.reducer.ts index 66fd2a275..67f9bf127 100644 --- a/src/app/state-management/reducers/published-data.reducer.ts +++ b/src/app/state-management/reducers/published-data.reducer.ts @@ -8,6 +8,14 @@ import * as fromActions from "state-management/actions/published-data.actions"; const reducer = createReducer( initialPublishedDataState, + on( + fromActions.fetchPublishedDataConfigCompleteAction, + (state, { publishedDataConfig }): PublishedDataState => ({ + ...state, + publishedDataConfig, + }), + ), + on( fromActions.fetchAllPublishedDataCompleteAction, (state, { publishedData }): PublishedDataState => ({ diff --git a/src/app/state-management/selectors/published-data.selectors.ts b/src/app/state-management/selectors/published-data.selectors.ts index baa12d848..cd9dc71c4 100644 --- a/src/app/state-management/selectors/published-data.selectors.ts +++ b/src/app/state-management/selectors/published-data.selectors.ts @@ -14,6 +14,11 @@ export const selectCurrentPublishedData = createSelector( (state) => state.currentPublishedData, ); +export const selectPublishedDataConfig = createSelector( + selectPublishedDataState, + (state) => state.publishedDataConfig, +); + export const selectPublishedDataCount = createSelector( selectPublishedDataState, (state) => state.totalCount, diff --git a/src/app/state-management/state/published-data.store.ts b/src/app/state-management/state/published-data.store.ts index 4a45b196f..8c837f786 100644 --- a/src/app/state-management/state/published-data.store.ts +++ b/src/app/state-management/state/published-data.store.ts @@ -8,6 +8,8 @@ export interface PublishedDataState { totalCount: number; filters: GenericFilters; + + publishedDataConfig?: any; } export const initialPublishedDataState: PublishedDataState = { @@ -21,4 +23,6 @@ export const initialPublishedDataState: PublishedDataState = { skip: 0, limit: 25, }, + + publishedDataConfig: {}, }; From 89bcd0294431952290c3a344c0620ad2e9a49fe5 Mon Sep 17 00:00:00 2001 From: martintrajanovski Date: Tue, 10 Jun 2025 15:27:22 +0200 Subject: [PATCH 04/21] refactor the published data edit form --- .../publisheddata.routing.module.ts | 2 + src/app/datasets/publish/publish.component.ts | 10 +- .../publisheddata-details.component.html | 13 +- .../publisheddata-edit.component.html | 125 +++----------- .../publisheddata-edit.component.scss | 4 + .../publisheddata-edit.component.ts | 156 ++++++++++-------- src/app/publisheddata/publisheddata.module.ts | 6 + .../actions/published-data.actions.ts | 12 +- .../effects/published-data.effects.ts | 22 +-- 9 files changed, 153 insertions(+), 197 deletions(-) diff --git a/src/app/app-routing/lazy/publisheddata-routing/publisheddata.routing.module.ts b/src/app/app-routing/lazy/publisheddata-routing/publisheddata.routing.module.ts index f8e37f935..a14e5fec2 100644 --- a/src/app/app-routing/lazy/publisheddata-routing/publisheddata.routing.module.ts +++ b/src/app/app-routing/lazy/publisheddata-routing/publisheddata.routing.module.ts @@ -1,6 +1,7 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { AuthGuard } from "app-routing/auth.guard"; +import { leavingPageGuard } from "app-routing/pending-changes.guard"; import { PublisheddataDashboardComponent } from "publisheddata/publisheddata-dashboard/publisheddata-dashboard.component"; import { PublisheddataDetailsComponent } from "publisheddata/publisheddata-details/publisheddata-details.component"; import { PublisheddataEditComponent } from "publisheddata/publisheddata-edit/publisheddata-edit.component"; @@ -20,6 +21,7 @@ const routes: Routes = [ path: ":id/edit", component: PublisheddataEditComponent, canActivate: [AuthGuard], + canDeactivate: [leavingPageGuard], }, ]; @NgModule({ diff --git a/src/app/datasets/publish/publish.component.ts b/src/app/datasets/publish/publish.component.ts index d6724e45f..4a7a26610 100644 --- a/src/app/datasets/publish/publish.component.ts +++ b/src/app/datasets/publish/publish.component.ts @@ -7,7 +7,7 @@ import { first, tap } from "rxjs/operators"; import { selectDatasetsInBatch } from "state-management/selectors/datasets.selectors"; import { prefillBatchAction } from "state-management/actions/datasets.actions"; import { - publishDatasetAction, + createDataPublicationAction, fetchPublishedDataCompleteAction, fetchPublishedDataConfigAction, } from "state-management/actions/published-data.actions"; @@ -41,7 +41,7 @@ export class PublishComponent implements OnInit, OnDestroy, EditableComponent { private datasets$ = this.store.select(selectDatasetsInBatch); private publishedDataConfig$ = this.store.select(selectPublishedDataConfig); private countSubscription: Subscription; - private publishedDataSubscription: Subscription; + private publishedDataConfigSubscription: Subscription; private beforeUnloadSubscription: Subscription; readonly panelOpenState = signal(false); @@ -112,7 +112,7 @@ export class PublishComponent implements OnInit, OnDestroy, EditableComponent { ) .subscribe(); - this.publishedDataSubscription = this.publishedDataConfig$.subscribe( + this.publishedDataConfigSubscription = this.publishedDataConfig$.subscribe( (publishedDataConfig) => { if (!isEmpty(publishedDataConfig)) { this.schema = publishedDataConfig.metadataSchema; @@ -159,7 +159,7 @@ export class PublishComponent implements OnInit, OnDestroy, EditableComponent { ngOnDestroy() { this.actionSubjectSubscription.unsubscribe(); this.countSubscription.unsubscribe(); - this.publishedDataSubscription.unsubscribe(); + this.publishedDataConfigSubscription.unsubscribe(); this.beforeUnloadSubscription.unsubscribe(); } @@ -176,7 +176,7 @@ export class PublishComponent implements OnInit, OnDestroy, EditableComponent { this._hasUnsavedChanges = false; - this.store.dispatch(publishDatasetAction({ data: publishedData })); + this.store.dispatch(createDataPublicationAction({ data: publishedData })); } hasUnsavedChanges() { diff --git a/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html b/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html index 021941205..3e852f455 100644 --- a/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html +++ b/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html @@ -81,9 +81,14 @@ - - - + + + + @@ -95,7 +100,7 @@ - +
Creator{{ value }}
Creators + + {{ creator.name }}{{ isLast ? "" : ", " }} + +
Authors
Publisher{{ value }}{{ value.name }}
diff --git a/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.html b/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.html index 3d3490e5d..1eefa53ed 100644 --- a/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.html +++ b/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.html @@ -25,74 +25,6 @@ /> - - - - {{ item }} - cancel - - - - - - - - - - - - - - - - - - - - - - - {{ item }} - cancel - - - - + + + Metadata + + Here you can edit the metadata. + + + + - + @@ -63,7 +71,7 @@
URL
Publication Year {{ value }}
- + - - - + + + - + - + @@ -116,7 +130,7 @@
Creators @@ -90,15 +98,21 @@
Authors{{ value }}
Contributors + + {{ contributor.name }}{{ isLast ? "" : ", " }} + +
Affiliation {{ value }}
Publisher {{ value.name }}
- + @@ -128,11 +142,11 @@ - + - +
Download Link {{ value }}
Number of Files {{ value }}
Resource Type {{ value }}
Data Description @@ -159,12 +173,12 @@ - + @@ -192,7 +206,11 @@ id="editBtn" class="edit-button" color="primary" - *ngIf="appConfig.editPublishedData" + *ngIf=" + appConfig.editPublishedData && + (publishedData.status === 'public' || + publishedData.status === 'private') + " (click)="onEditClick()" > Edit @@ -217,7 +235,7 @@
- + diff --git a/src/app/publisheddata/publisheddata-details/publisheddata-details.component.ts b/src/app/publisheddata/publisheddata-details/publisheddata-details.component.ts index dd9ea48c2..502eee377 100644 --- a/src/app/publisheddata/publisheddata-details/publisheddata-details.component.ts +++ b/src/app/publisheddata/publisheddata-details/publisheddata-details.component.ts @@ -4,6 +4,7 @@ import { Store } from "@ngrx/store"; import { ActivatedRoute, Router } from "@angular/router"; import { fetchPublishedDataAction, + publishPublishedDataAction, registerPublishedDataAction, } from "state-management/actions/published-data.actions"; import { Subscription } from "rxjs"; @@ -19,7 +20,7 @@ import { AppConfigService } from "app-config.service"; }) export class PublisheddataDetailsComponent implements OnInit, OnDestroy { currentData$ = this.store.select(selectCurrentPublishedData); - publishedData: PublishedData; + publishedData: PublishedData & { metadata?: any }; subscriptions: Subscription[] = []; appConfig = this.appConfigService.getConfig(); show = false; @@ -63,6 +64,10 @@ export class PublisheddataDetailsComponent implements OnInit, OnDestroy { this.store.dispatch(registerPublishedDataAction({ doi })); } + onPublishClick(doi: string) { + this.store.dispatch(publishPublishedDataAction({ doi })); + } + onEditClick() { const id = encodeURIComponent(this.doi); this.router.navigateByUrl("/publishedDatasets/" + id + "/edit"); diff --git a/src/app/state-management/actions/published-data.actions.ts b/src/app/state-management/actions/published-data.actions.ts index 3985ded08..97b5d6fa8 100644 --- a/src/app/state-management/actions/published-data.actions.ts +++ b/src/app/state-management/actions/published-data.actions.ts @@ -72,6 +72,18 @@ export const registerPublishedDataFailedAction = createAction( "[PublishedData] Register Published Data Failed", ); +export const publishPublishedDataAction = createAction( + "[PublishedData] Publish Published Data", + props<{ doi: string }>(), +); +export const publishPublishedDataCompleteAction = createAction( + "[PublishedData] Publish Published Data Complete", + props<{ publishedData: PublishedData }>(), +); +export const publishPublishedDataFailedAction = createAction( + "[PublishedData] Publish Published Data Failed", +); + export const resyncPublishedDataAction = createAction( "[PublishedData] Resync Published Data", props<{ doi: string; data: UpdatePublishedDataDto }>(), diff --git a/src/app/state-management/effects/published-data.effects.ts b/src/app/state-management/effects/published-data.effects.ts index c9ed27309..ea6a05664 100644 --- a/src/app/state-management/effects/published-data.effects.ts +++ b/src/app/state-management/effects/published-data.effects.ts @@ -136,6 +136,20 @@ export class PublishedDataEffects { ); }); + publishPublishedData$ = createEffect(() => { + return this.actions$.pipe( + ofType(fromActions.publishPublishedDataAction), + switchMap(({ doi }) => + this.publishedDataService.publishedDataControllerPublishV3(doi).pipe( + mergeMap((publishedData) => [ + fromActions.publishPublishedDataCompleteAction({ publishedData }), + ]), + catchError(() => of(fromActions.publishPublishedDataFailedAction())), + ), + ), + ); + }); + createDataPublicationCompleteMessage$ = createEffect(() => { return this.actions$.pipe( ofType(fromActions.createDataPublicationCompleteAction), @@ -220,6 +234,7 @@ export class PublishedDataEffects { fromActions.sortByColumnAction, fromActions.fetchPublishedDataAction, fromActions.createDataPublicationAction, + fromActions.publishPublishedDataAction, fromActions.registerPublishedDataAction, fromActions.resyncPublishedDataCompleteAction, ), @@ -238,6 +253,8 @@ export class PublishedDataEffects { fromActions.fetchPublishedDataFailedAction, fromActions.createDataPublicationCompleteAction, fromActions.createDataPublicationFailedAction, + fromActions.publishPublishedDataCompleteAction, + fromActions.publishPublishedDataFailedAction, fromActions.registerPublishedDataCompleteAction, fromActions.registerPublishedDataFailedAction, fromActions.resyncPublishedDataCompleteAction, diff --git a/src/app/state-management/reducers/published-data.reducer.ts b/src/app/state-management/reducers/published-data.reducer.ts index 67f9bf127..ec43f9206 100644 --- a/src/app/state-management/reducers/published-data.reducer.ts +++ b/src/app/state-management/reducers/published-data.reducer.ts @@ -40,6 +40,14 @@ const reducer = createReducer( }), ), + on( + fromActions.publishPublishedDataCompleteAction, + (state, { publishedData }): PublishedDataState => ({ + ...state, + currentPublishedData: publishedData, + }), + ), + on( fromActions.changePageAction, (state, { page, limit }): PublishedDataState => { From a08be644283e693534f9ba1b8b4abe497a6bc571 Mon Sep 17 00:00:00 2001 From: martin-trajanovski Date: Fri, 13 Jun 2025 14:45:48 +0200 Subject: [PATCH 06/21] ui improvements in the metadata and registration --- src/app/datasets/publish/publish.component.ts | 6 +++++- .../publisheddata-edit.component.html | 19 ++----------------- .../publisheddata-edit.component.ts | 14 ++++++++++++-- .../actions/published-data.actions.ts | 1 + .../effects/published-data.effects.ts | 9 ++++++--- 5 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/app/datasets/publish/publish.component.ts b/src/app/datasets/publish/publish.component.ts index 4a7a26610..b9b6d6d00 100644 --- a/src/app/datasets/publish/publish.component.ts +++ b/src/app/datasets/publish/publish.component.ts @@ -165,13 +165,17 @@ export class PublishComponent implements OnInit, OnDestroy, EditableComponent { public onCreateDataPublication() { const { title, abstract, datasetPids } = this.form; + const metadata = { + ...this.metadataData, + landingPage: this.appConfig.landingPage, + }; // TODO: Fix the types here const publishedData: any = { title: title, abstract: abstract, datasetPids: datasetPids, - metadata: this.metadataData, + metadata: metadata, }; this._hasUnsavedChanges = false; diff --git a/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.html b/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.html index 1eefa53ed..77904d832 100644 --- a/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.html +++ b/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.html @@ -8,11 +8,7 @@ > -
+
- diff --git a/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.ts b/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.ts index 89e2835d5..c0f98f285 100644 --- a/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.ts +++ b/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.ts @@ -21,6 +21,7 @@ import { fromEvent, Observable, Subscription } from "rxjs"; import { angularMaterialRenderers } from "@jsonforms/angular-material"; import { EditableComponent } from "app-routing/pending-changes.guard"; import { isEmpty } from "lodash-es"; +import { AppConfigService } from "app-config.service"; @Component({ selector: "publisheddata-edit", @@ -53,18 +54,27 @@ export class PublisheddataEditComponent publishedDataConfigSubscription: Subscription; beforeUnloadSubscription: Subscription; initialMetadata: string; + appConfig = this.appConfigService.getConfig(); constructor( private formBuilder: FormBuilder, private route: ActivatedRoute, private router: Router, private store: Store, + private appConfigService: AppConfigService, ) {} - public onUpdate() { + public onPublishedDataUpdate() { if (this.form.valid) { const { doi, ...rest } = this.form.value; - const metadata = this.metadataData; + const metadata = { + ...this.metadataData, + landingPage: this.appConfig.landingPage, + }; + + if (this.panelOpenState() && !this.metadataDataIsValid()) { + return; + } if (doi) { this.store.dispatch( diff --git a/src/app/state-management/actions/published-data.actions.ts b/src/app/state-management/actions/published-data.actions.ts index 97b5d6fa8..7f7c68290 100644 --- a/src/app/state-management/actions/published-data.actions.ts +++ b/src/app/state-management/actions/published-data.actions.ts @@ -70,6 +70,7 @@ export const registerPublishedDataCompleteAction = createAction( ); export const registerPublishedDataFailedAction = createAction( "[PublishedData] Register Published Data Failed", + props<{ error: string[] }>(), ); export const publishPublishedDataAction = createAction( diff --git a/src/app/state-management/effects/published-data.effects.ts b/src/app/state-management/effects/published-data.effects.ts index ea6a05664..4a7e34df9 100644 --- a/src/app/state-management/effects/published-data.effects.ts +++ b/src/app/state-management/effects/published-data.effects.ts @@ -189,7 +189,9 @@ export class PublishedDataEffects { }), fromActions.fetchPublishedDataAction({ id: doi }), ]), - catchError(() => of(fromActions.registerPublishedDataFailedAction())), + catchError((error) => + of(fromActions.registerPublishedDataFailedAction(error)), + ), ), ), ); @@ -215,10 +217,11 @@ export class PublishedDataEffects { registerPublishedDataFailedMessage$ = createEffect(() => { return this.actions$.pipe( ofType(fromActions.registerPublishedDataFailedAction), - switchMap(() => { + switchMap((errors) => { + debugger; const message = { type: MessageType.Error, - content: "Registration Failed", + content: `Registration Failed. ${errors.error.map((e) => e.replaceAll("instance.", "metadata.")).join(", ")}`, duration: 5000, }; return of(showMessageAction({ message })); From fec6dca6eb0aa30e6154984b548403f2534a395d Mon Sep 17 00:00:00 2001 From: martin-trajanovski Date: Wed, 18 Jun 2025 08:50:19 +0200 Subject: [PATCH 07/21] add ui schema to the jsonforms --- src/app/datasets/publish/publish.component.html | 1 + src/app/datasets/publish/publish.component.ts | 7 +++++++ .../publisheddata-details.component.html | 4 +--- .../publisheddata-edit/publisheddata-edit.component.html | 1 + .../publisheddata-edit/publisheddata-edit.component.ts | 7 +++++++ 5 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/app/datasets/publish/publish.component.html b/src/app/datasets/publish/publish.component.html index 3d2be7bcf..5d3394f9d 100644 --- a/src/app/datasets/publish/publish.component.html +++ b/src/app/datasets/publish/publish.component.html @@ -47,6 +47,7 @@ { if (!isEmpty(publishedDataConfig)) { this.schema = publishedDataConfig.metadataSchema; + // NOTE: We set the publicationYear by the system, so we remove it from the required fields in the frontend + this.schema.required.splice( + this.schema.required.indexOf("publicationYear"), + 1, + ); + this.uiSchema = publishedDataConfig.uiSchema; } }, ); diff --git a/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html b/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html index d4e1fc37a..27c0cb70b 100644 --- a/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html +++ b/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html @@ -197,9 +197,7 @@ - + + + diff --git a/src/app/datasets/publish/publish.component.scss b/src/app/datasets/publish/publish.component.scss index c0edda138..a407912ff 100644 --- a/src/app/datasets/publish/publish.component.scss +++ b/src/app/datasets/publish/publish.component.scss @@ -13,3 +13,11 @@ mat-card { mat-expansion-panel { margin: 1em 0; } + +button.save-and-continue { + margin-left: 1em; +} + +button.cancel { + margin-left: 1em; +} diff --git a/src/app/datasets/publish/publish.component.ts b/src/app/datasets/publish/publish.component.ts index 47e218143..8219feb8a 100644 --- a/src/app/datasets/publish/publish.component.ts +++ b/src/app/datasets/publish/publish.component.ts @@ -8,12 +8,16 @@ import { selectDatasetsInBatch } from "state-management/selectors/datasets.selec import { prefillBatchAction } from "state-management/actions/datasets.actions"; import { createDataPublicationAction, - fetchPublishedDataCompleteAction, + createDataPublicationCompleteAction, + fetchPublishedDataAction, fetchPublishedDataConfigAction, + resyncPublishedDataAction, + saveDataPublicationAction, + updatePublishedDataAction, } from "state-management/actions/published-data.actions"; import { - OutputDatasetObsoleteDto, + CreatePublishedDataDto, PublishedDataService, } from "@scicatproject/scicat-sdk-ts-angular"; import { Router } from "@angular/router"; @@ -50,9 +54,10 @@ export class PublishComponent implements OnInit, OnDestroy, EditableComponent { public separatorKeysCodes: number[] = [ENTER, COMMA]; public datasetCount: number; - public datasets: OutputDatasetObsoleteDto[] = []; today: number = Date.now(); public metadataFormErrors = []; + savedPublishedDataDoi: string | null = null; + initialMetadata = JSON.stringify({}); public form = { title: undefined, @@ -89,7 +94,8 @@ export class PublishComponent implements OnInit, OnDestroy, EditableComponent { onMetadataChange(data: any) { this.metadataData = data; - if (JSON.stringify(data) !== "{}") { + + if (JSON.stringify(data) !== this.initialMetadata) { this._hasUnsavedChanges = true; } } @@ -98,6 +104,43 @@ export class PublishComponent implements OnInit, OnDestroy, EditableComponent { this._hasUnsavedChanges = true; } + checkForSavedData() { + const savedPublishedData = JSON.parse( + localStorage.getItem("publishedData"), + ); + + if (savedPublishedData && savedPublishedData.id) { + this.savedPublishedDataDoi = savedPublishedData.doi; + this.store + .select(selectCurrentPublishedData) + .subscribe((publishedData) => { + if (publishedData) { + this.form.title = publishedData.title; + this.form.abstract = publishedData.abstract; + + if (publishedData.metadata) { + this.metadataData = publishedData.metadata; + + this.initialMetadata = JSON.stringify(publishedData.metadata); + } + } + + this._hasUnsavedChanges = false; + }); + + this.store.dispatch( + fetchPublishedDataAction({ id: this.savedPublishedDataDoi }), + ); + } else { + this.publishedDataApi + .publishedDataControllerFormPopulateV3(this.form.datasetPids[0]) + .subscribe((result) => { + this.form.abstract = result.abstract; + this.form.title = result.title; + }); + } + } + ngOnInit() { this.store.dispatch(prefillBatchAction()); this.store.dispatch(fetchPublishedDataConfigAction()); @@ -113,6 +156,8 @@ export class PublishComponent implements OnInit, OnDestroy, EditableComponent { ) .subscribe(); + this.checkForSavedData(); + this.publishedDataConfigSubscription = this.publishedDataConfig$.subscribe( (publishedDataConfig) => { if (!isEmpty(publishedDataConfig)) { @@ -130,19 +175,11 @@ export class PublishComponent implements OnInit, OnDestroy, EditableComponent { this.countSubscription = this.datasets$.subscribe((datasets) => { if (datasets) { this.datasetCount = datasets.length; - this.datasets = datasets; } }); - this.publishedDataApi - .publishedDataControllerFormPopulateV3(this.form.datasetPids[0]) - .subscribe((result) => { - this.form.abstract = result.abstract; - this.form.title = result.title; - }); - this.actionSubjectSubscription = this.actionsSubj.subscribe((data) => { - if (data.type === fetchPublishedDataCompleteAction.type) { + if (data.type === createDataPublicationCompleteAction.type) { this.store .select(selectCurrentPublishedData) .subscribe((publishedData) => { @@ -170,24 +207,58 @@ export class PublishComponent implements OnInit, OnDestroy, EditableComponent { this.beforeUnloadSubscription.unsubscribe(); } - public onCreateDataPublication() { + getPublishedDataForCreation() { const { title, abstract, datasetPids } = this.form; const metadata = { ...this.metadataData, landingPage: this.appConfig.landingPage, }; - - // TODO: Fix the types here - const publishedData: any = { + return { title: title, abstract: abstract, datasetPids: datasetPids, metadata: metadata, - }; + } as CreatePublishedDataDto; + } + + public onSaveAndContinue() { + const publishedData = this.getPublishedDataForCreation(); + + this._hasUnsavedChanges = false; + + if (this.savedPublishedDataDoi) { + this.store.dispatch( + resyncPublishedDataAction({ + doi: this.savedPublishedDataDoi, + data: publishedData, + }), + ); + } else { + this.store.dispatch(createDataPublicationAction({ data: publishedData })); + } + } + + public onSaveChanges() { + const publishedData = this.getPublishedDataForCreation(); + + if (this.savedPublishedDataDoi) { + this.store.dispatch( + updatePublishedDataAction({ + doi: this.savedPublishedDataDoi, + data: publishedData, + }), + ); + } else { + this.store.dispatch(saveDataPublicationAction({ data: publishedData })); + } + + this._hasUnsavedChanges = false; + } + public onCancel() { this._hasUnsavedChanges = false; - this.store.dispatch(createDataPublicationAction({ data: publishedData })); + this.router.navigateByUrl("/datasets/batch"); } hasUnsavedChanges() { diff --git a/src/app/state-management/actions/published-data.actions.ts b/src/app/state-management/actions/published-data.actions.ts index 7f7c68290..bbe88c638 100644 --- a/src/app/state-management/actions/published-data.actions.ts +++ b/src/app/state-management/actions/published-data.actions.ts @@ -59,6 +59,24 @@ export const createDataPublicationCompleteAction = createAction( export const createDataPublicationFailedAction = createAction( "[PublishedData] Create Data Publication Failed", ); +export const saveDataPublicationAction = createAction( + "[PublishedData] Save Data Publication", + props<{ data: CreatePublishedDataDto }>(), +); +export const saveDataPublicationCompleteAction = createAction( + "[PublishedData] Save Data Publication Complete", + props<{ publishedData: PublishedData }>(), +); +export const saveDataPublicationFailedAction = createAction( + "[PublishedData] Save Data Publication Failed", +); +export const saveDataPublicationInLocalStorage = createAction( + "[PublishedData] Save Data Publication In Local Storage", + props<{ publishedData: PublishedData }>(), +); +export const clearDataPublicationFromLocalStorage = createAction( + "[PublishedData] Clear Data Publication In Local Storage", +); export const registerPublishedDataAction = createAction( "[PublishedData] Register Published Data", @@ -97,6 +115,18 @@ export const resyncPublishedDataFailedAction = createAction( "[PublishedData] Resync Published Data Failed", ); +export const updatePublishedDataAction = createAction( + "[PublishedData] Update Published Data", + props<{ doi: string; data: UpdatePublishedDataDto }>(), +); +export const updatePublishedDataCompleteAction = createAction( + "[PublishedData] Update Published Data Complete", + props<{ publishedData: PublishedData }>(), +); +export const updatePublishedDataFailedAction = createAction( + "[PublishedData] Update Published Data Failed", +); + export const changePageAction = createAction( "[PublishedData] Change Page", props<{ page: number; limit: number }>(), diff --git a/src/app/state-management/effects/published-data.effects.ts b/src/app/state-management/effects/published-data.effects.ts index ffc73a68a..d5a085e27 100644 --- a/src/app/state-management/effects/published-data.effects.ts +++ b/src/app/state-management/effects/published-data.effects.ts @@ -11,6 +11,7 @@ import { selectQueryParams, } from "state-management/selectors/published-data.selectors"; import * as fromActions from "state-management/actions/published-data.actions"; +import * as datasetActions from "state-management/actions/datasets.actions"; import { mergeMap, map, @@ -18,6 +19,7 @@ import { switchMap, exhaustMap, filter, + tap, } from "rxjs/operators"; import { of } from "rxjs"; import { MessageType } from "state-management/models"; @@ -121,14 +123,55 @@ export class PublishedDataEffects { { dispatch: false }, ); + saveDataPublicationInLocalStorage$ = createEffect( + () => { + return this.actions$.pipe( + ofType(fromActions.saveDataPublicationInLocalStorage), + tap(({ publishedData }) => + localStorage.setItem("publishedData", JSON.stringify(publishedData)), + ), + ); + }, + { dispatch: false }, + ); + + clearDataPublicationFromLocalStorage$ = createEffect( + () => { + return this.actions$.pipe( + ofType(fromActions.clearDataPublicationFromLocalStorage), + tap(() => localStorage.removeItem("publishedData")), + ); + }, + { dispatch: false }, + ); + + saveDataPublication$ = createEffect(() => { + return this.actions$.pipe( + ofType(fromActions.saveDataPublicationAction), + switchMap(({ data }) => + this.publishedDataService.publishedDataControllerCreateV3(data).pipe( + mergeMap((publishedData) => [ + fromActions.saveDataPublicationCompleteAction({ publishedData }), + fromActions.saveDataPublicationInLocalStorage({ publishedData }), + ]), + catchError(() => of(fromActions.saveDataPublicationFailedAction())), + ), + ), + ); + }); + createDataPublication$ = createEffect(() => { return this.actions$.pipe( ofType(fromActions.createDataPublicationAction), switchMap(({ data }) => this.publishedDataService.publishedDataControllerCreateV3(data).pipe( mergeMap((publishedData) => [ - fromActions.createDataPublicationCompleteAction({ publishedData }), + fromActions.createDataPublicationCompleteAction({ + publishedData, + }), fromActions.fetchPublishedDataAction({ id: publishedData.doi }), + datasetActions.clearBatchAction(), + fromActions.clearDataPublicationFromLocalStorage(), ]), catchError(() => of(fromActions.createDataPublicationFailedAction())), ), @@ -197,6 +240,22 @@ export class PublishedDataEffects { ); }); + updatePublishedData$ = createEffect(() => { + return this.actions$.pipe( + ofType(fromActions.updatePublishedDataAction), + switchMap(({ doi, data }) => + this.publishedDataService + .publishedDataControllerUpdateV3(doi, data) + .pipe( + mergeMap((publishedData) => [ + fromActions.updatePublishedDataCompleteAction({ publishedData }), + ]), + catchError(() => of(fromActions.updatePublishedDataFailedAction())), + ), + ), + ); + }); + resyncPublishedData$ = createEffect(() => { return this.actions$.pipe( ofType(fromActions.resyncPublishedDataAction), @@ -207,6 +266,8 @@ export class PublishedDataEffects { mergeMap((publishedData) => [ fromActions.resyncPublishedDataCompleteAction(publishedData), fromActions.fetchPublishedDataAction({ id: doi }), + datasetActions.clearBatchAction(), + fromActions.clearDataPublicationFromLocalStorage(), ]), catchError(() => of(fromActions.resyncPublishedDataFailedAction())), ), @@ -236,9 +297,11 @@ export class PublishedDataEffects { fromActions.sortByColumnAction, fromActions.fetchPublishedDataAction, fromActions.createDataPublicationAction, + fromActions.saveDataPublicationAction, fromActions.publishPublishedDataAction, fromActions.registerPublishedDataAction, - fromActions.resyncPublishedDataCompleteAction, + fromActions.resyncPublishedDataAction, + fromActions.updatePublishedDataAction, ), switchMap(() => of(loadingAction())), ); @@ -254,12 +317,15 @@ export class PublishedDataEffects { fromActions.fetchPublishedDataCompleteAction, fromActions.fetchPublishedDataFailedAction, fromActions.createDataPublicationCompleteAction, + fromActions.saveDataPublicationCompleteAction, fromActions.createDataPublicationFailedAction, + fromActions.saveDataPublicationFailedAction, fromActions.publishPublishedDataCompleteAction, fromActions.publishPublishedDataFailedAction, fromActions.registerPublishedDataCompleteAction, fromActions.registerPublishedDataFailedAction, fromActions.resyncPublishedDataCompleteAction, + fromActions.updatePublishedDataCompleteAction, ), switchMap(() => of(loadingCompleteAction())), ); diff --git a/src/app/state-management/reducers/published-data.reducer.ts b/src/app/state-management/reducers/published-data.reducer.ts index ec43f9206..8d3502aed 100644 --- a/src/app/state-management/reducers/published-data.reducer.ts +++ b/src/app/state-management/reducers/published-data.reducer.ts @@ -32,6 +32,14 @@ const reducer = createReducer( }), ), + on( + fromActions.saveDataPublicationCompleteAction, + (state, { publishedData }): PublishedDataState => ({ + ...state, + currentPublishedData: publishedData, + }), + ), + on( fromActions.fetchPublishedDataCompleteAction, (state, { publishedData }): PublishedDataState => ({ From af6fa4801f74f9d000dffea6ed7d8fd30b36b1ce Mon Sep 17 00:00:00 2001 From: martin-trajanovski Date: Thu, 19 Jun 2025 21:20:08 +0200 Subject: [PATCH 10/21] improve error messages and fix some small issues --- src/app/datasets/publish/publish.component.ts | 14 ++++++------- .../publisheddata-dashboard.component.ts | 8 ++++++++ .../publisheddata-details.component.html | 4 +--- .../actions/published-data.actions.ts | 1 + .../effects/published-data.effects.ts | 20 +++++++++++++++++-- 5 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/app/datasets/publish/publish.component.ts b/src/app/datasets/publish/publish.component.ts index 8219feb8a..06aabcea7 100644 --- a/src/app/datasets/publish/publish.component.ts +++ b/src/app/datasets/publish/publish.component.ts @@ -18,6 +18,7 @@ import { import { CreatePublishedDataDto, + PublishedData, PublishedDataService, } from "@scicatproject/scicat-sdk-ts-angular"; import { Router } from "@angular/router"; @@ -180,13 +181,12 @@ export class PublishComponent implements OnInit, OnDestroy, EditableComponent { this.actionSubjectSubscription = this.actionsSubj.subscribe((data) => { if (data.type === createDataPublicationCompleteAction.type) { - this.store - .select(selectCurrentPublishedData) - .subscribe((publishedData) => { - const doi = encodeURIComponent(publishedData.doi); - this.router.navigateByUrl("/publishedDatasets/" + doi); - }) - .unsubscribe(); + const publishedData = ( + data as { type: string; publishedData: PublishedData } + ).publishedData; + + const doi = encodeURIComponent(publishedData.doi); + this.router.navigateByUrl("/publishedDatasets/" + doi); } }); diff --git a/src/app/publisheddata/publisheddata-dashboard/publisheddata-dashboard.component.ts b/src/app/publisheddata/publisheddata-dashboard/publisheddata-dashboard.component.ts index 0b9e21f19..4527288b7 100644 --- a/src/app/publisheddata/publisheddata-dashboard/publisheddata-dashboard.component.ts +++ b/src/app/publisheddata/publisheddata-dashboard/publisheddata-dashboard.component.ts @@ -50,6 +50,14 @@ export class PublisheddataDashboardComponent implements OnInit, OnDestroy { matchMode: "contains", hideOrder: 2, }, + { + id: "status", + label: "Status", + icon: "face", + canSort: true, + matchMode: "contains", + hideOrder: 3, + }, { id: "createdBy", icon: "account_circle", diff --git a/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html b/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html index 27c0cb70b..078c4badc 100644 --- a/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html +++ b/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html @@ -205,9 +205,7 @@ class="edit-button" color="primary" *ngIf=" - appConfig.editPublishedData && - (publishedData.status === 'public' || - publishedData.status === 'private') + appConfig.editPublishedData && publishedData.status === 'private' " (click)="onEditClick()" > diff --git a/src/app/state-management/actions/published-data.actions.ts b/src/app/state-management/actions/published-data.actions.ts index bbe88c638..a0e70f24a 100644 --- a/src/app/state-management/actions/published-data.actions.ts +++ b/src/app/state-management/actions/published-data.actions.ts @@ -101,6 +101,7 @@ export const publishPublishedDataCompleteAction = createAction( ); export const publishPublishedDataFailedAction = createAction( "[PublishedData] Publish Published Data Failed", + props<{ error: string[] }>(), ); export const resyncPublishedDataAction = createAction( diff --git a/src/app/state-management/effects/published-data.effects.ts b/src/app/state-management/effects/published-data.effects.ts index d5a085e27..26d1ccfe8 100644 --- a/src/app/state-management/effects/published-data.effects.ts +++ b/src/app/state-management/effects/published-data.effects.ts @@ -187,7 +187,9 @@ export class PublishedDataEffects { mergeMap((publishedData) => [ fromActions.publishPublishedDataCompleteAction({ publishedData }), ]), - catchError(() => of(fromActions.publishPublishedDataFailedAction())), + catchError((error) => + of(fromActions.publishPublishedDataFailedAction(error)), + ), ), ), ); @@ -281,7 +283,21 @@ export class PublishedDataEffects { switchMap((errors) => { const message = { type: MessageType.Error, - content: `Registration Failed. ${errors.error.map((e) => e.replaceAll("instance.", "metadata.")).join(", ")}`, + content: `Registration Failed. ${errors.error.map((e) => e.replaceAll("instance", "metadata")).join(", ")}`, + duration: 5000, + }; + return of(showMessageAction({ message })); + }), + ); + }); + + publishPublishedDataFailedMessage$ = createEffect(() => { + return this.actions$.pipe( + ofType(fromActions.publishPublishedDataFailedAction), + switchMap((errors) => { + const message = { + type: MessageType.Error, + content: `Publishing Failed. ${errors.error.map((e) => e.replaceAll("instance", "metadata")).join(", ")}`, duration: 5000, }; return of(showMessageAction({ message })); From 66fdeb058611e8af7efd154c74b5b83acb8e23b5 Mon Sep 17 00:00:00 2001 From: martin-trajanovski Date: Mon, 23 Jun 2025 16:55:24 +0200 Subject: [PATCH 11/21] improve the flow of editing dataset list of a private published data --- .../batch-view/batch-view.component.html | 37 +++++++++- .../batch-view/batch-view.component.ts | 33 +++++++++ src/app/datasets/publish/publish.component.ts | 56 +++++---------- .../publisheddata-details.component.html | 9 +++ .../publisheddata-details.component.ts | 10 +++ .../publisheddata-edit.component.html | 22 +++++- .../publisheddata-edit.component.scss | 8 +++ .../publisheddata-edit.component.ts | 3 +- .../actions/datasets.actions.ts | 4 ++ .../actions/published-data.actions.ts | 30 ++++++-- .../effects/published-data.effects.ts | 72 +++++++++++++++++-- .../reducers/datasets.reducer.ts | 6 ++ 12 files changed, 235 insertions(+), 55 deletions(-) diff --git a/src/app/datasets/batch-view/batch-view.component.html b/src/app/datasets/batch-view/batch-view.component.html index cce14bc9f..2ea44da6a 100644 --- a/src/app/datasets/batch-view/batch-view.component.html +++ b/src/app/datasets/batch-view/batch-view.component.html @@ -1,11 +1,45 @@
- + +
Related Publications{{ publishedData.metadata.relatedPublications }}{{ publishedData.metadata?.relatedPublications }}
Dataset IDs + +
diff --git a/src/app/publisheddata/publisheddata-details/publisheddata-details.component.ts b/src/app/publisheddata/publisheddata-details/publisheddata-details.component.ts index 502eee377..902ac1e85 100644 --- a/src/app/publisheddata/publisheddata-details/publisheddata-details.component.ts +++ b/src/app/publisheddata/publisheddata-details/publisheddata-details.component.ts @@ -4,6 +4,7 @@ import { Store } from "@ngrx/store"; import { ActivatedRoute, Router } from "@angular/router"; import { fetchPublishedDataAction, + fetchRelatedDatasetsAndAddToBatchAction, publishPublishedDataAction, registerPublishedDataAction, } from "state-management/actions/published-data.actions"; @@ -73,6 +74,15 @@ export class PublisheddataDetailsComponent implements OnInit, OnDestroy { this.router.navigateByUrl("/publishedDatasets/" + id + "/edit"); } + onEditDatasetList() { + this.store.dispatch( + fetchRelatedDatasetsAndAddToBatchAction({ + datasetPids: this.publishedData.datasetPids, + publishedDataDoi: this.publishedData.doi, + }), + ); + } + isUrl(dataDescription: string): boolean { return dataDescription.includes("http"); } diff --git a/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.html b/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.html index 668b8b840..c4cdd224c 100644 --- a/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.html +++ b/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.html @@ -58,14 +58,30 @@ - + diff --git a/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.scss b/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.scss index edc0dbfef..ef69a72b6 100644 --- a/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.scss +++ b/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.scss @@ -17,3 +17,11 @@ mat-card { mat-expansion-panel { margin: 1em 0; } + +button.save-and-continue { + margin-left: 1em; +} + +button.cancel { + margin-left: 1em; +} diff --git a/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.ts b/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.ts index 1e0f7d7fc..805bbf2b2 100644 --- a/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.ts +++ b/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.ts @@ -65,7 +65,7 @@ export class PublisheddataEditComponent private appConfigService: AppConfigService, ) {} - public onPublishedDataUpdate() { + public onPublishedDataUpdate(shouldRedirect: boolean = false) { if (this.form.valid) { const { doi, ...rest } = this.form.value; const metadata = { @@ -82,6 +82,7 @@ export class PublisheddataEditComponent resyncPublishedDataAction({ doi, data: { ...rest, metadata }, + redirect: shouldRedirect, }), ); } diff --git a/src/app/state-management/actions/datasets.actions.ts b/src/app/state-management/actions/datasets.actions.ts index f12a8881a..16b30d6e5 100644 --- a/src/app/state-management/actions/datasets.actions.ts +++ b/src/app/state-management/actions/datasets.actions.ts @@ -226,6 +226,10 @@ export const selectDatasetAction = createAction( "[Dataset] Select Dataset", props<{ dataset: OutputDatasetObsoleteDto }>(), ); +export const selectDatasetsAction = createAction( + "[Dataset] Select Datasets", + props<{ datasets: OutputDatasetObsoleteDto[] }>(), +); export const deselectDatasetAction = createAction( "[Dataset] Deselect Dataset", props<{ dataset: OutputDatasetObsoleteDto }>(), diff --git a/src/app/state-management/actions/published-data.actions.ts b/src/app/state-management/actions/published-data.actions.ts index a0e70f24a..54755d67a 100644 --- a/src/app/state-management/actions/published-data.actions.ts +++ b/src/app/state-management/actions/published-data.actions.ts @@ -1,8 +1,8 @@ import { createAction, props } from "@ngrx/store"; import { CreatePublishedDataDto, + PartialUpdatePublishedDataDto, PublishedData, - UpdatePublishedDataDto, } from "@scicatproject/scicat-sdk-ts-angular"; export const fetchAllPublishedDataAction = createAction( @@ -106,11 +106,15 @@ export const publishPublishedDataFailedAction = createAction( export const resyncPublishedDataAction = createAction( "[PublishedData] Resync Published Data", - props<{ doi: string; data: UpdatePublishedDataDto }>(), + props<{ + doi: string; + data: PartialUpdatePublishedDataDto; + redirect: boolean; + }>(), ); export const resyncPublishedDataCompleteAction = createAction( "[PublishedData] Resync Published Data Complete", - props<{ publishedData: PublishedData }>(), + props<{ publishedData: PublishedData; redirect: boolean }>(), ); export const resyncPublishedDataFailedAction = createAction( "[PublishedData] Resync Published Data Failed", @@ -118,7 +122,7 @@ export const resyncPublishedDataFailedAction = createAction( export const updatePublishedDataAction = createAction( "[PublishedData] Update Published Data", - props<{ doi: string; data: UpdatePublishedDataDto }>(), + props<{ doi: string; data: PartialUpdatePublishedDataDto }>(), ); export const updatePublishedDataCompleteAction = createAction( "[PublishedData] Update Published Data Complete", @@ -141,3 +145,21 @@ export const sortByColumnAction = createAction( export const clearPublishedDataStateAction = createAction( "[PublischedData] Clear State", ); + +export const storeEditingPublishedDataDoiAction = createAction( + "[PublishedData] Store Editing Published Data DOI", + props<{ publishedDataDoi: string }>(), +); + +export const fetchRelatedDatasetsAndAddToBatchAction = createAction( + "[PublishedData] Fetch Related Datasets And Add To Batch", + props<{ datasetPids: string[]; publishedDataDoi: string }>(), +); + +export const fetchRelatedDatasetsAndAddToBatchCompleteAction = createAction( + "[PublishedData] Fetch Related Datasets And Add To Batch Complete", +); + +export const fetchRelatedDatasetsAndAddToBatchFailedAction = createAction( + "[PublishedData] Fetch Related Datasets And Add To Batch Failed", +); diff --git a/src/app/state-management/effects/published-data.effects.ts b/src/app/state-management/effects/published-data.effects.ts index 26d1ccfe8..72873a6b8 100644 --- a/src/app/state-management/effects/published-data.effects.ts +++ b/src/app/state-management/effects/published-data.effects.ts @@ -2,6 +2,8 @@ import { Injectable } from "@angular/core"; import { Actions, createEffect, ofType } from "@ngrx/effects"; import { concatLatestFrom } from "@ngrx/operators"; import { + DatasetsV4Service, + OutputDatasetObsoleteDto, PublishedData, PublishedDataService, } from "@scicatproject/scicat-sdk-ts-angular"; @@ -112,8 +114,8 @@ export class PublishedDataEffects { return this.actions$.pipe( ofType(fromActions.resyncPublishedDataCompleteAction), concatLatestFrom(() => this.store.select(selectCurrentPublishedData)), - filter(([_, publishedData]) => !!publishedData), - exhaustMap(([_, publishedData]) => + filter(([{ redirect }, publishedData]) => !!publishedData && redirect), + exhaustMap(([, publishedData]) => this.router.navigateByUrl( "/publishedDatasets/" + encodeURIComponent(publishedData.doi), ), @@ -128,7 +130,7 @@ export class PublishedDataEffects { return this.actions$.pipe( ofType(fromActions.saveDataPublicationInLocalStorage), tap(({ publishedData }) => - localStorage.setItem("publishedData", JSON.stringify(publishedData)), + localStorage.setItem("editingPublishedDataDoi", publishedData.doi), ), ); }, @@ -139,7 +141,7 @@ export class PublishedDataEffects { () => { return this.actions$.pipe( ofType(fromActions.clearDataPublicationFromLocalStorage), - tap(() => localStorage.removeItem("publishedData")), + tap(() => localStorage.removeItem("editingPublishedDataDoi")), ); }, { dispatch: false }, @@ -261,13 +263,15 @@ export class PublishedDataEffects { resyncPublishedData$ = createEffect(() => { return this.actions$.pipe( ofType(fromActions.resyncPublishedDataAction), - switchMap(({ doi, data }) => + switchMap(({ doi, data, redirect }) => this.publishedDataService .publishedDataControllerResyncV3(doi, data) .pipe( mergeMap((publishedData) => [ - fromActions.resyncPublishedDataCompleteAction(publishedData), - fromActions.fetchPublishedDataAction({ id: doi }), + fromActions.resyncPublishedDataCompleteAction({ + publishedData, + redirect, + }), datasetActions.clearBatchAction(), fromActions.clearDataPublicationFromLocalStorage(), ]), @@ -305,6 +309,56 @@ export class PublishedDataEffects { ); }); + fetchRelatedDatasetsAndAddToBatch$ = createEffect(() => { + return this.actions$.pipe( + ofType(fromActions.fetchRelatedDatasetsAndAddToBatchAction), + switchMap(({ datasetPids, publishedDataDoi }) => + this.datasetsV4Service + .datasetsV4ControllerFindAllV4({ + filter: { where: { pid: { $in: datasetPids } } }, + }) + .pipe( + mergeMap((datasets) => [ + datasetActions.clearBatchAction(), + datasetActions.selectDatasetsAction({ + datasets: datasets as OutputDatasetObsoleteDto[], + }), + datasetActions.addToBatchAction(), + fromActions.fetchRelatedDatasetsAndAddToBatchCompleteAction(), + fromActions.storeEditingPublishedDataDoiAction({ + publishedDataDoi, + }), + ]), + catchError(() => + of(fromActions.fetchRelatedDatasetsAndAddToBatchFailedAction()), + ), + ), + ), + ); + }); + + storeEditingPublishedDataDoi$ = createEffect( + () => { + return this.actions$.pipe( + ofType(fromActions.storeEditingPublishedDataDoiAction), + tap(({ publishedDataDoi }) => + localStorage.setItem("editingPublishedDataDoi", publishedDataDoi), + ), + ); + }, + { dispatch: false }, + ); + + navigateToBatch$ = createEffect( + () => { + return this.actions$.pipe( + ofType(fromActions.fetchRelatedDatasetsAndAddToBatchCompleteAction), + tap(() => this.router.navigateByUrl("/datasets/batch")), + ); + }, + { dispatch: false }, + ); + loading$ = createEffect(() => { return this.actions$.pipe( ofType( @@ -318,6 +372,7 @@ export class PublishedDataEffects { fromActions.registerPublishedDataAction, fromActions.resyncPublishedDataAction, fromActions.updatePublishedDataAction, + fromActions.fetchRelatedDatasetsAndAddToBatchAction, ), switchMap(() => of(loadingAction())), ); @@ -342,6 +397,8 @@ export class PublishedDataEffects { fromActions.registerPublishedDataFailedAction, fromActions.resyncPublishedDataCompleteAction, fromActions.updatePublishedDataCompleteAction, + fromActions.fetchRelatedDatasetsAndAddToBatchCompleteAction, + fromActions.fetchRelatedDatasetsAndAddToBatchFailedAction, ), switchMap(() => of(loadingCompleteAction())), ); @@ -350,6 +407,7 @@ export class PublishedDataEffects { constructor( private actions$: Actions, private publishedDataService: PublishedDataService, + private datasetsV4Service: DatasetsV4Service, private router: Router, private store: Store, ) {} diff --git a/src/app/state-management/reducers/datasets.reducer.ts b/src/app/state-management/reducers/datasets.reducer.ts index 4cb17be1f..f470e3fc9 100644 --- a/src/app/state-management/reducers/datasets.reducer.ts +++ b/src/app/state-management/reducers/datasets.reducer.ts @@ -202,6 +202,12 @@ const reducer = createReducer( return { ...state, selectedSets }; } }), + + on(fromActions.selectDatasetsAction, (state, { datasets }) => ({ + ...state, + selectedSets: datasets, + })), + on(fromActions.deselectDatasetAction, (state, { dataset }): DatasetState => { const selectedSets = state.selectedSets.filter( (selectedSet) => selectedSet.pid !== dataset.pid, From 8dfa57cec7ee3c0c86dc841213b982af481bfe94 Mon Sep 17 00:00:00 2001 From: martin-trajanovski Date: Fri, 27 Jun 2025 11:19:56 +0200 Subject: [PATCH 12/21] final improvements and start fixing unit tests --- .../batch-view/batch-view.component.ts | 21 ++++--- .../datasets/publish/publish.component.html | 1 + .../publisheddata-dashboard.component.spec.ts | 15 ++--- .../publisheddata-details.component.html | 4 +- src/app/publisheddata/publisheddata.module.ts | 3 +- .../actions/published-data.actions.spec.ts | 19 +++--- .../effects/published-data.effects.spec.ts | 60 +++++++++++-------- .../effects/published-data.effects.ts | 7 ++- .../reducers/published-data.reducer.spec.ts | 20 ++++--- .../published-data.selectors.spec.ts | 18 +++--- 10 files changed, 97 insertions(+), 71 deletions(-) diff --git a/src/app/datasets/batch-view/batch-view.component.ts b/src/app/datasets/batch-view/batch-view.component.ts index 7b0c3d0a5..74350bc1c 100644 --- a/src/app/datasets/batch-view/batch-view.component.ts +++ b/src/app/datasets/batch-view/batch-view.component.ts @@ -215,27 +215,30 @@ export class BatchViewComponent implements OnInit, OnDestroy { this.store.dispatch( resyncPublishedDataAction({ doi: this.editingPublishedDataDoi, - redirect: true, + redirect: false, data: { datasetPids: this.datasetList.map((d) => d.pid) }, }), ); + + this.router.navigateByUrl(this.getPublishingDataUrl()); this.store.dispatch(clearBatchAction()); localStorage.removeItem("editingPublishedDataDoi"); - this.router.navigateByUrl( - `/publishedDatasets/${this.getPublishingDataUrl()}`, - ); + localStorage.removeItem("editingDatasetList"); } onCancelEdit() { + this.router.navigateByUrl(this.getPublishingDataUrl()); this.store.dispatch(clearBatchAction()); localStorage.removeItem("editingPublishedDataDoi"); - this.router.navigateByUrl( - `/publishedDatasets/${this.getPublishingDataUrl()}`, - ); + localStorage.removeItem("editingDatasetList"); } - getPublishingDataUrl() { - return encodeURIComponent(this.editingPublishedDataDoi); + getPublishingDataUrl(): string { + const isEditingDatasetList = + localStorage.getItem("editingDatasetList") === "true"; + const encodedDoi = encodeURIComponent(this.editingPublishedDataDoi); + + return `/publishedDatasets/${encodedDoi}${isEditingDatasetList ? "" : "/edit"}`; } ngOnInit() { diff --git a/src/app/datasets/publish/publish.component.html b/src/app/datasets/publish/publish.component.html index 26195ac4c..bde345993 100644 --- a/src/app/datasets/publish/publish.component.html +++ b/src/app/datasets/publish/publish.component.html @@ -59,6 +59,7 @@ (click)="onSaveChanges()" mat-raised-button color="primary" + [disabled]="!formIsValid()" > Save changes diff --git a/src/app/publisheddata/publisheddata-dashboard/publisheddata-dashboard.component.spec.ts b/src/app/publisheddata/publisheddata-dashboard/publisheddata-dashboard.component.spec.ts index 3b424b4d4..7024155b9 100644 --- a/src/app/publisheddata/publisheddata-dashboard/publisheddata-dashboard.component.spec.ts +++ b/src/app/publisheddata/publisheddata-dashboard/publisheddata-dashboard.component.spec.ts @@ -120,18 +120,19 @@ describe("PublisheddataDashboardComponent", () => { it("should add all DOI's to selectedDOIs if checked is true", () => { const published = createMock({ doi: "test", - creator: ["test"], - publisher: "test", - publicationYear: 2021, title: "test", abstract: "test", - dataDescription: "test", - resourceType: "test", - pidArray: [], + datasetPids: [], createdAt: "", registeredTime: "", - status: "", + status: PublishedData.StatusEnum.private, updatedAt: "", + metadata: { + creators: ["test creator"], + publisher: { name: "test" }, + publicationYear: 2021, + resourceType: "test", + }, }); spyOn(component.vm$, "pipe").and.returnValue( diff --git a/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html b/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html index 19d7cd7d7..25871ff48 100644 --- a/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html +++ b/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html @@ -1,5 +1,5 @@
-
+
@@ -239,7 +239,7 @@
-
+
diff --git a/src/app/publisheddata/publisheddata.module.ts b/src/app/publisheddata/publisheddata.module.ts index d045d0186..0040f1417 100644 --- a/src/app/publisheddata/publisheddata.module.ts +++ b/src/app/publisheddata/publisheddata.module.ts @@ -26,6 +26,7 @@ import { RouterModule } from "@angular/router"; import { MatExpansionModule } from "@angular/material/expansion"; import { JsonFormsModule } from "@jsonforms/angular"; import { JsonFormsAngularMaterialModule } from "@jsonforms/angular-material"; +import { DatasetEffects } from "state-management/effects/datasets.effects"; @NgModule({ declarations: [ @@ -35,7 +36,7 @@ import { JsonFormsAngularMaterialModule } from "@jsonforms/angular-material"; ], imports: [ CommonModule, - EffectsModule.forFeature([PublishedDataEffects]), + EffectsModule.forFeature([PublishedDataEffects, DatasetEffects]), FlexLayoutModule, LinkyModule, MatButtonModule, diff --git a/src/app/state-management/actions/published-data.actions.spec.ts b/src/app/state-management/actions/published-data.actions.spec.ts index 7c2bc4227..7b34cba7a 100644 --- a/src/app/state-management/actions/published-data.actions.spec.ts +++ b/src/app/state-management/actions/published-data.actions.spec.ts @@ -95,9 +95,11 @@ describe("Published Data Actions", () => { describe("publishDatasetAction", () => { it("should create an action", () => { - const action = fromActions.publishDatasetAction({ data: publishedData }); + const action = fromActions.createDataPublicationAction({ + data: publishedData, + }); expect({ ...action }).toEqual({ - type: "[PublishedData] Publish Dataset", + type: "[PublishedData] Create Data Publication", data: publishedData, }); }); @@ -105,11 +107,11 @@ describe("Published Data Actions", () => { describe("publishDatasetCompleteAction", () => { it("should create an action", () => { - const action = fromActions.publishDatasetCompleteAction({ + const action = fromActions.createDataPublicationCompleteAction({ publishedData, }); expect({ ...action }).toEqual({ - type: "[PublishedData] Publish Dataset Complete", + type: "[PublishedData] Create Data Publication Complete", publishedData, }); }); @@ -117,9 +119,9 @@ describe("Published Data Actions", () => { describe("publishDatasetFailedAction", () => { it("should create an action", () => { - const action = fromActions.publishDatasetFailedAction(); + const action = fromActions.createDataPublicationFailedAction(); expect({ ...action }).toEqual({ - type: "[PublishedData] Publish Dataset Failed", + type: "[PublishedData] Create Data Publication Failed", }); }); }); @@ -149,9 +151,12 @@ describe("Published Data Actions", () => { describe("registerPublishedDataFailedAction", () => { it("should create an action", () => { - const action = fromActions.registerPublishedDataFailedAction(); + const action = fromActions.registerPublishedDataFailedAction({ + error: [], + }); expect({ ...action }).toEqual({ type: "[PublishedData] Register Published Data Failed", + error: [], }); }); }); diff --git a/src/app/state-management/effects/published-data.effects.spec.ts b/src/app/state-management/effects/published-data.effects.spec.ts index 3f451e2fa..174a18520 100644 --- a/src/app/state-management/effects/published-data.effects.spec.ts +++ b/src/app/state-management/effects/published-data.effects.spec.ts @@ -22,22 +22,22 @@ import { TestObservable } from "jasmine-marbles/src/test-observables"; const publishedData = createMock({ doi: "testDOI", - affiliation: "test affiliation", - creator: ["test creator"], - publisher: "test publisher", - publicationYear: 2019, title: "test title", abstract: "test abstract", - dataDescription: "test description", - resourceType: "test type", - pidArray: ["testPid"], + datasetPids: ["testPid"], createdAt: "", registeredTime: "", updatedAt: "", - url: "", numberOfFiles: 1, sizeOfArchive: 1, - status: "pending_registration", + metadata: { + creators: ["test creator"], + affiliation: "test affiliation", + publisher: { name: "test publisher" }, + resourceType: "test type", + url: "", + }, + status: PublishedData.StatusEnum.private, }); describe("PublishedDataEffects", () => { @@ -211,8 +211,10 @@ describe("PublishedDataEffects", () => { describe("publishDataset$", () => { it("should result in a publishDatasetCompleteAction, a fetchPublishedDataAction", () => { const id = "testDOI"; - const action = fromActions.publishDatasetAction({ data: publishedData }); - const outcome1 = fromActions.publishDatasetCompleteAction({ + const action = fromActions.createDataPublicationAction({ + data: publishedData, + }); + const outcome1 = fromActions.createDataPublicationCompleteAction({ publishedData, }); const outcome2 = fromActions.fetchPublishedDataAction({ id }); @@ -227,12 +229,14 @@ describe("PublishedDataEffects", () => { b: outcome1, c: outcome2, }); - expect(effects.publishDataset$).toBeObservable(expected); + expect(effects.createDataPublication$).toBeObservable(expected); }); it("should result in a publishDatasetFailedAction", () => { - const action = fromActions.publishDatasetAction({ data: publishedData }); - const outcome = fromActions.publishDatasetFailedAction(); + const action = fromActions.createDataPublicationAction({ + data: publishedData, + }); + const outcome = fromActions.createDataPublicationFailedAction(); actions = hot("-a", { a: action }); const response = cold("-#", {}); @@ -241,7 +245,7 @@ describe("PublishedDataEffects", () => { ); const expected = cold("--b", { b: outcome }); - expect(effects.publishDataset$).toBeObservable(expected); + expect(effects.createDataPublication$).toBeObservable(expected); }); }); @@ -252,7 +256,7 @@ describe("PublishedDataEffects", () => { content: "Publication Successful", duration: 5000, }; - const action = fromActions.publishDatasetCompleteAction({ + const action = fromActions.createDataPublicationCompleteAction({ publishedData, }); const outcome = showMessageAction({ message }); @@ -260,7 +264,9 @@ describe("PublishedDataEffects", () => { actions = hot("-a", { a: action }); const expected = cold("-b", { b: outcome }); - expect(effects.publishDatasetCompleteMessage$).toBeObservable(expected); + expect(effects.createDataPublicationCompleteMessage$).toBeObservable( + expected, + ); }); }); @@ -271,13 +277,15 @@ describe("PublishedDataEffects", () => { content: "Publication Failed", duration: 5000, }; - const action = fromActions.publishDatasetFailedAction(); + const action = fromActions.createDataPublicationFailedAction(); const outcome = showMessageAction({ message }); actions = hot("-a", { a: action }); const expected = cold("-b", { b: outcome }); - expect(effects.publishDatasetFailedMessage$).toBeObservable(expected); + expect(effects.createDataPublicationFailedMessage$).toBeObservable( + expected, + ); }); }); @@ -303,7 +311,9 @@ describe("PublishedDataEffects", () => { it("should result in a registerPublishedDataFailedAction", () => { const doi = "testDOI"; const action = fromActions.registerPublishedDataAction({ doi }); - const outcome = fromActions.registerPublishedDataFailedAction(); + const outcome = fromActions.registerPublishedDataFailedAction({ + error: [], + }); actions = hot("-a", { a: action }); const response = cold("-#", {}); @@ -356,7 +366,7 @@ describe("PublishedDataEffects", () => { describe("ofType publishedDatasetAction", () => { it("should dispatch a loadingAction", () => { - const action = fromActions.publishDatasetAction({ + const action = fromActions.createDataPublicationAction({ data: publishedData, }); const outcome = loadingAction(); @@ -437,7 +447,7 @@ describe("PublishedDataEffects", () => { describe("ofType publishDatasetCompleteAction", () => { it("should dispatch a loadingCompleteAction", () => { - const action = fromActions.publishDatasetCompleteAction({ + const action = fromActions.createDataPublicationCompleteAction({ publishedData, }); const outcome = loadingCompleteAction(); @@ -451,7 +461,7 @@ describe("PublishedDataEffects", () => { describe("ofType publishDatasetFailedAction", () => { it("should dispatch a loadingCompleteAction", () => { - const action = fromActions.publishDatasetFailedAction(); + const action = fromActions.createDataPublicationFailedAction(); const outcome = loadingCompleteAction(); actions = hot("-a", { a: action }); @@ -477,7 +487,9 @@ describe("PublishedDataEffects", () => { describe("ofType registerPublishedDataFailedAction", () => { it("should dispatch a loadingCompleteAction", () => { - const action = fromActions.registerPublishedDataFailedAction(); + const action = fromActions.registerPublishedDataFailedAction({ + error: [], + }); const outcome = loadingCompleteAction(); actions = hot("-a", { a: action }); diff --git a/src/app/state-management/effects/published-data.effects.ts b/src/app/state-management/effects/published-data.effects.ts index 72873a6b8..db3c45e80 100644 --- a/src/app/state-management/effects/published-data.effects.ts +++ b/src/app/state-management/effects/published-data.effects.ts @@ -341,9 +341,10 @@ export class PublishedDataEffects { () => { return this.actions$.pipe( ofType(fromActions.storeEditingPublishedDataDoiAction), - tap(({ publishedDataDoi }) => - localStorage.setItem("editingPublishedDataDoi", publishedDataDoi), - ), + tap(({ publishedDataDoi }) => { + localStorage.setItem("editingPublishedDataDoi", publishedDataDoi); + localStorage.setItem("editingDatasetList", "true"); + }), ); }, { dispatch: false }, diff --git a/src/app/state-management/reducers/published-data.reducer.spec.ts b/src/app/state-management/reducers/published-data.reducer.spec.ts index 5139b9940..0cc26984c 100644 --- a/src/app/state-management/reducers/published-data.reducer.spec.ts +++ b/src/app/state-management/reducers/published-data.reducer.spec.ts @@ -6,22 +6,24 @@ import { PublishedData } from "@scicatproject/scicat-sdk-ts-angular"; const publishedData = createMock({ doi: "testDOI", - affiliation: "test affiliation", - creator: ["test creator"], - publisher: "test publisher", - publicationYear: 2019, + title: "test title", abstract: "test abstract", - dataDescription: "test description", - resourceType: "test type", - pidArray: ["testPid"], + datasetPids: ["testPid"], createdAt: "", registeredTime: "", updatedAt: "", - url: "", numberOfFiles: 1, sizeOfArchive: 1, - status: "pending_registration", + metadata: { + creators: ["test creator"], + affiliation: "test affiliation", + publisher: { name: "test publisher" }, + publicationYear: 2019, + resourceType: "test type", + url: "", + }, + status: PublishedData.StatusEnum.private, }); describe("PublishedData Reducer", () => { diff --git a/src/app/state-management/selectors/published-data.selectors.spec.ts b/src/app/state-management/selectors/published-data.selectors.spec.ts index 961ee0a42..0d9aa2638 100644 --- a/src/app/state-management/selectors/published-data.selectors.spec.ts +++ b/src/app/state-management/selectors/published-data.selectors.spec.ts @@ -6,22 +6,22 @@ import { PublishedData } from "@scicatproject/scicat-sdk-ts-angular"; const publishedData = createMock({ doi: "testDOI", - affiliation: "test affiliation", - creator: ["test creator"], - publisher: "test publisher", - publicationYear: 2019, title: "test title", abstract: "test abstract", - dataDescription: "test description", - resourceType: "test type", - pidArray: ["testPid"], + datasetPids: ["testPid"], createdAt: "", registeredTime: "", updatedAt: "", - url: "", numberOfFiles: 1, sizeOfArchive: 1, - status: "pending_registration", + metadata: { + creators: ["test creator"], + affiliation: "test affiliation", + publisher: { name: "test publisher" }, + resourceType: "test type", + url: "", + }, + status: PublishedData.StatusEnum.private, }); const filters: GenericFilters = { From ce10822023b2ca7b4747196a6464104d294c0c94 Mon Sep 17 00:00:00 2001 From: martin-trajanovski Date: Fri, 27 Jun 2025 12:34:57 +0200 Subject: [PATCH 13/21] fix rest of the unit tests --- .../publish/publish.component.spec.ts | 39 +---------- .../publisheddata-edit.component.spec.ts | 66 ++----------------- .../effects/published-data.effects.spec.ts | 37 +++++++---- 3 files changed, 32 insertions(+), 110 deletions(-) diff --git a/src/app/datasets/publish/publish.component.spec.ts b/src/app/datasets/publish/publish.component.spec.ts index 00c8be5e8..f89317211 100644 --- a/src/app/datasets/publish/publish.component.spec.ts +++ b/src/app/datasets/publish/publish.component.spec.ts @@ -84,31 +84,6 @@ describe("PublishComponent", () => { expect(component).toBeTruthy(); }); - describe("#addCreator()", () => { - it("should push a creator to the creator property in the form", () => { - const event = { - input: { - value: "", - }, - value: "testCreator", - }; - component.addCreator(event); - - expect(component.form.creators).toContain(event.value); - }); - }); - - describe("#removeCreator()", () => { - it("should remove a creator from the creator property in the form", () => { - const creator = "testCreator"; - component.form.creators = [creator]; - - component.removeCreator(creator); - - expect(component.form.creators).not.toContain(creator); - }); - }); - describe("#formIsValid()", () => { it("should return false if form has undefined properties", () => { component.form.title = undefined; @@ -121,20 +96,8 @@ describe("PublishComponent", () => { it("should return true if form has no undefined properties and their lengths > 0", () => { component.form = { title: "testTitle", - creators: ["testCreator"], - publisher: "testPublisher", - resourceType: "testType", - description: "testDescription", abstract: "testAbstract", - pidArray: ["testPid"], - publicationYear: 2019, - url: "testUrl", - dataDescription: "testDataDescription", - thumbnail: "testThumbnail", - numberOfFiles: 1, - sizeOfArchive: 100, - relatedPublications: ["testpub"], - downloadLink: "testlink", + datasetPids: ["testPid"], }; const isValid = component.formIsValid(); diff --git a/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.spec.ts b/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.spec.ts index cfe6221a7..f3dd30d5e 100644 --- a/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.spec.ts +++ b/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.spec.ts @@ -25,10 +25,14 @@ import { MatOptionModule } from "@angular/material/core"; import { MatButtonModule } from "@angular/material/button"; import { FlexLayoutModule } from "@ngbracket/ngx-layout"; import { PublishedDataService } from "@scicatproject/scicat-sdk-ts-angular"; +import { AppConfigService } from "app-config.service"; describe("PublisheddataEditComponent", () => { let component: PublisheddataEditComponent; let fixture: ComponentFixture; + const getConfig = () => ({ + landingPage: "https://test-landing-page.com", + }); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ @@ -62,6 +66,7 @@ describe("PublisheddataEditComponent", () => { { provide: PublishedDataService, useClass: MockPublishedDataApi }, { provide: Router, useClass: MockRouter }, { provide: Store, useClass: MockStore }, + { provide: AppConfigService, useValue: { getConfig } }, ], }, }); @@ -77,65 +82,4 @@ describe("PublisheddataEditComponent", () => { it("should create", () => { expect(component).toBeTruthy(); }); - - describe("#addCreator()", () => { - it("should push a creator to the creator property in the form", () => { - const event = { - chipInput: { - inputElement: { - value: "testCreator", - }, - }, - value: "testCreator", - } as MatChipInputEvent; - component.addCreator(event); - - expect(component.creator!.value).toContain(event.value); - }); - }); - - describe("#removeCreator()", () => { - it("should remove a creator from the creator property in the form", () => { - const creator = "testCreator"; - component.creator!.setValue([]); - component.creator!.value.push("firstCreator", creator); - - component.removeCreator(1); - - expect(component.creator!.value).not.toContain(creator); - }); - }); - - describe("#addRelatedPublication()", () => { - it("should push a related publication to the relatedPublications property in the form", () => { - const event = { - chipInput: { - inputElement: { - value: "testRelatedPublication", - }, - }, - value: "testRelatedPublication", - } as MatChipInputEvent; - component.addRelatedPublication(event); - - expect(component.relatedPublications!.value).toContain(event.value); - }); - }); - - describe("#removeRelatedPublication()", () => { - it("should remove a related publication from the relatedPublications property in the form", () => { - const relatedPublication = "testRelatedPublication"; - component.relatedPublications!.setValue([]); - component.relatedPublications!.value.push( - "firstRelatedPublication", - relatedPublication, - ); - - component.removeRelatedPublication(1); - - expect(component.relatedPublications!.value).not.toContain( - relatedPublication, - ); - }); - }); }); diff --git a/src/app/state-management/effects/published-data.effects.spec.ts b/src/app/state-management/effects/published-data.effects.spec.ts index 174a18520..338670ee5 100644 --- a/src/app/state-management/effects/published-data.effects.spec.ts +++ b/src/app/state-management/effects/published-data.effects.spec.ts @@ -4,6 +4,7 @@ import { provideMockActions } from "@ngrx/effects/testing"; import { provideMockStore } from "@ngrx/store/testing"; import { selectQueryParams } from "state-management/selectors/published-data.selectors"; import * as fromActions from "state-management/actions/published-data.actions"; +import { clearBatchAction } from "state-management/actions/datasets.actions"; import { hot, cold } from "jasmine-marbles"; import { MessageType } from "state-management/models"; import { @@ -15,6 +16,7 @@ import { Type } from "@angular/core"; import { Router } from "@angular/router"; import { MockRouter, createMock } from "shared/MockStubs"; import { + DatasetsV4Service, PublishedData, PublishedDataService, } from "@scicatproject/scicat-sdk-ts-angular"; @@ -63,6 +65,12 @@ describe("PublishedDataEffects", () => { "publishedDataControllerRegisterV3", ]), }, + { + provide: DatasetsV4Service, + useValue: jasmine.createSpyObj("datasetsV4Service", [ + "datasetsV4ControllerFindAllV4", + ]), + }, { provide: Router, useClass: MockRouter }, ], }); @@ -218,6 +226,8 @@ describe("PublishedDataEffects", () => { publishedData, }); const outcome2 = fromActions.fetchPublishedDataAction({ id }); + const outcome3 = clearBatchAction(); + const outcome4 = fromActions.clearDataPublicationFromLocalStorage(); actions = hot("-a", { a: action }); const response = cold("-a|", { a: publishedData }); @@ -225,9 +235,11 @@ describe("PublishedDataEffects", () => { response, ); - const expected = cold("--(bc)", { + const expected = cold("--(bcde)", { b: outcome1, c: outcome2, + d: outcome3, + e: outcome4, }); expect(effects.createDataPublication$).toBeObservable(expected); }); @@ -309,20 +321,23 @@ describe("PublishedDataEffects", () => { }); it("should result in a registerPublishedDataFailedAction", () => { - const doi = "testDOI"; - const action = fromActions.registerPublishedDataAction({ doi }); - const outcome = fromActions.registerPublishedDataFailedAction({ - error: [], + const error = new Error("Test"); + const message = { + type: MessageType.Error, + content: "Registration Failed. " + error.message, + duration: 5000, + }; + const action = fromActions.registerPublishedDataFailedAction({ + error: [error.message], }); + const outcome = showMessageAction({ message }); actions = hot("-a", { a: action }); - const response = cold("-#", {}); - publishedDataApi.publishedDataControllerRegisterV3.and.returnValue( - response, - ); - const expected = cold("--b", { b: outcome }); - expect(effects.registerPublishedData$).toBeObservable(expected); + const expected = cold("-b", { b: outcome }); + expect(effects.registerPublishedDataFailedMessage$).toBeObservable( + expected, + ); }); }); From f58c6e36fbe0928373ef2be5dedcfadbaee2164f Mon Sep 17 00:00:00 2001 From: martin-trajanovski Date: Fri, 27 Jun 2025 12:51:36 +0200 Subject: [PATCH 14/21] fix some linting issues --- src/app/datasets/publish/publish.component.ts | 8 ++++---- .../publisheddata-edit/publisheddata-edit.component.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/datasets/publish/publish.component.ts b/src/app/datasets/publish/publish.component.ts index 0fa90570c..7521da0b9 100644 --- a/src/app/datasets/publish/publish.component.ts +++ b/src/app/datasets/publish/publish.component.ts @@ -41,10 +41,6 @@ import { EditableComponent } from "app-routing/pending-changes.guard"; }) export class PublishComponent implements OnInit, OnDestroy, EditableComponent { private _hasUnsavedChanges = false; - renderers = angularMaterialRenderers; - schema: any = {}; - uiSchema: any = {}; - metadataData: any = {}; private datasets$ = this.store.select(selectDatasetsInBatch); private publishedDataConfig$ = this.store.select(selectPublishedDataConfig); private countSubscription: Subscription; @@ -53,6 +49,10 @@ export class PublishComponent implements OnInit, OnDestroy, EditableComponent { readonly panelOpenState = signal(false); appConfig = this.appConfigService.getConfig(); + renderers = angularMaterialRenderers; + schema: any = {}; + uiSchema: any = {}; + metadataData: any = {}; public separatorKeysCodes: number[] = [ENTER, COMMA]; public datasetCount: number; diff --git a/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.ts b/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.ts index 805bbf2b2..37b520671 100644 --- a/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.ts +++ b/src/app/publisheddata/publisheddata-edit/publisheddata-edit.component.ts @@ -33,13 +33,13 @@ export class PublisheddataEditComponent implements OnInit, OnDestroy, EditableComponent { private _hasUnsavedChanges = false; + private publishedDataConfig$ = this.store.select(selectPublishedDataConfig); renderers = angularMaterialRenderers; schema: any = {}; uiSchema: any = {}; metadataData: any = {}; public metadataFormErrors = []; readonly panelOpenState = signal(false); - private publishedDataConfig$ = this.store.select(selectPublishedDataConfig); routeSubscription = new Subscription(); publishedData$: Observable = new Observable(); attachments: Attachment[] = []; @@ -65,7 +65,7 @@ export class PublisheddataEditComponent private appConfigService: AppConfigService, ) {} - public onPublishedDataUpdate(shouldRedirect: boolean = false) { + public onPublishedDataUpdate(shouldRedirect = false) { if (this.form.valid) { const { doi, ...rest } = this.form.value; const metadata = { From abb7eed9adab7006424664b098980e755cd62f65 Mon Sep 17 00:00:00 2001 From: martin-trajanovski Date: Fri, 27 Jun 2025 13:19:15 +0200 Subject: [PATCH 15/21] linting issue --- .../lazy/datasets-routing/datasets.routing.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/app-routing/lazy/datasets-routing/datasets.routing.module.ts b/src/app/app-routing/lazy/datasets-routing/datasets.routing.module.ts index 494952e4a..6715b60a4 100644 --- a/src/app/app-routing/lazy/datasets-routing/datasets.routing.module.ts +++ b/src/app/app-routing/lazy/datasets-routing/datasets.routing.module.ts @@ -1,7 +1,7 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { AuthGuard } from "app-routing/auth.guard"; -import { leavingPageGuard } from 'app-routing/pending-changes.guard'; +import { leavingPageGuard } from "app-routing/pending-changes.guard"; import { BatchViewComponent } from "datasets/batch-view/batch-view.component"; import { DashboardComponent } from "datasets/dashboard/dashboard.component"; import { DatablocksComponent } from "datasets/datablocks-table/datablocks-table.component"; From c9ee8eb4d3d3f47f998ab070a9b486bdae1c6462 Mon Sep 17 00:00:00 2001 From: martin-trajanovski Date: Fri, 27 Jun 2025 14:48:23 +0200 Subject: [PATCH 16/21] fix some of the failing e2e tests --- cypress/e2e/datasets/datasets-publish.cy.js | 2 +- src/app/datasets/dataset-table/dataset-table.component.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cypress/e2e/datasets/datasets-publish.cy.js b/cypress/e2e/datasets/datasets-publish.cy.js index b2a0a115a..14a1f29b3 100644 --- a/cypress/e2e/datasets/datasets-publish.cy.js +++ b/cypress/e2e/datasets/datasets-publish.cy.js @@ -37,7 +37,7 @@ describe("Datasets", () => { cy.get("#abstractInput").type("some abstract text"); - cy.get("#createDataPublicationButton").click(); + cy.get("#saveAndContinueButton").click(); cy.get("#doiRow").should("exist"); }); diff --git a/src/app/datasets/dataset-table/dataset-table.component.ts b/src/app/datasets/dataset-table/dataset-table.component.ts index 35d1d5431..93bdd9627 100644 --- a/src/app/datasets/dataset-table/dataset-table.component.ts +++ b/src/app/datasets/dataset-table/dataset-table.component.ts @@ -527,6 +527,8 @@ export class DatasetTableComponent implements OnInit, OnDestroy { if (!currentUser) { this.rowSelectionMode = "none"; + } else { + this.rowSelectionMode = "multi"; } if (tableColumns) { From b3383c990b10e94b63fc44bcd86ea69c3f5bfe60 Mon Sep 17 00:00:00 2001 From: martin-trajanovski Date: Mon, 30 Jun 2025 15:52:23 +0200 Subject: [PATCH 17/21] access improvements and add amend button --- .../publisheddata-details.component.html | 14 ++++++++++- .../publisheddata-details.component.ts | 7 ++++++ .../actions/published-data.actions.ts | 13 ++++++++++ .../effects/published-data.effects.ts | 24 +++++++++++++++++++ 4 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html b/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html index 25871ff48..3669f284d 100644 --- a/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html +++ b/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html @@ -27,6 +27,14 @@ + + (), ); +export const deletePublishedDataAction = createAction( + "[PublishedData] Delete Published Data", + props<{ doi: string }>(), +); +export const deletePublishedDataCompleteAction = createAction( + "[PublishedData] Delete Published Data Complete", + props<{ doi: string }>(), +); +export const deletePublishedDataFailedAction = createAction( + "[PublishedData] Delete Published Data Failed", + props<{ error: string[] }>(), +); + export const publishPublishedDataAction = createAction( "[PublishedData] Publish Published Data", props<{ doi: string }>(), diff --git a/src/app/state-management/effects/published-data.effects.ts b/src/app/state-management/effects/published-data.effects.ts index a64b11955..9d7482f53 100644 --- a/src/app/state-management/effects/published-data.effects.ts +++ b/src/app/state-management/effects/published-data.effects.ts @@ -263,6 +263,32 @@ export class PublishedDataEffects { ); }); + navigateToPublishedDatasets$ = createEffect( + () => { + return this.actions$.pipe( + ofType(fromActions.deletePublishedDataCompleteAction), + tap(() => this.router.navigateByUrl("/publishedDatasets")), + ); + }, + { dispatch: false }, + ); + + deletePublishedData$ = createEffect(() => { + return this.actions$.pipe( + ofType(fromActions.deletePublishedDataAction), + switchMap(({ doi }) => + this.publishedDataService.publishedDataControllerRemoveV3(doi).pipe( + mergeMap(() => [ + fromActions.deletePublishedDataCompleteAction({ doi }), + ]), + catchError((error) => + of(fromActions.deletePublishedDataFailedAction(error)), + ), + ), + ), + ); + }); + updatePublishedData$ = createEffect(() => { return this.actions$.pipe( ofType(fromActions.updatePublishedDataAction), @@ -393,6 +419,7 @@ export class PublishedDataEffects { fromActions.resyncPublishedDataAction, fromActions.updatePublishedDataAction, fromActions.amendPublishedDataAction, + fromActions.deletePublishedDataAction, fromActions.fetchRelatedDatasetsAndAddToBatchAction, ), switchMap(() => of(loadingAction())), @@ -424,6 +451,8 @@ export class PublishedDataEffects { fromActions.fetchRelatedDatasetsAndAddToBatchFailedAction, fromActions.amendPublishedDataCompleteAction, fromActions.amendPublishedDataFailedAction, + fromActions.deletePublishedDataCompleteAction, + fromActions.deletePublishedDataFailedAction, ), switchMap(() => of(loadingCompleteAction())), ); From b77f9b14718856a54a14d25699d386c49691d0b6 Mon Sep 17 00:00:00 2001 From: martin-trajanovski Date: Tue, 1 Jul 2025 14:28:56 +0200 Subject: [PATCH 19/21] fix lint errors --- .../publisheddata-details.component.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/publisheddata/publisheddata-details/publisheddata-details.component.ts b/src/app/publisheddata/publisheddata-details/publisheddata-details.component.ts index b16565fb1..19ee145cd 100644 --- a/src/app/publisheddata/publisheddata-details/publisheddata-details.component.ts +++ b/src/app/publisheddata/publisheddata-details/publisheddata-details.component.ts @@ -66,7 +66,11 @@ export class PublisheddataDetailsComponent implements OnInit, OnDestroy { } onRegisterClick(doi: string) { - if (confirm("Are you sure you want to register this published data? Keep in mind that no further changes can be made after this action.")) { + if ( + confirm( + "Are you sure you want to register this published data? Keep in mind that no further changes can be made after this action.", + ) + ) { this.store.dispatch(registerPublishedDataAction({ doi })); } } From 34556e5a57fb2444006c11e74b72fd03849003c5 Mon Sep 17 00:00:00 2001 From: martin-trajanovski Date: Wed, 2 Jul 2025 15:09:41 +0200 Subject: [PATCH 20/21] test: add e2e tests for the published data --- .../e2e/published-data/published-data.cy.js | 575 ++++++++++++++++++ .../batch-view/batch-view.component.html | 5 +- src/app/datasets/publish/publish.component.ts | 2 - .../publisheddata-details.component.html | 11 +- .../publisheddata-edit.component.html | 11 +- .../publisheddata-edit.component.ts | 6 +- 6 files changed, 599 insertions(+), 11 deletions(-) create mode 100644 cypress/e2e/published-data/published-data.cy.js diff --git a/cypress/e2e/published-data/published-data.cy.js b/cypress/e2e/published-data/published-data.cy.js new file mode 100644 index 000000000..17ab932e1 --- /dev/null +++ b/cypress/e2e/published-data/published-data.cy.js @@ -0,0 +1,575 @@ +import { testData } from "../../fixtures/testData"; + +describe("Datasets general", () => { + const title = "publishedDataTitle"; + const abstract = "publishedDataAbstract"; + const userPublishedDataTitle = "userSpecificPublishedDataTitle"; + const userPublishedDataAbstract = "userSpecificPublishedDataAbstract"; + beforeEach(() => { + cy.login(Cypress.env("username"), Cypress.env("password")); + }); + + after(() => { + cy.removeDatasets(); + }); + + describe("Published data creation, update and registration", () => { + it("should be able to create new published data in private state", () => { + cy.createDataset("raw"); + + cy.visit("/datasets"); + + cy.get(".dataset-table mat-table mat-header-row").should("exist"); + + cy.finishedLoading(); + + cy.get('[data-cy="text-search"] input[type="search"]') + .clear() + .type("Cypress"); + + cy.isLoading(); + + cy.get(".dataset-table mat-row input[type='checkbox']").first().click(); + + cy.get("#addToBatchButton").click(); + + cy.get("#cartOnHeaderButton").click(); + + cy.get("a.button").click(); + + cy.get("#publishButton").click(); + + cy.get("#titleInput").type(title); + + cy.get("#abstractInput").type(abstract); + + cy.get("#saveAndContinueButton").click(); + + cy.get("#doiRow").should("exist"); + + cy.get("[data-cy='status']").contains("private"); + }); + + it("should prevent leaving published data form unsaved", () => { + cy.createDataset("raw"); + + cy.visit("/datasets"); + + cy.get(".dataset-table mat-table mat-header-row").should("exist"); + + cy.finishedLoading(); + + cy.get('[data-cy="text-search"] input[type="search"]') + .clear() + .type("Cypress"); + + cy.isLoading(); + + cy.get(".dataset-table mat-row input[type='checkbox']").first().click(); + + cy.get("#addToBatchButton").click(); + + cy.get("#cartOnHeaderButton").click(); + + cy.get("a.button").click(); + + cy.get("#publishButton").click(); + + cy.get("#titleInput").type(title); + + cy.get("#abstractInput").type(abstract); + + cy.get("#cancelButton").click(); + + cy.on("window:confirm", (str) => { + expect(str).to.equal( + "You have unsaved changes. Press Cancel to go back and save these changes, or OK to leave without saving.", + ); + + return false; + }); + + cy.get("#saveButton").click(); + + cy.get("#cancelButton").click(); + + cy.get('[data-cy="batch-table"] mat-row').should("exist"); + }); + + it("should be able to edit dataset list after creating the published data", () => { + cy.createDataset("raw"); + cy.createDataset("raw"); + + cy.visit("/datasets"); + + cy.get(".dataset-table mat-table mat-header-row").should("exist"); + + cy.finishedLoading(); + + cy.get('[data-cy="text-search"] input[type="search"]') + .clear() + .type("Cypress"); + + cy.isLoading(); + + cy.get(".dataset-table mat-row input[type='checkbox']").first().click(); + + cy.get("#addToBatchButton").click(); + + cy.get("#cartOnHeaderButton").click(); + + cy.get("a.button").click(); + + cy.get("#publishButton").click(); + + cy.get("#titleInput").type(title); + + cy.get("#abstractInput").type(abstract); + + cy.get("#saveButton").click(); + + cy.get("#cancelButton").click(); + + cy.get('[data-cy="batch-table"] mat-row').should("exist"); + + cy.visit("/datasets"); + + cy.get(".dataset-table mat-table mat-header-row").should("exist"); + + cy.finishedLoading(); + + cy.get('[data-cy="text-search"] input[type="search"]') + .clear() + .type("Cypress"); + + cy.isLoading(); + + cy.get(".dataset-table mat-row input[type='checkbox']").last().click(); + + cy.get("#addToBatchButton").click(); + + cy.get("#cartOnHeaderButton").click(); + + cy.get("a.button").click(); + + cy.get('[data-cy="batch-table"] mat-row').its("length").should("eq", 2); + + cy.get("#saveChangesButton").click(); + + cy.get('[data-cy="editPublishedDataForm"]').should("exist"); + cy.get("#titleInput").should("have.value", title); + cy.get("#abstractInput").should("have.value", abstract); + }); + + it("other users should not be able to see private published data that they do not own", () => { + cy.login(Cypress.env("guestUsername"), Cypress.env("guestPassword")); + const title = "some title text"; + + cy.visit("/publishedDatasets"); + + cy.get("app-publisheddata-dashboard mat-table mat-header-row").should( + "exist", + ); + + cy.finishedLoading(); + + cy.get('input[formcontrolname="globalSearch"]').clear().type(title); + + cy.isLoading(); + + cy.get("app-publisheddata-dashboard mat-table mat-row").should( + "not.contain", + title, + ); + }); + + it("should not be able to publish invalid private published data", () => { + cy.visit("/publishedDatasets"); + + cy.get("app-publisheddata-dashboard mat-table mat-header-row").should( + "exist", + ); + + cy.finishedLoading(); + + cy.get('input[formcontrolname="globalSearch"]').clear().type(title); + + cy.isLoading(); + + cy.get("app-publisheddata-dashboard mat-table mat-row") + .contains(title) + .first() + .click(); + + cy.get('[data-cy="status"]').contains("private"); + + cy.get('[data-cy="publishButton"]').click(); + + cy.get("simple-snack-bar").should( + "contain", + 'Publishing Failed. metadata requires property "creators"', + ); + }); + + it("admins should be able to edit their private published data", () => { + const creatorName = "Creator name"; + const resourceType = "resource type"; + const publisherName = "publisher name"; + const publisherIndetifierScheme = "publisher identifier scheme"; + cy.visit("/publishedDatasets"); + + cy.get("app-publisheddata-dashboard mat-table mat-header-row").should( + "exist", + ); + + cy.finishedLoading(); + + cy.get('input[formcontrolname="globalSearch"]').clear().type(title); + + cy.isLoading(); + + cy.get("app-publisheddata-dashboard mat-table mat-row") + .contains(title) + .first() + .click(); + + cy.get('[data-cy="status"]').contains("private"); + + cy.get("#editBtn").click(); + + cy.get('[data-cy="editPublishedDataForm"]').should("exist"); + + cy.get('[data-cy="metadata"]').click(); + cy.get("jsonforms").should("exist"); + + cy.get("button.save-and-continue").should("be.disabled"); + + cy.get('[aria-label="Add to Creators button"]').click(); + + cy.get('[aria-label="Add to Creators button"]') + .closest(".array-layout") + .find('input[id^="#/properties/name"]') + .first() + .clear() + .type(creatorName); + + cy.get('[id="#/properties/resourceType"]').clear().type(resourceType); + + cy.get('[ng-reflect-path="publisher"]') + .parent() + .find('[id="#/properties/name"]') + .clear() + .type(publisherName); + + cy.get('[ng-reflect-path="publisher"]') + .parent() + .should("contain", "is a required property"); + cy.get('[ng-reflect-path="publisher"]') + .parent() + .find('input[id="#/properties/publisherIdentifierScheme"]') + .clear() + .type(publisherIndetifierScheme); + + cy.get("button.save-and-continue").should("not.be.disabled"); + + cy.get("button.save-and-continue").click(); + + cy.get('[data-cy="status"]').contains("private"); + + cy.get('[data-cy="showHideMetadata"]').click(); + + cy.get("ngx-json-viewer section").contains("metadata").click(); + + cy.get("ngx-json-viewer section").contains(creatorName); + cy.get("ngx-json-viewer section").contains(publisherName); + cy.get("ngx-json-viewer section").contains(publisherIndetifierScheme); + cy.get("ngx-json-viewer section").contains(resourceType); + }); + + it("should be able to edit dataset list after creating the published data", () => { + const newDatasetName = "Test dataset name"; + cy.createDataset("raw", newDatasetName); + cy.visit("/publishedDatasets"); + + cy.get("app-publisheddata-dashboard mat-table mat-header-row").should( + "exist", + ); + + cy.finishedLoading(); + + cy.get('input[formcontrolname="globalSearch"]').clear().type(title); + + cy.isLoading(); + + cy.get("app-publisheddata-dashboard mat-table mat-row") + .contains(title) + .first() + .click(); + + cy.get('[data-cy="status"]').contains("private"); + + cy.get('[data-cy="editDatasetList"]').click(); + + cy.get('[data-cy="batch-table"] mat-row').should("exist"); + + cy.visit("/datasets"); + + cy.get(".dataset-table mat-table mat-header-row").should("exist"); + + cy.finishedLoading(); + + cy.get('[data-cy="text-search"] input[type="search"]') + .clear() + .type(newDatasetName); + + cy.isLoading(); + + cy.get(".dataset-table mat-row input[type='checkbox']").first().click(); + + cy.get("#addToBatchButton").click(); + + cy.get("#cartOnHeaderButton").click(); + + cy.get("a.button").click(); + + cy.get('[data-cy="batch-table"] mat-row').its("length").should("eq", 3); + + cy.get("#saveChangesButton").click(); + }); + + it("should be able to publish their private published data", () => { + cy.visit("/publishedDatasets"); + + cy.get("app-publisheddata-dashboard mat-table mat-header-row").should( + "exist", + ); + + cy.finishedLoading(); + + cy.get('input[formcontrolname="globalSearch"]').clear().type(title); + + cy.isLoading(); + + cy.get("app-publisheddata-dashboard mat-table mat-row") + .contains(title) + .first() + .click(); + + cy.get('[data-cy="status"]').contains("private"); + + cy.get('[data-cy="publishButton"]').click(); + + cy.get('[data-cy="status"]').contains("public"); + }); + + it("should not be able to edit dataset list on a published data that is public", () => { + cy.visit("/publishedDatasets"); + + cy.get("app-publisheddata-dashboard mat-table mat-header-row").should( + "exist", + ); + + cy.finishedLoading(); + + cy.get('input[formcontrolname="globalSearch"]').clear().type(title); + + cy.isLoading(); + + cy.get("app-publisheddata-dashboard mat-table mat-row") + .contains(title) + .first() + .click(); + + cy.get('[data-cy="status"]').contains("public"); + + cy.get("#editDatasetList").should("not.exist"); + }); + + it("other users should be able to see public published data that they do not own", () => { + cy.login(Cypress.env("guestUsername"), Cypress.env("guestPassword")); + cy.visit("/publishedDatasets"); + + cy.get("app-publisheddata-dashboard mat-table mat-header-row").should( + "exist", + ); + + cy.finishedLoading(); + + cy.get('input[formcontrolname="globalSearch"]').clear().type(title); + + cy.isLoading(); + + cy.get("app-publisheddata-dashboard mat-table mat-row").should( + "contain", + title, + ); + }); + + it("should be able to register their public published data", () => { + cy.visit("/publishedDatasets"); + + cy.get("app-publisheddata-dashboard mat-table mat-header-row").should( + "exist", + ); + + cy.finishedLoading(); + + cy.get('input[formcontrolname="globalSearch"]').clear().type(title); + + cy.isLoading(); + + cy.get("app-publisheddata-dashboard mat-table mat-row") + .contains(title) + .first() + .click(); + + cy.get('[data-cy="status"]').contains("public"); + + cy.get('[data-cy="registerButton"]').click(); + + cy.get('[data-cy="status"]').contains("registered"); + }); + + it("regular users should be able to create and edit their private published data but not after it gets public", () => { + cy.login(Cypress.env("guestUsername"), Cypress.env("guestPassword")); + const creatorName = "Creator name"; + const resourceType = "resource type"; + const publisherName = "publisher name"; + const publisherIndetifierScheme = "publisher identifier scheme"; + cy.createDataset("raw"); + + cy.visit("/datasets"); + + cy.get(".dataset-table mat-table mat-header-row").should("exist"); + + cy.finishedLoading(); + + cy.get('[data-cy="text-search"] input[type="search"]') + .clear() + .type("Cypress"); + + cy.isLoading(); + + cy.get(".dataset-table mat-row input[type='checkbox']").first().click(); + + cy.get("#addToBatchButton").click(); + + cy.get("#cartOnHeaderButton").click(); + + cy.get("a.button").click(); + + cy.get("#publishButton").click(); + + cy.get("#titleInput").type(userPublishedDataTitle); + + cy.get("#abstractInput").type(userPublishedDataAbstract); + + cy.get("#saveAndContinueButton").click(); + + cy.get("#doiRow").should("exist"); + + cy.get("[data-cy='status']").contains("private"); + + cy.get("#deleteBtn").should("exist"); + + cy.get("#editBtn").click(); + + cy.get('[data-cy="editPublishedDataForm"]').should("exist"); + + cy.get('[data-cy="metadata"]').click(); + cy.get("jsonforms").should("exist"); + + cy.get("button.save-and-continue").should("be.disabled"); + + cy.get('[aria-label="Add to Creators button"]').click(); + + cy.get('[aria-label="Add to Creators button"]') + .closest(".array-layout") + .find('input[id^="#/properties/name"]') + .first() + .clear() + .type(creatorName); + + cy.get('[id="#/properties/resourceType"]').clear().type(resourceType); + + cy.get('[ng-reflect-path="publisher"]') + .parent() + .find('[id="#/properties/name"]') + .clear() + .type(publisherName); + + cy.get('[ng-reflect-path="publisher"]') + .parent() + .should("contain", "is a required property"); + cy.get('[ng-reflect-path="publisher"]') + .parent() + .find('input[id="#/properties/publisherIdentifierScheme"]') + .clear() + .type(publisherIndetifierScheme); + + cy.get("button.save-and-continue").should("not.be.disabled"); + + cy.get("button.save-and-continue").click(); + + cy.get('[data-cy="status"]').contains("private"); + + cy.get('[data-cy="publishButton"]').click(); + + cy.get('[data-cy="status"]').contains("public"); + + cy.get("#editBtn").should("not.exist"); + cy.get("#deleteBtn").should("not.exist"); + }); + + it.only("admins should be able to edit public published data", () => { + const newCreatorName = "new creator name"; + cy.visit("/publishedDatasets"); + + cy.get("app-publisheddata-dashboard mat-table mat-header-row").should( + "exist", + ); + + cy.finishedLoading(); + + cy.get('input[formcontrolname="globalSearch"]') + .clear() + .type(userPublishedDataTitle); + + cy.isLoading(); + + cy.get("app-publisheddata-dashboard mat-table mat-row") + .contains(userPublishedDataTitle) + .first() + .click(); + + cy.get('[data-cy="status"]').contains("public"); + + cy.get("#editBtn").click(); + + cy.get('[data-cy="editPublishedDataForm"]').should("exist"); + + cy.get('[data-cy="metadata"]').click(); + cy.get("jsonforms").should("exist"); + + cy.get("button.save-and-continue").should("not.be.disabled"); + + cy.get('[aria-label="Add to Creators button"]') + .closest(".array-layout") + .find('input[id^="#/properties/name"]') + .first() + .clear() + .type(newCreatorName); + + cy.get("button.save-and-continue").should("not.be.disabled"); + + cy.get("button.save-and-continue").click(); + + cy.get('[data-cy="status"]').contains("public"); + + cy.get('[data-cy="showHideMetadata"]').click(); + + cy.get("ngx-json-viewer section").contains("metadata").click(); + cy.get("ngx-json-viewer section").contains(newCreatorName); + }); + }); +}); diff --git a/src/app/datasets/batch-view/batch-view.component.html b/src/app/datasets/batch-view/batch-view.component.html index 2ea44da6a..15a49a181 100644 --- a/src/app/datasets/batch-view/batch-view.component.html +++ b/src/app/datasets/batch-view/batch-view.component.html @@ -9,7 +9,7 @@