From 139081aee2eee9426a837620606be2a960ad214a Mon Sep 17 00:00:00 2001 From: David Wiessner Date: Mon, 12 Aug 2024 08:42:38 +0000 Subject: [PATCH 001/245] Prepare ingestor frontend minimal ui --- .../app-header/app-header.component.html | 10 +++- src/app/app-routing/app-routing.module.ts | 7 +++ .../ingestor.feature.module.ts | 8 +++ .../ingestor.routing.module.ts | 15 +++++ .../ingestor-metadata-editor.component.html | 4 ++ .../ingestor-metadata-editor.component.scss | 0 .../ingestor-metadata-editor.component.ts | 18 ++++++ src/app/ingestor/ingestor.module.ts | 29 ++++++++++ .../ingestor/ingestor/ingestor.component.html | 36 ++++++++++++ .../ingestor/ingestor/ingestor.component.scss | 5 ++ .../ingestor/ingestor.component.spec.ts | 24 ++++++++ .../ingestor/ingestor/ingestor.component.ts | 58 +++++++++++++++++++ 12 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 src/app/app-routing/lazy/ingestor-routing/ingestor.feature.module.ts create mode 100644 src/app/app-routing/lazy/ingestor-routing/ingestor.routing.module.ts create mode 100644 src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.html create mode 100644 src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.scss create mode 100644 src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts create mode 100644 src/app/ingestor/ingestor.module.ts create mode 100644 src/app/ingestor/ingestor/ingestor.component.html create mode 100644 src/app/ingestor/ingestor/ingestor.component.scss create mode 100644 src/app/ingestor/ingestor/ingestor.component.spec.ts create mode 100644 src/app/ingestor/ingestor/ingestor.component.ts diff --git a/src/app/_layout/app-header/app-header.component.html b/src/app/_layout/app-header/app-header.component.html index b844e86598..88baab4099 100644 --- a/src/app/_layout/app-header/app-header.component.html +++ b/src/app/_layout/app-header/app-header.component.html @@ -67,7 +67,15 @@

{{ status }}

- + + +
+ + cloud_upload + Ingestor (OpenEM) +
+
diff --git a/src/app/app-routing/app-routing.module.ts b/src/app/app-routing/app-routing.module.ts index 3310f2c053..2f95ca27e5 100644 --- a/src/app/app-routing/app-routing.module.ts +++ b/src/app/app-routing/app-routing.module.ts @@ -105,6 +105,13 @@ export const routes: Routes = [ (m) => m.HelpFeatureModule, ), }, + { + path: "ingestor", + loadChildren: () => + import("./lazy/ingestor-routing/ingestor.feature.module").then( + (m) => m.IngestorFeatureModule, + ), + }, { path: "logbooks", loadChildren: () => diff --git a/src/app/app-routing/lazy/ingestor-routing/ingestor.feature.module.ts b/src/app/app-routing/lazy/ingestor-routing/ingestor.feature.module.ts new file mode 100644 index 0000000000..8796c9c34e --- /dev/null +++ b/src/app/app-routing/lazy/ingestor-routing/ingestor.feature.module.ts @@ -0,0 +1,8 @@ +import { NgModule } from "@angular/core"; +import { IngestorRoutingModule } from "./ingestor.routing.module"; +import { IngestorModule } from "ingestor/ingestor.module"; + +@NgModule({ + imports: [IngestorModule, IngestorRoutingModule], +}) +export class IngestorFeatureModule {} diff --git a/src/app/app-routing/lazy/ingestor-routing/ingestor.routing.module.ts b/src/app/app-routing/lazy/ingestor-routing/ingestor.routing.module.ts new file mode 100644 index 0000000000..c1a5b047d5 --- /dev/null +++ b/src/app/app-routing/lazy/ingestor-routing/ingestor.routing.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; +import { IngestorComponent } from "ingestor/ingestor/ingestor.component"; + +const routes: Routes = [ + { + path: "", + component: IngestorComponent, + }, +]; +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class IngestorRoutingModule {} diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.html b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.html new file mode 100644 index 0000000000..d5ac4b7128 --- /dev/null +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.html @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.scss b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts new file mode 100644 index 0000000000..19b7d76c4a --- /dev/null +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts @@ -0,0 +1,18 @@ +import { Component, EventEmitter, Output } from '@angular/core'; + +@Component({ + selector: 'app-metadata-editor', + templateUrl: './ingestor-metadata-editor.component.html', + styleUrls: ['./ingestor-metadata-editor.component.scss'] +}) +export class IngestorMetadataEditorComponent { + metadata: string = ''; + + // Optional: EventEmitter, um Änderungen an der Metadata zu melden + @Output() metadataChange = new EventEmitter(); + + onMetadataChange(newMetadata: string) { + this.metadata = newMetadata; + this.metadataChange.emit(this.metadata); + } +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor.module.ts b/src/app/ingestor/ingestor.module.ts new file mode 100644 index 0000000000..a693ac724f --- /dev/null +++ b/src/app/ingestor/ingestor.module.ts @@ -0,0 +1,29 @@ +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { IngestorComponent } from "./ingestor/ingestor.component"; +import { MatCardModule } from "@angular/material/card"; +import { RouterModule } from "@angular/router"; +import { IngestorMetadataEditorComponent } from "./ingestor-metadata-editor/ingestor-metadata-editor.component"; +import { MatButtonModule } from "@angular/material/button"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatInputModule } from "@angular/material/input"; +import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; +import { FormsModule } from "@angular/forms"; + +@NgModule({ + declarations: [ + IngestorComponent, + IngestorMetadataEditorComponent + ], + imports: [ + CommonModule, + MatCardModule, + FormsModule, + MatFormFieldModule, + MatInputModule, + MatButtonModule, + MatProgressSpinnerModule, + RouterModule + ], +}) +export class IngestorModule { } diff --git a/src/app/ingestor/ingestor/ingestor.component.html b/src/app/ingestor/ingestor/ingestor.component.html new file mode 100644 index 0000000000..151391c679 --- /dev/null +++ b/src/app/ingestor/ingestor/ingestor.component.html @@ -0,0 +1,36 @@ +

+ + + Ingestor-Connection + + +

+

No Backend connected

+
+ + +

+ +

+ + + Ingest Dataset + + +

+
+ +
+ + + +
+
+ +
+ + +

\ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor.component.scss b/src/app/ingestor/ingestor/ingestor.component.scss new file mode 100644 index 0000000000..b85eb9646e --- /dev/null +++ b/src/app/ingestor/ingestor/ingestor.component.scss @@ -0,0 +1,5 @@ +.ingestor-vertical-layout { + display: flex; + flex-direction: column; + gap: 1em; +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor.component.spec.ts b/src/app/ingestor/ingestor/ingestor.component.spec.ts new file mode 100644 index 0000000000..52186b1077 --- /dev/null +++ b/src/app/ingestor/ingestor/ingestor.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { IngestorComponent } from './ingestor.component'; + +describe('IngestorComponent', () => { + let component: IngestorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ IngestorComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(IngestorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); \ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor.component.ts b/src/app/ingestor/ingestor/ingestor.component.ts new file mode 100644 index 0000000000..d300b87a0f --- /dev/null +++ b/src/app/ingestor/ingestor/ingestor.component.ts @@ -0,0 +1,58 @@ +import { Component, OnInit, ViewChild } from "@angular/core"; +import { AppConfigService, HelpMessages } from "app-config.service"; +import { HttpClient } from '@angular/common/http'; +import { IngestorMetadataEditorComponent } from '../ingestor-metadata-editor/ingestor-metadata-editor.component'; + +@Component({ + selector: "ingestor", + templateUrl: "./ingestor.component.html", + styleUrls: ["./ingestor.component.scss"], +}) +export class IngestorComponent implements OnInit { + @ViewChild(IngestorMetadataEditorComponent) metadataEditor: IngestorMetadataEditorComponent; + + appConfig = this.appConfigService.getConfig(); + facility: string | null = null; + ingestManual: string | null = null; + gettingStarted: string | null = null; + shoppingCartEnabled = false; + helpMessages: HelpMessages; + filePath: string = ''; + loading: boolean = false; + + constructor(public appConfigService: AppConfigService, private http: HttpClient) {} + + ngOnInit() { + this.facility = this.appConfig.facility; + this.ingestManual = this.appConfig.ingestManual; + this.helpMessages = new HelpMessages( + this.appConfig.helpMessages?.gettingStarted, + this.appConfig.helpMessages?.ingestManual, + ); + this.gettingStarted = this.appConfig.gettingStarted; + } + + upload() { + this.loading = true; + const payload = { + metadata: this.metadataEditor.metadata, + filePath: this.filePath + }; + + console.log('Uploading', payload); + + setTimeout(() => { + this.loading = false; + }, 2000); + /*this.http.post('/api/upload', payload).subscribe( + response => { + console.log('Upload successful', response); + this.loading = false; + }, + error => { + console.error('Upload failed', error); + this.loading = false; + } + );*/ + } +} \ No newline at end of file From 6e5f33c32b5fe71cf57f67f33f569feecb2c21e4 Mon Sep 17 00:00:00 2001 From: David Wiessner Date: Thu, 15 Aug 2024 11:21:47 +0000 Subject: [PATCH 002/245] Update API --- .github/workflows/test.yml | 2 +- .vscode/settings.json | 16 --- CI/ESS/e2e/config.e2e.json | 2 +- CI/ESS/e2e/cypress.config.ts | 2 +- cypress.config.ts | 2 +- scripts/local.proxy.config.json | 2 +- scripts/sample_data.sh | 8 +- src/app/app-config.service.spec.ts | 2 +- .../ingestor/ingestor/ingestor.component.html | 55 ++++++++- .../ingestor/ingestor/ingestor.component.ts | 111 ++++++++++++++++-- src/app/shared/sdk/lb.config.ts | 2 +- src/assets/config.json | 2 +- 12 files changed, 164 insertions(+), 42 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 31ea6b31b6..c4c8d31d16 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -124,7 +124,7 @@ jobs: - name: Wait for Backend run: | npm install -g wait-on - wait-on http://localhost:3000/api/v3/health --timeout 200000 + wait-on http://backend.localhost/api/v3/health --timeout 200000 - name: Run Cypress tests uses: cypress-io/github-action@v6 diff --git a/.vscode/settings.json b/.vscode/settings.json index 3bd83d36a2..f5f9be20e7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,20 +1,4 @@ { - "workbench.colorCustomizations": { - "activityBar.background": "#0e3041", - "activityBar.foreground": "#e7e7e7", - "activityBar.inactiveForeground": "#e7e7e799", - "activityBarBadge.background": "#b82888", - "activityBarBadge.foreground": "#e7e7e7", - "titleBar.activeBackground": "#051117", - "titleBar.inactiveBackground": "#05111799", - "titleBar.activeForeground": "#e7e7e7", - "titleBar.inactiveForeground": "#e7e7e799", - "statusBar.background": "#051117", - "statusBarItem.hoverBackground": "#0e3041", - "statusBar.foreground": "#e7e7e7", - "activityBar.activeBackground": "#0e3041", - "activityBar.activeBorder": "#b82888" - }, "peacock.color": "#051117", "editor.tabSize": 2, "diffEditor.wordWrap": "on", diff --git a/CI/ESS/e2e/config.e2e.json b/CI/ESS/e2e/config.e2e.json index 5ff1f6c9d8..a5d14bc917 100644 --- a/CI/ESS/e2e/config.e2e.json +++ b/CI/ESS/e2e/config.e2e.json @@ -25,7 +25,7 @@ "jsonMetadataEnabled": true, "jupyterHubUrl": "https://jupyterhub.esss.lu.se/", "landingPage": "doi.ess.eu/detail/", - "lbBaseURL": "http://localhost:3000", + "lbBaseURL": "http://backend.localhost", "localColumns": [ { "name": "select", diff --git a/CI/ESS/e2e/cypress.config.ts b/CI/ESS/e2e/cypress.config.ts index 65eb049682..0753fc83c8 100644 --- a/CI/ESS/e2e/cypress.config.ts +++ b/CI/ESS/e2e/cypress.config.ts @@ -3,7 +3,7 @@ import { defineConfig } from "cypress"; export default defineConfig({ e2e: { baseUrl: "http://localhost:4200", - lbBaseUrl: "http://localhost:3000/api/v3", + lbBaseUrl: "http://backend.localhost/api/v3", lbLoginEndpoint: "/Users/login?include=user", lbTokenPrefix: "Bearer ", viewportWidth: 1280, diff --git a/cypress.config.ts b/cypress.config.ts index e09b065064..6640cd2b0d 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -3,7 +3,7 @@ import { defineConfig } from "cypress"; export default defineConfig({ e2e: { baseUrl: "http://127.0.0.1:4200", - lbBaseUrl: "http://localhost:3000/api/v3", + lbBaseUrl: "http://backend.localhost/api/v3", lbLoginEndpoint: "/Users/login", lbTokenPrefix: "Bearer ", viewportWidth: 1280, diff --git a/scripts/local.proxy.config.json b/scripts/local.proxy.config.json index ca6147ce54..c447e20553 100644 --- a/scripts/local.proxy.config.json +++ b/scripts/local.proxy.config.json @@ -1,6 +1,6 @@ { "/api/v3/*": { - "target": "http://localhost:3000", + "target": "http://backend.localhost", "secure": false, "logLevel": "debug", "changeOrigin": true diff --git a/scripts/sample_data.sh b/scripts/sample_data.sh index 692833eb3d..727782e5ba 100755 --- a/scripts/sample_data.sh +++ b/scripts/sample_data.sh @@ -1,11 +1,11 @@ #!/bin/sh # UPDATE ACCESS TOKEN -http POST http://localhost:3000/api/v3/RawDatasets?access_token=wDBhvBhKAc0OL8Hrc0WP3CcuIFSUVjEFyxF38sNHTUgHZaunDlxqxBeDOSVVJaJx < raw_dataset.json +http POST http://backend.localhost/api/v3/RawDatasets?access_token=wDBhvBhKAc0OL8Hrc0WP3CcuIFSUVjEFyxF38sNHTUgHZaunDlxqxBeDOSVVJaJx < raw_dataset.json -http POST http://localhost:3000/api/v3/Datablocks?access_token=S5GtalRhAs1jT0PJovPmv8A3QbLwoHeUoRgh3VKiqFcchULvO3zU4VYjSko589fN < datablock_two.json +http POST http://backend.localhost/api/v3/Datablocks?access_token=S5GtalRhAs1jT0PJovPmv8A3QbLwoHeUoRgh3VKiqFcchULvO3zU4VYjSko589fN < datablock_two.json -# curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d raw_dataset.json 'http://localhost:3000/api/v3/RawDatasets?access_token=QNV0Su9omZy9R6I38H7iv0aMbbWW0j7LsGvve2YoWJvif4MwyLGSSkdZ8RWsIDVq' +# curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d raw_dataset.json 'http://backend.localhost/api/v3/RawDatasets?access_token=QNV0Su9omZy9R6I38H7iv0aMbbWW0j7LsGvve2YoWJvif4MwyLGSSkdZ8RWsIDVq' -# curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d datablock.json 'http://localhost:3000/api/v3/Datablocks?access_token=QNV0Su9omZy9R6I38H7iv0aMbbWW0j7LsGvve2YoWJvif4MwyLGSSkdZ8RWsIDVq' +# curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d datablock.json 'http://backend.localhost/api/v3/Datablocks?access_token=QNV0Su9omZy9R6I38H7iv0aMbbWW0j7LsGvve2YoWJvif4MwyLGSSkdZ8RWsIDVq' diff --git a/src/app/app-config.service.spec.ts b/src/app/app-config.service.spec.ts index 3a59efee6a..5956ab44fc 100644 --- a/src/app/app-config.service.spec.ts +++ b/src/app/app-config.service.spec.ts @@ -34,7 +34,7 @@ const appConfig: AppConfig = { jsonMetadataEnabled: true, jupyterHubUrl: "https://jupyterhub.esss.lu.se/", landingPage: "doi2.psi.ch/detail/", - lbBaseURL: "http://127.0.0.1:3000", + lbBaseURL: "http://backend.localhost", localColumns: [ { name: "select", diff --git a/src/app/ingestor/ingestor/ingestor.component.html b/src/app/ingestor/ingestor/ingestor.component.html index 151391c679..61ab5ab393 100644 --- a/src/app/ingestor/ingestor/ingestor.component.html +++ b/src/app/ingestor/ingestor/ingestor.component.html @@ -4,14 +4,42 @@ Ingestor-Connection -
-

No Backend connected

+
+ +
+
+ +

No Backend connected

+ +

Please provide a valid Backend URL

+ + + + + +
+ + + +

Connected to {{ connectedFacilityBackend }} + change +

+

-

+

Ingest Dataset @@ -26,11 +54,28 @@

-
+

+ +

+ + + Return Value + + +

+
+
+

{{returnValue}}

+
+ +

\ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor.component.ts b/src/app/ingestor/ingestor/ingestor.component.ts index d300b87a0f..e3722a06d9 100644 --- a/src/app/ingestor/ingestor/ingestor.component.ts +++ b/src/app/ingestor/ingestor/ingestor.component.ts @@ -1,7 +1,8 @@ -import { Component, OnInit, ViewChild } from "@angular/core"; +import { Component, OnInit, SimpleChanges, ViewChild } from "@angular/core"; import { AppConfigService, HelpMessages } from "app-config.service"; import { HttpClient } from '@angular/common/http'; import { IngestorMetadataEditorComponent } from '../ingestor-metadata-editor/ingestor-metadata-editor.component'; +import { ActivatedRoute, Router } from '@angular/router'; @Component({ selector: "ingestor", @@ -9,6 +10,7 @@ import { IngestorMetadataEditorComponent } from '../ingestor-metadata-editor/ing styleUrls: ["./ingestor.component.scss"], }) export class IngestorComponent implements OnInit { + @ViewChild(IngestorMetadataEditorComponent) metadataEditor: IngestorMetadataEditorComponent; appConfig = this.appConfigService.getConfig(); @@ -19,8 +21,13 @@ export class IngestorComponent implements OnInit { helpMessages: HelpMessages; filePath: string = ''; loading: boolean = false; + forwardFacilityBackend: string = ''; + connectedFacilityBackend: string = ''; + connectingToFacilityBackend: boolean = false; + lastUsedFacilityBackends: string[] = []; + returnValue: string = ''; - constructor(public appConfigService: AppConfigService, private http: HttpClient) {} + constructor(public appConfigService: AppConfigService, private http: HttpClient, private route: ActivatedRoute, private router: Router) { } ngOnInit() { this.facility = this.appConfig.facility; @@ -30,29 +37,115 @@ export class IngestorComponent implements OnInit { this.appConfig.helpMessages?.ingestManual, ); this.gettingStarted = this.appConfig.gettingStarted; + this.connectingToFacilityBackend = true; + // Get the GET parameter 'backendUrl' from the URL + this.route.queryParams.subscribe(params => { + const backendUrl = params['backendUrl']; + if (backendUrl) { + this.connectToFacilityBackend(backendUrl); + } + else { + this.lastUsedFacilityBackends = this.loadLastUsedFacilityBackendsFromLocalStorage(); + this.connectingToFacilityBackend = false; + } + }); + } + + connectToFacilityBackend(facilityBackendUrl: string): boolean { + // Store the connected facility backend URL in the local storage + this.storeLastUsedFacilityBackendInLocalStorage(facilityBackendUrl); + + let facilityBackendUrlCleaned = facilityBackendUrl.slice(); + // Check if last symbol is a slash and add version endpoint + if (facilityBackendUrlCleaned.slice(-1) !== '/') { + facilityBackendUrlCleaned += '/'; + } + + let facilityBackendUrlVersion = facilityBackendUrlCleaned + 'Version'; + + // Try to connect to the facility backend/version to check if it is available + console.log('Connecting to facility backend: ' + facilityBackendUrlVersion); + this.http.get(facilityBackendUrlVersion).subscribe( + response => { + console.log('Connected to facility backend', response); + // If the connection is successful, store the connected facility backend URL + this.connectedFacilityBackend = facilityBackendUrlCleaned; + this.connectingToFacilityBackend = false; + }, + error => { + console.error('Failed to connect to facility backend', error); + this.connectedFacilityBackend = ''; + this.connectingToFacilityBackend = false; + this.lastUsedFacilityBackends = this.loadLastUsedFacilityBackendsFromLocalStorage(); + } + ); + + return true; } upload() { this.loading = true; + this.returnValue = ''; const payload = { - metadata: this.metadataEditor.metadata, - filePath: this.filePath + filePath: this.filePath, + metaData: this.metadataEditor.metadata }; console.log('Uploading', payload); - setTimeout(() => { - this.loading = false; - }, 2000); - /*this.http.post('/api/upload', payload).subscribe( + this.http.post(this.connectedFacilityBackend + 'Dataset/Ingest', payload).subscribe( response => { console.log('Upload successful', response); + this.returnValue = JSON.stringify(response); this.loading = false; }, error => { console.error('Upload failed', error); this.loading = false; } - );*/ + ); + } + + forwardToIngestorPage() { + if (this.forwardFacilityBackend) { + this.router.navigate(['/ingestor'], { queryParams: { backendUrl: this.forwardFacilityBackend } }); + } + } + + disconnectIngestor() { + this.returnValue = ''; + this.connectedFacilityBackend = ''; + // Remove the GET parameter 'backendUrl' from the URL + this.router.navigate(['/ingestor']); + } + + // Helper functions + selectFacilityBackend(facilityBackend: string) { + this.forwardFacilityBackend = facilityBackend; + } + + storeLastUsedFacilityBackendInLocalStorage(item: string) { + // Add the item to a list and store the list in the local Storage + let lastUsedFacilityBackends = this.loadLastUsedFacilityBackendsFromLocalStorage(); + if (!lastUsedFacilityBackends) { + lastUsedFacilityBackends = []; + } + + // Check if the item is already in the list, if yes ignore it + if (lastUsedFacilityBackends.includes(item)) { + return; + } + + lastUsedFacilityBackends.push(item); + localStorage.setItem('lastUsedFacilityBackends', JSON.stringify(lastUsedFacilityBackends)); + } + + loadLastUsedFacilityBackendsFromLocalStorage(): string[] { + // Load the list from the local Storage + const lastUsedFacilityBackends = localStorage.getItem('lastUsedFacilityBackends'); + if (lastUsedFacilityBackends) { + return JSON.parse(lastUsedFacilityBackends); + } + return []; } } \ No newline at end of file diff --git a/src/app/shared/sdk/lb.config.ts b/src/app/shared/sdk/lb.config.ts index 9f8cf2b649..398216a63b 100644 --- a/src/app/shared/sdk/lb.config.ts +++ b/src/app/shared/sdk/lb.config.ts @@ -14,7 +14,7 @@ * * export class MyApp { * constructor() { - * LoopBackConfig.setBaseURL('http://localhost:3000'); + * LoopBackConfig.setBaseURL('http://backend.localhost'); * LoopBackConfig.setApiVersion('api'); * } * } diff --git a/src/assets/config.json b/src/assets/config.json index c32cf212b4..e893ad62f9 100644 --- a/src/assets/config.json +++ b/src/assets/config.json @@ -25,7 +25,7 @@ "jsonMetadataEnabled": true, "jupyterHubUrl": "https://jupyterhub.esss.lu.se/", "landingPage": "doi.ess.eu/detail/", - "lbBaseURL": "http://127.0.0.1:3000", + "lbBaseURL": "http://backend.localhost", "localColumns": [ { "name": "select", From 5ef4b42ed37a158f303b6707b781f3b49029e036 Mon Sep 17 00:00:00 2001 From: David Wiessner Date: Mon, 2 Sep 2024 11:53:34 +0000 Subject: [PATCH 003/245] static list of backends --- .../ingestor/ingestor/ingestor.component.ts | 27 +++---------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/src/app/ingestor/ingestor/ingestor.component.ts b/src/app/ingestor/ingestor/ingestor.component.ts index e3722a06d9..e2931bc54e 100644 --- a/src/app/ingestor/ingestor/ingestor.component.ts +++ b/src/app/ingestor/ingestor/ingestor.component.ts @@ -38,6 +38,7 @@ export class IngestorComponent implements OnInit { ); this.gettingStarted = this.appConfig.gettingStarted; this.connectingToFacilityBackend = true; + this.lastUsedFacilityBackends = this.loadLastUsedFacilityBackends(); // Get the GET parameter 'backendUrl' from the URL this.route.queryParams.subscribe(params => { const backendUrl = params['backendUrl']; @@ -45,16 +46,12 @@ export class IngestorComponent implements OnInit { this.connectToFacilityBackend(backendUrl); } else { - this.lastUsedFacilityBackends = this.loadLastUsedFacilityBackendsFromLocalStorage(); this.connectingToFacilityBackend = false; } }); } connectToFacilityBackend(facilityBackendUrl: string): boolean { - // Store the connected facility backend URL in the local storage - this.storeLastUsedFacilityBackendInLocalStorage(facilityBackendUrl); - let facilityBackendUrlCleaned = facilityBackendUrl.slice(); // Check if last symbol is a slash and add version endpoint if (facilityBackendUrlCleaned.slice(-1) !== '/') { @@ -76,7 +73,7 @@ export class IngestorComponent implements OnInit { console.error('Failed to connect to facility backend', error); this.connectedFacilityBackend = ''; this.connectingToFacilityBackend = false; - this.lastUsedFacilityBackends = this.loadLastUsedFacilityBackendsFromLocalStorage(); + this.lastUsedFacilityBackends = this.loadLastUsedFacilityBackends(); } ); @@ -124,25 +121,9 @@ export class IngestorComponent implements OnInit { this.forwardFacilityBackend = facilityBackend; } - storeLastUsedFacilityBackendInLocalStorage(item: string) { - // Add the item to a list and store the list in the local Storage - let lastUsedFacilityBackends = this.loadLastUsedFacilityBackendsFromLocalStorage(); - if (!lastUsedFacilityBackends) { - lastUsedFacilityBackends = []; - } - - // Check if the item is already in the list, if yes ignore it - if (lastUsedFacilityBackends.includes(item)) { - return; - } - - lastUsedFacilityBackends.push(item); - localStorage.setItem('lastUsedFacilityBackends', JSON.stringify(lastUsedFacilityBackends)); - } - - loadLastUsedFacilityBackendsFromLocalStorage(): string[] { + loadLastUsedFacilityBackends(): string[] { // Load the list from the local Storage - const lastUsedFacilityBackends = localStorage.getItem('lastUsedFacilityBackends'); + const lastUsedFacilityBackends = '["http://localhost:8000"]'; if (lastUsedFacilityBackends) { return JSON.parse(lastUsedFacilityBackends); } From 1864db427ec6d3106fcb27a1a32f9aee79cf0106 Mon Sep 17 00:00:00 2001 From: David Wiessner Date: Mon, 9 Sep 2024 13:07:02 +0000 Subject: [PATCH 004/245] Remove unused file path input field in ingestor component --- src/app/ingestor/ingestor/ingestor.component.html | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/app/ingestor/ingestor/ingestor.component.html b/src/app/ingestor/ingestor/ingestor.component.html index 61ab5ab393..3d19f732df 100644 --- a/src/app/ingestor/ingestor/ingestor.component.html +++ b/src/app/ingestor/ingestor/ingestor.component.html @@ -48,11 +48,6 @@
-
- - - -
@@ -69,7 +82,23 @@
-

{{returnValue}}

+

{{returnValue}}

+
+ + +

+ +

+ + + Error message + + + +

+

diff --git a/src/app/ingestor/ingestor/ingestor.component.scss b/src/app/ingestor/ingestor/ingestor.component.scss index b85eb9646e..96cdeec1ba 100644 --- a/src/app/ingestor/ingestor/ingestor.component.scss +++ b/src/app/ingestor/ingestor/ingestor.component.scss @@ -2,4 +2,15 @@ display: flex; flex-direction: column; gap: 1em; +} + +/* src/app/ingestor/ingestor.component.scss */ +.ingestor-mixed-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.ingestor-close-button { + margin-left: auto; } \ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor.component.ts b/src/app/ingestor/ingestor/ingestor.component.ts index e2931bc54e..82213f95c5 100644 --- a/src/app/ingestor/ingestor/ingestor.component.ts +++ b/src/app/ingestor/ingestor/ingestor.component.ts @@ -3,6 +3,7 @@ import { AppConfigService, HelpMessages } from "app-config.service"; import { HttpClient } from '@angular/common/http'; import { IngestorMetadataEditorComponent } from '../ingestor-metadata-editor/ingestor-metadata-editor.component'; import { ActivatedRoute, Router } from '@angular/router'; +import { INGESTOR_API_ENDPOINTS_V1 } from "./ingestor-api-endpoints"; @Component({ selector: "ingestor", @@ -19,12 +20,17 @@ export class IngestorComponent implements OnInit { gettingStarted: string | null = null; shoppingCartEnabled = false; helpMessages: HelpMessages; + filePath: string = ''; loading: boolean = false; forwardFacilityBackend: string = ''; + connectedFacilityBackend: string = ''; + connectedFacilityBackendVersion: string = ''; connectingToFacilityBackend: boolean = false; lastUsedFacilityBackends: string[] = []; + + errorMessage: string = ''; returnValue: string = ''; constructor(public appConfigService: AppConfigService, private http: HttpClient, private route: ActivatedRoute, private router: Router) { } @@ -58,7 +64,7 @@ export class IngestorComponent implements OnInit { facilityBackendUrlCleaned += '/'; } - let facilityBackendUrlVersion = facilityBackendUrlCleaned + 'Version'; + let facilityBackendUrlVersion = facilityBackendUrlCleaned + INGESTOR_API_ENDPOINTS_V1.OTHER.VERSION; // Try to connect to the facility backend/version to check if it is available console.log('Connecting to facility backend: ' + facilityBackendUrlVersion); @@ -68,9 +74,11 @@ export class IngestorComponent implements OnInit { // If the connection is successful, store the connected facility backend URL this.connectedFacilityBackend = facilityBackendUrlCleaned; this.connectingToFacilityBackend = false; + this.connectedFacilityBackendVersion = response['version']; }, error => { - console.error('Failed to connect to facility backend', error); + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}
`; + console.error('Request failed', error); this.connectedFacilityBackend = ''; this.connectingToFacilityBackend = false; this.lastUsedFacilityBackends = this.loadLastUsedFacilityBackends(); @@ -90,13 +98,14 @@ export class IngestorComponent implements OnInit { console.log('Uploading', payload); - this.http.post(this.connectedFacilityBackend + 'Dataset/Ingest', payload).subscribe( + this.http.post(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.DATASET, payload).subscribe( response => { console.log('Upload successful', response); this.returnValue = JSON.stringify(response); this.loading = false; }, error => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}]
`; console.error('Upload failed', error); this.loading = false; } @@ -105,6 +114,14 @@ export class IngestorComponent implements OnInit { forwardToIngestorPage() { if (this.forwardFacilityBackend) { + this.connectingToFacilityBackend = true; + + // If current route is equal to the forward route, the router will not navigate to the new route + if (this.connectedFacilityBackend === this.forwardFacilityBackend) { + this.connectToFacilityBackend(this.forwardFacilityBackend); + return; + } + this.router.navigate(['/ingestor'], { queryParams: { backendUrl: this.forwardFacilityBackend } }); } } @@ -123,10 +140,14 @@ export class IngestorComponent implements OnInit { loadLastUsedFacilityBackends(): string[] { // Load the list from the local Storage - const lastUsedFacilityBackends = '["http://localhost:8000"]'; + const lastUsedFacilityBackends = '["http://localhost:8000", "http://localhost:8888"]'; if (lastUsedFacilityBackends) { return JSON.parse(lastUsedFacilityBackends); } return []; } + + clearErrorMessage(): void { + this.errorMessage = ''; + } } \ No newline at end of file From 80d60bdaa27f0d4edc27d672070543991ef03342 Mon Sep 17 00:00:00 2001 From: David Wiessner Date: Thu, 12 Sep 2024 15:19:05 +0000 Subject: [PATCH 006/245] Change back to localhost:3000 instead of backend.localhost --- .github/workflows/test.yml | 2 +- CI/ESS/e2e/config.e2e.json | 2 +- CI/ESS/e2e/cypress.config.ts | 2 +- cypress.config.ts | 2 +- scripts/local.proxy.config.json | 2 +- scripts/sample_data.sh | 8 ++++---- src/app/app-config.service.spec.ts | 2 +- src/app/shared/sdk/lb.config.ts | 2 +- src/assets/config.json | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ccd2e6df40..223e5bca8d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -124,7 +124,7 @@ jobs: - name: Wait for Backend run: | npm install -g wait-on - wait-on http://backend.localhost/api/v3/health --timeout 200000 + wait-on http://localhost:3000/api/v3/health --timeout 200000 - name: Run Cypress tests uses: cypress-io/github-action@v6 diff --git a/CI/ESS/e2e/config.e2e.json b/CI/ESS/e2e/config.e2e.json index a5d14bc917..5ff1f6c9d8 100644 --- a/CI/ESS/e2e/config.e2e.json +++ b/CI/ESS/e2e/config.e2e.json @@ -25,7 +25,7 @@ "jsonMetadataEnabled": true, "jupyterHubUrl": "https://jupyterhub.esss.lu.se/", "landingPage": "doi.ess.eu/detail/", - "lbBaseURL": "http://backend.localhost", + "lbBaseURL": "http://localhost:3000", "localColumns": [ { "name": "select", diff --git a/CI/ESS/e2e/cypress.config.ts b/CI/ESS/e2e/cypress.config.ts index 7b82107959..3d875e105e 100644 --- a/CI/ESS/e2e/cypress.config.ts +++ b/CI/ESS/e2e/cypress.config.ts @@ -3,7 +3,7 @@ import { defineConfig } from "cypress"; export default defineConfig({ e2e: { baseUrl: "http://localhost:4200", - lbBaseUrl: "http://backend.localhost/api/v3", + lbBaseUrl: "http://localhost:3000/api/v3", lbLoginEndpoint: "/Users/login?include=user", lbTokenPrefix: "Bearer ", viewportWidth: 1280, diff --git a/cypress.config.ts b/cypress.config.ts index 59f2aaafc9..e19f1c8b9c 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -3,7 +3,7 @@ import { defineConfig } from "cypress"; export default defineConfig({ e2e: { baseUrl: "http://127.0.0.1:4200", - lbBaseUrl: "http://backend.localhost/api/v3", + lbBaseUrl: "http://localhost:3000/api/v3", lbLoginEndpoint: "/Users/login", lbTokenPrefix: "Bearer ", viewportWidth: 1280, diff --git a/scripts/local.proxy.config.json b/scripts/local.proxy.config.json index c447e20553..ca6147ce54 100644 --- a/scripts/local.proxy.config.json +++ b/scripts/local.proxy.config.json @@ -1,6 +1,6 @@ { "/api/v3/*": { - "target": "http://backend.localhost", + "target": "http://localhost:3000", "secure": false, "logLevel": "debug", "changeOrigin": true diff --git a/scripts/sample_data.sh b/scripts/sample_data.sh index 727782e5ba..692833eb3d 100755 --- a/scripts/sample_data.sh +++ b/scripts/sample_data.sh @@ -1,11 +1,11 @@ #!/bin/sh # UPDATE ACCESS TOKEN -http POST http://backend.localhost/api/v3/RawDatasets?access_token=wDBhvBhKAc0OL8Hrc0WP3CcuIFSUVjEFyxF38sNHTUgHZaunDlxqxBeDOSVVJaJx < raw_dataset.json +http POST http://localhost:3000/api/v3/RawDatasets?access_token=wDBhvBhKAc0OL8Hrc0WP3CcuIFSUVjEFyxF38sNHTUgHZaunDlxqxBeDOSVVJaJx < raw_dataset.json -http POST http://backend.localhost/api/v3/Datablocks?access_token=S5GtalRhAs1jT0PJovPmv8A3QbLwoHeUoRgh3VKiqFcchULvO3zU4VYjSko589fN < datablock_two.json +http POST http://localhost:3000/api/v3/Datablocks?access_token=S5GtalRhAs1jT0PJovPmv8A3QbLwoHeUoRgh3VKiqFcchULvO3zU4VYjSko589fN < datablock_two.json -# curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d raw_dataset.json 'http://backend.localhost/api/v3/RawDatasets?access_token=QNV0Su9omZy9R6I38H7iv0aMbbWW0j7LsGvve2YoWJvif4MwyLGSSkdZ8RWsIDVq' +# curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d raw_dataset.json 'http://localhost:3000/api/v3/RawDatasets?access_token=QNV0Su9omZy9R6I38H7iv0aMbbWW0j7LsGvve2YoWJvif4MwyLGSSkdZ8RWsIDVq' -# curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d datablock.json 'http://backend.localhost/api/v3/Datablocks?access_token=QNV0Su9omZy9R6I38H7iv0aMbbWW0j7LsGvve2YoWJvif4MwyLGSSkdZ8RWsIDVq' +# curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d datablock.json 'http://localhost:3000/api/v3/Datablocks?access_token=QNV0Su9omZy9R6I38H7iv0aMbbWW0j7LsGvve2YoWJvif4MwyLGSSkdZ8RWsIDVq' diff --git a/src/app/app-config.service.spec.ts b/src/app/app-config.service.spec.ts index e3310ebca4..a043999e8b 100644 --- a/src/app/app-config.service.spec.ts +++ b/src/app/app-config.service.spec.ts @@ -34,7 +34,7 @@ const appConfig: AppConfig = { jsonMetadataEnabled: true, jupyterHubUrl: "https://jupyterhub.esss.lu.se/", landingPage: "doi2.psi.ch/detail/", - lbBaseURL: "http://backend.localhost", + lbBaseURL: "http://localhost:3000", localColumns: [ { name: "select", diff --git a/src/app/shared/sdk/lb.config.ts b/src/app/shared/sdk/lb.config.ts index 398216a63b..9f8cf2b649 100644 --- a/src/app/shared/sdk/lb.config.ts +++ b/src/app/shared/sdk/lb.config.ts @@ -14,7 +14,7 @@ * * export class MyApp { * constructor() { - * LoopBackConfig.setBaseURL('http://backend.localhost'); + * LoopBackConfig.setBaseURL('http://localhost:3000'); * LoopBackConfig.setApiVersion('api'); * } * } diff --git a/src/assets/config.json b/src/assets/config.json index d2a42303f0..c41fd6c041 100644 --- a/src/assets/config.json +++ b/src/assets/config.json @@ -25,7 +25,7 @@ "jsonMetadataEnabled": true, "jupyterHubUrl": "https://jupyterhub.esss.lu.se/", "landingPage": "doi.ess.eu/detail/", - "lbBaseURL": "http://backend.localhost", + "lbBaseURL": "http://localhost:3000", "localColumns": [ { "name": "select", From 27a65441c80270c95ef61abc827be8a72677d48e Mon Sep 17 00:00:00 2001 From: David Wiessner Date: Thu, 12 Sep 2024 15:34:59 +0000 Subject: [PATCH 007/245] Change back the files to original state --- .vscode/settings.json | 16 ++++++++++++++++ src/app/app-config.service.spec.ts | 2 +- src/assets/config.json | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index f5f9be20e7..3bd83d36a2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,20 @@ { + "workbench.colorCustomizations": { + "activityBar.background": "#0e3041", + "activityBar.foreground": "#e7e7e7", + "activityBar.inactiveForeground": "#e7e7e799", + "activityBarBadge.background": "#b82888", + "activityBarBadge.foreground": "#e7e7e7", + "titleBar.activeBackground": "#051117", + "titleBar.inactiveBackground": "#05111799", + "titleBar.activeForeground": "#e7e7e7", + "titleBar.inactiveForeground": "#e7e7e799", + "statusBar.background": "#051117", + "statusBarItem.hoverBackground": "#0e3041", + "statusBar.foreground": "#e7e7e7", + "activityBar.activeBackground": "#0e3041", + "activityBar.activeBorder": "#b82888" + }, "peacock.color": "#051117", "editor.tabSize": 2, "diffEditor.wordWrap": "on", diff --git a/src/app/app-config.service.spec.ts b/src/app/app-config.service.spec.ts index a043999e8b..4538eea22c 100644 --- a/src/app/app-config.service.spec.ts +++ b/src/app/app-config.service.spec.ts @@ -34,7 +34,7 @@ const appConfig: AppConfig = { jsonMetadataEnabled: true, jupyterHubUrl: "https://jupyterhub.esss.lu.se/", landingPage: "doi2.psi.ch/detail/", - lbBaseURL: "http://localhost:3000", + lbBaseURL: "http://127.0.0.1:3000", localColumns: [ { name: "select", diff --git a/src/assets/config.json b/src/assets/config.json index c41fd6c041..759ea95070 100644 --- a/src/assets/config.json +++ b/src/assets/config.json @@ -25,7 +25,7 @@ "jsonMetadataEnabled": true, "jupyterHubUrl": "https://jupyterhub.esss.lu.se/", "landingPage": "doi.ess.eu/detail/", - "lbBaseURL": "http://localhost:3000", + "lbBaseURL": "http://127.0.0.1:3000", "localColumns": [ { "name": "select", From 03738a9bbf1759b3f19a2983e2b08cb36ad0d8d0 Mon Sep 17 00:00:00 2001 From: David Wiessner Date: Wed, 18 Sep 2024 15:25:39 +0000 Subject: [PATCH 008/245] fix ingestor endpoint --- src/app/ingestor/ingestor/ingestor-api-endpoints.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/ingestor/ingestor/ingestor-api-endpoints.ts b/src/app/ingestor/ingestor/ingestor-api-endpoints.ts index 12e3624f51..aa0ee1e755 100644 --- a/src/app/ingestor/ingestor/ingestor-api-endpoints.ts +++ b/src/app/ingestor/ingestor/ingestor-api-endpoints.ts @@ -1,5 +1,5 @@ export const INGESTOR_API_ENDPOINTS_V1 = { - DATASET: "datasets", + DATASET: "dataset", TRANSFER: "transfer", OTHER: { VERSION: 'version', From ff6bed97dffc66b31cc0dc881107c2b22cf8f2c1 Mon Sep 17 00:00:00 2001 From: David Wiessner Date: Wed, 18 Sep 2024 15:34:24 +0000 Subject: [PATCH 009/245] fix sonarcube issues --- src/app/ingestor/ingestor/ingestor.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/ingestor/ingestor/ingestor.component.ts b/src/app/ingestor/ingestor/ingestor.component.ts index 82213f95c5..99cf89e0f0 100644 --- a/src/app/ingestor/ingestor/ingestor.component.ts +++ b/src/app/ingestor/ingestor/ingestor.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, SimpleChanges, ViewChild } from "@angular/core"; +import { Component, OnInit, ViewChild } from "@angular/core"; import { AppConfigService, HelpMessages } from "app-config.service"; import { HttpClient } from '@angular/common/http'; import { IngestorMetadataEditorComponent } from '../ingestor-metadata-editor/ingestor-metadata-editor.component'; @@ -60,7 +60,7 @@ export class IngestorComponent implements OnInit { connectToFacilityBackend(facilityBackendUrl: string): boolean { let facilityBackendUrlCleaned = facilityBackendUrl.slice(); // Check if last symbol is a slash and add version endpoint - if (facilityBackendUrlCleaned.slice(-1) !== '/') { + if (!facilityBackendUrlCleaned.endsWith('/')) { facilityBackendUrlCleaned += '/'; } From dd61bdde3fcedf803267eda5594de288df40e786 Mon Sep 17 00:00:00 2001 From: David Wiessner Date: Thu, 3 Oct 2024 13:14:03 +0000 Subject: [PATCH 010/245] Prepare showing the transfer list --- .../ingestor-metadata-editor.component.ts | 15 +-- src/app/ingestor/ingestor.module.ts | 6 +- .../ingestor/ingestor/ingestor.component.html | 92 +++++++++++++++---- .../ingestor/ingestor/ingestor.component.ts | 57 +++++++++++- 4 files changed, 139 insertions(+), 31 deletions(-) diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts index 19b7d76c4a..d9cd5b12bb 100644 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts @@ -8,11 +8,14 @@ import { Component, EventEmitter, Output } from '@angular/core'; export class IngestorMetadataEditorComponent { metadata: string = ''; - // Optional: EventEmitter, um Änderungen an der Metadata zu melden - @Output() metadataChange = new EventEmitter(); + clearMetadata() { + this.metadata = ''; + } + // Optional: EventEmitter, um Änderungen an der Metadata zu melden + @Output() metadataChange = new EventEmitter(); - onMetadataChange(newMetadata: string) { - this.metadata = newMetadata; - this.metadataChange.emit(this.metadata); - } + onMetadataChange(newMetadata: string) { + this.metadata = newMetadata; + this.metadataChange.emit(this.metadata); + } } \ No newline at end of file diff --git a/src/app/ingestor/ingestor.module.ts b/src/app/ingestor/ingestor.module.ts index bd04008153..febb5625ce 100644 --- a/src/app/ingestor/ingestor.module.ts +++ b/src/app/ingestor/ingestor.module.ts @@ -11,6 +11,8 @@ import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; import { FormsModule } from "@angular/forms"; import { MatListModule } from '@angular/material/list'; import { MatIconModule } from '@angular/material/icon'; +import { MatTabsModule } from "@angular/material/tabs"; +import { MatTableModule } from "@angular/material/table"; @NgModule({ declarations: [ @@ -27,7 +29,9 @@ import { MatIconModule } from '@angular/material/icon'; MatProgressSpinnerModule, RouterModule, MatListModule, - MatIconModule + MatIconModule, + MatTabsModule, + MatTableModule, ], }) export class IngestorModule { } diff --git a/src/app/ingestor/ingestor/ingestor.component.html b/src/app/ingestor/ingestor/ingestor.component.html index a61b5ca5ba..b458d1edda 100644 --- a/src/app/ingestor/ingestor/ingestor.component.html +++ b/src/app/ingestor/ingestor/ingestor.component.html @@ -1,7 +1,7 @@

- Ingestor-Connection + Control center

@@ -30,29 +30,80 @@ - - - - Backend URL - {{ connectedFacilityBackend }} change - - - Connection Status - Connected - - - Version - {{ connectedFacilityBackendVersion }} - - + + + + + + + + + + + + + + + + + + + + + + +
ID {{element.transferId}} + Status {{element.status}} + Action + + +
+
+ + + + + Backend URL + {{ connectedFacilityBackend }} change + + + Connection Status + Connected + + + Version + {{ connectedFacilityBackendVersion + }} + + + + + Todo + +
+ +

+ Create new transfer

-

+

Ingest Dataset @@ -62,7 +113,7 @@

- diff --git a/src/app/ingestor/ingestor/ingestor.component.ts b/src/app/ingestor/ingestor/ingestor.component.ts index 99cf89e0f0..55d4200fce 100644 --- a/src/app/ingestor/ingestor/ingestor.component.ts +++ b/src/app/ingestor/ingestor/ingestor.component.ts @@ -5,6 +5,11 @@ import { IngestorMetadataEditorComponent } from '../ingestor-metadata-editor/ing import { ActivatedRoute, Router } from '@angular/router'; import { INGESTOR_API_ENDPOINTS_V1 } from "./ingestor-api-endpoints"; +interface TransferDataListEntry { + transferId: string; + status: string; +} + @Component({ selector: "ingestor", templateUrl: "./ingestor.component.html", @@ -25,10 +30,13 @@ export class IngestorComponent implements OnInit { loading: boolean = false; forwardFacilityBackend: string = ''; + createNewTransfer: boolean = false; connectedFacilityBackend: string = ''; connectedFacilityBackendVersion: string = ''; connectingToFacilityBackend: boolean = false; lastUsedFacilityBackends: string[] = []; + transferDataSource: TransferDataListEntry[] = []; // List of files to be transferred + displayedColumns: string[] = ['transferId', 'status', 'actions']; errorMessage: string = ''; returnValue: string = ''; @@ -44,12 +52,14 @@ export class IngestorComponent implements OnInit { ); this.gettingStarted = this.appConfig.gettingStarted; this.connectingToFacilityBackend = true; + this.createNewTransfer = false; this.lastUsedFacilityBackends = this.loadLastUsedFacilityBackends(); + this.transferDataSource = []; // Get the GET parameter 'backendUrl' from the URL this.route.queryParams.subscribe(params => { const backendUrl = params['backendUrl']; if (backendUrl) { - this.connectToFacilityBackend(backendUrl); + this.apiConnectToFacilityBackend(backendUrl); } else { this.connectingToFacilityBackend = false; @@ -57,7 +67,7 @@ export class IngestorComponent implements OnInit { }); } - connectToFacilityBackend(facilityBackendUrl: string): boolean { + apiConnectToFacilityBackend(facilityBackendUrl: string): boolean { let facilityBackendUrlCleaned = facilityBackendUrl.slice(); // Check if last symbol is a slash and add version endpoint if (!facilityBackendUrlCleaned.endsWith('/')) { @@ -88,7 +98,23 @@ export class IngestorComponent implements OnInit { return true; } - upload() { + async apiGetTransferList(): Promise { + await this.http.get(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.TRANSFER).subscribe( + response => { + console.log('Transfer list received', response); + return response['transfers']; + }, + error => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}]
`; + console.error('Request failed', error); + return []; + } + ); + + return []; + } + + apiUpload() { this.loading = true; this.returnValue = ''; const payload = { @@ -118,7 +144,7 @@ export class IngestorComponent implements OnInit { // If current route is equal to the forward route, the router will not navigate to the new route if (this.connectedFacilityBackend === this.forwardFacilityBackend) { - this.connectToFacilityBackend(this.forwardFacilityBackend); + this.apiConnectToFacilityBackend(this.forwardFacilityBackend); return; } @@ -150,4 +176,27 @@ export class IngestorComponent implements OnInit { clearErrorMessage(): void { this.errorMessage = ''; } + + openNewTransferDialog(): void { + this.createNewTransfer = true; + this.metadataEditor.clearMetadata(); + } + + onRefreshTransferList(): void { + const TEST_DATALIST: TransferDataListEntry[] = [ + { transferId: '1', status: 'In progress' }, + { transferId: '2', status: 'Done' }, + { transferId: '3', status: 'Failed' }, + ]; + + this.transferDataSource = TEST_DATALIST; + console.log(this.transferDataSource); + // TODO activate when the API is ready + //this.apiGetTransferList(); + } + + onCancelTransfer(transferId: string) { + console.log('Cancel transfer', transferId); + // TODO activate when the API is ready + } } \ No newline at end of file From cd3f6b375891928268bda061c069dfd6169ab92d Mon Sep 17 00:00:00 2001 From: David Wiessner Date: Thu, 28 Nov 2024 07:12:09 +0000 Subject: [PATCH 011/245] json-forms poc --- package.json | 2 + .../ingestor-metadata-editor-helper.ts | 53 +++ .../ingestor-metadata-editor-schematest.ts | 421 ++++++++++++++++++ .../ingestor-metadata-editor.component.ts | 38 +- src/app/ingestor/ingestor.module.ts | 24 +- .../ingestor.confirm-transfer-dialog.html | 21 + .../ingestor.confirm-transfer-dialog.ts | 27 ++ .../ingestor.dialog-stepper.component.css | 17 + .../ingestor.dialog-stepper.component.html | 16 + .../ingestor.dialog-stepper.component.ts | 10 + .../ingestor.extractor-metadata-dialog.html | 17 + .../ingestor.extractor-metadata-dialog.ts | 27 ++ .../dialog/ingestor.new-transfer-dialog.html | 37 ++ .../dialog/ingestor.new-transfer-dialog.ts | 35 ++ .../dialog/ingestor.user-metadata-dialog.html | 46 ++ .../dialog/ingestor.user-metadata-dialog.ts | 40 ++ .../ingestor/ingestor-api-endpoints.ts | 1 + .../ingestor/ingestor/ingestor.component.html | 53 +-- .../ingestor/ingestor/ingestor.component.scss | 54 ++- .../ingestor/ingestor/ingestor.component.ts | 137 ++++-- 20 files changed, 980 insertions(+), 96 deletions(-) create mode 100644 src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts create mode 100644 src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest.ts create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.html create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.ts create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.css create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.html create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.ts create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.ts create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.html create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.ts create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.ts diff --git a/package.json b/package.json index 3ec659024a..0a28ad1979 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,8 @@ "@angular/platform-server": "^16", "@angular/router": "^16", "@angular/service-worker": "^16", + "@jsonforms/angular": "^3.2.1", + "@jsonforms/angular-material": "^3.2.1", "@ngbracket/ngx-layout": "^16.0.0", "@ngrx/effects": "^16", "@ngrx/router-store": "^16", diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts new file mode 100644 index 0000000000..632419f9cd --- /dev/null +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts @@ -0,0 +1,53 @@ +export interface Schema { + type?: string; + properties?: { + [key: string]: { + type: string; + format?: string; + enum?: string[]; + minLength?: number; + }; + }; + required?: string[]; +} + +export interface UISchema { + type: string; + elements: { type: string; scope: string; label?: boolean }[]; +} + +export class IngestorMetadaEditorHelper { + static generateUISchemaFromSchema(schema: string): UISchema { + const parsedSchema: Schema = JSON.parse(schema); + + const flattenProperties = (properties: any, parentKey: string = ''): any[] => { + return Object.keys(properties).reduce((acc, key) => { + const property = properties[key]; + const fullKey = parentKey ? `${parentKey}.${key}` : key; + + if (property.type === 'object' && property.properties) { + acc.push({ + type: 'Label', + text: key.charAt(0).toUpperCase() + key.slice(1) + }); + acc.push(...flattenProperties(property.properties, fullKey)); + } else { + acc.push({ + type: 'Control', + scope: `#/properties/${fullKey}`, + label: parsedSchema.required && parsedSchema.required.includes(key) ? true : undefined + }); + } + + return acc; + }, []); + }; + + const uischema = { + type: 'VerticalLayout', + elements: flattenProperties(parsedSchema.properties) + }; + + return uischema; + } +}; \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest.ts new file mode 100644 index 0000000000..8ed4d8d0ea --- /dev/null +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest.ts @@ -0,0 +1,421 @@ +export const schema_mask2 = { + type: 'object', + properties: { + id: { + type: 'string', + }, + title: { + type: 'string', + }, + description: { + type: 'string', + }, + status: { + type: 'string', + enum: ['active', 'completed', 'archived'], + }, + priority: { + type: 'integer', + minimum: 1, + maximum: 5, + }, + first_name: { + type: 'string', + description: 'first name', + }, + work_status: { + type: 'boolean', + description: 'work status', + }, + email: { + type: 'string', + description: 'email', + pattern: '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$', + }, + work_phone: { + type: 'string', + description: 'work phone', + }, + name: { + type: 'string', + description: 'name', + }, + name_org: { + type: 'string', + description: 'Name of the organization', + }, + type_org: { + type: 'string', + description: 'Type of organization, academic, commercial, governmental, etc.', + enum: ['Academic', 'Commercial', 'Government', 'Other'], + }, + country: { + type: 'string', + description: 'Country of the institution', + }, + role: { + type: 'string', + description: 'Role of the author, for example principal investigator', + }, + orcid: { + type: 'string', + description: 'ORCID of the author, a type of unique identifier', + }, + funder_name: { + type: 'string', + description: 'funding organization/person.', + }, + start_date: { + type: 'string', + format: 'date', + description: 'start date', + }, + end_date: { + type: 'string', + format: 'date', + description: 'end date', + }, + budget: { + type: 'number', + description: 'budget', + }, + project_id: { + type: 'string', + description: 'project id', + }, + grants: { + type: 'array', + items: { + type: 'object', + properties: { + grant_name: { + type: 'string', + description: 'name of the grant', + }, + start_date: { + type: 'string', + format: 'date', + description: 'start date', + }, + end_date: { + type: 'string', + format: 'date', + description: 'end date', + }, + budget: { + type: 'number', + description: 'budget', + }, + project_id: { + type: 'string', + description: 'project id', + }, + country: { + type: 'string', + description: 'Country of the institution', + }, + }, + }, + description: 'List of grants associated with the project', + }, + authors: { + type: 'array', + items: { + type: 'object', + properties: { + first_name: { + type: 'string', + description: 'first name', + }, + work_status: { + type: 'boolean', + description: 'work status', + }, + email: { + type: 'string', + description: 'email', + pattern: '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$', + }, + work_phone: { + type: 'string', + description: 'work phone', + }, + name: { + type: 'string', + description: 'name', + }, + name_org: { + type: 'string', + description: 'Name of the organization', + }, + type_org: { + type: 'string', + description: 'Type of organization, academic, commercial, governmental, etc.', + enum: ['Academic', 'Commercial', 'Government', 'Other'], + }, + country: { + type: 'string', + description: 'Country of the institution', + }, + role: { + type: 'string', + description: 'Role of the author, for example principal investigator', + }, + orcid: { + type: 'string', + description: 'ORCID of the author, a type of unique identifier', + }, + }, + }, + description: 'List of authors associated with the project', + }, + instruments: { + type: 'array', + items: { + type: 'object', + properties: { + microscope: { + type: 'string', + description: 'Name/Type of the Microscope', + }, + illumination: { + type: 'string', + description: 'Mode of illumination used during data collection', + }, + imaging: { + type: 'string', + description: 'Mode of imaging used during data collection', + }, + electron_source: { + type: 'string', + description: 'Type of electron source used in the microscope, such as FEG', + }, + acceleration_voltage: { + type: 'number', + description: 'Voltage used for the electron acceleration, in kV', + }, + c2_aperture: { + type: 'number', + description: 'C2 aperture size used in data acquisition, in µm', + }, + cs: { + type: 'number', + description: 'Spherical aberration of the instrument, in mm', + }, + }, + }, + description: 'List of instruments used in the project', + }, + organizational: { + type: 'object', + properties: { + id: { + type: 'string', + }, + title: { + type: 'string', + }, + description: { + type: 'string', + }, + status: { + type: 'string', + enum: ['active', 'completed', 'archived'], + }, + priority: { + type: 'integer', + minimum: 1, + maximum: 5, + }, + first_name: { + type: 'string', + description: 'first name', + }, + work_status: { + type: 'boolean', + description: 'work status', + }, + email: { + type: 'string', + description: 'email', + pattern: '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$', + }, + work_phone: { + type: 'string', + description: 'work phone', + }, + name: { + type: 'string', + description: 'name', + }, + name_org: { + type: 'string', + description: 'Name of the organization', + }, + type_org: { + type: 'string', + description: 'Type of organization, academic, commercial, governmental, etc.', + enum: ['Academic', 'Commercial', 'Government', 'Other'], + }, + country: { + type: 'string', + description: 'Country of the institution', + }, + role: { + type: 'string', + description: 'Role of the author, for example principal investigator', + }, + orcid: { + type: 'string', + description: 'ORCID of the author, a type of unique identifier', + }, + funder_name: { + type: 'string', + description: 'funding organization/person.', + }, + start_date: { + type: 'string', + format: 'date', + description: 'start date', + }, + end_date: { + type: 'string', + format: 'date', + description: 'end date', + }, + budget: { + type: 'number', + description: 'budget', + }, + project_id: { + type: 'string', + description: 'project id', + }, + grants: { + type: 'array', + items: { + type: 'object', + properties: { + grant_name: { + type: 'string', + description: 'name of the grant', + }, + start_date: { + type: 'string', + format: 'date', + description: 'start date', + }, + end_date: { + type: 'string', + format: 'date', + description: 'end date', + }, + budget: { + type: 'number', + description: 'budget', + }, + project_id: { + type: 'string', + description: 'project id', + }, + country: { + type: 'string', + description: 'Country of the institution', + }, + }, + }, + description: 'List of grants associated with the project', + }, + authors: { + type: 'array', + items: { + type: 'object', + properties: { + first_name: { + type: 'string', + description: 'first name', + }, + work_status: { + type: 'boolean', + description: 'work status', + }, + email: { + type: 'string', + description: 'email', + pattern: '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$', + }, + work_phone: { + type: 'string', + description: 'work phone', + }, + name: { + type: 'string', + description: 'name', + }, + name_org: { + type: 'string', + description: 'Name of the organization', + }, + type_org: { + type: 'string', + description: 'Type of organization, academic, commercial, governmental, etc.', + enum: ['Academic', 'Commercial', 'Government', 'Other'], + }, + country: { + type: 'string', + description: 'Country of the institution', + }, + role: { + type: 'string', + description: 'Role of the author, for example principal investigator', + }, + orcid: { + type: 'string', + description: 'ORCID of the author, a type of unique identifier', + }, + }, + }, + description: 'List of authors associated with the project', + }, + instruments: { + type: 'array', + items: { + type: 'object', + properties: { + microscope: { + type: 'string', + description: 'Name/Type of the Microscope', + }, + illumination: { + type: 'string', + description: 'Mode of illumination used during data collection', + }, + imaging: { + type: 'string', + description: 'Mode of imaging used during data collection', + }, + electron_source: { + type: 'string', + description: 'Type of electron source used in the microscope, such as FEG', + }, + acceleration_voltage: { + type: 'number', + description: 'Voltage used for the electron acceleration, in kV', + }, + c2_aperture: { + type: 'number', + description: 'C2 aperture size used in data acquisition, in µm', + }, + cs: { + type: 'number', + description: 'Spherical aberration of the instrument, in mm', + }, + }, + }, + description: 'List of instruments used in the project', + }, + }, + }, + }, + required: ['id', 'title', 'status', 'name', 'email', 'work_phone', 'orcid', 'country', 'type_org', 'name_org'], +}; \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts index d9cd5b12bb..56b2a9fc35 100644 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts @@ -1,21 +1,35 @@ -import { Component, EventEmitter, Output } from '@angular/core'; +import { Component, EventEmitter, Output, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { angularMaterialRenderers } from '@jsonforms/angular-material'; +import { IngestorMetadaEditorHelper, Schema, UISchema } from './ingestor-metadata-editor-helper'; @Component({ selector: 'app-metadata-editor', - templateUrl: './ingestor-metadata-editor.component.html', - styleUrls: ['./ingestor-metadata-editor.component.scss'] + template: ``, }) -export class IngestorMetadataEditorComponent { - metadata: string = ''; - clearMetadata() { - this.metadata = ''; +export class IngestorMetadataEditorComponent implements OnChanges { + @Input() data: string; + @Input() schema: Schema; + + @Output() dataChange = new EventEmitter(); + + renderers = angularMaterialRenderers; + + uischema: UISchema; + + ngOnChanges(changes: SimpleChanges) { + if (changes.schema) { + this.uischema = IngestorMetadaEditorHelper.generateUISchemaFromSchema(JSON.stringify(this.schema)); + } } - // Optional: EventEmitter, um Änderungen an der Metadata zu melden - @Output() metadataChange = new EventEmitter(); - onMetadataChange(newMetadata: string) { - this.metadata = newMetadata; - this.metadataChange.emit(this.metadata); + onDataChange(event: any) { + this.dataChange.emit(event); } } \ No newline at end of file diff --git a/src/app/ingestor/ingestor.module.ts b/src/app/ingestor/ingestor.module.ts index febb5625ce..c2248030ec 100644 --- a/src/app/ingestor/ingestor.module.ts +++ b/src/app/ingestor/ingestor.module.ts @@ -13,11 +13,27 @@ import { MatListModule } from '@angular/material/list'; import { MatIconModule } from '@angular/material/icon'; import { MatTabsModule } from "@angular/material/tabs"; import { MatTableModule } from "@angular/material/table"; +import { MatDialogModule } from "@angular/material/dialog"; +import { MatSelectModule } from "@angular/material/select"; +import { MatOptionModule } from "@angular/material/core"; +import { IngestorNewTransferDialogComponent } from "./ingestor/dialog/ingestor.new-transfer-dialog"; +import { IngestorUserMetadataDialog } from "./ingestor/dialog/ingestor.user-metadata-dialog"; +import { JsonFormsModule } from '@jsonforms/angular'; +import { JsonFormsAngularMaterialModule } from "@jsonforms/angular-material"; +import { IngestorExtractorMetadataDialog } from "./ingestor/dialog/ingestor.extractor-metadata-dialog"; +import { IngestorConfirmTransferDialog } from "./ingestor/dialog/ingestor.confirm-transfer-dialog"; +import { MatStepperModule } from "@angular/material/stepper"; +import { IngestorDialogStepperComponent } from "./ingestor/dialog/ingestor.dialog-stepper.component"; @NgModule({ declarations: [ IngestorComponent, - IngestorMetadataEditorComponent + IngestorMetadataEditorComponent, + IngestorNewTransferDialogComponent, + IngestorUserMetadataDialog, + IngestorExtractorMetadataDialog, + IngestorConfirmTransferDialog, + IngestorDialogStepperComponent, ], imports: [ CommonModule, @@ -32,6 +48,12 @@ import { MatTableModule } from "@angular/material/table"; MatIconModule, MatTabsModule, MatTableModule, + MatDialogModule, + MatSelectModule, + MatOptionModule, + MatStepperModule, + JsonFormsModule, + JsonFormsAngularMaterialModule, ], }) export class IngestorModule { } diff --git a/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.html new file mode 100644 index 0000000000..45d7bcc19b --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.html @@ -0,0 +1,21 @@ +
+

+ Confirm transfer +

+ +
+ + + +

Confirm Metadata

+ + + + +
+ + + + \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.ts b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.ts new file mode 100644 index 0000000000..4e5521049b --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.ts @@ -0,0 +1,27 @@ +import {ChangeDetectionStrategy, Component, Inject} from '@angular/core'; +import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog'; + +@Component({ + selector: 'ingestor.confirm-transfer-dialog', + templateUrl: 'ingestor.confirm-transfer-dialog.html', + changeDetection: ChangeDetectionStrategy.OnPush, + styleUrls: ['../ingestor.component.scss'], +}) + +export class IngestorConfirmTransferDialog { + constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: any) {} + + onClickBack(): void { + console.log('Next button clicked'); + if (this.data && this.data.onClickNext) { + this.data.onClickNext(2); // Beispielwert für den Schritt + } + } + + onClickConfirm(): void { + console.log('Confirm button clicked'); + if (this.data && this.data.onClickNext) { + this.data.onClickConfirm(); + } + } +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.css b/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.css new file mode 100644 index 0000000000..8e114ace88 --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.css @@ -0,0 +1,17 @@ +.stepper { + display: flex; + flex-direction: column; + align-items: center; +} + +.stepper div { + margin: 5px; +} + +.stepper div.active { + font-weight: bold; +} + +button { + margin: 5px; +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.html b/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.html new file mode 100644 index 0000000000..9902301bb8 --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.html @@ -0,0 +1,16 @@ +
+ + + Select your ingestion method + + + Fill out user-specific metadata + + + Correct dataset-specific metadata + + + Confirm inputs + + +
\ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.ts new file mode 100644 index 0000000000..cda2927a18 --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.ts @@ -0,0 +1,10 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'ingestor-dialog-stepper', + templateUrl: './ingestor.dialog-stepper.component.html', + styleUrls: ['./ingestor.dialog-stepper.component.css'] +}) +export class IngestorDialogStepperComponent { + @Input() activeStep: number = 0; +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html new file mode 100644 index 0000000000..ef83ca5d45 --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html @@ -0,0 +1,17 @@ +
+

+ Correct dataset-specific metadata +

+ +
+ + + + + + + + + \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.ts b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.ts new file mode 100644 index 0000000000..c636b26543 --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.ts @@ -0,0 +1,27 @@ +import {ChangeDetectionStrategy, Component, Inject} from '@angular/core'; +import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog'; + +@Component({ + selector: 'ingestor.extractor-metadata-dialog', + templateUrl: 'ingestor.extractor-metadata-dialog.html', + styleUrls: ['../ingestor.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) + +export class IngestorExtractorMetadataDialog { + constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: any) {} + + onClickBack(): void { + console.log('Next button clicked'); + if (this.data && this.data.onClickNext) { + this.data.onClickNext(1); // Beispielwert für den Schritt + } + } + + onClickNext(): void { + console.log('Next button clicked'); + if (this.data && this.data.onClickNext) { + this.data.onClickNext(3); // Beispielwert für den Schritt + } + } +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.html new file mode 100644 index 0000000000..a054ec9e35 --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.html @@ -0,0 +1,37 @@ +
+

+ Select your ingestion method +

+ +
+ + + +

First mask where user needs to select path and method

+ +
+ + File Path + + +
+ +
+ + Extraction Method + + + {{ method }} + + + +
+ +
+ + + + \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.ts b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.ts new file mode 100644 index 0000000000..09464dc94a --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.ts @@ -0,0 +1,35 @@ +import {ChangeDetectionStrategy, Component, Inject, OnInit} from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; +import { HttpClient } from '@angular/common/http'; + +@Component({ + selector: 'ingestor.new-transfer-dialog', + templateUrl: 'ingestor.new-transfer-dialog.html', + changeDetection: ChangeDetectionStrategy.OnPush, + styleUrls: ['../ingestor.component.scss'] +}) + +export class IngestorNewTransferDialogComponent implements OnInit { + filePath: string = ''; + selectedMethod: string = ''; + extractionMethods: string[] = []; + + constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: any, private http: HttpClient) {} + + ngOnInit(): void { + console.log('Initialisieren'); + /*this.http.get('INGESTOR_API_ENDPOINTS_V1.extractor').subscribe((response: any) => { + this.extractionMethods = response; + console.log('Extraktoren geladen:', this.extractionMethods); + });*/ + + this.extractionMethods = ['Extraktor 1', 'Extraktor 2', 'Extraktor 3']; + } + + onClickNext(): void { + console.log('Next button clicked'); + if (this.data && this.data.onClickNext) { + this.data.onClickNext(1); // Beispielwert für den Schritt + } + } +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html new file mode 100644 index 0000000000..f39a46a2bd --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html @@ -0,0 +1,46 @@ +
+

+ Fill out user-specific metadata +

+ +
+ + + +
+
+ + +
+ person +
+ Organizational Information +
+ + + +
+ + + +
+ description +
+ Sample Information +
+ + +
+
+
+ +
+ + + + \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.ts b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.ts new file mode 100644 index 0000000000..3de23c2960 --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.ts @@ -0,0 +1,40 @@ +import {ChangeDetectionStrategy, Component, Inject} from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; +import { Schema } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; +import { schema_mask2 } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest'; + +@Component({ + selector: 'ingestor.user-metadata-dialog', + templateUrl: 'ingestor.user-metadata-dialog.html', + changeDetection: ChangeDetectionStrategy.OnPush, + styleUrls: ['../ingestor.component.scss'], +}) + +export class IngestorUserMetadataDialog { + metadataSchema: Schema; + metadataEditorData: string; + + constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: any) { + this.metadataSchema = schema_mask2; + this.metadataEditorData = data.metadataEditorData; + } + + onClickBack(): void { + console.log('Next button clicked'); + if (this.data && this.data.onClickNext) { + this.data.onClickNext(0); // Beispielwert für den Schritt + } + } + + onClickNext(): void { + console.log('Next button clicked'); + if (this.data && this.data.onClickNext) { + this.data.onClickNext(2); // Beispielwert für den Schritt + } + } + + onDataChange(event: any) { + this.metadataEditorData = event; + console.log(event); + } +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor-api-endpoints.ts b/src/app/ingestor/ingestor/ingestor-api-endpoints.ts index aa0ee1e755..6cd5860805 100644 --- a/src/app/ingestor/ingestor/ingestor-api-endpoints.ts +++ b/src/app/ingestor/ingestor/ingestor-api-endpoints.ts @@ -4,4 +4,5 @@ export const INGESTOR_API_ENDPOINTS_V1 = { OTHER: { VERSION: 'version', }, + EXTRACTOR: 'extractor', }; diff --git a/src/app/ingestor/ingestor/ingestor.component.html b/src/app/ingestor/ingestor/ingestor.component.html index b458d1edda..0f94f8fe97 100644 --- a/src/app/ingestor/ingestor/ingestor.component.html +++ b/src/app/ingestor/ingestor/ingestor.component.html @@ -18,12 +18,12 @@ [(ngModel)]="forwardFacilityBackend"> + (click)="onClickForwardToIngestorPage()">Connect @@ -33,7 +33,7 @@ - + @@ -74,7 +76,7 @@ Backend URL {{ connectedFacilityBackend }} change + (click)="onClickDisconnectIngestor()">change Connection Status @@ -87,39 +89,24 @@ - - Todo - -

- Create new transfer

-

+

- Ingest Dataset + New transfer -

-
- -
- -
+

diff --git a/src/app/ingestor/ingestor/ingestor.component.scss b/src/app/ingestor/ingestor/ingestor.component.scss index 96cdeec1ba..685d6ee35f 100644 --- a/src/app/ingestor/ingestor/ingestor.component.scss +++ b/src/app/ingestor/ingestor/ingestor.component.scss @@ -1,10 +1,12 @@ +@use "sass:map"; +@use "@angular/material" as mat; + .ingestor-vertical-layout { display: flex; flex-direction: column; gap: 1em; } -/* src/app/ingestor/ingestor.component.scss */ .ingestor-mixed-header { display: flex; justify-content: space-between; @@ -13,4 +15,52 @@ .ingestor-close-button { margin-left: auto; -} \ No newline at end of file +} + +.form-full-width { + width: 100%; +} + +mat-card { + margin: 1em; + + .section-icon { + height: auto !important; + width: auto !important; + + mat-icon { + vertical-align: middle; + } + } +} + +@mixin color($theme) { + $color-config: map-get($theme, "color"); + $primary: map-get($color-config, "primary"); + $header-1: map-get($color-config, "header-1"); + $accent: map-get($color-config, "accent"); + mat-card { + .organizational-header { + background-color: mat.get-color-from-palette($primary, "lighter"); + } + + .sample-header { + background-color: mat.get-color-from-palette($header-1, "lighter"); + } + + .instrument-header { + background-color: mat.get-color-from-palette($accent, "lighter"); + } + + .acquisition-header { + background-color: mat.get-color-from-palette($accent, "lighter"); + } + } +} + +@mixin theme($theme) { + $color-config: mat.get-color-config($theme); + @if $color-config != null { + @include color($theme); + } +} diff --git a/src/app/ingestor/ingestor/ingestor.component.ts b/src/app/ingestor/ingestor/ingestor.component.ts index 55d4200fce..8daab0e076 100644 --- a/src/app/ingestor/ingestor/ingestor.component.ts +++ b/src/app/ingestor/ingestor/ingestor.component.ts @@ -1,58 +1,56 @@ -import { Component, OnInit, ViewChild } from "@angular/core"; -import { AppConfigService, HelpMessages } from "app-config.service"; +import { Component, inject, OnInit } from "@angular/core"; +import { AppConfigService } from "app-config.service"; import { HttpClient } from '@angular/common/http'; -import { IngestorMetadataEditorComponent } from '../ingestor-metadata-editor/ingestor-metadata-editor.component'; import { ActivatedRoute, Router } from '@angular/router'; import { INGESTOR_API_ENDPOINTS_V1 } from "./ingestor-api-endpoints"; +import { IngestorNewTransferDialogComponent } from "./dialog/ingestor.new-transfer-dialog"; +import { MatDialog } from "@angular/material/dialog"; +import { IngestorUserMetadataDialog } from "./dialog/ingestor.user-metadata-dialog"; +import { IngestorExtractorMetadataDialog } from "./dialog/ingestor.extractor-metadata-dialog"; +import { IngestorConfirmTransferDialog } from "./dialog/ingestor.confirm-transfer-dialog"; -interface TransferDataListEntry { +interface ITransferDataListEntry { transferId: string; status: string; } +interface IIngestionRequestInformation { + filePath: string; + availableMethods: string[]; + userMetaData: string; + extractorMetaData: string; +} + @Component({ selector: "ingestor", templateUrl: "./ingestor.component.html", styleUrls: ["./ingestor.component.scss"], }) export class IngestorComponent implements OnInit { - - @ViewChild(IngestorMetadataEditorComponent) metadataEditor: IngestorMetadataEditorComponent; - - appConfig = this.appConfigService.getConfig(); - facility: string | null = null; - ingestManual: string | null = null; - gettingStarted: string | null = null; - shoppingCartEnabled = false; - helpMessages: HelpMessages; + readonly dialog = inject(MatDialog); filePath: string = ''; loading: boolean = false; forwardFacilityBackend: string = ''; - createNewTransfer: boolean = false; connectedFacilityBackend: string = ''; connectedFacilityBackendVersion: string = ''; connectingToFacilityBackend: boolean = false; + lastUsedFacilityBackends: string[] = []; - transferDataSource: TransferDataListEntry[] = []; // List of files to be transferred + + transferDataSource: ITransferDataListEntry[] = []; // List of files to be transferred displayedColumns: string[] = ['transferId', 'status', 'actions']; errorMessage: string = ''; returnValue: string = ''; + metadataEditorData: string = ""; // TODO + constructor(public appConfigService: AppConfigService, private http: HttpClient, private route: ActivatedRoute, private router: Router) { } ngOnInit() { - this.facility = this.appConfig.facility; - this.ingestManual = this.appConfig.ingestManual; - this.helpMessages = new HelpMessages( - this.appConfig.helpMessages?.gettingStarted, - this.appConfig.helpMessages?.ingestManual, - ); - this.gettingStarted = this.appConfig.gettingStarted; this.connectingToFacilityBackend = true; - this.createNewTransfer = false; this.lastUsedFacilityBackends = this.loadLastUsedFacilityBackends(); this.transferDataSource = []; // Get the GET parameter 'backendUrl' from the URL @@ -98,20 +96,24 @@ export class IngestorComponent implements OnInit { return true; } - async apiGetTransferList(): Promise { - await this.http.get(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.TRANSFER).subscribe( + apiGetTransferList(page: number, pageSize: number, transferId?: string): void { + const params: any = { + page: page.toString(), + pageSize: pageSize.toString(), + }; + if (transferId) { + params.transferId = transferId; + } + this.http.get(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.TRANSFER, { params }).subscribe( response => { console.log('Transfer list received', response); - return response['transfers']; + this.transferDataSource = response['transfers']; }, error => { this.errorMessage += `${new Date().toLocaleString()}: ${error.message}]
`; console.error('Request failed', error); - return []; } ); - - return []; } apiUpload() { @@ -119,7 +121,7 @@ export class IngestorComponent implements OnInit { this.returnValue = ''; const payload = { filePath: this.filePath, - metaData: this.metadataEditor.metadata + metaData: 'todo'//this.metadataEditor.metadata }; console.log('Uploading', payload); @@ -138,7 +140,7 @@ export class IngestorComponent implements OnInit { ); } - forwardToIngestorPage() { + onClickForwardToIngestorPage() { if (this.forwardFacilityBackend) { this.connectingToFacilityBackend = true; @@ -152,7 +154,7 @@ export class IngestorComponent implements OnInit { } } - disconnectIngestor() { + onClickDisconnectIngestor() { this.returnValue = ''; this.connectedFacilityBackend = ''; // Remove the GET parameter 'backendUrl' from the URL @@ -160,7 +162,7 @@ export class IngestorComponent implements OnInit { } // Helper functions - selectFacilityBackend(facilityBackend: string) { + onClickSelectFacilityBackend(facilityBackend: string) { this.forwardFacilityBackend = facilityBackend; } @@ -177,26 +179,65 @@ export class IngestorComponent implements OnInit { this.errorMessage = ''; } - openNewTransferDialog(): void { - this.createNewTransfer = true; - this.metadataEditor.clearMetadata(); + onClickNext(step: number): void { + console.log('Next step', step); + this.dialog.closeAll(); + + let dialogRef = null; + + switch (step) { + case 0: + dialogRef = this.dialog.open(IngestorNewTransferDialogComponent, { + data: { onClickNext: this.onClickNext.bind(this), metadataEditorData: this.metadataEditorData }, + disableClose: true + }); + + break; + case 1: + dialogRef = this.dialog.open(IngestorUserMetadataDialog, { + data: { onClickNext: this.onClickNext.bind(this), metadataEditorData: this.metadataEditorData }, + disableClose: true + }); + break; + case 2: + dialogRef = this.dialog.open(IngestorExtractorMetadataDialog, { + data: { onClickNext: this.onClickNext.bind(this), metadataEditorData: this.metadataEditorData }, + disableClose: true + }); + break; + case 3: + dialogRef = this.dialog.open(IngestorConfirmTransferDialog, { + data: { onClickNext: this.onClickNext.bind(this), metadataEditorData: this.metadataEditorData }, + disableClose: true + }); + break; + default: + console.error('Unknown step', step); + } + + // Error if the dialog reference is not set + if (dialogRef === null) return; + + /*dialogRef.afterClosed().subscribe(result => { + console.log(`Dialog result: ${result}`); + });*/ } - onRefreshTransferList(): void { - const TEST_DATALIST: TransferDataListEntry[] = [ - { transferId: '1', status: 'In progress' }, - { transferId: '2', status: 'Done' }, - { transferId: '3', status: 'Failed' }, - ]; - - this.transferDataSource = TEST_DATALIST; - console.log(this.transferDataSource); - // TODO activate when the API is ready - //this.apiGetTransferList(); + onClickRefreshTransferList(): void { + this.apiGetTransferList(1, 100); } onCancelTransfer(transferId: string) { console.log('Cancel transfer', transferId); - // TODO activate when the API is ready + this.http.delete(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.TRANSFER + '/' + transferId).subscribe( + response => { + console.log('Transfer cancelled', response); + this.apiGetTransferList(1, 100); + }, + error => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}
`; + console.error('Cancel transfer failed', error); + } + ); } } \ No newline at end of file From dfec300977583c31bada51f5e54707ef9e9728b2 Mon Sep 17 00:00:00 2001 From: David Wiessner Date: Tue, 3 Dec 2024 09:35:19 +0000 Subject: [PATCH 012/245] frontend update - improved json form integration --- .../ingestor-metadata-editor-helper.ts | 24 +- .../ingestor-metadata-editor-schematest.ts | 210 ------------------ .../ingestor-metadata-editor.component.ts | 2 +- src/app/ingestor/ingestor.module.ts | 12 +- ...estor.confirm-transfer-dialog.component.ts | 39 ++++ .../ingestor.confirm-transfer-dialog.html | 3 +- .../ingestor.confirm-transfer-dialog.ts | 27 --- ...tor.dialog-stepper.component.component.ts} | 0 ...tor.extractor-metadata-dialog.component.ts | 44 ++++ .../ingestor.extractor-metadata-dialog.html | 44 +++- .../ingestor.extractor-metadata-dialog.ts | 27 --- .../ingestor.new-transfer-dialog.component.ts | 56 +++++ .../dialog/ingestor.new-transfer-dialog.html | 9 +- .../dialog/ingestor.new-transfer-dialog.ts | 35 --- ...ngestor.user-metadata-dialog.component.ts} | 15 +- .../dialog/ingestor.user-metadata-dialog.html | 2 +- .../ingestor/ingestor/ingestor.component.html | 2 +- .../ingestor/ingestor/ingestor.component.scss | 4 + .../ingestor/ingestor/ingestor.component.ts | 52 +++-- 19 files changed, 266 insertions(+), 341 deletions(-) create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts delete mode 100644 src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.ts rename src/app/ingestor/ingestor/dialog/{ingestor.dialog-stepper.component.ts => ingestor.dialog-stepper.component.component.ts} (100%) create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts delete mode 100644 src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.ts create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts delete mode 100644 src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.ts rename src/app/ingestor/ingestor/dialog/{ingestor.user-metadata-dialog.ts => ingestor.user-metadata-dialog.component.ts} (67%) diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts index 632419f9cd..edecaf8654 100644 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts @@ -1,3 +1,5 @@ +import { IIngestionRequestInformation } from "ingestor/ingestor/ingestor.component"; + export interface Schema { type?: string; properties?: { @@ -19,8 +21,12 @@ export interface UISchema { export class IngestorMetadaEditorHelper { static generateUISchemaFromSchema(schema: string): UISchema { const parsedSchema: Schema = JSON.parse(schema); - + const flattenProperties = (properties: any, parentKey: string = ''): any[] => { + if (!properties) { + return []; + } + return Object.keys(properties).reduce((acc, key) => { const property = properties[key]; const fullKey = parentKey ? `${parentKey}.${key}` : key; @@ -47,7 +53,21 @@ export class IngestorMetadaEditorHelper { type: 'VerticalLayout', elements: flattenProperties(parsedSchema.properties) }; - + return uischema; } + + static mergeUserAndExtractorMetadata(userMetadata: Object, extractorMetadata: Object, space: number): string { + return JSON.stringify({ ...userMetadata, ...extractorMetadata }, null, space); + } + + static createEmptyRequestInformation = (): IIngestionRequestInformation => { + return { + selectedPath: '', + selectedMethod: '', + userMetaData: {}, + extractorMetaData: {}, + mergedMetaDataString: '' + }; + }; }; \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest.ts index 8ed4d8d0ea..05b606f414 100644 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest.ts +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest.ts @@ -206,216 +206,6 @@ export const schema_mask2 = { }, description: 'List of instruments used in the project', }, - organizational: { - type: 'object', - properties: { - id: { - type: 'string', - }, - title: { - type: 'string', - }, - description: { - type: 'string', - }, - status: { - type: 'string', - enum: ['active', 'completed', 'archived'], - }, - priority: { - type: 'integer', - minimum: 1, - maximum: 5, - }, - first_name: { - type: 'string', - description: 'first name', - }, - work_status: { - type: 'boolean', - description: 'work status', - }, - email: { - type: 'string', - description: 'email', - pattern: '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$', - }, - work_phone: { - type: 'string', - description: 'work phone', - }, - name: { - type: 'string', - description: 'name', - }, - name_org: { - type: 'string', - description: 'Name of the organization', - }, - type_org: { - type: 'string', - description: 'Type of organization, academic, commercial, governmental, etc.', - enum: ['Academic', 'Commercial', 'Government', 'Other'], - }, - country: { - type: 'string', - description: 'Country of the institution', - }, - role: { - type: 'string', - description: 'Role of the author, for example principal investigator', - }, - orcid: { - type: 'string', - description: 'ORCID of the author, a type of unique identifier', - }, - funder_name: { - type: 'string', - description: 'funding organization/person.', - }, - start_date: { - type: 'string', - format: 'date', - description: 'start date', - }, - end_date: { - type: 'string', - format: 'date', - description: 'end date', - }, - budget: { - type: 'number', - description: 'budget', - }, - project_id: { - type: 'string', - description: 'project id', - }, - grants: { - type: 'array', - items: { - type: 'object', - properties: { - grant_name: { - type: 'string', - description: 'name of the grant', - }, - start_date: { - type: 'string', - format: 'date', - description: 'start date', - }, - end_date: { - type: 'string', - format: 'date', - description: 'end date', - }, - budget: { - type: 'number', - description: 'budget', - }, - project_id: { - type: 'string', - description: 'project id', - }, - country: { - type: 'string', - description: 'Country of the institution', - }, - }, - }, - description: 'List of grants associated with the project', - }, - authors: { - type: 'array', - items: { - type: 'object', - properties: { - first_name: { - type: 'string', - description: 'first name', - }, - work_status: { - type: 'boolean', - description: 'work status', - }, - email: { - type: 'string', - description: 'email', - pattern: '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$', - }, - work_phone: { - type: 'string', - description: 'work phone', - }, - name: { - type: 'string', - description: 'name', - }, - name_org: { - type: 'string', - description: 'Name of the organization', - }, - type_org: { - type: 'string', - description: 'Type of organization, academic, commercial, governmental, etc.', - enum: ['Academic', 'Commercial', 'Government', 'Other'], - }, - country: { - type: 'string', - description: 'Country of the institution', - }, - role: { - type: 'string', - description: 'Role of the author, for example principal investigator', - }, - orcid: { - type: 'string', - description: 'ORCID of the author, a type of unique identifier', - }, - }, - }, - description: 'List of authors associated with the project', - }, - instruments: { - type: 'array', - items: { - type: 'object', - properties: { - microscope: { - type: 'string', - description: 'Name/Type of the Microscope', - }, - illumination: { - type: 'string', - description: 'Mode of illumination used during data collection', - }, - imaging: { - type: 'string', - description: 'Mode of imaging used during data collection', - }, - electron_source: { - type: 'string', - description: 'Type of electron source used in the microscope, such as FEG', - }, - acceleration_voltage: { - type: 'number', - description: 'Voltage used for the electron acceleration, in kV', - }, - c2_aperture: { - type: 'number', - description: 'C2 aperture size used in data acquisition, in µm', - }, - cs: { - type: 'number', - description: 'Spherical aberration of the instrument, in mm', - }, - }, - }, - description: 'List of instruments used in the project', - }, - }, - }, }, required: ['id', 'title', 'status', 'name', 'email', 'work_phone', 'orcid', 'country', 'type_org', 'name_org'], }; \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts index 56b2a9fc35..65b7f330c8 100644 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts @@ -14,7 +14,7 @@ import { IngestorMetadaEditorHelper, Schema, UISchema } from './ingestor-metadat }) export class IngestorMetadataEditorComponent implements OnChanges { - @Input() data: string; + @Input() data: Object; @Input() schema: Schema; @Output() dataChange = new EventEmitter(); diff --git a/src/app/ingestor/ingestor.module.ts b/src/app/ingestor/ingestor.module.ts index c2248030ec..359e3dd500 100644 --- a/src/app/ingestor/ingestor.module.ts +++ b/src/app/ingestor/ingestor.module.ts @@ -16,14 +16,15 @@ import { MatTableModule } from "@angular/material/table"; import { MatDialogModule } from "@angular/material/dialog"; import { MatSelectModule } from "@angular/material/select"; import { MatOptionModule } from "@angular/material/core"; -import { IngestorNewTransferDialogComponent } from "./ingestor/dialog/ingestor.new-transfer-dialog"; -import { IngestorUserMetadataDialog } from "./ingestor/dialog/ingestor.user-metadata-dialog"; +import { MatAutocompleteModule } from "@angular/material/autocomplete"; +import { IngestorNewTransferDialogComponent } from "./ingestor/dialog/ingestor.new-transfer-dialog.component"; +import { IngestorUserMetadataDialog } from "./ingestor/dialog/ingestor.user-metadata-dialog.component"; import { JsonFormsModule } from '@jsonforms/angular'; import { JsonFormsAngularMaterialModule } from "@jsonforms/angular-material"; -import { IngestorExtractorMetadataDialog } from "./ingestor/dialog/ingestor.extractor-metadata-dialog"; -import { IngestorConfirmTransferDialog } from "./ingestor/dialog/ingestor.confirm-transfer-dialog"; +import { IngestorExtractorMetadataDialog } from "./ingestor/dialog/ingestor.extractor-metadata-dialog.component"; +import { IngestorConfirmTransferDialog } from "./ingestor/dialog/ingestor.confirm-transfer-dialog.component"; import { MatStepperModule } from "@angular/material/stepper"; -import { IngestorDialogStepperComponent } from "./ingestor/dialog/ingestor.dialog-stepper.component"; +import { IngestorDialogStepperComponent } from "./ingestor/dialog/ingestor.dialog-stepper.component.component"; @NgModule({ declarations: [ @@ -52,6 +53,7 @@ import { IngestorDialogStepperComponent } from "./ingestor/dialog/ingestor.dialo MatSelectModule, MatOptionModule, MatStepperModule, + MatAutocompleteModule, JsonFormsModule, JsonFormsAngularMaterialModule, ], diff --git a/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts new file mode 100644 index 0000000000..d214fed38a --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts @@ -0,0 +1,39 @@ +import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; +import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { IIngestionRequestInformation } from '../ingestor.component'; +import { connectableObservableDescriptor } from 'rxjs/internal/observable/ConnectableObservable'; +import { IngestorMetadaEditorHelper } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; + +@Component({ + selector: 'ingestor.confirm-transfer-dialog', + templateUrl: 'ingestor.confirm-transfer-dialog.html', + changeDetection: ChangeDetectionStrategy.OnPush, + styleUrls: ['../ingestor.component.scss'], +}) + +export class IngestorConfirmTransferDialog { + createNewTransferData: IIngestionRequestInformation = IngestorMetadaEditorHelper.createEmptyRequestInformation(); + provideMergeMetaData: string = ''; + + constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: any) { + this.createNewTransferData = data.createNewTransferData; + } + + ngOnInit() { + const space = 2; + this.provideMergeMetaData = IngestorMetadaEditorHelper.mergeUserAndExtractorMetadata(this.createNewTransferData.userMetaData, this.createNewTransferData.extractorMetaData, space); + } + + onClickBack(): void { + if (this.data && this.data.onClickNext) { + this.data.onClickNext(2); // Beispielwert für den Schritt + } + } + + onClickConfirm(): void { + if (this.data && this.data.onClickNext) { + this.createNewTransferData.mergedMetaDataString = this.provideMergeMetaData; + this.data.onClickConfirm(); + } + } +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.html index 45d7bcc19b..52dfdddc6a 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.html +++ b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.html @@ -10,8 +10,9 @@

Confirm Metadata

+ - + diff --git a/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.ts b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.ts deleted file mode 100644 index 4e5521049b..0000000000 --- a/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {ChangeDetectionStrategy, Component, Inject} from '@angular/core'; -import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog'; - -@Component({ - selector: 'ingestor.confirm-transfer-dialog', - templateUrl: 'ingestor.confirm-transfer-dialog.html', - changeDetection: ChangeDetectionStrategy.OnPush, - styleUrls: ['../ingestor.component.scss'], -}) - -export class IngestorConfirmTransferDialog { - constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: any) {} - - onClickBack(): void { - console.log('Next button clicked'); - if (this.data && this.data.onClickNext) { - this.data.onClickNext(2); // Beispielwert für den Schritt - } - } - - onClickConfirm(): void { - console.log('Confirm button clicked'); - if (this.data && this.data.onClickNext) { - this.data.onClickConfirm(); - } - } -} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.component.ts similarity index 100% rename from src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.ts rename to src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.component.ts diff --git a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts new file mode 100644 index 0000000000..e37ebd7e7a --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts @@ -0,0 +1,44 @@ +import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; +import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { IIngestionRequestInformation } from '../ingestor.component'; +import { IngestorMetadaEditorHelper, Schema } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; + +@Component({ + selector: 'ingestor.extractor-metadata-dialog', + templateUrl: 'ingestor.extractor-metadata-dialog.html', + styleUrls: ['../ingestor.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) + +export class IngestorExtractorMetadataDialog { + metadataSchema: Schema; + createNewTransferData: IIngestionRequestInformation = IngestorMetadaEditorHelper.createEmptyRequestInformation(); + + waitForExtractedMetaData: boolean = true; + + constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: any) { + this.metadataSchema = {}; + this.createNewTransferData = data.createNewTransferData; + } + + ngOnInit() { + this.waitForExtractedMetaData = false; + } + + + onClickBack(): void { + if (this.data && this.data.onClickNext) { + this.data.onClickNext(1); // Beispielwert für den Schritt + } + } + + onClickNext(): void { + if (this.data && this.data.onClickNext) { + this.data.onClickNext(3); // Beispielwert für den Schritt + } + } + + onDataChange(event: any) { + this.createNewTransferData.extractorMetaData = event; + } +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html index ef83ca5d45..6366b1d430 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html +++ b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html @@ -2,16 +2,52 @@

Correct dataset-specific metadata

- - - +
+ +
Wait for ingestor...
+
+ +
+ +
+
+ + +
+ biotech +
+ Instrument Information +
+ + + +
+ + +
+ category-search +
+ Acquisition Information +
+ + +
+
+
+
- + \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.ts b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.ts deleted file mode 100644 index c636b26543..0000000000 --- a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {ChangeDetectionStrategy, Component, Inject} from '@angular/core'; -import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog'; - -@Component({ - selector: 'ingestor.extractor-metadata-dialog', - templateUrl: 'ingestor.extractor-metadata-dialog.html', - styleUrls: ['../ingestor.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) - -export class IngestorExtractorMetadataDialog { - constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: any) {} - - onClickBack(): void { - console.log('Next button clicked'); - if (this.data && this.data.onClickNext) { - this.data.onClickNext(1); // Beispielwert für den Schritt - } - } - - onClickNext(): void { - console.log('Next button clicked'); - if (this.data && this.data.onClickNext) { - this.data.onClickNext(3); // Beispielwert für den Schritt - } - } -} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts new file mode 100644 index 0000000000..352663e37a --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts @@ -0,0 +1,56 @@ +import { ChangeDetectionStrategy, Component, Inject, OnInit } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; +import { HttpClient } from '@angular/common/http'; +import { IIngestionRequestInformation } from '../ingestor.component' +import { IngestorMetadaEditorHelper } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; + +@Component({ + selector: 'ingestor.new-transfer-dialog', + templateUrl: 'ingestor.new-transfer-dialog.html', + changeDetection: ChangeDetectionStrategy.OnPush, + styleUrls: ['../ingestor.component.scss'] +}) + +export class IngestorNewTransferDialogComponent implements OnInit { + extractionMethods: string[] = []; + availableFilePaths: string[] = []; + + createNewTransferData: IIngestionRequestInformation = IngestorMetadaEditorHelper.createEmptyRequestInformation(); + + constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: any, private http: HttpClient) { + this.createNewTransferData = data.createNewTransferData; + } + + ngOnInit(): void { + this.apiGetExtractionMethods(); + this.apiGetAvailableFilePaths(); + } + + apiGetExtractionMethods(): void { + // Get reqeuest auf den Extractor endpoint + /*this.http.get('INGESTOR_API_ENDPOINTS_V1.extractor').subscribe((response: any) => { + this.extractionMethods = response; + console.log('Extraktoren geladen:', this.extractionMethods); + });*/ + + const fakeData = ['Extraktor 1', 'Extraktor 2', 'Extraktor 3']; + this.extractionMethods = fakeData; + } + + apiGetAvailableFilePaths(): void { + // Get request auf den Dataset endpoint + /*this.http.get('INGESTOR_API_ENDPOINTS_V1.dataset').subscribe((response: any) => { + this.availableFilePaths = response; + console.log('Pfade geladen:', this.availableFilePaths); + });*/ + + const fakeData = ['Path 1', 'Path 2', 'Path 3']; + this.availableFilePaths = fakeData; + } + + onClickNext(): void { + if (this.data && this.data.onClickNext) { + this.data.onClickNext(1); // Open next dialog + } + } +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.html index a054ec9e35..16675bd3df 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.html +++ b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.html @@ -14,14 +14,19 @@

File Path - + + + + {{ method }} + +
Extraction Method - + {{ method }} diff --git a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.ts b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.ts deleted file mode 100644 index 09464dc94a..0000000000 --- a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.ts +++ /dev/null @@ -1,35 +0,0 @@ -import {ChangeDetectionStrategy, Component, Inject, OnInit} from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; -import { HttpClient } from '@angular/common/http'; - -@Component({ - selector: 'ingestor.new-transfer-dialog', - templateUrl: 'ingestor.new-transfer-dialog.html', - changeDetection: ChangeDetectionStrategy.OnPush, - styleUrls: ['../ingestor.component.scss'] -}) - -export class IngestorNewTransferDialogComponent implements OnInit { - filePath: string = ''; - selectedMethod: string = ''; - extractionMethods: string[] = []; - - constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: any, private http: HttpClient) {} - - ngOnInit(): void { - console.log('Initialisieren'); - /*this.http.get('INGESTOR_API_ENDPOINTS_V1.extractor').subscribe((response: any) => { - this.extractionMethods = response; - console.log('Extraktoren geladen:', this.extractionMethods); - });*/ - - this.extractionMethods = ['Extraktor 1', 'Extraktor 2', 'Extraktor 3']; - } - - onClickNext(): void { - console.log('Next button clicked'); - if (this.data && this.data.onClickNext) { - this.data.onClickNext(1); // Beispielwert für den Schritt - } - } -} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.ts b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts similarity index 67% rename from src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.ts rename to src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts index 3de23c2960..c2fc722861 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts @@ -1,7 +1,8 @@ import {ChangeDetectionStrategy, Component, Inject} from '@angular/core'; import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; -import { Schema } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; +import { IngestorMetadaEditorHelper, Schema } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; import { schema_mask2 } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest'; +import { IIngestionRequestInformation } from '../ingestor.component'; @Component({ selector: 'ingestor.user-metadata-dialog', @@ -12,29 +13,27 @@ import { schema_mask2 } from 'ingestor/ingestor-metadata-editor/ingestor-metadat export class IngestorUserMetadataDialog { metadataSchema: Schema; - metadataEditorData: string; + + createNewTransferData: IIngestionRequestInformation = IngestorMetadaEditorHelper.createEmptyRequestInformation(); constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: any) { this.metadataSchema = schema_mask2; - this.metadataEditorData = data.metadataEditorData; + this.createNewTransferData = data.createNewTransferData; } onClickBack(): void { - console.log('Next button clicked'); if (this.data && this.data.onClickNext) { this.data.onClickNext(0); // Beispielwert für den Schritt } } onClickNext(): void { - console.log('Next button clicked'); if (this.data && this.data.onClickNext) { this.data.onClickNext(2); // Beispielwert für den Schritt } } - onDataChange(event: any) { - this.metadataEditorData = event; - console.log(event); + onDataChange(event: Object) { + this.createNewTransferData.userMetaData = event; } } \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html index f39a46a2bd..4ec7ae4c1d 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html +++ b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html @@ -20,7 +20,7 @@

Organizational Information - diff --git a/src/app/ingestor/ingestor/ingestor.component.html b/src/app/ingestor/ingestor/ingestor.component.html index 0f94f8fe97..187f6f2f76 100644 --- a/src/app/ingestor/ingestor/ingestor.component.html +++ b/src/app/ingestor/ingestor/ingestor.component.html @@ -104,7 +104,7 @@ diff --git a/src/app/ingestor/ingestor/ingestor.component.scss b/src/app/ingestor/ingestor/ingestor.component.scss index 685d6ee35f..defee924f0 100644 --- a/src/app/ingestor/ingestor/ingestor.component.scss +++ b/src/app/ingestor/ingestor/ingestor.component.scss @@ -21,6 +21,10 @@ width: 100%; } +.metadata-preview { + height: 50vh !important; +} + mat-card { margin: 1em; diff --git a/src/app/ingestor/ingestor/ingestor.component.ts b/src/app/ingestor/ingestor/ingestor.component.ts index 8daab0e076..80dfd07722 100644 --- a/src/app/ingestor/ingestor/ingestor.component.ts +++ b/src/app/ingestor/ingestor/ingestor.component.ts @@ -3,22 +3,36 @@ import { AppConfigService } from "app-config.service"; import { HttpClient } from '@angular/common/http'; import { ActivatedRoute, Router } from '@angular/router'; import { INGESTOR_API_ENDPOINTS_V1 } from "./ingestor-api-endpoints"; -import { IngestorNewTransferDialogComponent } from "./dialog/ingestor.new-transfer-dialog"; +import { IngestorNewTransferDialogComponent } from "./dialog/ingestor.new-transfer-dialog.component"; import { MatDialog } from "@angular/material/dialog"; -import { IngestorUserMetadataDialog } from "./dialog/ingestor.user-metadata-dialog"; -import { IngestorExtractorMetadataDialog } from "./dialog/ingestor.extractor-metadata-dialog"; -import { IngestorConfirmTransferDialog } from "./dialog/ingestor.confirm-transfer-dialog"; +import { IngestorUserMetadataDialog } from "./dialog/ingestor.user-metadata-dialog.component"; +import { IngestorExtractorMetadataDialog } from "./dialog/ingestor.extractor-metadata-dialog.component"; +import { IngestorConfirmTransferDialog } from "./dialog/ingestor.confirm-transfer-dialog.component"; +import { IngestorMetadaEditorHelper } from "ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper"; interface ITransferDataListEntry { transferId: string; status: string; } -interface IIngestionRequestInformation { - filePath: string; - availableMethods: string[]; - userMetaData: string; - extractorMetaData: string; +interface ISciCatHeader { + datasetName: string; + description: string; + creationLocation: string; + dataFormat: string; + ownerGroup: string; + type: string; + license: string; + keywords: string[]; + scientificMetadata: string; +} + +export interface IIngestionRequestInformation { + selectedPath: string; + selectedMethod: string; + userMetaData: Object; + extractorMetaData: Object; + mergedMetaDataString: string; } @Component({ @@ -45,7 +59,7 @@ export class IngestorComponent implements OnInit { errorMessage: string = ''; returnValue: string = ''; - metadataEditorData: string = ""; // TODO + createNewTransferData: IIngestionRequestInformation = IngestorMetadaEditorHelper.createEmptyRequestInformation(); constructor(public appConfigService: AppConfigService, private http: HttpClient, private route: ActivatedRoute, private router: Router) { } @@ -120,8 +134,8 @@ export class IngestorComponent implements OnInit { this.loading = true; this.returnValue = ''; const payload = { - filePath: this.filePath, - metaData: 'todo'//this.metadataEditor.metadata + filePath: this.createNewTransferData.selectedPath, + metaData: this.createNewTransferData.mergedMetaDataString, }; console.log('Uploading', payload); @@ -179,8 +193,12 @@ export class IngestorComponent implements OnInit { this.errorMessage = ''; } + onClickAddIngestion(): void { + this.createNewTransferData = IngestorMetadaEditorHelper.createEmptyRequestInformation(); + this.onClickNext(0); + } + onClickNext(step: number): void { - console.log('Next step', step); this.dialog.closeAll(); let dialogRef = null; @@ -188,26 +206,26 @@ export class IngestorComponent implements OnInit { switch (step) { case 0: dialogRef = this.dialog.open(IngestorNewTransferDialogComponent, { - data: { onClickNext: this.onClickNext.bind(this), metadataEditorData: this.metadataEditorData }, + data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData }, disableClose: true }); break; case 1: dialogRef = this.dialog.open(IngestorUserMetadataDialog, { - data: { onClickNext: this.onClickNext.bind(this), metadataEditorData: this.metadataEditorData }, + data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData }, disableClose: true }); break; case 2: dialogRef = this.dialog.open(IngestorExtractorMetadataDialog, { - data: { onClickNext: this.onClickNext.bind(this), metadataEditorData: this.metadataEditorData }, + data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData }, disableClose: true }); break; case 3: dialogRef = this.dialog.open(IngestorConfirmTransferDialog, { - data: { onClickNext: this.onClickNext.bind(this), metadataEditorData: this.metadataEditorData }, + data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData }, disableClose: true }); break; From 7f5847f26c35887d14a57f98ed5c703b76fe4303 Mon Sep 17 00:00:00 2001 From: David Wiessner Date: Tue, 3 Dec 2024 10:39:43 +0000 Subject: [PATCH 013/245] Assignment of the input menus to the appropriate metadata --- .../ingestor-metadata-editor-helper.ts | 1 + .../ingestor-metadata-editor-schematest.ts | 480 ++++++++++++++---- .../ingestor/ingestor/_ingestor-theme.scss | 39 ++ ...estor.confirm-transfer-dialog.component.ts | 43 +- ...tor.extractor-metadata-dialog.component.ts | 15 +- .../ingestor.extractor-metadata-dialog.html | 11 +- ...ingestor.user-metadata-dialog.component.ts | 23 +- .../dialog/ingestor.user-metadata-dialog.html | 19 +- .../ingestor/ingestor/ingestor.component.scss | 36 +- .../ingestor/ingestor/ingestor.component.ts | 15 +- src/styles.scss | 2 + 11 files changed, 514 insertions(+), 170 deletions(-) create mode 100644 src/app/ingestor/ingestor/_ingestor-theme.scss diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts index edecaf8654..df2d8bc0a1 100644 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts @@ -65,6 +65,7 @@ export class IngestorMetadaEditorHelper { return { selectedPath: '', selectedMethod: '', + scicatHeader: {}, userMetaData: {}, extractorMetaData: {}, mergedMetaDataString: '' diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest.ts index 05b606f414..630a06f277 100644 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest.ts +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest.ts @@ -1,88 +1,6 @@ -export const schema_mask2 = { +export const organizational_schema = { type: 'object', properties: { - id: { - type: 'string', - }, - title: { - type: 'string', - }, - description: { - type: 'string', - }, - status: { - type: 'string', - enum: ['active', 'completed', 'archived'], - }, - priority: { - type: 'integer', - minimum: 1, - maximum: 5, - }, - first_name: { - type: 'string', - description: 'first name', - }, - work_status: { - type: 'boolean', - description: 'work status', - }, - email: { - type: 'string', - description: 'email', - pattern: '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$', - }, - work_phone: { - type: 'string', - description: 'work phone', - }, - name: { - type: 'string', - description: 'name', - }, - name_org: { - type: 'string', - description: 'Name of the organization', - }, - type_org: { - type: 'string', - description: 'Type of organization, academic, commercial, governmental, etc.', - enum: ['Academic', 'Commercial', 'Government', 'Other'], - }, - country: { - type: 'string', - description: 'Country of the institution', - }, - role: { - type: 'string', - description: 'Role of the author, for example principal investigator', - }, - orcid: { - type: 'string', - description: 'ORCID of the author, a type of unique identifier', - }, - funder_name: { - type: 'string', - description: 'funding organization/person.', - }, - start_date: { - type: 'string', - format: 'date', - description: 'start date', - }, - end_date: { - type: 'string', - format: 'date', - description: 'end date', - }, - budget: { - type: 'number', - description: 'budget', - }, - project_id: { - type: 'string', - description: 'project id', - }, grants: { type: 'array', items: { @@ -169,43 +87,397 @@ export const schema_mask2 = { }, description: 'List of authors associated with the project', }, - instruments: { + funder: { type: 'array', items: { type: 'object', properties: { - microscope: { + funder_name: { type: 'string', - description: 'Name/Type of the Microscope', + description: 'funding organization/person.', }, - illumination: { + type_org: { type: 'string', - description: 'Mode of illumination used during data collection', + description: 'Type of organization, academic, commercial, governmental, etc.', + enum: ['Academic', 'Commercial', 'Government', 'Other'], }, - imaging: { + country: { type: 'string', - description: 'Mode of imaging used during data collection', + description: 'Country of the institution', }, - electron_source: { + }, + }, + description: 'Description of the project funding', + }, + }, + required: ['authors', 'funder'], +}; + +export const acquisition_schema = { + type: 'object', + properties: { + nominal_defocus: { + type: 'object', + description: 'Target defocus set, min and max values in µm.', + }, + calibrated_defocus: { + type: 'object', + description: 'Machine estimated defocus, min and max values in µm. Has a tendency to be off.', + }, + nominal_magnification: { + type: 'integer', + description: 'Magnification level as indicated by the instrument, no unit', + }, + calibrated_magnification: { + type: 'integer', + description: 'Calculated magnification, no unit', + }, + holder: { + type: 'string', + description: 'Speciman holder model', + }, + holder_cryogen: { + type: 'string', + description: 'Type of cryogen used in the holder - if the holder is cooled seperately', + }, + temperature_range: { + type: 'object', + description: 'Temperature during data collection, in K with min and max values.', + }, + microscope_software: { + type: 'string', + description: 'Software used for instrument control', + }, + detector: { + type: 'string', + description: 'Make and model of the detector used', + }, + detector_mode: { + type: 'string', + description: 'Operating mode of the detector', + }, + dose_per_movie: { + type: 'object', + description: 'Average dose per image/movie/tilt - given in electrons per square Angstrom', + }, + energy_filter: { + type: 'object', + description: 'Whether an energy filter was used and its specifics.', + }, + image_size: { + type: 'object', + description: 'The size of the image in pixels, height and width given.', + }, + date_time: { + type: 'string', + description: 'Time and date of the data acquisition', + }, + exposure_time: { + type: 'object', + description: 'Time of data acquisition per movie/tilt - in s', + }, + cryogen: { + type: 'string', + description: 'Cryogen used in cooling the instrument and sample, usually nitrogen', + }, + frames_per_movie: { + type: 'integer', + description: 'Number of frames that on average constitute a full movie, can be a bit hard to define for some detectors', + }, + grids_imaged: { + type: 'integer', + description: 'Number of grids imaged for this project - here with qualifier during this data acquisition', + }, + images_generated: { + type: 'integer', + description: 'Number of images generated total for this data collection - might need a qualifier for tilt series to determine whether full series or individual tilts are counted', + }, + binning_camera: { + type: 'number', + description: 'Level of binning on the images applied during data collection', + }, + pixel_size: { + type: 'object', + description: 'Pixel size, in Angstrom', + }, + specialist_optics: { + type: 'object', + description: 'Any type of special optics, such as a phaseplate', + }, + beamshift: { + type: 'object', + description: 'Movement of the beam above the sample for data collection purposes that does not require movement of the stage. Given in mrad.', + }, + beamtilt: { + type: 'object', + description: 'Another way to move the beam above the sample for data collection purposes that does not require movement of the stage. Given in mrad.', + }, + imageshift: { + type: 'object', + description: 'Movement of the Beam below the image in order to shift the image on the detector. Given in µm.', + }, + beamtiltgroups: { + type: 'integer', + description: 'Number of Beamtilt groups present in this dataset - for optimized processing split dataset into groups of same tilt angle. Despite its name Beamshift is often used to achieve this result.', + }, + gainref_flip_rotate: { + type: 'string', + description: 'Whether and how you have to flip or rotate the gainref in order to align with your acquired images', + }, + }, + required: ['detector', 'dose_per_movie', 'date_time', 'binning_camera', 'pixel_size'], +}; + +export const sample_schema = { + type: 'object', + properties: { + overall_molecule: { + type: 'object', + description: 'Description of the overall molecule', + properties: { + molecular_type: { + type: 'string', + description: 'Description of the overall molecular type, i.e., a complex', + }, + name_sample: { + type: 'string', + description: 'Name of the full sample', + }, + source: { + type: 'string', + description: 'Where the sample was taken from, i.e., natural host, recombinantly expressed, etc.', + }, + molecular_weight: { + type: 'object', + description: 'Molecular weight in Da', + }, + assembly: { + type: 'string', + description: 'What type of higher order structure your sample forms - if any.', + enum: ['FILAMENT', 'HELICAL ARRAY', 'PARTICLE'], + }, + }, + required: ['molecular_type', 'name_sample', 'source', 'assembly'], + }, + molecule: { + type: 'array', + items: { + type: 'object', + properties: { + name_mol: { type: 'string', - description: 'Type of electron source used in the microscope, such as FEG', + description: 'Name of an individual molecule (often protein) in the sample', }, - acceleration_voltage: { - type: 'number', - description: 'Voltage used for the electron acceleration, in kV', + molecular_type: { + type: 'string', + description: 'Description of the overall molecular type, i.e., a complex', }, - c2_aperture: { - type: 'number', - description: 'C2 aperture size used in data acquisition, in µm', + molecular_class: { + type: 'string', + description: 'Class of the molecule', + enum: ['Antibiotic', 'Carbohydrate', 'Chimera', 'None of these'], }, - cs: { - type: 'number', - description: 'Spherical aberration of the instrument, in mm', + sequence: { + type: 'string', + description: 'Full sequence of the sample as in the data, i.e., cleaved tags should also be removed from sequence here', + }, + natural_source: { + type: 'string', + description: 'Scientific name of the natural host organism', + }, + taxonomy_id_source: { + type: 'string', + description: 'Taxonomy ID of the natural source organism', + }, + expression_system: { + type: 'string', + description: 'Scientific name of the organism used to produce the molecule of interest', + }, + taxonomy_id_expression: { + type: 'string', + description: 'Taxonomy ID of the expression system organism', + }, + gene_name: { + type: 'string', + description: 'Name of the gene of interest', + }, + }, + }, + required: ['name_mol', 'molecular_type', 'molecular_class', 'sequence', 'natural_source', 'taxonomy_id_source', 'expression_system', 'taxonomy_id_expression'], + }, + ligands: { + type: 'array', + items: { + type: 'object', + properties: { + present: { + type: 'boolean', + description: 'Whether the model contains any ligands', + }, + smiles: { + type: 'string', + description: 'Provide a valid SMILES string of your ligand', }, + reference: { + type: 'string', + description: 'Link to a reference of your ligand, i.e., CCD, PubChem, etc.', + }, + }, + }, + description: 'List of ligands associated with the sample', + }, + specimen: { + type: 'object', + description: 'Description of the specimen', + properties: { + buffer: { + type: 'string', + description: 'Name/composition of the (chemical) sample buffer during grid preparation', + }, + concentration: { + type: 'object', + description: 'Concentration of the (supra)molecule in the sample, in mg/ml', + }, + ph: { + type: 'number', + description: 'pH of the sample buffer', + }, + vitrification: { + type: 'boolean', + description: 'Whether the sample was vitrified', + }, + vitrification_cryogen: { + type: 'string', + description: 'Which cryogen was used for vitrification', + }, + humidity: { + type: 'object', + description: 'Environmental humidity just before vitrification, in %', + }, + temperature: { + type: 'object', + description: 'Environmental temperature just before vitrification, in K', + minimum: 0.0, + }, + staining: { + type: 'boolean', + description: 'Whether the sample was stained', + }, + embedding: { + type: 'boolean', + description: 'Whether the sample was embedded', + }, + shadowing: { + type: 'boolean', + description: 'Whether the sample was shadowed', + }, + }, + required: ['ph', 'vitrification', 'vitrification_cryogen', 'staining', 'embedding', 'shadowing'], + }, + grid: { + type: 'object', + description: 'Description of the grid used', + properties: { + manufacturer: { + type: 'string', + description: 'Grid manufacturer', + }, + material: { + type: 'string', + description: 'Material out of which the grid is made', + }, + mesh: { + type: 'number', + description: 'Grid mesh in lines per inch', + }, + film_support: { + type: 'boolean', + description: 'Whether a support film was used', + }, + film_material: { + type: 'string', + description: 'Type of material the support film is made of', + }, + film_topology: { + type: 'string', + description: 'Topology of the support film', + }, + film_thickness: { + type: 'string', + description: 'Thickness of the support film', + }, + pretreatment_type: { + type: 'string', + description: 'Type of pretreatment of the grid, i.e., glow discharge', + }, + pretreatment_time: { + type: 'object', + description: 'Length of time of the pretreatment in s', + }, + pretreatment_pressure: { + type: 'object', + description: 'Pressure of the chamber during pretreatment, in Pa', + }, + pretreatment_atmosphere: { + type: 'string', + description: 'Atmospheric conditions in the chamber during pretreatment, i.e., addition of specific gases, etc.', }, }, - description: 'List of instruments used in the project', }, }, - required: ['id', 'title', 'status', 'name', 'email', 'work_phone', 'orcid', 'country', 'type_org', 'name_org'], -}; \ No newline at end of file + required: ['overall_molecule', 'molecule', 'specimen', 'grid'], +}; + +export const instrument_schema = { + type: 'object', + properties: { + microscope: { + type: 'string', + description: 'Name/Type of the Microscope', + }, + illumination: { + type: 'string', + description: 'Mode of illumination used during data collection', + }, + imaging: { + type: 'string', + description: 'Mode of imaging used during data collection', + }, + electron_source: { + type: 'string', + description: 'Type of electron source used in the microscope, such as FEG', + }, + acceleration_voltage: { + type: 'object', + description: 'Voltage used for the electron acceleration, in kV', + }, + c2_aperture: { + type: 'object', + description: 'C2 aperture size used in data acquisition, in µm', + }, + cs: { + type: 'object', + description: 'Spherical aberration of the instrument, in mm', + }, + }, + required: ['microscope', 'illumination', 'imaging', 'electron_source', 'acceleration_voltage', 'cs'], +}; + +export const scicatheader_schema = { + type: "object", + properties: { + datasetName: { type: "string" }, + description: { type: "string" }, + creationLocation: { type: "string" }, + dataFormat: { type: "string" }, + ownerGroup: { type: "string" }, + type: { type: "string" }, + license: { type: "string" }, + keywords: { + type: "array", + items: { type: "string" } + }, + scientificMetadata: { type: "string" } + }, + required: ["datasetName", "description", "creationLocation", "dataFormat", "ownerGroup", "type", "license", "keywords", "scientificMetadata"] +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/_ingestor-theme.scss b/src/app/ingestor/ingestor/_ingestor-theme.scss new file mode 100644 index 0000000000..39875ec92b --- /dev/null +++ b/src/app/ingestor/ingestor/_ingestor-theme.scss @@ -0,0 +1,39 @@ +@use "sass:map"; +@use "@angular/material" as mat; + +@mixin color($theme) { + $color-config: map-get($theme, "color"); + $primary: map-get($color-config, "primary"); + $header-1: map-get($color-config, "header-1"); + $header-2: map-get($color-config, "header-2"); + $header-3: map-get($color-config, "header-3"); + $accent: map-get($color-config, "accent"); + mat-card { + .scicat-header { + background-color: mat.get-color-from-palette($primary, "lighter"); + } + + .organizational-header { + background-color: mat.get-color-from-palette($header-1, "lighter"); + } + + .sample-header { + background-color: mat.get-color-from-palette($header-2, "lighter"); + } + + .instrument-header { + background-color: mat.get-color-from-palette($header-3, "lighter"); + } + + .acquisition-header { + background-color: mat.get-color-from-palette($accent, "lighter"); + } + } +} + +@mixin theme($theme) { + $color-config: mat.get-color-config($theme); + @if $color-config != null { + @include color($theme); + } +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts index d214fed38a..76637b90fd 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts @@ -1,9 +1,27 @@ import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { IIngestionRequestInformation } from '../ingestor.component'; -import { connectableObservableDescriptor } from 'rxjs/internal/observable/ConnectableObservable'; import { IngestorMetadaEditorHelper } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; +interface ISciCatHeader { + datasetName: string; + description: string; + creationLocation: string; + dataFormat: string; + ownerGroup: string; + type: string; + license: string; + keywords: string[]; + scientificMetadata: IScientificMetadata; +} + +interface IScientificMetadata { + organization: Object; + sample: Object; + acquisition: Object; + instrument: Object; +} + @Component({ selector: 'ingestor.confirm-transfer-dialog', templateUrl: 'ingestor.confirm-transfer-dialog.html', @@ -20,8 +38,29 @@ export class IngestorConfirmTransferDialog { } ngOnInit() { + this.provideMergeMetaData = this.createMetaDataString(); + } + + createMetaDataString(): string { const space = 2; - this.provideMergeMetaData = IngestorMetadaEditorHelper.mergeUserAndExtractorMetadata(this.createNewTransferData.userMetaData, this.createNewTransferData.extractorMetaData, space); + const scicatMetadata: ISciCatHeader = { + datasetName: this.createNewTransferData.scicatHeader['datasetName'], + description: this.createNewTransferData.scicatHeader['description'], + creationLocation: this.createNewTransferData.scicatHeader['creationLocation'], + dataFormat: this.createNewTransferData.scicatHeader['dataFormat'], + ownerGroup: this.createNewTransferData.scicatHeader['ownerGroup'], + type: this.createNewTransferData.scicatHeader['type'], + license: this.createNewTransferData.scicatHeader['license'], + keywords: this.createNewTransferData.scicatHeader['keywords'], + scientificMetadata: { + organization: this.createNewTransferData.userMetaData['organization'], + sample: this.createNewTransferData.userMetaData['sample'], + acquisition: this.createNewTransferData.extractorMetaData['acquisition'], + instrument: this.createNewTransferData.extractorMetaData['instrument'], + }, + }; + + return JSON.stringify(scicatMetadata, null, space); } onClickBack(): void { diff --git a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts index e37ebd7e7a..d094471392 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts @@ -2,6 +2,7 @@ import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { IIngestionRequestInformation } from '../ingestor.component'; import { IngestorMetadaEditorHelper, Schema } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; +import { acquisition_schema, instrument_schema } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest'; @Component({ selector: 'ingestor.extractor-metadata-dialog', @@ -11,13 +12,15 @@ import { IngestorMetadaEditorHelper, Schema } from 'ingestor/ingestor-metadata-e }) export class IngestorExtractorMetadataDialog { - metadataSchema: Schema; + metadataSchemaInstrument: Schema; + metadataSchemaAcquisition: Schema; createNewTransferData: IIngestionRequestInformation = IngestorMetadaEditorHelper.createEmptyRequestInformation(); waitForExtractedMetaData: boolean = true; constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: any) { - this.metadataSchema = {}; + this.metadataSchemaInstrument = instrument_schema; + this.metadataSchemaAcquisition = acquisition_schema; this.createNewTransferData = data.createNewTransferData; } @@ -38,7 +41,11 @@ export class IngestorExtractorMetadataDialog { } } - onDataChange(event: any) { - this.createNewTransferData.extractorMetaData = event; + onDataChangeUserMetadataInstrument(event: any) { + this.createNewTransferData.extractorMetaData['instrument'] = event; + } + + onDataChangeUserMetadataAcquisition(event: any) { + this.createNewTransferData.extractorMetaData['acquisition'] = event; } } \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html index 6366b1d430..c3283d3e05 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html +++ b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html @@ -27,19 +27,22 @@

Instrument Information - + - +
category-search
Acquisition Information
+

diff --git a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts index c2fc722861..e5532f221b 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts @@ -1,7 +1,7 @@ import {ChangeDetectionStrategy, Component, Inject} from '@angular/core'; import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; import { IngestorMetadaEditorHelper, Schema } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; -import { schema_mask2 } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest'; +import { organizational_schema, sample_schema, scicatheader_schema } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest'; import { IIngestionRequestInformation } from '../ingestor.component'; @Component({ @@ -12,12 +12,15 @@ import { IIngestionRequestInformation } from '../ingestor.component'; }) export class IngestorUserMetadataDialog { - metadataSchema: Schema; - + metadataSchemaOrganizational: Schema; + metadataSchemaSample: Schema; + scicatHeaderSchema: Schema; createNewTransferData: IIngestionRequestInformation = IngestorMetadaEditorHelper.createEmptyRequestInformation(); constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: any) { - this.metadataSchema = schema_mask2; + this.metadataSchemaOrganizational = organizational_schema; + this.metadataSchemaSample = sample_schema; + this.scicatHeaderSchema = scicatheader_schema; this.createNewTransferData = data.createNewTransferData; } @@ -33,7 +36,15 @@ export class IngestorUserMetadataDialog { } } - onDataChange(event: Object) { - this.createNewTransferData.userMetaData = event; + onDataChangeUserMetadataOrganization(event: Object) { + this.createNewTransferData.userMetaData['organization'] = event; + } + + onDataChangeUserMetadataSample(event: Object) { + this.createNewTransferData.userMetaData['sample'] = event; + } + + onDataChangeUserScicatHeader(event: Object) { + this.createNewTransferData.scicatHeader = event; } } \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html index 4ec7ae4c1d..abf714cf8c 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html +++ b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html @@ -12,6 +12,19 @@

+ + +
+ info +
+ SciCat Information +
+ + + +
+
@@ -20,8 +33,8 @@

Organizational Information - + @@ -33,6 +46,8 @@

Sample Information +

diff --git a/src/app/ingestor/ingestor/ingestor.component.scss b/src/app/ingestor/ingestor/ingestor.component.scss index defee924f0..6daf14159e 100644 --- a/src/app/ingestor/ingestor/ingestor.component.scss +++ b/src/app/ingestor/ingestor/ingestor.component.scss @@ -1,6 +1,3 @@ -@use "sass:map"; -@use "@angular/material" as mat; - .ingestor-vertical-layout { display: flex; flex-direction: column; @@ -36,35 +33,4 @@ mat-card { vertical-align: middle; } } -} - -@mixin color($theme) { - $color-config: map-get($theme, "color"); - $primary: map-get($color-config, "primary"); - $header-1: map-get($color-config, "header-1"); - $accent: map-get($color-config, "accent"); - mat-card { - .organizational-header { - background-color: mat.get-color-from-palette($primary, "lighter"); - } - - .sample-header { - background-color: mat.get-color-from-palette($header-1, "lighter"); - } - - .instrument-header { - background-color: mat.get-color-from-palette($accent, "lighter"); - } - - .acquisition-header { - background-color: mat.get-color-from-palette($accent, "lighter"); - } - } -} - -@mixin theme($theme) { - $color-config: mat.get-color-config($theme); - @if $color-config != null { - @include color($theme); - } -} +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor.component.ts b/src/app/ingestor/ingestor/ingestor.component.ts index 80dfd07722..a6c7def86d 100644 --- a/src/app/ingestor/ingestor/ingestor.component.ts +++ b/src/app/ingestor/ingestor/ingestor.component.ts @@ -15,21 +15,10 @@ interface ITransferDataListEntry { status: string; } -interface ISciCatHeader { - datasetName: string; - description: string; - creationLocation: string; - dataFormat: string; - ownerGroup: string; - type: string; - license: string; - keywords: string[]; - scientificMetadata: string; -} - export interface IIngestionRequestInformation { selectedPath: string; selectedMethod: string; + scicatHeader: Object; userMetaData: Object; extractorMetaData: Object; mergedMetaDataString: string; @@ -52,7 +41,7 @@ export class IngestorComponent implements OnInit { connectingToFacilityBackend: boolean = false; lastUsedFacilityBackends: string[] = []; - + transferDataSource: ITransferDataListEntry[] = []; // List of files to be transferred displayedColumns: string[] = ['transferId', 'status', 'actions']; diff --git a/src/styles.scss b/src/styles.scss index 9aee94e724..150dc3ab4c 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -29,6 +29,7 @@ @use "./app/shared/modules/shared-table/shared-table-theme" as shared-table; @use "./app/shared/modules/table/table-theme" as table; @use "./app/users/user-settings/user-settings-theme" as user-settings; +@use "./app/ingestor/ingestor/ingestor-theme" as ingestor; $my-custom-button-level: mat.define-typography-level( $font-weight: 400, @@ -221,6 +222,7 @@ $theme: map-merge( @include shared-table.theme($theme); @include table.theme($theme); @include user-settings.theme($theme); +@include ingestor.theme($theme); @include mat.button-density(0); @include mat.icon-button-density(0); From eabe2647505b1cff8411b8f758af999e3d013c7f Mon Sep 17 00:00:00 2001 From: David Wiessner Date: Thu, 12 Dec 2024 07:56:23 +0000 Subject: [PATCH 014/245] fix package.json for json-forms --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0a28ad1979..d7eeff7c93 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,8 @@ "@angular/platform-server": "^16", "@angular/router": "^16", "@angular/service-worker": "^16", - "@jsonforms/angular": "^3.2.1", - "@jsonforms/angular-material": "^3.2.1", + "@jsonforms/angular": "3.2.*", + "@jsonforms/angular-material": "3.2.*", "@ngbracket/ngx-layout": "^16.0.0", "@ngrx/effects": "^16", "@ngrx/router-store": "^16", From b91fb860cb46f96837d91c07db922f50935954cc Mon Sep 17 00:00:00 2001 From: David Wiessner Date: Sat, 14 Dec 2024 11:39:16 +0000 Subject: [PATCH 015/245] Embedding ingestor backend part 1 --- .../customRenderer/all-of-renderer.ts | 14 +++ .../customRenderer/any-of-renderer.ts | 38 +++++++ .../customRenderer/custom-renderers.ts | 20 ++++ .../customRenderer/one-of-renderer.ts | 14 +++ .../ingestor-metadata-editor-helper.ts | 101 ++++++++++-------- .../ingestor-metadata-editor.component.ts | 18 ++-- ...estor.confirm-transfer-dialog.component.ts | 5 +- ...tor.extractor-metadata-dialog.component.ts | 14 +-- .../ingestor.extractor-metadata-dialog.html | 10 +- .../ingestor.new-transfer-dialog.component.ts | 81 ++++++++++---- .../dialog/ingestor.new-transfer-dialog.html | 15 +-- ...ingestor.user-metadata-dialog.component.ts | 36 ++++++- .../dialog/ingestor.user-metadata-dialog.html | 20 ++-- .../ingestor/ingestor/ingestor.component.scss | 22 ++++ .../ingestor/ingestor/ingestor.component.ts | 19 +--- 15 files changed, 310 insertions(+), 117 deletions(-) create mode 100644 src/app/ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer.ts create mode 100644 src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts create mode 100644 src/app/ingestor/ingestor-metadata-editor/customRenderer/custom-renderers.ts create mode 100644 src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer.ts new file mode 100644 index 0000000000..91085fbaf7 --- /dev/null +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; +import { JsonFormsControl } from '@jsonforms/angular'; + +@Component({ + selector: 'AllOfRenderer', + template: `
AllOf Renderer
` +}) +export class AllOfRenderer extends JsonFormsControl { + data: any[] = []; + + ngOnInit() { + this.data = this.uischema?.options?.items || []; + } +} diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts new file mode 100644 index 0000000000..4add5848a0 --- /dev/null +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts @@ -0,0 +1,38 @@ +import { Component } from '@angular/core'; +import { JsonFormsControl } from '@jsonforms/angular'; +import { JsonSchema } from '@jsonforms/core'; + +@Component({ + selector: 'checkbox-with-price-control', + template: ` +
+ +
+
+

Versand ist kostenlos!

+
+ `, +}) +export class AnyOfRenderer extends JsonFormsControl { + schema: JsonSchema; + label: string; + + ngOnInit() { + super.ngOnInit(); + this.schema = this.scopedSchema as JsonSchema; + this.data = this.data || false; + this.label = `${this.label} (${this.schema.title})`; + } + + onCheckboxChange(event: Event) { + const input = event.target as HTMLInputElement; + this.data = input.checked; + this.onChange(this.data); + + this.data = "TEST"; + + } +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/custom-renderers.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/custom-renderers.ts new file mode 100644 index 0000000000..8b807f113b --- /dev/null +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/custom-renderers.ts @@ -0,0 +1,20 @@ +import { isAllOfControl, isAnyOfControl, isOneOfControl, JsonFormsRendererRegistryEntry } from '@jsonforms/core'; +import { OneOfRenderer } from 'ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer'; +import { AllOfRenderer } from 'ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer'; +import { AnyOfRenderer } from 'ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer'; +import { isRangeControl, RankedTester, rankWith } from '@jsonforms/core'; + +export const customRenderers: JsonFormsRendererRegistryEntry[] = [ + { + tester: rankWith(4, isOneOfControl), + renderer: OneOfRenderer + }, + { + tester: rankWith(4, isAllOfControl), + renderer: AllOfRenderer + }, + { + tester: rankWith(4, isAnyOfControl), + renderer: AnyOfRenderer + } +]; \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts new file mode 100644 index 0000000000..efc546ae48 --- /dev/null +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; +import { JsonFormsControl } from '@jsonforms/angular'; + +@Component({ + selector: 'OneOfRenderer', + template: `
OneOf Renderer
` +}) +export class OneOfRenderer extends JsonFormsControl { + data: any[] = []; + + ngOnInit() { + this.data = this.uischema?.options?.items || []; + } +} diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts index df2d8bc0a1..5208f602b3 100644 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts @@ -1,4 +1,17 @@ -import { IIngestionRequestInformation } from "ingestor/ingestor/ingestor.component"; +export interface IIngestionRequestInformation { + selectedPath: string; + selectedMethod: IExtractionMethod; + scicatHeader: Object; + userMetaData: Object; + extractorMetaData: Object; + extractorMetaDataReady: boolean; + mergedMetaDataString: string; +} + +export interface IExtractionMethod { + name: string; + schema: string; // Base64 encoded JSON schema +}; export interface Schema { type?: string; @@ -8,54 +21,55 @@ export interface Schema { format?: string; enum?: string[]; minLength?: number; + maxLength?: number; + pattern?: string; + items?: Schema; + additionalProperties?: boolean | Schema; + description?: string; + title?: string; + anyOf?: any[]; + allOf?: any[]; + oneOf?: any[]; + not?: Schema; + required?: string[]; + properties?: { + [key: string]: Schema; + }; }; }; required?: string[]; -} - -export interface UISchema { - type: string; - elements: { type: string; scope: string; label?: boolean }[]; + additionalProperties?: boolean | Schema; + description?: string; + title?: string; + anyOf?: any[]; + allOf?: any[]; + oneOf?: any[]; + not?: Schema; } export class IngestorMetadaEditorHelper { - static generateUISchemaFromSchema(schema: string): UISchema { - const parsedSchema: Schema = JSON.parse(schema); + // Resolve all $ref in a schema + static resolveRefs(schema: any, rootSchema: any): any { + if (schema === null || schema === undefined) { + return schema; + } - const flattenProperties = (properties: any, parentKey: string = ''): any[] => { - if (!properties) { - return []; - } - - return Object.keys(properties).reduce((acc, key) => { - const property = properties[key]; - const fullKey = parentKey ? `${parentKey}.${key}` : key; - - if (property.type === 'object' && property.properties) { - acc.push({ - type: 'Label', - text: key.charAt(0).toUpperCase() + key.slice(1) - }); - acc.push(...flattenProperties(property.properties, fullKey)); - } else { - acc.push({ - type: 'Control', - scope: `#/properties/${fullKey}`, - label: parsedSchema.required && parsedSchema.required.includes(key) ? true : undefined - }); + if (schema.$ref) { + const refPath = schema.$ref.replace('#/', '').split('/'); + let ref = rootSchema; + refPath.forEach((part) => { + ref = ref[part]; + }); + return IngestorMetadaEditorHelper.resolveRefs(ref, rootSchema); + } else if (typeof schema === 'object') { + for (const key in schema) { + if (schema.hasOwnProperty(key)) { + schema[key] = IngestorMetadaEditorHelper.resolveRefs(schema[key], rootSchema); } - - return acc; - }, []); - }; - - const uischema = { - type: 'VerticalLayout', - elements: flattenProperties(parsedSchema.properties) - }; - - return uischema; - } + } + } + return schema; + }; static mergeUserAndExtractorMetadata(userMetadata: Object, extractorMetadata: Object, space: number): string { return JSON.stringify({ ...userMetadata, ...extractorMetadata }, null, space); @@ -64,11 +78,12 @@ export class IngestorMetadaEditorHelper { static createEmptyRequestInformation = (): IIngestionRequestInformation => { return { selectedPath: '', - selectedMethod: '', + selectedMethod: { name: '', schema: '' }, scicatHeader: {}, userMetaData: {}, extractorMetaData: {}, - mergedMetaDataString: '' + extractorMetaDataReady: false, + mergedMetaDataString: '', }; }; }; \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts index 65b7f330c8..db5348c195 100644 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts @@ -1,32 +1,26 @@ import { Component, EventEmitter, Output, Input, OnChanges, SimpleChanges } from '@angular/core'; import { angularMaterialRenderers } from '@jsonforms/angular-material'; -import { IngestorMetadaEditorHelper, Schema, UISchema } from './ingestor-metadata-editor-helper'; +import { Schema } from './ingestor-metadata-editor-helper'; +import { customRenderers } from './customRenderer/custom-renderers'; @Component({ selector: 'app-metadata-editor', template: ``, }) -export class IngestorMetadataEditorComponent implements OnChanges { +export class IngestorMetadataEditorComponent { @Input() data: Object; @Input() schema: Schema; @Output() dataChange = new EventEmitter(); - renderers = angularMaterialRenderers; - - uischema: UISchema; - - ngOnChanges(changes: SimpleChanges) { - if (changes.schema) { - this.uischema = IngestorMetadaEditorHelper.generateUISchemaFromSchema(JSON.stringify(this.schema)); - } + get combinedRenderers() { + return [...angularMaterialRenderers, ...customRenderers]; } onDataChange(event: any) { diff --git a/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts index 76637b90fd..1968c67429 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts @@ -1,7 +1,6 @@ import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { IIngestionRequestInformation } from '../ingestor.component'; -import { IngestorMetadaEditorHelper } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; +import { IIngestionRequestInformation, IngestorMetadaEditorHelper } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; interface ISciCatHeader { datasetName: string; @@ -32,9 +31,11 @@ interface IScientificMetadata { export class IngestorConfirmTransferDialog { createNewTransferData: IIngestionRequestInformation = IngestorMetadaEditorHelper.createEmptyRequestInformation(); provideMergeMetaData: string = ''; + backendURL: string = ''; constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: any) { this.createNewTransferData = data.createNewTransferData; + this.backendURL = data.backendURL; } ngOnInit() { diff --git a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts index d094471392..fae49b798a 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts @@ -1,7 +1,6 @@ import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { IIngestionRequestInformation } from '../ingestor.component'; -import { IngestorMetadaEditorHelper, Schema } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; +import { IIngestionRequestInformation, IngestorMetadaEditorHelper, Schema } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; import { acquisition_schema, instrument_schema } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest'; @Component({ @@ -16,16 +15,15 @@ export class IngestorExtractorMetadataDialog { metadataSchemaAcquisition: Schema; createNewTransferData: IIngestionRequestInformation = IngestorMetadaEditorHelper.createEmptyRequestInformation(); - waitForExtractedMetaData: boolean = true; + backendURL: string = ''; + extractorMetaDataReady: boolean = true; constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: any) { + this.backendURL = data.backendURL; this.metadataSchemaInstrument = instrument_schema; this.metadataSchemaAcquisition = acquisition_schema; this.createNewTransferData = data.createNewTransferData; - } - - ngOnInit() { - this.waitForExtractedMetaData = false; + this.extractorMetaDataReady = data.extractorMetaDataReady } @@ -36,6 +34,8 @@ export class IngestorExtractorMetadataDialog { } onClickNext(): void { + console.log(this.createNewTransferData.selectedMethod) + if (this.data && this.data.onClickNext) { this.data.onClickNext(3); // Beispielwert für den Schritt } diff --git a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html index c3283d3e05..15298bee8e 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html +++ b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html @@ -9,13 +9,15 @@

-
+
-
Wait for ingestor...
+
+ Wait for the Ingestor... +
-
+
@@ -51,6 +53,6 @@

- \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts index 352663e37a..0433f6122a 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts @@ -1,8 +1,8 @@ import { ChangeDetectionStrategy, Component, Inject, OnInit } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; import { HttpClient } from '@angular/common/http'; -import { IIngestionRequestInformation } from '../ingestor.component' -import { IngestorMetadaEditorHelper } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; +import { IExtractionMethod, IIngestionRequestInformation, IngestorMetadaEditorHelper } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; +import { INGESTOR_API_ENDPOINTS_V1 } from '../ingestor-api-endpoints'; @Component({ selector: 'ingestor.new-transfer-dialog', @@ -12,13 +12,19 @@ import { IngestorMetadaEditorHelper } from 'ingestor/ingestor-metadata-editor/in }) export class IngestorNewTransferDialogComponent implements OnInit { - extractionMethods: string[] = []; + extractionMethods: IExtractionMethod[] = []; availableFilePaths: string[] = []; + backendURL: string = ''; + extractionMethodsError: string = ''; + availableFilePathsError: string = ''; + + uiNextButtonReady: boolean = false; createNewTransferData: IIngestionRequestInformation = IngestorMetadaEditorHelper.createEmptyRequestInformation(); constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: any, private http: HttpClient) { this.createNewTransferData = data.createNewTransferData; + this.backendURL = data.backendURL; } ngOnInit(): void { @@ -26,26 +32,61 @@ export class IngestorNewTransferDialogComponent implements OnInit { this.apiGetAvailableFilePaths(); } - apiGetExtractionMethods(): void { - // Get reqeuest auf den Extractor endpoint - /*this.http.get('INGESTOR_API_ENDPOINTS_V1.extractor').subscribe((response: any) => { - this.extractionMethods = response; - console.log('Extraktoren geladen:', this.extractionMethods); - });*/ + set selectedPath(value: string) { + this.createNewTransferData.selectedPath = value; + this.validateNextButton(); + } + + get selectedPath(): string { + return this.createNewTransferData.selectedPath; + } + + set selectedMethod(value: IExtractionMethod) { + this.createNewTransferData.selectedMethod = value; + this.validateNextButton(); + } - const fakeData = ['Extraktor 1', 'Extraktor 2', 'Extraktor 3']; - this.extractionMethods = fakeData; + get selectedMethod(): IExtractionMethod { + return this.createNewTransferData.selectedMethod; + } + + apiGetExtractionMethods(): void { + this.http.get(this.backendURL + INGESTOR_API_ENDPOINTS_V1.EXTRACTOR).subscribe( + (response: any) => { + if (response.methods && response.methods.length > 0) { + this.extractionMethods = response.methods; + } + else { + this.extractionMethodsError = 'No extraction methods found.'; + } + }, + (error: any) => { + this.extractionMethodsError = error.message; + console.error(this.extractionMethodsError); + } + ); } apiGetAvailableFilePaths(): void { - // Get request auf den Dataset endpoint - /*this.http.get('INGESTOR_API_ENDPOINTS_V1.dataset').subscribe((response: any) => { - this.availableFilePaths = response; - console.log('Pfade geladen:', this.availableFilePaths); - });*/ + this.http.get(this.backendURL + INGESTOR_API_ENDPOINTS_V1.DATASET).subscribe( + (response: any) => { + if (response.datasets && response.datasets.length > 0) { + this.availableFilePaths = response.datasets; + } + else { + this.availableFilePathsError = 'No datasets found.'; + } + }, + (error: any) => { + this.availableFilePathsError = error.message; + console.error(this.availableFilePathsError); + } + ); + } - const fakeData = ['Path 1', 'Path 2', 'Path 3']; - this.availableFilePaths = fakeData; + onClickRetryRequests(): void { + this.apiGetExtractionMethods(); + this.apiGetAvailableFilePaths(); } onClickNext(): void { @@ -53,4 +94,8 @@ export class IngestorNewTransferDialogComponent implements OnInit { this.data.onClickNext(1); // Open next dialog } } + + validateNextButton(): void { + this.uiNextButtonReady = !!this.createNewTransferData.selectedPath && !!this.createNewTransferData.selectedMethod?.name; + } } \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.html index 16675bd3df..6bd1a6d738 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.html +++ b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.html @@ -14,29 +14,32 @@

File Path - + - - {{ method }} + + {{ filePath }} + {{ availableFilePathsError }}
Extraction Method - + - {{ method }} + {{ method.name }} + {{ extractionMethodsError }}
+ - \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts index e5532f221b..cbf4cacd83 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts @@ -1,8 +1,7 @@ import {ChangeDetectionStrategy, Component, Inject} from '@angular/core'; import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; -import { IngestorMetadaEditorHelper, Schema } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; +import { IIngestionRequestInformation, IngestorMetadaEditorHelper, Schema } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; import { organizational_schema, sample_schema, scicatheader_schema } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest'; -import { IIngestionRequestInformation } from '../ingestor.component'; @Component({ selector: 'ingestor.user-metadata-dialog', @@ -16,12 +15,37 @@ export class IngestorUserMetadataDialog { metadataSchemaSample: Schema; scicatHeaderSchema: Schema; createNewTransferData: IIngestionRequestInformation = IngestorMetadaEditorHelper.createEmptyRequestInformation(); + backendURL: string = ''; + + uiNextButtonReady: boolean = true; // Change to false when dev is ready + + isCardContentVisible = { + scicat: true, + organizational: true, + sample: true + }; constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: any) { - this.metadataSchemaOrganizational = organizational_schema; - this.metadataSchemaSample = sample_schema; + const encodedSchema = data.createNewTransferData.selectedMethod.schema; + const decodedSchema = atob(encodedSchema); + const schema = JSON.parse(decodedSchema); + + console.log(schema); + const resolvedSchema = IngestorMetadaEditorHelper.resolveRefs(schema, schema); + + console.log(resolvedSchema); + + const organizationalSchema = resolvedSchema.properties.organizational; + const sampleSchema = resolvedSchema.properties.sample; + + console.log(organizationalSchema); + console.log(sampleSchema); + + this.metadataSchemaOrganizational = organizationalSchema; + this.metadataSchemaSample = sampleSchema; this.scicatHeaderSchema = scicatheader_schema; this.createNewTransferData = data.createNewTransferData; + this.backendURL = data.backendURL; } onClickBack(): void { @@ -47,4 +71,8 @@ export class IngestorUserMetadataDialog { onDataChangeUserScicatHeader(event: Object) { this.createNewTransferData.scicatHeader = event; } + + toggleCardContent(card: string): void { + this.isCardContentVisible[card] = !this.isCardContentVisible[card]; + } } \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html index abf714cf8c..8e3fd19142 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html +++ b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html @@ -13,39 +13,45 @@

- +
info
SciCat Information + + {{ isCardContentVisible.scicat ? 'expand_less' : 'expand_more' }}
- +
- +
person
Organizational Information + + {{ isCardContentVisible.organizational ? 'expand_less' : 'expand_more' }}
- +
- +
description
Sample Information + + {{ isCardContentVisible.sample ? 'expand_less' : 'expand_more' }}
- + @@ -56,6 +62,6 @@

- \ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor.component.scss b/src/app/ingestor/ingestor/ingestor.component.scss index 6daf14159e..bbfccd1e5a 100644 --- a/src/app/ingestor/ingestor/ingestor.component.scss +++ b/src/app/ingestor/ingestor/ingestor.component.scss @@ -25,6 +25,10 @@ mat-card { margin: 1em; + .mat-mdc-card-content { + padding: 16px; + } + .section-icon { height: auto !important; width: auto !important; @@ -33,4 +37,22 @@ mat-card { vertical-align: middle; } } +} + +.spinner-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; +} + +.spinner-text { + margin-top: 10px; + font-size: 16px; + text-align: center; +} + +.spacer { + flex: 1 1 auto; } \ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor.component.ts b/src/app/ingestor/ingestor/ingestor.component.ts index a6c7def86d..ba34313352 100644 --- a/src/app/ingestor/ingestor/ingestor.component.ts +++ b/src/app/ingestor/ingestor/ingestor.component.ts @@ -8,22 +8,13 @@ import { MatDialog } from "@angular/material/dialog"; import { IngestorUserMetadataDialog } from "./dialog/ingestor.user-metadata-dialog.component"; import { IngestorExtractorMetadataDialog } from "./dialog/ingestor.extractor-metadata-dialog.component"; import { IngestorConfirmTransferDialog } from "./dialog/ingestor.confirm-transfer-dialog.component"; -import { IngestorMetadaEditorHelper } from "ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper"; +import { IIngestionRequestInformation, IngestorMetadaEditorHelper } from "ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper"; interface ITransferDataListEntry { transferId: string; status: string; } -export interface IIngestionRequestInformation { - selectedPath: string; - selectedMethod: string; - scicatHeader: Object; - userMetaData: Object; - extractorMetaData: Object; - mergedMetaDataString: string; -} - @Component({ selector: "ingestor", templateUrl: "./ingestor.component.html", @@ -195,26 +186,26 @@ export class IngestorComponent implements OnInit { switch (step) { case 0: dialogRef = this.dialog.open(IngestorNewTransferDialogComponent, { - data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData }, + data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData, backendURL: this.connectedFacilityBackend }, disableClose: true }); break; case 1: dialogRef = this.dialog.open(IngestorUserMetadataDialog, { - data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData }, + data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData, backendURL: this.connectedFacilityBackend }, disableClose: true }); break; case 2: dialogRef = this.dialog.open(IngestorExtractorMetadataDialog, { - data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData }, + data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData, backendURL: this.connectedFacilityBackend }, disableClose: true }); break; case 3: dialogRef = this.dialog.open(IngestorConfirmTransferDialog, { - data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData }, + data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData, backendURL: this.connectedFacilityBackend }, disableClose: true }); break; From 2428606d503e5852924be2fbc6fe59aab337bc9b Mon Sep 17 00:00:00 2001 From: David Wiessner Date: Sat, 14 Dec 2024 16:32:41 +0000 Subject: [PATCH 016/245] Embedding ingestor backend part 2 --- .../customRenderer/any-of-renderer.ts | 89 ++++++++++++------- .../customRenderer/one-of-renderer.ts | 71 +++++++++++++-- .../ingestor-metadata-editor-helper.ts | 41 ++------- .../ingestor-metadata-editor.component.ts | 7 +- src/app/ingestor/ingestor.module.ts | 6 ++ ...tor.extractor-metadata-dialog.component.ts | 27 ++++-- .../ingestor.extractor-metadata-dialog.html | 3 +- ...ingestor.user-metadata-dialog.component.ts | 18 ++-- 8 files changed, 164 insertions(+), 98 deletions(-) diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts index 4add5848a0..3e212da8d8 100644 --- a/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts @@ -1,38 +1,65 @@ import { Component } from '@angular/core'; -import { JsonFormsControl } from '@jsonforms/angular'; -import { JsonSchema } from '@jsonforms/core'; +import { JsonFormsAngularService, JsonFormsControl } from '@jsonforms/angular'; +import { ControlProps, JsonSchema } from '@jsonforms/core'; +import { configuredRenderer } from '../ingestor-metadata-editor-helper'; @Component({ - selector: 'checkbox-with-price-control', - template: ` -
- -
-
-

Versand ist kostenlos!

+ selector: 'app-anyof-renderer', + template: ` +
+ {{anyOfTitle}} + + + +
+ +
+
+
- `, + ` }) export class AnyOfRenderer extends JsonFormsControl { - schema: JsonSchema; - label: string; - - ngOnInit() { - super.ngOnInit(); - this.schema = this.scopedSchema as JsonSchema; - this.data = this.data || false; - this.label = `${this.label} (${this.schema.title})`; - } - - onCheckboxChange(event: Event) { - const input = event.target as HTMLInputElement; - this.data = input.checked; - this.onChange(this.data); - - this.data = "TEST"; - - } + + dataAsString: string; + options: string[] = []; + anyOfTitle: string; + + rendererService: JsonFormsAngularService; + + defaultRenderer = configuredRenderer; + passedProps: ControlProps; + + constructor(service: JsonFormsAngularService) { + super(service); + this.rendererService = service; + } + + public mapAdditionalProps(props: ControlProps) { + this.passedProps = props; + this.anyOfTitle = props.label || 'AnyOf'; + this.options = props.schema.anyOf.map((option: any) => option.title || option.type || JSON.stringify(option)); + } + + public getTabSchema(tabOption: string): JsonSchema { + const selectedSchema = (this.passedProps.schema.anyOf as any).find((option: any) => option.title === tabOption || option.type === tabOption || JSON.stringify(option) === tabOption); + return selectedSchema; + } + + public onInnerJsonFormsChange(event: any) { + // Check if data is equal to the passedProps.data + if (event !== this.passedProps.data) { + const updatedData = this.rendererService.getState().jsonforms.core.data; + + // aktualisiere das aktuelle Datenobjekt + const pathSegments = this.passedProps.path.split('.'); + let current = updatedData; + for (let i = 0; i < pathSegments.length - 1; i++) { + current = current[pathSegments[i]]; + } + current[pathSegments[pathSegments.length - 1]] = event; + + this.rendererService.setData(updatedData); + } + } } \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts index efc546ae48..42665d5c62 100644 --- a/src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts @@ -1,14 +1,69 @@ import { Component } from '@angular/core'; -import { JsonFormsControl } from '@jsonforms/angular'; +import { JsonFormsAngularService, JsonFormsControl } from '@jsonforms/angular'; +import { ControlProps, JsonSchema } from '@jsonforms/core'; +import { configuredRenderer } from '../ingestor-metadata-editor-helper'; @Component({ - selector: 'OneOfRenderer', - template: `
OneOf Renderer
` + selector: 'app-oneof-component', + template: ` +
+

{{anyOfTitle}}

+ + + {{option}} + + +
+ +
+
+ ` }) export class OneOfRenderer extends JsonFormsControl { - data: any[] = []; - ngOnInit() { - this.data = this.uischema?.options?.items || []; - } -} + dataAsString: string; + options: string[] = []; + anyOfTitle: string; + selectedOption: string; + selectedAnyOption: JsonSchema; + + rendererService: JsonFormsAngularService; + + defaultRenderer = configuredRenderer; + passedProps: ControlProps; + + constructor(service: JsonFormsAngularService) { + super(service); + this.rendererService = service; + } + + public mapAdditionalProps(props: ControlProps) { + this.passedProps = props; + this.anyOfTitle = props.label || 'AnyOf'; + this.options = props.schema.anyOf.map((option: any) => option.title || option.type || JSON.stringify(option)); + if (!props.data) { + this.selectedOption = 'null'; // Auf "null" setzen, wenn die Daten leer sind + } + } + + public onOptionChange() { + this.selectedAnyOption = (this.passedProps.schema.anyOf as any).find((option: any) => option.title === this.selectedOption || option.type === this.selectedOption || JSON.stringify(option) === this.selectedOption); + } + + public onInnerJsonFormsChange(event: any) { + // Check if data is equal to the passedProps.data + if (event !== this.passedProps.data) { + const updatedData = this.rendererService.getState().jsonforms.core.data; + + // aktualisiere das aktuelle Datenobjekt + const pathSegments = this.passedProps.path.split('.'); + let current = updatedData; + for (let i = 0; i < pathSegments.length - 1; i++) { + current = current[pathSegments[i]]; + } + current[pathSegments[pathSegments.length - 1]] = event; + + this.rendererService.setData(updatedData); + } + } +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts index 5208f602b3..b1b4ff58bd 100644 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts @@ -1,3 +1,6 @@ +import { angularMaterialRenderers } from "@jsonforms/angular-material"; +import { customRenderers } from "./customRenderer/custom-renderers"; + export interface IIngestionRequestInformation { selectedPath: string; selectedMethod: IExtractionMethod; @@ -13,39 +16,11 @@ export interface IExtractionMethod { schema: string; // Base64 encoded JSON schema }; -export interface Schema { - type?: string; - properties?: { - [key: string]: { - type: string; - format?: string; - enum?: string[]; - minLength?: number; - maxLength?: number; - pattern?: string; - items?: Schema; - additionalProperties?: boolean | Schema; - description?: string; - title?: string; - anyOf?: any[]; - allOf?: any[]; - oneOf?: any[]; - not?: Schema; - required?: string[]; - properties?: { - [key: string]: Schema; - }; - }; - }; - required?: string[]; - additionalProperties?: boolean | Schema; - description?: string; - title?: string; - anyOf?: any[]; - allOf?: any[]; - oneOf?: any[]; - not?: Schema; -} +export const configuredRenderer = [ + ...angularMaterialRenderers, + ...customRenderers, +]; + export class IngestorMetadaEditorHelper { // Resolve all $ref in a schema diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts index db5348c195..d6921ccbbb 100644 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts @@ -1,7 +1,8 @@ import { Component, EventEmitter, Output, Input, OnChanges, SimpleChanges } from '@angular/core'; import { angularMaterialRenderers } from '@jsonforms/angular-material'; -import { Schema } from './ingestor-metadata-editor-helper'; import { customRenderers } from './customRenderer/custom-renderers'; +import { JsonSchema } from '@jsonforms/core'; +import { configuredRenderer } from './ingestor-metadata-editor-helper'; @Component({ selector: 'app-metadata-editor', @@ -15,12 +16,12 @@ import { customRenderers } from './customRenderer/custom-renderers'; export class IngestorMetadataEditorComponent { @Input() data: Object; - @Input() schema: Schema; + @Input() schema: JsonSchema; @Output() dataChange = new EventEmitter(); get combinedRenderers() { - return [...angularMaterialRenderers, ...customRenderers]; + return configuredRenderer; } onDataChange(event: any) { diff --git a/src/app/ingestor/ingestor.module.ts b/src/app/ingestor/ingestor.module.ts index 359e3dd500..eb79e0e644 100644 --- a/src/app/ingestor/ingestor.module.ts +++ b/src/app/ingestor/ingestor.module.ts @@ -25,6 +25,9 @@ import { IngestorExtractorMetadataDialog } from "./ingestor/dialog/ingestor.extr import { IngestorConfirmTransferDialog } from "./ingestor/dialog/ingestor.confirm-transfer-dialog.component"; import { MatStepperModule } from "@angular/material/stepper"; import { IngestorDialogStepperComponent } from "./ingestor/dialog/ingestor.dialog-stepper.component.component"; +import { AnyOfRenderer } from "./ingestor-metadata-editor/customRenderer/any-of-renderer"; +import { OneOfRenderer } from "./ingestor-metadata-editor/customRenderer/one-of-renderer"; +import { MatRadioModule } from "@angular/material/radio"; @NgModule({ declarations: [ @@ -35,6 +38,8 @@ import { IngestorDialogStepperComponent } from "./ingestor/dialog/ingestor.dialo IngestorExtractorMetadataDialog, IngestorConfirmTransferDialog, IngestorDialogStepperComponent, + AnyOfRenderer, + OneOfRenderer, ], imports: [ CommonModule, @@ -53,6 +58,7 @@ import { IngestorDialogStepperComponent } from "./ingestor/dialog/ingestor.dialo MatSelectModule, MatOptionModule, MatStepperModule, + MatRadioModule, MatAutocompleteModule, JsonFormsModule, JsonFormsAngularMaterialModule, diff --git a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts index fae49b798a..cdae97e556 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts @@ -1,7 +1,8 @@ import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { IIngestionRequestInformation, IngestorMetadaEditorHelper, Schema } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; +import { IIngestionRequestInformation, IngestorMetadaEditorHelper } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; import { acquisition_schema, instrument_schema } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest'; +import { JsonSchema } from '@jsonforms/core'; @Component({ selector: 'ingestor.extractor-metadata-dialog', @@ -11,19 +12,27 @@ import { acquisition_schema, instrument_schema } from 'ingestor/ingestor-metadat }) export class IngestorExtractorMetadataDialog { - metadataSchemaInstrument: Schema; - metadataSchemaAcquisition: Schema; + metadataSchemaInstrument: JsonSchema; + metadataSchemaAcquisition: JsonSchema; createNewTransferData: IIngestionRequestInformation = IngestorMetadaEditorHelper.createEmptyRequestInformation(); backendURL: string = ''; - extractorMetaDataReady: boolean = true; + extractorMetaDataReady: boolean = false; constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: any) { - this.backendURL = data.backendURL; - this.metadataSchemaInstrument = instrument_schema; - this.metadataSchemaAcquisition = acquisition_schema; - this.createNewTransferData = data.createNewTransferData; - this.extractorMetaDataReady = data.extractorMetaDataReady + const encodedSchema = data.createNewTransferData.selectedMethod.schema; + const decodedSchema = atob(encodedSchema); + const schema = JSON.parse(decodedSchema); + + const resolvedSchema = IngestorMetadaEditorHelper.resolveRefs(schema, schema); + const instrumentSchema = resolvedSchema.properties.instrument; + const acqusitionSchema = resolvedSchema.properties.acquisition; + + this.metadataSchemaInstrument = instrumentSchema; + this.metadataSchemaAcquisition = acqusitionSchema; + this.createNewTransferData = data.createNewTransferData; + this.backendURL = data.backendURL; + this.extractorMetaDataReady = true //data.extractorMetaDataReady } diff --git a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html index 15298bee8e..3178a15a54 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html +++ b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html @@ -53,6 +53,5 @@

- + \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts index cbf4cacd83..c2087642f0 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts @@ -1,7 +1,8 @@ import {ChangeDetectionStrategy, Component, Inject} from '@angular/core'; import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; -import { IIngestionRequestInformation, IngestorMetadaEditorHelper, Schema } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; -import { organizational_schema, sample_schema, scicatheader_schema } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest'; +import { IIngestionRequestInformation, IngestorMetadaEditorHelper } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; +import { scicatheader_schema } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest'; +import { JsonSchema } from '@jsonforms/core'; @Component({ selector: 'ingestor.user-metadata-dialog', @@ -11,9 +12,9 @@ import { organizational_schema, sample_schema, scicatheader_schema } from 'inges }) export class IngestorUserMetadataDialog { - metadataSchemaOrganizational: Schema; - metadataSchemaSample: Schema; - scicatHeaderSchema: Schema; + metadataSchemaOrganizational: JsonSchema; + metadataSchemaSample: JsonSchema; + scicatHeaderSchema: JsonSchema; createNewTransferData: IIngestionRequestInformation = IngestorMetadaEditorHelper.createEmptyRequestInformation(); backendURL: string = ''; @@ -30,16 +31,9 @@ export class IngestorUserMetadataDialog { const decodedSchema = atob(encodedSchema); const schema = JSON.parse(decodedSchema); - console.log(schema); const resolvedSchema = IngestorMetadaEditorHelper.resolveRefs(schema, schema); - - console.log(resolvedSchema); - const organizationalSchema = resolvedSchema.properties.organizational; const sampleSchema = resolvedSchema.properties.sample; - - console.log(organizationalSchema); - console.log(sampleSchema); this.metadataSchemaOrganizational = organizationalSchema; this.metadataSchemaSample = sampleSchema; From 2e42ddf60400827266f52c59509e02b7f84fa263 Mon Sep 17 00:00:00 2001 From: David Wiessner Date: Sun, 15 Dec 2024 15:02:32 +0000 Subject: [PATCH 017/245] Embedding ingestor backend part 3 --- .../customRenderer/any-of-renderer.ts | 9 +- .../ingestor-metadata-editor-helper.ts | 38 +------ ...> ingestor-metadata-editor-schema_demo.ts} | 10 +- .../ingestor-metadata-editor.component.ts | 4 +- ...estor.confirm-transfer-dialog.component.ts | 30 +---- ...tor.extractor-metadata-dialog.component.ts | 40 +++---- .../ingestor.extractor-metadata-dialog.html | 40 +++++-- .../ingestor.new-transfer-dialog.component.ts | 29 ++++- .../dialog/ingestor.new-transfer-dialog.html | 2 +- ...ingestor.user-metadata-dialog.component.ts | 25 ++--- .../dialog/ingestor.user-metadata-dialog.html | 2 +- .../ingestor/ingestor-api-endpoints.ts | 9 ++ .../ingestor/ingestor.component-helper.ts | 104 ++++++++++++++++++ .../ingestor/ingestor/ingestor.component.scss | 4 + .../ingestor/ingestor/ingestor.component.ts | 71 +++++++++--- 15 files changed, 282 insertions(+), 135 deletions(-) rename src/app/ingestor/ingestor-metadata-editor/{ingestor-metadata-editor-schematest.ts => ingestor-metadata-editor-schema_demo.ts} (98%) create mode 100644 src/app/ingestor/ingestor/ingestor.component-helper.ts diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts index 3e212da8d8..530d762196 100644 --- a/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts @@ -9,9 +9,9 @@ import { configuredRenderer } from '../ingestor-metadata-editor-helper';
{{anyOfTitle}} - + -
+
@@ -24,6 +24,7 @@ export class AnyOfRenderer extends JsonFormsControl { dataAsString: string; options: string[] = []; anyOfTitle: string; + selectedTabIndex: number = 0; // default value rendererService: JsonFormsAngularService; @@ -39,6 +40,10 @@ export class AnyOfRenderer extends JsonFormsControl { this.passedProps = props; this.anyOfTitle = props.label || 'AnyOf'; this.options = props.schema.anyOf.map((option: any) => option.title || option.type || JSON.stringify(option)); + + if (this.options.includes("null") && !props.data) { + this.selectedTabIndex = this.options.indexOf("null"); + } } public getTabSchema(tabOption: string): JsonSchema { diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts index b1b4ff58bd..65c4d640d3 100644 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts @@ -1,28 +1,12 @@ import { angularMaterialRenderers } from "@jsonforms/angular-material"; import { customRenderers } from "./customRenderer/custom-renderers"; -export interface IIngestionRequestInformation { - selectedPath: string; - selectedMethod: IExtractionMethod; - scicatHeader: Object; - userMetaData: Object; - extractorMetaData: Object; - extractorMetaDataReady: boolean; - mergedMetaDataString: string; -} - -export interface IExtractionMethod { - name: string; - schema: string; // Base64 encoded JSON schema -}; - export const configuredRenderer = [ ...angularMaterialRenderers, ...customRenderers, ]; - -export class IngestorMetadaEditorHelper { +export class IngestorMetadataEditorHelper { // Resolve all $ref in a schema static resolveRefs(schema: any, rootSchema: any): any { if (schema === null || schema === undefined) { @@ -35,30 +19,14 @@ export class IngestorMetadaEditorHelper { refPath.forEach((part) => { ref = ref[part]; }); - return IngestorMetadaEditorHelper.resolveRefs(ref, rootSchema); + return IngestorMetadataEditorHelper.resolveRefs(ref, rootSchema); } else if (typeof schema === 'object') { for (const key in schema) { if (schema.hasOwnProperty(key)) { - schema[key] = IngestorMetadaEditorHelper.resolveRefs(schema[key], rootSchema); + schema[key] = IngestorMetadataEditorHelper.resolveRefs(schema[key], rootSchema); } } } return schema; }; - - static mergeUserAndExtractorMetadata(userMetadata: Object, extractorMetadata: Object, space: number): string { - return JSON.stringify({ ...userMetadata, ...extractorMetadata }, null, space); - } - - static createEmptyRequestInformation = (): IIngestionRequestInformation => { - return { - selectedPath: '', - selectedMethod: { name: '', schema: '' }, - scicatHeader: {}, - userMetaData: {}, - extractorMetaData: {}, - extractorMetaDataReady: false, - mergedMetaDataString: '', - }; - }; }; \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schema_demo.ts similarity index 98% rename from src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest.ts rename to src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schema_demo.ts index 630a06f277..5874d74d42 100644 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest.ts +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schema_demo.ts @@ -1,4 +1,4 @@ -export const organizational_schema = { +export const demo_organizational_schema = { type: 'object', properties: { grants: { @@ -113,7 +113,7 @@ export const organizational_schema = { required: ['authors', 'funder'], }; -export const acquisition_schema = { +export const demo_acquisition_schema = { type: 'object', properties: { nominal_defocus: { @@ -228,7 +228,7 @@ export const acquisition_schema = { required: ['detector', 'dose_per_movie', 'date_time', 'binning_camera', 'pixel_size'], }; -export const sample_schema = { +export const demo_sample_schema = { type: 'object', properties: { overall_molecule: { @@ -428,7 +428,7 @@ export const sample_schema = { required: ['overall_molecule', 'molecule', 'specimen', 'grid'], }; -export const instrument_schema = { +export const demo_instrument_schema = { type: 'object', properties: { microscope: { @@ -463,7 +463,7 @@ export const instrument_schema = { required: ['microscope', 'illumination', 'imaging', 'electron_source', 'acceleration_voltage', 'cs'], }; -export const scicatheader_schema = { +export const demo_scicatheader_schema = { type: "object", properties: { datasetName: { type: "string" }, diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts index d6921ccbbb..308a64143b 100644 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts @@ -1,6 +1,4 @@ -import { Component, EventEmitter, Output, Input, OnChanges, SimpleChanges } from '@angular/core'; -import { angularMaterialRenderers } from '@jsonforms/angular-material'; -import { customRenderers } from './customRenderer/custom-renderers'; +import { Component, EventEmitter, Output, Input } from '@angular/core'; import { JsonSchema } from '@jsonforms/core'; import { configuredRenderer } from './ingestor-metadata-editor-helper'; diff --git a/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts index 1968c67429..52285fca6b 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts @@ -1,25 +1,6 @@ import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { IIngestionRequestInformation, IngestorMetadaEditorHelper } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; - -interface ISciCatHeader { - datasetName: string; - description: string; - creationLocation: string; - dataFormat: string; - ownerGroup: string; - type: string; - license: string; - keywords: string[]; - scientificMetadata: IScientificMetadata; -} - -interface IScientificMetadata { - organization: Object; - sample: Object; - acquisition: Object; - instrument: Object; -} +import { IDialogDataObject, IIngestionRequestInformation, IngestorHelper, ISciCatHeader } from '../ingestor.component-helper'; @Component({ selector: 'ingestor.confirm-transfer-dialog', @@ -29,11 +10,11 @@ interface IScientificMetadata { }) export class IngestorConfirmTransferDialog { - createNewTransferData: IIngestionRequestInformation = IngestorMetadaEditorHelper.createEmptyRequestInformation(); + createNewTransferData: IIngestionRequestInformation = IngestorHelper.createEmptyRequestInformation(); provideMergeMetaData: string = ''; backendURL: string = ''; - constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: any) { + constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: IDialogDataObject) { this.createNewTransferData = data.createNewTransferData; this.backendURL = data.backendURL; } @@ -53,8 +34,9 @@ export class IngestorConfirmTransferDialog { type: this.createNewTransferData.scicatHeader['type'], license: this.createNewTransferData.scicatHeader['license'], keywords: this.createNewTransferData.scicatHeader['keywords'], + filePath: this.createNewTransferData.scicatHeader['filePath'], scientificMetadata: { - organization: this.createNewTransferData.userMetaData['organization'], + organizational: this.createNewTransferData.userMetaData['organizational'], sample: this.createNewTransferData.userMetaData['sample'], acquisition: this.createNewTransferData.extractorMetaData['acquisition'], instrument: this.createNewTransferData.extractorMetaData['instrument'], @@ -73,7 +55,7 @@ export class IngestorConfirmTransferDialog { onClickConfirm(): void { if (this.data && this.data.onClickNext) { this.createNewTransferData.mergedMetaDataString = this.provideMergeMetaData; - this.data.onClickConfirm(); + this.data.onClickNext(4); } } } \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts index cdae97e556..97ba8e811d 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts @@ -1,8 +1,7 @@ import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { IIngestionRequestInformation, IngestorMetadaEditorHelper } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; -import { acquisition_schema, instrument_schema } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest'; import { JsonSchema } from '@jsonforms/core'; +import { IDialogDataObject, IIngestionRequestInformation, IngestorHelper } from '../ingestor.component-helper'; @Component({ selector: 'ingestor.extractor-metadata-dialog', @@ -14,28 +13,29 @@ import { JsonSchema } from '@jsonforms/core'; export class IngestorExtractorMetadataDialog { metadataSchemaInstrument: JsonSchema; metadataSchemaAcquisition: JsonSchema; - createNewTransferData: IIngestionRequestInformation = IngestorMetadaEditorHelper.createEmptyRequestInformation(); + createNewTransferData: IIngestionRequestInformation = IngestorHelper.createEmptyRequestInformation(); backendURL: string = ''; extractorMetaDataReady: boolean = false; + extractorMetaDataError: boolean = false; - constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: any) { - const encodedSchema = data.createNewTransferData.selectedMethod.schema; - const decodedSchema = atob(encodedSchema); - const schema = JSON.parse(decodedSchema); - - const resolvedSchema = IngestorMetadaEditorHelper.resolveRefs(schema, schema); - const instrumentSchema = resolvedSchema.properties.instrument; - const acqusitionSchema = resolvedSchema.properties.acquisition; + isCardContentVisible = { + instrument: true, + acquisition: true + }; + + constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: IDialogDataObject) { + this.createNewTransferData = data.createNewTransferData; + this.backendURL = data.backendURL; + const instrumentSchema = this.createNewTransferData.selectedResolvedDecodedSchema.properties.instrument; + const acqusitionSchema = this.createNewTransferData.selectedResolvedDecodedSchema.properties.acquisition; this.metadataSchemaInstrument = instrumentSchema; this.metadataSchemaAcquisition = acqusitionSchema; - this.createNewTransferData = data.createNewTransferData; - this.backendURL = data.backendURL; - this.extractorMetaDataReady = true //data.extractorMetaDataReady + this.extractorMetaDataReady = this.createNewTransferData.extractorMetaDataReady; + this.extractorMetaDataError = this.createNewTransferData.apiErrorInformation.metaDataExtraction; } - onClickBack(): void { if (this.data && this.data.onClickNext) { this.data.onClickNext(1); // Beispielwert für den Schritt @@ -43,18 +43,20 @@ export class IngestorExtractorMetadataDialog { } onClickNext(): void { - console.log(this.createNewTransferData.selectedMethod) - if (this.data && this.data.onClickNext) { this.data.onClickNext(3); // Beispielwert für den Schritt } } - onDataChangeUserMetadataInstrument(event: any) { + onDataChangeExtractorMetadataInstrument(event: Object) { this.createNewTransferData.extractorMetaData['instrument'] = event; } - onDataChangeUserMetadataAcquisition(event: any) { + onDataChangeExtractorMetadataAcquisition(event: Object) { this.createNewTransferData.extractorMetaData['acquisition'] = event; } + + toggleCardContent(card: string): void { + this.isCardContentVisible[card] = !this.isCardContentVisible[card]; + } } \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html index 3178a15a54..fe985f0d6a 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html +++ b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html @@ -12,39 +12,54 @@

-
- Wait for the Ingestor... -
+
+ Wait for the Ingestor... +
+
+ + +
+ error +
+ The automatic metadata extraction was not successful. Please enter + the data manually. +
+
+
- +
biotech
Instrument Information + + {{ isCardContentVisible.instrument ? 'expand_less' : 'expand_more' }}
- + + [data]="createNewTransferData.extractorMetaData['instrument']" + (dataChange)="onDataChangeExtractorMetadataInstrument($event)">
- +
category-search
Acquisition Information + + {{ isCardContentVisible.acquisition ? 'expand_less' : 'expand_more' }}
- + + [data]="createNewTransferData.extractorMetaData['acquisition']" + (dataChange)="onDataChangeExtractorMetadataAcquisition($event)">
@@ -53,5 +68,6 @@

- + \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts index 0433f6122a..198b11fdfd 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts @@ -1,8 +1,9 @@ import { ChangeDetectionStrategy, Component, Inject, OnInit } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; import { HttpClient } from '@angular/common/http'; -import { IExtractionMethod, IIngestionRequestInformation, IngestorMetadaEditorHelper } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; import { INGESTOR_API_ENDPOINTS_V1 } from '../ingestor-api-endpoints'; +import { IDialogDataObject, IExtractionMethod, IIngestionRequestInformation, IngestorHelper } from '../ingestor.component-helper'; +import { IngestorMetadataEditorHelper } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; @Component({ selector: 'ingestor.new-transfer-dialog', @@ -20,9 +21,9 @@ export class IngestorNewTransferDialogComponent implements OnInit { uiNextButtonReady: boolean = false; - createNewTransferData: IIngestionRequestInformation = IngestorMetadaEditorHelper.createEmptyRequestInformation(); + createNewTransferData: IIngestionRequestInformation = IngestorHelper.createEmptyRequestInformation(); - constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: any, private http: HttpClient) { + constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: IDialogDataObject, private http: HttpClient) { this.createNewTransferData = data.createNewTransferData; this.backendURL = data.backendURL; } @@ -84,6 +85,26 @@ export class IngestorNewTransferDialogComponent implements OnInit { ); } + generateExampleDataForSciCatHeader(): void { + this.data.createNewTransferData.scicatHeader['filePath'] = this.createNewTransferData.selectedPath; + this.data.createNewTransferData.scicatHeader['keywords'] = ['OpenEM']; + + const nameWithoutPath = this.createNewTransferData.selectedPath.split('/|\\')[-1] ?? this.createNewTransferData.selectedPath; + this.data.createNewTransferData.scicatHeader['datasetName'] = nameWithoutPath; + this.data.createNewTransferData.scicatHeader['license'] = 'MIT License'; + this.data.createNewTransferData.scicatHeader['type'] = 'raw'; + this.data.createNewTransferData.scicatHeader['dataFormat'] = 'root'; + this.data.createNewTransferData.scicatHeader['owner'] = 'User'; + } + + prepareSchemaForProcessing(): void { + const encodedSchema = this.createNewTransferData.selectedMethod.schema; + const decodedSchema = atob(encodedSchema); + const schema = JSON.parse(decodedSchema); + const resolvedSchema = IngestorMetadataEditorHelper.resolveRefs(schema, schema); + this.createNewTransferData.selectedResolvedDecodedSchema = resolvedSchema; + } + onClickRetryRequests(): void { this.apiGetExtractionMethods(); this.apiGetAvailableFilePaths(); @@ -91,6 +112,8 @@ export class IngestorNewTransferDialogComponent implements OnInit { onClickNext(): void { if (this.data && this.data.onClickNext) { + this.generateExampleDataForSciCatHeader(); + this.prepareSchemaForProcessing(); this.data.onClickNext(1); // Open next dialog } } diff --git a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.html index 6bd1a6d738..a80e8c4d8a 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.html +++ b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.html @@ -9,7 +9,7 @@

-

First mask where user needs to select path and method

+

Please select the dataset to be uploaded and the appropriate metadata extractor method.

diff --git a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts index c2087642f0..427bacd801 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts @@ -1,8 +1,8 @@ import {ChangeDetectionStrategy, Component, Inject} from '@angular/core'; import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; -import { IIngestionRequestInformation, IngestorMetadaEditorHelper } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; -import { scicatheader_schema } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schematest'; +import { IngestorMetadataEditorHelper } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; import { JsonSchema } from '@jsonforms/core'; +import { IDialogDataObject, IIngestionRequestInformation, IngestorHelper, SciCatHeader_Schema } from '../ingestor.component-helper'; @Component({ selector: 'ingestor.user-metadata-dialog', @@ -15,7 +15,7 @@ export class IngestorUserMetadataDialog { metadataSchemaOrganizational: JsonSchema; metadataSchemaSample: JsonSchema; scicatHeaderSchema: JsonSchema; - createNewTransferData: IIngestionRequestInformation = IngestorMetadaEditorHelper.createEmptyRequestInformation(); + createNewTransferData: IIngestionRequestInformation = IngestorHelper.createEmptyRequestInformation(); backendURL: string = ''; uiNextButtonReady: boolean = true; // Change to false when dev is ready @@ -26,20 +26,15 @@ export class IngestorUserMetadataDialog { sample: true }; - constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: any) { - const encodedSchema = data.createNewTransferData.selectedMethod.schema; - const decodedSchema = atob(encodedSchema); - const schema = JSON.parse(decodedSchema); - - const resolvedSchema = IngestorMetadaEditorHelper.resolveRefs(schema, schema); - const organizationalSchema = resolvedSchema.properties.organizational; - const sampleSchema = resolvedSchema.properties.sample; + constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: IDialogDataObject) { + this.createNewTransferData = data.createNewTransferData; + this.backendURL = data.backendURL; + const organizationalSchema = this.createNewTransferData.selectedResolvedDecodedSchema.properties.organizational; + const sampleSchema = this.createNewTransferData.selectedResolvedDecodedSchema.properties.sample; this.metadataSchemaOrganizational = organizationalSchema; this.metadataSchemaSample = sampleSchema; - this.scicatHeaderSchema = scicatheader_schema; - this.createNewTransferData = data.createNewTransferData; - this.backendURL = data.backendURL; + this.scicatHeaderSchema = SciCatHeader_Schema; } onClickBack(): void { @@ -55,7 +50,7 @@ export class IngestorUserMetadataDialog { } onDataChangeUserMetadataOrganization(event: Object) { - this.createNewTransferData.userMetaData['organization'] = event; + this.createNewTransferData.userMetaData['organizational'] = event; } onDataChangeUserMetadataSample(event: Object) { diff --git a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html index 8e3fd19142..39fbf41a49 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html +++ b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html @@ -37,7 +37,7 @@

{{ isCardContentVisible.organizational ? 'expand_less' : 'expand_more' }} - diff --git a/src/app/ingestor/ingestor/ingestor-api-endpoints.ts b/src/app/ingestor/ingestor/ingestor-api-endpoints.ts index 6cd5860805..df2d088330 100644 --- a/src/app/ingestor/ingestor/ingestor-api-endpoints.ts +++ b/src/app/ingestor/ingestor/ingestor-api-endpoints.ts @@ -6,3 +6,12 @@ export const INGESTOR_API_ENDPOINTS_V1 = { }, EXTRACTOR: 'extractor', }; + +export interface IPostExtractorEndpoint { + filePath: string, + methodName: string, +} + +export interface IPostDatasetEndpoint { + metaData: string +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor.component-helper.ts b/src/app/ingestor/ingestor/ingestor.component-helper.ts new file mode 100644 index 0000000000..1d31525d57 --- /dev/null +++ b/src/app/ingestor/ingestor/ingestor.component-helper.ts @@ -0,0 +1,104 @@ +import { JsonSchema } from '@jsonforms/core'; + +export interface IExtractionMethod { + name: string; + schema: string; // Base64 encoded JSON schema +}; + +export interface IIngestionRequestInformation { + selectedPath: string; + selectedMethod: IExtractionMethod; + selectedResolvedDecodedSchema: JsonSchema; + scicatHeader: Object; + userMetaData: { + organizational: Object, + sample: Object, + }; + extractorMetaData: { + instrument: Object, + acquisition: Object, + }; + extractorMetaDataReady: boolean; + extractMetaDataRequested: boolean; + mergedMetaDataString: string; + + apiErrorInformation: { + metaDataExtraction: boolean; + } +} + +// There are many more... see DerivedDataset.ts +export interface ISciCatHeader { + datasetName: string; + description: string; + creationLocation: string; + dataFormat: string; + ownerGroup: string; + type: string; + license: string; + keywords: string[]; + filePath: string; + scientificMetadata: IScientificMetadata; +} + +export interface IScientificMetadata { + organizational: Object; + sample: Object; + acquisition: Object; + instrument: Object; +} + +export interface IDialogDataObject { + createNewTransferData: IIngestionRequestInformation; + backendURL: string; + onClickNext: (step: number) => void; +} + +export class IngestorHelper { + static createEmptyRequestInformation = (): IIngestionRequestInformation => { + return { + selectedPath: '', + selectedMethod: { name: '', schema: '' }, + selectedResolvedDecodedSchema: {}, + scicatHeader: {}, + userMetaData: { + organizational: {}, + sample: {}, + }, + extractorMetaData: { + instrument: {}, + acquisition: {}, + }, + extractorMetaDataReady: false, + extractMetaDataRequested: false, + mergedMetaDataString: '', + apiErrorInformation: { + metaDataExtraction: false, + }, + }; + }; + + static mergeUserAndExtractorMetadata(userMetadata: Object, extractorMetadata: Object, space: number): string { + return JSON.stringify({ ...userMetadata, ...extractorMetadata }, null, space); + }; +} + +export const SciCatHeader_Schema: JsonSchema = { + type: "object", + properties: { + datasetName: { type: "string" }, + description: { type: "string" }, + creationLocation: { type: "string" }, + dataFormat: { type: "string" }, + ownerGroup: { type: "string" }, + filePath: { type: "string", readOnly: true }, // disabled, because its selected in the first step + type: { type: "string" }, + license: { type: "string" }, + keywords: { + type: "array", + items: { type: "string" } + }, + // scientificMetadata: { type: "string" } ; is created during the ingestor process + }, + required: ["datasetName", "creationLocation", "dataFormat", "ownerGroup", "type", "license", "keywords", "scientificMetadata", "filePath"] +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor.component.scss b/src/app/ingestor/ingestor/ingestor.component.scss index bbfccd1e5a..a5c5dcad67 100644 --- a/src/app/ingestor/ingestor/ingestor.component.scss +++ b/src/app/ingestor/ingestor/ingestor.component.scss @@ -55,4 +55,8 @@ mat-card { .spacer { flex: 1 1 auto; +} + +.error-message { + color: red; } \ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor.component.ts b/src/app/ingestor/ingestor/ingestor.component.ts index ba34313352..2b49f2f90a 100644 --- a/src/app/ingestor/ingestor/ingestor.component.ts +++ b/src/app/ingestor/ingestor/ingestor.component.ts @@ -2,13 +2,13 @@ import { Component, inject, OnInit } from "@angular/core"; import { AppConfigService } from "app-config.service"; import { HttpClient } from '@angular/common/http'; import { ActivatedRoute, Router } from '@angular/router'; -import { INGESTOR_API_ENDPOINTS_V1 } from "./ingestor-api-endpoints"; +import { INGESTOR_API_ENDPOINTS_V1, IPostDatasetEndpoint, IPostExtractorEndpoint } from "./ingestor-api-endpoints"; import { IngestorNewTransferDialogComponent } from "./dialog/ingestor.new-transfer-dialog.component"; import { MatDialog } from "@angular/material/dialog"; import { IngestorUserMetadataDialog } from "./dialog/ingestor.user-metadata-dialog.component"; import { IngestorExtractorMetadataDialog } from "./dialog/ingestor.extractor-metadata-dialog.component"; import { IngestorConfirmTransferDialog } from "./dialog/ingestor.confirm-transfer-dialog.component"; -import { IIngestionRequestInformation, IngestorMetadaEditorHelper } from "ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper"; +import { IIngestionRequestInformation, IngestorHelper } from "./ingestor.component-helper"; interface ITransferDataListEntry { transferId: string; @@ -39,7 +39,7 @@ export class IngestorComponent implements OnInit { errorMessage: string = ''; returnValue: string = ''; - createNewTransferData: IIngestionRequestInformation = IngestorMetadaEditorHelper.createEmptyRequestInformation(); + createNewTransferData: IIngestionRequestInformation = IngestorHelper.createEmptyRequestInformation(); constructor(public appConfigService: AppConfigService, private http: HttpClient, private route: ActivatedRoute, private router: Router) { } @@ -112,17 +112,14 @@ export class IngestorComponent implements OnInit { apiUpload() { this.loading = true; - this.returnValue = ''; - const payload = { - filePath: this.createNewTransferData.selectedPath, + + const payload: IPostDatasetEndpoint = { metaData: this.createNewTransferData.mergedMetaDataString, }; - console.log('Uploading', payload); - this.http.post(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.DATASET, payload).subscribe( response => { - console.log('Upload successful', response); + console.log('Upload successfully started', response); this.returnValue = JSON.stringify(response); this.loading = false; }, @@ -134,6 +131,42 @@ export class IngestorComponent implements OnInit { ); } + async apiStartMetadataExtraction(): Promise { + this.createNewTransferData.apiErrorInformation.metaDataExtraction = false; + + if (this.createNewTransferData.extractMetaDataRequested) { + console.log(this.createNewTransferData.extractMetaDataRequested, ' already requested'); // Debugging + return false; + } + + this.createNewTransferData.extractorMetaDataReady = false; + this.createNewTransferData.extractMetaDataRequested = true; + + const payload: IPostExtractorEndpoint = { + filePath: this.createNewTransferData.selectedPath, + methodName: this.createNewTransferData.selectedMethod.name, + }; + + return new Promise((resolve) => { + this.http.post(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.EXTRACTOR, payload).subscribe( + response => { + console.log('Metadata extraction result', response); + this.createNewTransferData.extractorMetaData.instrument = (response as any).instrument ?? {}; + this.createNewTransferData.extractorMetaData.acquisition = (response as any).acquisition ?? {}; + this.createNewTransferData.extractorMetaDataReady = true; + resolve(true); + }, + error => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}]
`; + console.error('Metadata extraction failed', error); + this.createNewTransferData.extractorMetaDataReady = true; + this.createNewTransferData.apiErrorInformation.metaDataExtraction = true; + resolve(false); + } + ); + }); + } + onClickForwardToIngestorPage() { if (this.forwardFacilityBackend) { this.connectingToFacilityBackend = true; @@ -174,8 +207,8 @@ export class IngestorComponent implements OnInit { } onClickAddIngestion(): void { - this.createNewTransferData = IngestorMetadaEditorHelper.createEmptyRequestInformation(); - this.onClickNext(0); + this.createNewTransferData = IngestorHelper.createEmptyRequestInformation(); + this.onClickNext(0); // Open first dialog to start the ingestion process } onClickNext(step: number): void { @@ -185,6 +218,8 @@ export class IngestorComponent implements OnInit { switch (step) { case 0: + this.createNewTransferData.extractMetaDataRequested = false; + this.createNewTransferData.extractorMetaDataReady = false; dialogRef = this.dialog.open(IngestorNewTransferDialogComponent, { data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData, backendURL: this.connectedFacilityBackend }, disableClose: true @@ -192,6 +227,13 @@ export class IngestorComponent implements OnInit { break; case 1: + this.apiStartMetadataExtraction().then((response: boolean) => { + if (response) console.log('Metadata extraction finished'); + else console.error('Metadata extraction failed'); + }).catch(error => { + console.error('Metadata extraction error', error); + }); + dialogRef = this.dialog.open(IngestorUserMetadataDialog, { data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData, backendURL: this.connectedFacilityBackend }, disableClose: true @@ -209,16 +251,15 @@ export class IngestorComponent implements OnInit { disableClose: true }); break; + case 4: + this.apiUpload(); + break; default: console.error('Unknown step', step); } // Error if the dialog reference is not set if (dialogRef === null) return; - - /*dialogRef.afterClosed().subscribe(result => { - console.log(`Dialog result: ${result}`); - });*/ } onClickRefreshTransferList(): void { From 476c006199e57df17ac79a38a0223de56bdefa01 Mon Sep 17 00:00:00 2001 From: Despina Adamopoulou <16343312+despadam@users.noreply.github.com> Date: Mon, 16 Sep 2024 22:57:28 +0200 Subject: [PATCH 018/245] Update frontend to match with release-jobs backend (#1585) * update job view to match release-jobs * update job schema and timestamp fields * update jobs-detail page * fix testing and linting --- src/app/datasets/archiving.service.ts | 8 +- .../datafiles-action.component.spec.ts | 4 +- .../datasets/datafiles/datafiles.component.ts | 13 +- .../jobs-dashboard-new.component.ts | 23 +- .../jobs-dashboard.component.spec.ts | 4 +- .../jobs-dashboard.component.ts | 19 +- .../jobs-detail/jobs-detail.component.html | 203 +++++++++++------- .../jobs-detail/jobs-detail.component.scss | 33 +-- .../shared-table/_shared-table-theme.scss | 1 + 9 files changed, 178 insertions(+), 130 deletions(-) diff --git a/src/app/datasets/archiving.service.ts b/src/app/datasets/archiving.service.ts index fcf68c4a12..99cfb094f2 100644 --- a/src/app/datasets/archiving.service.ts +++ b/src/app/datasets/archiving.service.ts @@ -30,7 +30,7 @@ export class ArchivingService { ) { const extra = archive ? {} : destinationPath; const jobParams = { - username: user.username, + datasetIds: datasets.map((dataset) => dataset.pid), ...extra, }; @@ -40,12 +40,8 @@ export class ArchivingService { const data = { jobParams, - emailJobInitiator: user.email, + createdBy: user.username, // Revise this, files == []...? See earlier version of this method in dataset-table component for context - datasetList: datasets.map((dataset) => ({ - pid: dataset.pid, - files: [], - })), type: archive ? "archive" : "retrieve", }; diff --git a/src/app/datasets/datafiles-actions/datafiles-action.component.spec.ts b/src/app/datasets/datafiles-actions/datafiles-action.component.spec.ts index 46e362e2b5..0919c32cc0 100644 --- a/src/app/datasets/datafiles-actions/datafiles-action.component.spec.ts +++ b/src/app/datasets/datafiles-actions/datafiles-action.component.spec.ts @@ -185,7 +185,7 @@ describe("1000: DatafilesActionComponent", () => { beforeEach(() => { fixture = TestBed.createComponent(DatafilesActionComponent); component = fixture.componentInstance; - component.files = structuredClone(actionFiles); + component.files = JSON.parse(JSON.stringify(actionFiles)); component.actionConfig = actionsConfig[0]; component.actionDataset = actionDataset; component.maxFileSize = lowerMaxFileSizeLimit; @@ -484,7 +484,7 @@ describe("1000: DatafilesActionComponent", () => { component.maxFileSize = lowerMaxFileSizeLimit; break; } - component.files = structuredClone(actionFiles); + component.files = JSON.parse(JSON.stringify(actionFiles)); switch (selectedFiles) { case selectedFilesType.file1: component.files[0].selected = true; diff --git a/src/app/datasets/datafiles/datafiles.component.ts b/src/app/datasets/datafiles/datafiles.component.ts index d807d09145..91da0d4b1e 100644 --- a/src/app/datasets/datafiles/datafiles.component.ts +++ b/src/app/datasets/datafiles/datafiles.component.ts @@ -304,15 +304,12 @@ export class DatafilesComponent if (email) { this.getSelectedFiles(); const data = { - emailJobInitiator: email, - creationTime: new Date(), + createdBy: email, + createdAt: new Date(), type: "public", - datasetList: [ - { - pid: this.datasetPid, - files: this.getSelectedFiles(), - }, - ], + jobParams: { + datasetIds: [this.datasetPid], + }, }; this.store.dispatch(submitJobAction({ job: data })); } diff --git a/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.ts b/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.ts index 611ee66cec..fb6689b8c8 100644 --- a/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.ts +++ b/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.ts @@ -4,6 +4,7 @@ import { Component, OnDestroy, } from "@angular/core"; +import { Router } from "@angular/router"; import { SciCatDataSource } from "../../shared/services/scicat.datasource"; import { ScicatDataService } from "../../shared/services/scicat-data-service"; import { ExportExcelService } from "../../shared/services/export-excel.service"; @@ -30,11 +31,11 @@ export class JobsDashboardNewComponent implements OnDestroy, AfterViewChecked { hideOrder: 0, }, { - id: "emailJobInitiator", - label: "Initiator", + id: "createdBy", + label: "Creator", icon: "person", canSort: true, - matchMode: "contains", + matchMode: "is", hideOrder: 1, }, { @@ -46,7 +47,7 @@ export class JobsDashboardNewComponent implements OnDestroy, AfterViewChecked { hideOrder: 2, }, { - id: "creationTime", + id: "createdAt", icon: "schedule", label: "Created at local time", format: "date medium ", @@ -64,22 +65,13 @@ export class JobsDashboardNewComponent implements OnDestroy, AfterViewChecked { hideOrder: 4, }, { - id: "jobStatusMessage", + id: "statusCode", icon: "traffic", label: "Status", - format: "json", canSort: true, matchMode: "contains", hideOrder: 5, }, - { - id: "datasetList", - icon: "list", - label: "Datasets", - format: "json", - canSort: true, - hideOrder: 6, - }, { id: "jobResultObject", icon: "work_outline", @@ -102,6 +94,7 @@ export class JobsDashboardNewComponent implements OnDestroy, AfterViewChecked { private cdRef: ChangeDetectorRef, private dataService: ScicatDataService, private exportService: ExportExcelService, + private router: Router, ) { this.dataSource = new SciCatDataSource( this.appConfigService, @@ -125,4 +118,4 @@ export class JobsDashboardNewComponent implements OnDestroy, AfterViewChecked { const id = encodeURIComponent(job.id); this.router.navigateByUrl("/user/jobs/" + id); */ } -} +} \ No newline at end of file diff --git a/src/app/jobs/jobs-dashboard/jobs-dashboard.component.spec.ts b/src/app/jobs/jobs-dashboard/jobs-dashboard.component.spec.ts index 0084ef2f6c..4e50368af3 100644 --- a/src/app/jobs/jobs-dashboard/jobs-dashboard.component.spec.ts +++ b/src/app/jobs/jobs-dashboard/jobs-dashboard.component.spec.ts @@ -100,8 +100,8 @@ describe("JobsDashboardComponent", () => { dispatchSpy = spyOn(store, "dispatch"); const mode = JobViewMode.myJobs; - component.email = "test@email.com"; - const viewMode = { emailJobInitiator: component.email }; + component.username = "testName"; + const viewMode = { createdBy: component.username }; component.onModeChange(mode); expect(dispatchSpy).toHaveBeenCalledTimes(1); diff --git a/src/app/jobs/jobs-dashboard/jobs-dashboard.component.ts b/src/app/jobs/jobs-dashboard/jobs-dashboard.component.ts index 2033ad245a..cc1ae02dd7 100644 --- a/src/app/jobs/jobs-dashboard/jobs-dashboard.component.ts +++ b/src/app/jobs/jobs-dashboard/jobs-dashboard.component.ts @@ -48,7 +48,7 @@ export class JobsDashboardComponent implements OnInit, OnDestroy { jobs: JobsTableData[] = []; profile: any; - email = ""; + username = ""; subscriptions: Subscription[] = []; @@ -98,11 +98,8 @@ export class JobsDashboardComponent implements OnInit, OnDestroy { id: job._id, initiator: job.emailJobInitiator, type: job.type, - createdAt: this.datePipe.transform( - job.creationTime, - "yyyy-MM-dd HH:mm", - ), - statusMessage: job.jobStatusMessage, + createdAt: this.datePipe.transform(job.createdAt, "yyyy-MM-dd HH:mm"), + statusMessage: job.statusMessage, })); } return tableData; @@ -129,7 +126,7 @@ export class JobsDashboardComponent implements OnInit, OnDestroy { break; } case JobViewMode.myJobs: { - viewMode = { emailJobInitiator: this.email }; + viewMode = { createdBy: this.username }; break; } default: { @@ -154,11 +151,11 @@ export class JobsDashboardComponent implements OnInit, OnDestroy { // map column names back to original names switch (event.active) { case "statusMessage": { - event.active = "jobStatusMessage"; + event.active = "statusMessage"; break; } case "initiator": { - event.active = "emailJobInitiator"; + event.active = "createdBy"; break; } default: { @@ -181,13 +178,13 @@ export class JobsDashboardComponent implements OnInit, OnDestroy { this.subscriptions.push( this.store.select(selectCurrentUser).subscribe((current) => { if (current) { - this.email = current.email; + this.username = current.username; if (!current.realm) { this.store.select(selectProfile).subscribe((profile) => { if (profile) { this.profile = profile; - this.email = profile.email; + this.username = profile.username; } this.onModeChange(JobViewMode.myJobs); }); diff --git a/src/app/jobs/jobs-detail/jobs-detail.component.html b/src/app/jobs/jobs-detail/jobs-detail.component.html index ac46789303..42b3234ba6 100644 --- a/src/app/jobs/jobs-detail/jobs-detail.component.html +++ b/src/app/jobs/jobs-detail/jobs-detail.component.html @@ -1,73 +1,132 @@ -
- - -

Action + Action + - - + +
- - - - - - - - - - - - - - - - - - - - - - - - - - -
- {{ daSet.pid | json }} +
+
+ + +
+ description
-
- - - - - - - - -
- mail - Email Job Initiator - {{ job.emailJobInitiator }}
- bubble_chart - Type - {{ value }}
- brightness_high - Creation Time - {{ value | date: "yyyy-MM-dd HH:mm" }}
- gavel - Execution Time - {{ value | date: "yyyy-MM-dd HH:mm" }}
- settings - Job Params - {{ value | json }}
- markunread - Date Of Last Message - {{ value | date: "yyyy-MM-dd HH:mm" }}
- folder - Dataset List -
- calendar_today - Created At - {{ value | date: "yyyy-MM-dd HH:mm" }}
- calendar_today - Updated At - {{ value | date: "yyyy-MM-dd HH:mm" }}
- - -
+ General Information + + + + + + + + + + + + + + + +
ID{{ value }}
Type{{ value }}
Created At{{ value | date: "yyyy-MM-dd HH:mm" }}
+
+ + + +
+ person +
+ Users and Ownership +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Created By{{ value }}
Contact Email{{ value }}
Owner User{{ value }}
Owner Group{{ value }}
Access Groups{{ value }}
Updated By{{ value }}
+
+
+ + +
+ analytics +
+ Status +
+ + + + + + + + + + + + + + +
Status Code{{ value }}
Status Message{{ value }}
Updated At{{ value }}
+
+
+ + +
+ library_books +
+ Parameters and Configuration +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Job Parameters{{ value | json }}
Configuration Version{{ value }}
Job Result Object{{ value | json }}
Datasets ValidationNo
Datasets ValidationYes
Is PublishedNo
Is PublishedYes
+
+
+ + diff --git a/src/app/jobs/jobs-detail/jobs-detail.component.scss b/src/app/jobs/jobs-detail/jobs-detail.component.scss index 2687093ee7..2d1be484f3 100644 --- a/src/app/jobs/jobs-detail/jobs-detail.component.scss +++ b/src/app/jobs/jobs-detail/jobs-detail.component.scss @@ -1,20 +1,25 @@ -.job-detail { - mat-card { - margin: 1em; +mat-card { + margin: 1em; - table { - td { - padding: 0.3em; - } + .section-icon { + height: auto !important; + width: auto !important; - th { - text-align: left; - padding-right: 0.5em; + mat-icon { + vertical-align: middle; + } + } + + table { + th { + min-width: 10rem; + padding-right: 0.5rem; + text-align: left; + } - mat-icon { - vertical-align: middle; - } - } + td { + width: 100%; + padding: 0.5rem 0; } } } diff --git a/src/app/shared/modules/shared-table/_shared-table-theme.scss b/src/app/shared/modules/shared-table/_shared-table-theme.scss index 4124c88a0c..735cd85127 100644 --- a/src/app/shared/modules/shared-table/_shared-table-theme.scss +++ b/src/app/shared/modules/shared-table/_shared-table-theme.scss @@ -31,6 +31,7 @@ mat-row:hover { background-color: mat.get-color-from-palette($hover, "lighter"); + cursor: pointer; } .mat-form-field-appearance-outline { From b546101c0c42fb315177c764dbdb613818825b6a Mon Sep 17 00:00:00 2001 From: Despina Adamopoulou <16343312+despadam@users.noreply.github.com> Date: Thu, 19 Sep 2024 16:21:54 +0200 Subject: [PATCH 019/245] Remote ingestor (#1586) * Prepare ingestor frontend minimal ui * Update API * static list of backends * Remove unused file path input field in ingestor component * Extend ui and adjust api calls * Change back to localhost:3000 instead of backend.localhost * Change back the files to original state * fix ingestor endpoint * fix sonarcube issues --------- Co-authored-by: David Wiessner --- .../app-header/app-header.component.html | 10 +- src/app/app-routing/app-routing.module.ts | 7 + .../ingestor.feature.module.ts | 8 + .../ingestor.routing.module.ts | 15 ++ .../ingestor-metadata-editor.component.html | 4 + .../ingestor-metadata-editor.component.scss | 3 + .../ingestor-metadata-editor.component.ts | 18 +++ src/app/ingestor/ingestor.module.ts | 33 ++++ .../ingestor/ingestor-api-endpoints.ts | 7 + .../ingestor/ingestor/ingestor.component.html | 105 ++++++++++++ .../ingestor/ingestor/ingestor.component.scss | 16 ++ .../ingestor/ingestor.component.spec.ts | 24 +++ .../ingestor/ingestor/ingestor.component.ts | 153 ++++++++++++++++++ 13 files changed, 402 insertions(+), 1 deletion(-) create mode 100644 src/app/app-routing/lazy/ingestor-routing/ingestor.feature.module.ts create mode 100644 src/app/app-routing/lazy/ingestor-routing/ingestor.routing.module.ts create mode 100644 src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.html create mode 100644 src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.scss create mode 100644 src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts create mode 100644 src/app/ingestor/ingestor.module.ts create mode 100644 src/app/ingestor/ingestor/ingestor-api-endpoints.ts create mode 100644 src/app/ingestor/ingestor/ingestor.component.html create mode 100644 src/app/ingestor/ingestor/ingestor.component.scss create mode 100644 src/app/ingestor/ingestor/ingestor.component.spec.ts create mode 100644 src/app/ingestor/ingestor/ingestor.component.ts diff --git a/src/app/_layout/app-header/app-header.component.html b/src/app/_layout/app-header/app-header.component.html index b844e86598..88baab4099 100644 --- a/src/app/_layout/app-header/app-header.component.html +++ b/src/app/_layout/app-header/app-header.component.html @@ -67,7 +67,15 @@

{{ status }}

- + + +
+ + cloud_upload + Ingestor (OpenEM) +
+
diff --git a/src/app/app-routing/app-routing.module.ts b/src/app/app-routing/app-routing.module.ts index 3310f2c053..2f95ca27e5 100644 --- a/src/app/app-routing/app-routing.module.ts +++ b/src/app/app-routing/app-routing.module.ts @@ -105,6 +105,13 @@ export const routes: Routes = [ (m) => m.HelpFeatureModule, ), }, + { + path: "ingestor", + loadChildren: () => + import("./lazy/ingestor-routing/ingestor.feature.module").then( + (m) => m.IngestorFeatureModule, + ), + }, { path: "logbooks", loadChildren: () => diff --git a/src/app/app-routing/lazy/ingestor-routing/ingestor.feature.module.ts b/src/app/app-routing/lazy/ingestor-routing/ingestor.feature.module.ts new file mode 100644 index 0000000000..8796c9c34e --- /dev/null +++ b/src/app/app-routing/lazy/ingestor-routing/ingestor.feature.module.ts @@ -0,0 +1,8 @@ +import { NgModule } from "@angular/core"; +import { IngestorRoutingModule } from "./ingestor.routing.module"; +import { IngestorModule } from "ingestor/ingestor.module"; + +@NgModule({ + imports: [IngestorModule, IngestorRoutingModule], +}) +export class IngestorFeatureModule {} diff --git a/src/app/app-routing/lazy/ingestor-routing/ingestor.routing.module.ts b/src/app/app-routing/lazy/ingestor-routing/ingestor.routing.module.ts new file mode 100644 index 0000000000..c1a5b047d5 --- /dev/null +++ b/src/app/app-routing/lazy/ingestor-routing/ingestor.routing.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; +import { IngestorComponent } from "ingestor/ingestor/ingestor.component"; + +const routes: Routes = [ + { + path: "", + component: IngestorComponent, + }, +]; +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class IngestorRoutingModule {} diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.html b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.html new file mode 100644 index 0000000000..2e2b11f4ef --- /dev/null +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.html @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.scss b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.scss new file mode 100644 index 0000000000..89fe8050d3 --- /dev/null +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.scss @@ -0,0 +1,3 @@ +.ingestor-metadata-editor { + width: 100%; +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts new file mode 100644 index 0000000000..19b7d76c4a --- /dev/null +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts @@ -0,0 +1,18 @@ +import { Component, EventEmitter, Output } from '@angular/core'; + +@Component({ + selector: 'app-metadata-editor', + templateUrl: './ingestor-metadata-editor.component.html', + styleUrls: ['./ingestor-metadata-editor.component.scss'] +}) +export class IngestorMetadataEditorComponent { + metadata: string = ''; + + // Optional: EventEmitter, um Änderungen an der Metadata zu melden + @Output() metadataChange = new EventEmitter(); + + onMetadataChange(newMetadata: string) { + this.metadata = newMetadata; + this.metadataChange.emit(this.metadata); + } +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor.module.ts b/src/app/ingestor/ingestor.module.ts new file mode 100644 index 0000000000..bd04008153 --- /dev/null +++ b/src/app/ingestor/ingestor.module.ts @@ -0,0 +1,33 @@ +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { IngestorComponent } from "./ingestor/ingestor.component"; +import { MatCardModule } from "@angular/material/card"; +import { RouterModule } from "@angular/router"; +import { IngestorMetadataEditorComponent } from "./ingestor-metadata-editor/ingestor-metadata-editor.component"; +import { MatButtonModule } from "@angular/material/button"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatInputModule } from "@angular/material/input"; +import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; +import { FormsModule } from "@angular/forms"; +import { MatListModule } from '@angular/material/list'; +import { MatIconModule } from '@angular/material/icon'; + +@NgModule({ + declarations: [ + IngestorComponent, + IngestorMetadataEditorComponent + ], + imports: [ + CommonModule, + MatCardModule, + FormsModule, + MatFormFieldModule, + MatInputModule, + MatButtonModule, + MatProgressSpinnerModule, + RouterModule, + MatListModule, + MatIconModule + ], +}) +export class IngestorModule { } diff --git a/src/app/ingestor/ingestor/ingestor-api-endpoints.ts b/src/app/ingestor/ingestor/ingestor-api-endpoints.ts new file mode 100644 index 0000000000..aa0ee1e755 --- /dev/null +++ b/src/app/ingestor/ingestor/ingestor-api-endpoints.ts @@ -0,0 +1,7 @@ +export const INGESTOR_API_ENDPOINTS_V1 = { + DATASET: "dataset", + TRANSFER: "transfer", + OTHER: { + VERSION: 'version', + }, +}; diff --git a/src/app/ingestor/ingestor/ingestor.component.html b/src/app/ingestor/ingestor/ingestor.component.html new file mode 100644 index 0000000000..a61b5ca5ba --- /dev/null +++ b/src/app/ingestor/ingestor/ingestor.component.html @@ -0,0 +1,105 @@ +

+ + + Ingestor-Connection + + +

+ +
+
+ +

No Backend connected

+ +

Please provide a valid Backend URL

+ + + + + +
+ + + + + + + Backend URL + {{ connectedFacilityBackend }} change + + + Connection Status + Connected + + + Version + {{ connectedFacilityBackendVersion }} + + + +
+ + +

+ +

+ + + Ingest Dataset + + +

+
+ +
+ +
+ + +

+ +

+ + + Return Value + + +

+
+
+

{{returnValue}}

+
+ + +

+ +

+ + + Error message + + + +

+

+
+ + +

\ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor.component.scss b/src/app/ingestor/ingestor/ingestor.component.scss new file mode 100644 index 0000000000..96cdeec1ba --- /dev/null +++ b/src/app/ingestor/ingestor/ingestor.component.scss @@ -0,0 +1,16 @@ +.ingestor-vertical-layout { + display: flex; + flex-direction: column; + gap: 1em; +} + +/* src/app/ingestor/ingestor.component.scss */ +.ingestor-mixed-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.ingestor-close-button { + margin-left: auto; +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor.component.spec.ts b/src/app/ingestor/ingestor/ingestor.component.spec.ts new file mode 100644 index 0000000000..52186b1077 --- /dev/null +++ b/src/app/ingestor/ingestor/ingestor.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { IngestorComponent } from './ingestor.component'; + +describe('IngestorComponent', () => { + let component: IngestorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ IngestorComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(IngestorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); \ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor.component.ts b/src/app/ingestor/ingestor/ingestor.component.ts new file mode 100644 index 0000000000..99cf89e0f0 --- /dev/null +++ b/src/app/ingestor/ingestor/ingestor.component.ts @@ -0,0 +1,153 @@ +import { Component, OnInit, ViewChild } from "@angular/core"; +import { AppConfigService, HelpMessages } from "app-config.service"; +import { HttpClient } from '@angular/common/http'; +import { IngestorMetadataEditorComponent } from '../ingestor-metadata-editor/ingestor-metadata-editor.component'; +import { ActivatedRoute, Router } from '@angular/router'; +import { INGESTOR_API_ENDPOINTS_V1 } from "./ingestor-api-endpoints"; + +@Component({ + selector: "ingestor", + templateUrl: "./ingestor.component.html", + styleUrls: ["./ingestor.component.scss"], +}) +export class IngestorComponent implements OnInit { + + @ViewChild(IngestorMetadataEditorComponent) metadataEditor: IngestorMetadataEditorComponent; + + appConfig = this.appConfigService.getConfig(); + facility: string | null = null; + ingestManual: string | null = null; + gettingStarted: string | null = null; + shoppingCartEnabled = false; + helpMessages: HelpMessages; + + filePath: string = ''; + loading: boolean = false; + forwardFacilityBackend: string = ''; + + connectedFacilityBackend: string = ''; + connectedFacilityBackendVersion: string = ''; + connectingToFacilityBackend: boolean = false; + lastUsedFacilityBackends: string[] = []; + + errorMessage: string = ''; + returnValue: string = ''; + + constructor(public appConfigService: AppConfigService, private http: HttpClient, private route: ActivatedRoute, private router: Router) { } + + ngOnInit() { + this.facility = this.appConfig.facility; + this.ingestManual = this.appConfig.ingestManual; + this.helpMessages = new HelpMessages( + this.appConfig.helpMessages?.gettingStarted, + this.appConfig.helpMessages?.ingestManual, + ); + this.gettingStarted = this.appConfig.gettingStarted; + this.connectingToFacilityBackend = true; + this.lastUsedFacilityBackends = this.loadLastUsedFacilityBackends(); + // Get the GET parameter 'backendUrl' from the URL + this.route.queryParams.subscribe(params => { + const backendUrl = params['backendUrl']; + if (backendUrl) { + this.connectToFacilityBackend(backendUrl); + } + else { + this.connectingToFacilityBackend = false; + } + }); + } + + connectToFacilityBackend(facilityBackendUrl: string): boolean { + let facilityBackendUrlCleaned = facilityBackendUrl.slice(); + // Check if last symbol is a slash and add version endpoint + if (!facilityBackendUrlCleaned.endsWith('/')) { + facilityBackendUrlCleaned += '/'; + } + + let facilityBackendUrlVersion = facilityBackendUrlCleaned + INGESTOR_API_ENDPOINTS_V1.OTHER.VERSION; + + // Try to connect to the facility backend/version to check if it is available + console.log('Connecting to facility backend: ' + facilityBackendUrlVersion); + this.http.get(facilityBackendUrlVersion).subscribe( + response => { + console.log('Connected to facility backend', response); + // If the connection is successful, store the connected facility backend URL + this.connectedFacilityBackend = facilityBackendUrlCleaned; + this.connectingToFacilityBackend = false; + this.connectedFacilityBackendVersion = response['version']; + }, + error => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}
`; + console.error('Request failed', error); + this.connectedFacilityBackend = ''; + this.connectingToFacilityBackend = false; + this.lastUsedFacilityBackends = this.loadLastUsedFacilityBackends(); + } + ); + + return true; + } + + upload() { + this.loading = true; + this.returnValue = ''; + const payload = { + filePath: this.filePath, + metaData: this.metadataEditor.metadata + }; + + console.log('Uploading', payload); + + this.http.post(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.DATASET, payload).subscribe( + response => { + console.log('Upload successful', response); + this.returnValue = JSON.stringify(response); + this.loading = false; + }, + error => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}]
`; + console.error('Upload failed', error); + this.loading = false; + } + ); + } + + forwardToIngestorPage() { + if (this.forwardFacilityBackend) { + this.connectingToFacilityBackend = true; + + // If current route is equal to the forward route, the router will not navigate to the new route + if (this.connectedFacilityBackend === this.forwardFacilityBackend) { + this.connectToFacilityBackend(this.forwardFacilityBackend); + return; + } + + this.router.navigate(['/ingestor'], { queryParams: { backendUrl: this.forwardFacilityBackend } }); + } + } + + disconnectIngestor() { + this.returnValue = ''; + this.connectedFacilityBackend = ''; + // Remove the GET parameter 'backendUrl' from the URL + this.router.navigate(['/ingestor']); + } + + // Helper functions + selectFacilityBackend(facilityBackend: string) { + this.forwardFacilityBackend = facilityBackend; + } + + loadLastUsedFacilityBackends(): string[] { + // Load the list from the local Storage + const lastUsedFacilityBackends = '["http://localhost:8000", "http://localhost:8888"]'; + if (lastUsedFacilityBackends) { + return JSON.parse(lastUsedFacilityBackends); + } + return []; + } + + clearErrorMessage(): void { + this.errorMessage = ''; + } +} \ No newline at end of file From fd0ed2ff046396e4804617b2eae77cc886f3f226 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Wed, 16 Oct 2024 09:25:42 +0200 Subject: [PATCH 020/245] EM export in the header --- .../app-header/app-header.component.html | 11 +++++ src/app/app-routing/app-routing.module.ts | 7 ++++ .../emexport.feature.module.ts | 8 ++++ .../emexport.routing.module.ts | 26 ++++++++++++ src/app/emexport/emexport.module.ts | 33 +++++++++++++++ .../emexport/emexport/emexport.component.html | 23 +++++++++++ .../emexport/emexport/emexport.component.scss | 35 ++++++++++++++++ .../emexport/emexport/emexport.component.ts | 41 +++++++++++++++++++ src/app/emexport/empiar/empiar.component.html | 4 ++ src/app/emexport/empiar/empiar.component.scss | 0 src/app/emexport/empiar/empiar.component.ts | 18 ++++++++ src/app/emexport/onedep/onedep.component.html | 4 ++ src/app/emexport/onedep/onedep.component.scss | 0 src/app/emexport/onedep/onedep.component.ts | 31 ++++++++++++++ 14 files changed, 241 insertions(+) create mode 100644 src/app/app-routing/lazy/emexport-routing/emexport.feature.module.ts create mode 100644 src/app/app-routing/lazy/emexport-routing/emexport.routing.module.ts create mode 100644 src/app/emexport/emexport.module.ts create mode 100644 src/app/emexport/emexport/emexport.component.html create mode 100644 src/app/emexport/emexport/emexport.component.scss create mode 100644 src/app/emexport/emexport/emexport.component.ts create mode 100644 src/app/emexport/empiar/empiar.component.html create mode 100644 src/app/emexport/empiar/empiar.component.scss create mode 100644 src/app/emexport/empiar/empiar.component.ts create mode 100644 src/app/emexport/onedep/onedep.component.html create mode 100644 src/app/emexport/onedep/onedep.component.scss create mode 100644 src/app/emexport/onedep/onedep.component.ts diff --git a/src/app/_layout/app-header/app-header.component.html b/src/app/_layout/app-header/app-header.component.html index 88baab4099..195563a3d4 100644 --- a/src/app/_layout/app-header/app-header.component.html +++ b/src/app/_layout/app-header/app-header.component.html @@ -76,6 +76,17 @@
>
+ + +
+ + assignment + Export EM data +
+
+ +
diff --git a/src/app/app-routing/app-routing.module.ts b/src/app/app-routing/app-routing.module.ts index 2f95ca27e5..85e9660f07 100644 --- a/src/app/app-routing/app-routing.module.ts +++ b/src/app/app-routing/app-routing.module.ts @@ -112,6 +112,13 @@ export const routes: Routes = [ (m) => m.IngestorFeatureModule, ), }, + { + path: "emexport", + loadChildren: () => + import("./lazy/emexport-routing/emexport.feature.module").then( + (m) => m.EmExportFeatureModule, + ), + }, { path: "logbooks", loadChildren: () => diff --git a/src/app/app-routing/lazy/emexport-routing/emexport.feature.module.ts b/src/app/app-routing/lazy/emexport-routing/emexport.feature.module.ts new file mode 100644 index 0000000000..194e47f547 --- /dev/null +++ b/src/app/app-routing/lazy/emexport-routing/emexport.feature.module.ts @@ -0,0 +1,8 @@ +import { NgModule } from "@angular/core"; +import { EmExportRoutingModule } from "./emexport.routing.module"; +import { EmExportModule } from "emexport/emexport.module"; + +@NgModule({ + imports: [EmExportModule, EmExportRoutingModule], +}) +export class EmExportFeatureModule {} diff --git a/src/app/app-routing/lazy/emexport-routing/emexport.routing.module.ts b/src/app/app-routing/lazy/emexport-routing/emexport.routing.module.ts new file mode 100644 index 0000000000..6b4116ea0b --- /dev/null +++ b/src/app/app-routing/lazy/emexport-routing/emexport.routing.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { OneDepComponent } from 'emexport/onedep/onedep.component'; +import { EmpiarComponent } from 'emexport/empiar/empiar.component'; +import { EmExportComponent } from "emexport/emexport/emexport.component"; + +const routes: Routes = [ + { + path: "", + component: EmExportComponent, + }, + { + path: "onedep", + component: OneDepComponent, + }, + { + path: "empiar", + component: EmpiarComponent, + }, +]; +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class EmExportRoutingModule {} diff --git a/src/app/emexport/emexport.module.ts b/src/app/emexport/emexport.module.ts new file mode 100644 index 0000000000..d54b86fe85 --- /dev/null +++ b/src/app/emexport/emexport.module.ts @@ -0,0 +1,33 @@ +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { MatCardModule } from "@angular/material/card"; +import { RouterModule } from "@angular/router"; +import { MatButtonModule } from "@angular/material/button"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatInputModule } from "@angular/material/input"; +import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; +import { FormsModule } from "@angular/forms"; +import { MatListModule } from '@angular/material/list'; +import { MatIconModule } from '@angular/material/icon'; +import { EmExportComponent } from "./emexport/emexport.component"; +import { OneDepComponent } from "./onedep/onedep.component" + +@NgModule({ + declarations: [ + EmExportComponent, + OneDepComponent + ], + imports: [ + CommonModule, + MatCardModule, + FormsModule, + MatFormFieldModule, + MatInputModule, + MatButtonModule, + MatProgressSpinnerModule, + RouterModule, + MatListModule, + MatIconModule + ], +}) +export class EmExportModule { } diff --git a/src/app/emexport/emexport/emexport.component.html b/src/app/emexport/emexport/emexport.component.html new file mode 100644 index 0000000000..6bdd48eeac --- /dev/null +++ b/src/app/emexport/emexport/emexport.component.html @@ -0,0 +1,23 @@ +
+ + + + OneDep deposition to PDB or EMDB + + + + + + + + + + + EMPIAR deposition of raw micrograps + + + + + + +
\ No newline at end of file diff --git a/src/app/emexport/emexport/emexport.component.scss b/src/app/emexport/emexport/emexport.component.scss new file mode 100644 index 0000000000..c815327512 --- /dev/null +++ b/src/app/emexport/emexport/emexport.component.scss @@ -0,0 +1,35 @@ +.container { + display: flex; + justify-content: space-between; /* Distributes space between the cards */ + gap: 2em; /* Space between columns */ + } + + .onedep-card, .empiar-card{ + flex: 1; /* Makes both cards take equal width */ + width:30%; + min-width: 300px; + margin-top:20px; + margin-left:30px; + margin-right:30px; + + } + .centered-header { + display: flex; /* Use flexbox for centering */ + justify-content: center; /* Center content horizontally */ + width: 100%; /* Full width to center */ + } + + .exmexport-vertical-layout { + display: flex; + flex-direction: column; + align-items: center; /* Center elements horizontally */ + gap: 1em; + } + + .centered-content { + display: flex; /* Flexbox for centering */ + flex-direction: column; /* Align items in a column */ + align-items: center; /* Center items horizontally */ + justify-content: center; /* Center items vertically */ + } + \ No newline at end of file diff --git a/src/app/emexport/emexport/emexport.component.ts b/src/app/emexport/emexport/emexport.component.ts new file mode 100644 index 0000000000..ea5afea2c9 --- /dev/null +++ b/src/app/emexport/emexport/emexport.component.ts @@ -0,0 +1,41 @@ +import { Component, OnInit, ViewChild } from "@angular/core"; +import { AppConfigService, HelpMessages } from "app-config.service"; +import { HttpClient } from '@angular/common/http'; +import { ActivatedRoute, Router } from '@angular/router'; +// import { OneDepComponent } from '../onedep/onedep.component'; + + +@Component({ + selector: "emexport", + templateUrl: "./emexport.component.html", + styleUrls: ["./emexport.component.scss"], +}) +export class EmExportComponent implements OnInit { + + // @ViewChild(OneDepComponent) metadataEditor: OneDepComponent; + + appConfig = this.appConfigService.getConfig(); + facility: string | null = null; + ingestManual: string | null = null; + gettingStarted: string | null = null; + shoppingCartEnabled = false; + helpMessages: HelpMessages; + + + constructor(public appConfigService: AppConfigService, private http: HttpClient, private route: ActivatedRoute, private router: Router) { } + + ngOnInit() { + this.facility = this.appConfig.facility; + this.helpMessages = new HelpMessages( + this.appConfig.helpMessages?.gettingStarted, + this.appConfig.helpMessages?.ingestManual, + ); + this.gettingStarted = this.appConfig.gettingStarted; + } + goToOneDep(){ + this.router.navigateByUrl("/emexport/onedep"); + } + goToEMPIAR(){ + this.router.navigateByUrl("/emexport/empiar"); + } +} \ No newline at end of file diff --git a/src/app/emexport/empiar/empiar.component.html b/src/app/emexport/empiar/empiar.component.html new file mode 100644 index 0000000000..7fe6bedc3a --- /dev/null +++ b/src/app/emexport/empiar/empiar.component.html @@ -0,0 +1,4 @@ + +
+

EMPIAR here!

+
\ No newline at end of file diff --git a/src/app/emexport/empiar/empiar.component.scss b/src/app/emexport/empiar/empiar.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/emexport/empiar/empiar.component.ts b/src/app/emexport/empiar/empiar.component.ts new file mode 100644 index 0000000000..231735c2d9 --- /dev/null +++ b/src/app/emexport/empiar/empiar.component.ts @@ -0,0 +1,18 @@ +import { Component, OnInit, ViewChild } from "@angular/core"; +import { AppConfigService, HelpMessages } from "app-config.service"; +import { HttpClient } from '@angular/common/http'; +import { ActivatedRoute, Router } from '@angular/router'; + +@Component({ + selector: 'oneempiardep', + templateUrl: './empiar.component.html', + styleUrls: ['./empiar.component.scss'] +}) +export class EmpiarComponent implements OnInit { + + empiar : boolean; + + ngOnInit() { + this.empiar = true; + } +} \ No newline at end of file diff --git a/src/app/emexport/onedep/onedep.component.html b/src/app/emexport/onedep/onedep.component.html new file mode 100644 index 0000000000..0fcef09a33 --- /dev/null +++ b/src/app/emexport/onedep/onedep.component.html @@ -0,0 +1,4 @@ + +
+

One Dep here!

+
\ No newline at end of file diff --git a/src/app/emexport/onedep/onedep.component.scss b/src/app/emexport/onedep/onedep.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/emexport/onedep/onedep.component.ts b/src/app/emexport/onedep/onedep.component.ts new file mode 100644 index 0000000000..053355af00 --- /dev/null +++ b/src/app/emexport/onedep/onedep.component.ts @@ -0,0 +1,31 @@ +import { Component, OnInit, ViewChild } from "@angular/core"; +import { AppConfigService, HelpMessages } from "app-config.service"; +import { HttpClient } from '@angular/common/http'; +import { ActivatedRoute, Router } from '@angular/router'; + +@Component({ + selector: 'onedep', + templateUrl: './onedep.component.html', + styleUrls: ['./onedep.component.scss'] +}) +export class OneDepComponent implements OnInit { + + appConfig = this.appConfigService.getConfig(); + facility: string | null = null; + ingestManual: string | null = null; + gettingStarted: string | null = null; + shoppingCartEnabled = false; + helpMessages: HelpMessages; + + + constructor(public appConfigService: AppConfigService, private http: HttpClient, private route: ActivatedRoute, private router: Router) { } + + ngOnInit() { + this.facility = this.appConfig.facility; + this.helpMessages = new HelpMessages( + this.appConfig.helpMessages?.gettingStarted, + this.appConfig.helpMessages?.ingestManual, + ); + this.gettingStarted = this.appConfig.gettingStarted; + } +} \ No newline at end of file From 97ce726b4aa0d30ebb20382e92407d7d9c01a06b Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Wed, 16 Oct 2024 17:09:54 +0200 Subject: [PATCH 021/245] OneDep from dataset --- .../app-header/app-header.component.html | 10 --- src/app/app-routing/app-routing.module.ts | 7 -- .../datasets.routing.module.ts | 5 ++ .../emexport.feature.module.ts | 8 -- .../emexport.routing.module.ts | 26 ------ .../dataset-detail.component.html | 18 ++++ .../dataset-detail.component.scss | 4 + .../dataset-detail.component.ts | 1 + src/app/datasets/datasets.module.ts | 2 + src/app/datasets/onedep/onedep.component.html | 75 ++++++++++++++++ src/app/datasets/onedep/onedep.component.scss | 39 ++++++++ src/app/datasets/onedep/onedep.component.ts | 89 +++++++++++++++++++ src/app/emexport/emexport.module.ts | 33 ------- .../emexport/emexport/emexport.component.html | 23 ----- .../emexport/emexport/emexport.component.scss | 35 -------- .../emexport/emexport/emexport.component.ts | 41 --------- src/app/emexport/onedep/onedep.component.html | 4 - src/app/emexport/onedep/onedep.component.scss | 0 src/app/emexport/onedep/onedep.component.ts | 31 ------- .../empiar/empiar.component.html | 0 .../empiar/empiar.component.scss | 0 .../{emexport => }/empiar/empiar.component.ts | 2 +- 22 files changed, 234 insertions(+), 219 deletions(-) delete mode 100644 src/app/app-routing/lazy/emexport-routing/emexport.feature.module.ts delete mode 100644 src/app/app-routing/lazy/emexport-routing/emexport.routing.module.ts create mode 100644 src/app/datasets/onedep/onedep.component.html create mode 100644 src/app/datasets/onedep/onedep.component.scss create mode 100644 src/app/datasets/onedep/onedep.component.ts delete mode 100644 src/app/emexport/emexport.module.ts delete mode 100644 src/app/emexport/emexport/emexport.component.html delete mode 100644 src/app/emexport/emexport/emexport.component.scss delete mode 100644 src/app/emexport/emexport/emexport.component.ts delete mode 100644 src/app/emexport/onedep/onedep.component.html delete mode 100644 src/app/emexport/onedep/onedep.component.scss delete mode 100644 src/app/emexport/onedep/onedep.component.ts rename src/app/{emexport => }/empiar/empiar.component.html (100%) rename src/app/{emexport => }/empiar/empiar.component.scss (100%) rename src/app/{emexport => }/empiar/empiar.component.ts (94%) diff --git a/src/app/_layout/app-header/app-header.component.html b/src/app/_layout/app-header/app-header.component.html index 195563a3d4..dced69ad42 100644 --- a/src/app/_layout/app-header/app-header.component.html +++ b/src/app/_layout/app-header/app-header.component.html @@ -76,16 +76,6 @@
>
- - -
- - assignment - Export EM data -
- -
diff --git a/src/app/app-routing/app-routing.module.ts b/src/app/app-routing/app-routing.module.ts index 85e9660f07..2f95ca27e5 100644 --- a/src/app/app-routing/app-routing.module.ts +++ b/src/app/app-routing/app-routing.module.ts @@ -112,13 +112,6 @@ export const routes: Routes = [ (m) => m.IngestorFeatureModule, ), }, - { - path: "emexport", - loadChildren: () => - import("./lazy/emexport-routing/emexport.feature.module").then( - (m) => m.EmExportFeatureModule, - ), - }, { path: "logbooks", loadChildren: () => 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 d1637dbd59..14feaf6945 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 @@ -6,6 +6,7 @@ import { DashboardComponent } from "datasets/dashboard/dashboard.component"; import { DatablocksComponent } from "datasets/datablocks-table/datablocks-table.component"; import { DatasetDetailsDashboardComponent } from "datasets/dataset-details-dashboard/dataset-details-dashboard.component"; import { PublishComponent } from "datasets/publish/publish.component"; +import { OneDepComponent } from "datasets/onedep/onedep.component"; const routes: Routes = [ { @@ -35,6 +36,10 @@ const routes: Routes = [ component: DatablocksComponent, canActivate: [AuthGuard], }, + { + path: ":id/onedep", + component: OneDepComponent, + }, ]; @NgModule({ imports: [RouterModule.forChild(routes)], diff --git a/src/app/app-routing/lazy/emexport-routing/emexport.feature.module.ts b/src/app/app-routing/lazy/emexport-routing/emexport.feature.module.ts deleted file mode 100644 index 194e47f547..0000000000 --- a/src/app/app-routing/lazy/emexport-routing/emexport.feature.module.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { NgModule } from "@angular/core"; -import { EmExportRoutingModule } from "./emexport.routing.module"; -import { EmExportModule } from "emexport/emexport.module"; - -@NgModule({ - imports: [EmExportModule, EmExportRoutingModule], -}) -export class EmExportFeatureModule {} diff --git a/src/app/app-routing/lazy/emexport-routing/emexport.routing.module.ts b/src/app/app-routing/lazy/emexport-routing/emexport.routing.module.ts deleted file mode 100644 index 6b4116ea0b..0000000000 --- a/src/app/app-routing/lazy/emexport-routing/emexport.routing.module.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { NgModule } from "@angular/core"; -import { RouterModule, Routes } from "@angular/router"; - -import { OneDepComponent } from 'emexport/onedep/onedep.component'; -import { EmpiarComponent } from 'emexport/empiar/empiar.component'; -import { EmExportComponent } from "emexport/emexport/emexport.component"; - -const routes: Routes = [ - { - path: "", - component: EmExportComponent, - }, - { - path: "onedep", - component: OneDepComponent, - }, - { - path: "empiar", - component: EmpiarComponent, - }, -]; -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule], -}) -export class EmExportRoutingModule {} diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.html b/src/app/datasets/dataset-detail/dataset-detail.component.html index d049b4fe82..f9c575b4f7 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.html +++ b/src/app/datasets/dataset-detail/dataset-detail.component.html @@ -21,7 +21,25 @@ Public + + +
diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.scss b/src/app/datasets/dataset-detail/dataset-detail.component.scss index 14d17981ed..5eb33c71ce 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.scss +++ b/src/app/datasets/dataset-detail/dataset-detail.component.scss @@ -51,6 +51,10 @@ mat-card { .jupyter-button { margin: 1em 0 0 1em; } +.emexport-button { + margin: 1em 0 0 3em; + color: hsla(185, 43%, 45%, 0.458); +} .public-toggle { margin: 1em 1em 0 0; float: right; diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.ts b/src/app/datasets/dataset-detail/dataset-detail.component.ts index c13db28b0f..f343936679 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.ts +++ b/src/app/datasets/dataset-detail/dataset-detail.component.ts @@ -314,4 +314,5 @@ export class DatasetDetailComponent openAttachment(encoded: string) { this.attachmentService.openAttachment(encoded); } + } diff --git a/src/app/datasets/datasets.module.ts b/src/app/datasets/datasets.module.ts index f9092cc4b2..8f8d905a14 100644 --- a/src/app/datasets/datasets.module.ts +++ b/src/app/datasets/datasets.module.ts @@ -90,6 +90,7 @@ import { CdkDrag, CdkDragHandle, CdkDropList } from "@angular/cdk/drag-drop"; import { FiltersModule } from "shared/modules/filters/filters.module"; import { userReducer } from "state-management/reducers/user.reducer"; import { MatSnackBarModule } from "@angular/material/snack-bar"; +import { OneDepComponent } from "./onedep/onedep.component"; @NgModule({ imports: [ @@ -179,6 +180,7 @@ import { MatSnackBarModule } from "@angular/material/snack-bar"; DatafilesActionsComponent, DatafilesActionComponent, DatasetsFilterSettingsComponent, + OneDepComponent, ], providers: [ ArchivingService, diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html new file mode 100644 index 0000000000..346cccafb6 --- /dev/null +++ b/src/app/datasets/onedep/onedep.component.html @@ -0,0 +1,75 @@ +

OneDep Component is working!

+ +
+
+
+ + +
+ assignment +
+ Begin deposition +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Name{{ dataset.datasetName || "-" }} + + Name + + +
Description + + + + Description + + +
PID + {{ dataset.pid }} +
Type{{ value }}
Creation Time{{ value | date: "yyyy-MM-dd HH:mm" }}
Keywords + + + {{ keyword }} + + +
+
+
+
+ +
+ + + + {{ da.caption }} + + +
+
+
diff --git a/src/app/datasets/onedep/onedep.component.scss b/src/app/datasets/onedep/onedep.component.scss new file mode 100644 index 0000000000..b862d7d649 --- /dev/null +++ b/src/app/datasets/onedep/onedep.component.scss @@ -0,0 +1,39 @@ +mat-card { + margin: 1em; + + .section-icon { + height: auto !important; + width: auto !important; + + mat-icon { + vertical-align: middle; + } + } + + table { + th { + min-width: 10rem; + padding-right: 0.5rem; + text-align: left; + } + + td { + width: 100%; + padding: 0.5rem 0; + + .sample-edit { + padding-left: 0.5rem; + + mat-icon { + font-size: medium; + } + } + + } + + .full-width { + width: 100%; + } + } + } + \ No newline at end of file diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts new file mode 100644 index 0000000000..93ed41e1d2 --- /dev/null +++ b/src/app/datasets/onedep/onedep.component.ts @@ -0,0 +1,89 @@ +import { Component, OnInit, ViewChild } from "@angular/core"; +import { AppConfigService, HelpMessages } from "app-config.service"; +import { HttpClient } from '@angular/common/http'; +import { ActivatedRoute, Router } from '@angular/router'; +import { + FormArray, + FormBuilder, + FormControl, + FormGroup, + Validators, +} from "@angular/forms"; +import { Store } from "@ngrx/store"; +import { Dataset } from "shared/sdk/models"; +import { + selectCurrentAttachments, + selectCurrentDataset, + selectCurrentDatasetWithoutFileInfo, +} from "state-management/selectors/datasets.selectors"; + +import { + selectCurrentUser, + selectIsAdmin, + selectIsLoading, + selectProfile, +} from "state-management/selectors/user.selectors"; + +import { combineLatest, fromEvent, Observable, Subscription } from "rxjs"; + +import { map } from "rxjs/operators"; + +@Component({ + selector: 'onedep', + templateUrl: './onedep.component.html', + styleUrls: ['./onedep.component.scss'] +}) +export class OneDepComponent implements OnInit { + + appConfig = this.appConfigService.getConfig(); + facility: string | null = null; + ingestManual: string | null = null; + gettingStarted: string | null = null; + shoppingCartEnabled = false; + helpMessages: HelpMessages; + editingAllowed = false; + editEnabled = false; + + dataset: Dataset | undefined; + form: FormGroup; + attachments$ = this.store.select(selectCurrentAttachments); + datasetWithout$ = this.store.select(selectCurrentDatasetWithoutFileInfo); + userProfile$ = this.store.select(selectProfile); + isAdmin$ = this.store.select(selectIsAdmin); + accessGroups$: Observable = this.userProfile$.pipe( + map((profile) => (profile ? profile.accessGroups : [])), + ); + private subscriptions: Subscription[] = []; + + constructor(public appConfigService: AppConfigService, + private http: HttpClient, + private route: ActivatedRoute, + private router: Router, + private store: Store, + private fb: FormBuilder) { } + + + ngOnInit() { + this.form = this.fb.group({ + datasetName: new FormControl("", [Validators.required]), + description: new FormControl("", [Validators.required]), + keywords: this.fb.array([]), + }); + + this.subscriptions.push( + this.store.select(selectCurrentDataset).subscribe((dataset) => { + this.dataset = dataset; + console.log(dataset); + if (this.dataset) { + combineLatest([this.accessGroups$, this.isAdmin$]).subscribe( + ([groups, isAdmin]) => { + this.editingAllowed = + groups.indexOf(this.dataset.ownerGroup) !== -1 || isAdmin; + }, + ); + } + }), + ); + } + +} \ No newline at end of file diff --git a/src/app/emexport/emexport.module.ts b/src/app/emexport/emexport.module.ts deleted file mode 100644 index d54b86fe85..0000000000 --- a/src/app/emexport/emexport.module.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { NgModule } from "@angular/core"; -import { CommonModule } from "@angular/common"; -import { MatCardModule } from "@angular/material/card"; -import { RouterModule } from "@angular/router"; -import { MatButtonModule } from "@angular/material/button"; -import { MatFormFieldModule } from "@angular/material/form-field"; -import { MatInputModule } from "@angular/material/input"; -import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; -import { FormsModule } from "@angular/forms"; -import { MatListModule } from '@angular/material/list'; -import { MatIconModule } from '@angular/material/icon'; -import { EmExportComponent } from "./emexport/emexport.component"; -import { OneDepComponent } from "./onedep/onedep.component" - -@NgModule({ - declarations: [ - EmExportComponent, - OneDepComponent - ], - imports: [ - CommonModule, - MatCardModule, - FormsModule, - MatFormFieldModule, - MatInputModule, - MatButtonModule, - MatProgressSpinnerModule, - RouterModule, - MatListModule, - MatIconModule - ], -}) -export class EmExportModule { } diff --git a/src/app/emexport/emexport/emexport.component.html b/src/app/emexport/emexport/emexport.component.html deleted file mode 100644 index 6bdd48eeac..0000000000 --- a/src/app/emexport/emexport/emexport.component.html +++ /dev/null @@ -1,23 +0,0 @@ -
- - - - OneDep deposition to PDB or EMDB - - - - - - - - - - - EMPIAR deposition of raw micrograps - - - - - - -
\ No newline at end of file diff --git a/src/app/emexport/emexport/emexport.component.scss b/src/app/emexport/emexport/emexport.component.scss deleted file mode 100644 index c815327512..0000000000 --- a/src/app/emexport/emexport/emexport.component.scss +++ /dev/null @@ -1,35 +0,0 @@ -.container { - display: flex; - justify-content: space-between; /* Distributes space between the cards */ - gap: 2em; /* Space between columns */ - } - - .onedep-card, .empiar-card{ - flex: 1; /* Makes both cards take equal width */ - width:30%; - min-width: 300px; - margin-top:20px; - margin-left:30px; - margin-right:30px; - - } - .centered-header { - display: flex; /* Use flexbox for centering */ - justify-content: center; /* Center content horizontally */ - width: 100%; /* Full width to center */ - } - - .exmexport-vertical-layout { - display: flex; - flex-direction: column; - align-items: center; /* Center elements horizontally */ - gap: 1em; - } - - .centered-content { - display: flex; /* Flexbox for centering */ - flex-direction: column; /* Align items in a column */ - align-items: center; /* Center items horizontally */ - justify-content: center; /* Center items vertically */ - } - \ No newline at end of file diff --git a/src/app/emexport/emexport/emexport.component.ts b/src/app/emexport/emexport/emexport.component.ts deleted file mode 100644 index ea5afea2c9..0000000000 --- a/src/app/emexport/emexport/emexport.component.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Component, OnInit, ViewChild } from "@angular/core"; -import { AppConfigService, HelpMessages } from "app-config.service"; -import { HttpClient } from '@angular/common/http'; -import { ActivatedRoute, Router } from '@angular/router'; -// import { OneDepComponent } from '../onedep/onedep.component'; - - -@Component({ - selector: "emexport", - templateUrl: "./emexport.component.html", - styleUrls: ["./emexport.component.scss"], -}) -export class EmExportComponent implements OnInit { - - // @ViewChild(OneDepComponent) metadataEditor: OneDepComponent; - - appConfig = this.appConfigService.getConfig(); - facility: string | null = null; - ingestManual: string | null = null; - gettingStarted: string | null = null; - shoppingCartEnabled = false; - helpMessages: HelpMessages; - - - constructor(public appConfigService: AppConfigService, private http: HttpClient, private route: ActivatedRoute, private router: Router) { } - - ngOnInit() { - this.facility = this.appConfig.facility; - this.helpMessages = new HelpMessages( - this.appConfig.helpMessages?.gettingStarted, - this.appConfig.helpMessages?.ingestManual, - ); - this.gettingStarted = this.appConfig.gettingStarted; - } - goToOneDep(){ - this.router.navigateByUrl("/emexport/onedep"); - } - goToEMPIAR(){ - this.router.navigateByUrl("/emexport/empiar"); - } -} \ No newline at end of file diff --git a/src/app/emexport/onedep/onedep.component.html b/src/app/emexport/onedep/onedep.component.html deleted file mode 100644 index 0fcef09a33..0000000000 --- a/src/app/emexport/onedep/onedep.component.html +++ /dev/null @@ -1,4 +0,0 @@ - -
-

One Dep here!

-
\ No newline at end of file diff --git a/src/app/emexport/onedep/onedep.component.scss b/src/app/emexport/onedep/onedep.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/app/emexport/onedep/onedep.component.ts b/src/app/emexport/onedep/onedep.component.ts deleted file mode 100644 index 053355af00..0000000000 --- a/src/app/emexport/onedep/onedep.component.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Component, OnInit, ViewChild } from "@angular/core"; -import { AppConfigService, HelpMessages } from "app-config.service"; -import { HttpClient } from '@angular/common/http'; -import { ActivatedRoute, Router } from '@angular/router'; - -@Component({ - selector: 'onedep', - templateUrl: './onedep.component.html', - styleUrls: ['./onedep.component.scss'] -}) -export class OneDepComponent implements OnInit { - - appConfig = this.appConfigService.getConfig(); - facility: string | null = null; - ingestManual: string | null = null; - gettingStarted: string | null = null; - shoppingCartEnabled = false; - helpMessages: HelpMessages; - - - constructor(public appConfigService: AppConfigService, private http: HttpClient, private route: ActivatedRoute, private router: Router) { } - - ngOnInit() { - this.facility = this.appConfig.facility; - this.helpMessages = new HelpMessages( - this.appConfig.helpMessages?.gettingStarted, - this.appConfig.helpMessages?.ingestManual, - ); - this.gettingStarted = this.appConfig.gettingStarted; - } -} \ No newline at end of file diff --git a/src/app/emexport/empiar/empiar.component.html b/src/app/empiar/empiar.component.html similarity index 100% rename from src/app/emexport/empiar/empiar.component.html rename to src/app/empiar/empiar.component.html diff --git a/src/app/emexport/empiar/empiar.component.scss b/src/app/empiar/empiar.component.scss similarity index 100% rename from src/app/emexport/empiar/empiar.component.scss rename to src/app/empiar/empiar.component.scss diff --git a/src/app/emexport/empiar/empiar.component.ts b/src/app/empiar/empiar.component.ts similarity index 94% rename from src/app/emexport/empiar/empiar.component.ts rename to src/app/empiar/empiar.component.ts index 231735c2d9..ae9b3eba27 100644 --- a/src/app/emexport/empiar/empiar.component.ts +++ b/src/app/empiar/empiar.component.ts @@ -4,7 +4,7 @@ import { HttpClient } from '@angular/common/http'; import { ActivatedRoute, Router } from '@angular/router'; @Component({ - selector: 'oneempiardep', + selector: 'empiar', templateUrl: './empiar.component.html', styleUrls: ['./empiar.component.scss'] }) From e29f3df2399b3eacb1c70dae1b0fdba99c516cb0 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Thu, 17 Oct 2024 17:56:02 +0200 Subject: [PATCH 022/245] OneDep component recieves dataset - turned off cleaning in dataset-details dashboard onDestroy --- .../dataset-detail.component.html | 31 +++++--- .../dataset-detail.component.scss | 2 +- .../dataset-detail.component.ts | 4 ++ .../dataset-details-dashboard.component.ts | 2 +- src/app/datasets/onedep/onedep.component.html | 55 +-------------- src/app/datasets/onedep/onedep.component.ts | 70 ++++++------------- 6 files changed, 50 insertions(+), 114 deletions(-) diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.html b/src/app/datasets/dataset-detail/dataset-detail.component.html index f9c575b4f7..ba5b6e7f24 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.html +++ b/src/app/datasets/dataset-detail/dataset-detail.component.html @@ -12,23 +12,23 @@ Jupyter Hub
-
- - Public - -
- + --> + +
+ + Public + +
diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.scss b/src/app/datasets/dataset-detail/dataset-detail.component.scss index 5eb33c71ce..efb4f1bdce 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.scss +++ b/src/app/datasets/dataset-detail/dataset-detail.component.scss @@ -53,7 +53,7 @@ mat-card { } .emexport-button { margin: 1em 0 0 3em; - color: hsla(185, 43%, 45%, 0.458); + background-color: hsla(185, 43%, 45%, 0.458); } .public-toggle { margin: 1em 1em 0 0; diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.ts b/src/app/datasets/dataset-detail/dataset-detail.component.ts index f343936679..c223b518be 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.ts +++ b/src/app/datasets/dataset-detail/dataset-detail.component.ts @@ -25,6 +25,7 @@ import { addKeywordFilterAction, clearFacetsAction, updatePropertyAction, + selectDatasetAction, } from "state-management/actions/datasets.actions"; import { Router } from "@angular/router"; import { selectCurrentProposal } from "state-management/selectors/proposals.selectors"; @@ -88,6 +89,9 @@ export class DatasetDetailComponent editingAllowed = false; editEnabled = false; show = false; + + @Output() emClick = new EventEmitter(); + readonly separatorKeyCodes: number[] = [ENTER, COMMA, SPACE]; constructor( diff --git a/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.ts b/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.ts index aa2b8dcecb..c857094866 100644 --- a/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.ts +++ b/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.ts @@ -289,7 +289,7 @@ export class DatasetDetailsDashboardComponent } ngOnDestroy() { - this.store.dispatch(clearCurrentDatasetStateAction()); + //this.store.dispatch(clearCurrentDatasetStateAction()); this.store.dispatch(clearCurrentProposalStateAction()); this.store.dispatch(clearCurrentSampleStateAction()); this.subscriptions.forEach((subscription) => { diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index 346cccafb6..822153b384 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -10,66 +10,17 @@ Begin deposition - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Name{{ dataset.datasetName || "-" }} - - Name - - -
Description - - - - Description - - -
PID - {{ dataset.pid }} -
Type{{ value }}
Creation Time{{ value | date: "yyyy-MM-dd HH:mm" }}
Keywords - - - {{ keyword }} - - -
-
+ -
+
diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index 93ed41e1d2..b1f0a3e4e7 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -1,9 +1,8 @@ -import { Component, OnInit, ViewChild } from "@angular/core"; +import { Component, OnInit } from "@angular/core"; import { AppConfigService, HelpMessages } from "app-config.service"; import { HttpClient } from '@angular/common/http'; import { ActivatedRoute, Router } from '@angular/router'; import { - FormArray, FormBuilder, FormControl, FormGroup, @@ -12,21 +11,12 @@ import { import { Store } from "@ngrx/store"; import { Dataset } from "shared/sdk/models"; import { - selectCurrentAttachments, + selectCurrentDataset, - selectCurrentDatasetWithoutFileInfo, } from "state-management/selectors/datasets.selectors"; -import { - selectCurrentUser, - selectIsAdmin, - selectIsLoading, - selectProfile, -} from "state-management/selectors/user.selectors"; - -import { combineLatest, fromEvent, Observable, Subscription } from "rxjs"; +import { Subscription } from "rxjs"; -import { map } from "rxjs/operators"; @Component({ selector: 'onedep', @@ -36,54 +26,36 @@ import { map } from "rxjs/operators"; export class OneDepComponent implements OnInit { appConfig = this.appConfigService.getConfig(); - facility: string | null = null; - ingestManual: string | null = null; - gettingStarted: string | null = null; - shoppingCartEnabled = false; - helpMessages: HelpMessages; - editingAllowed = false; - editEnabled = false; - dataset: Dataset | undefined; + cd$ = this.store.select(selectCurrentDataset); form: FormGroup; - attachments$ = this.store.select(selectCurrentAttachments); - datasetWithout$ = this.store.select(selectCurrentDatasetWithoutFileInfo); - userProfile$ = this.store.select(selectProfile); - isAdmin$ = this.store.select(selectIsAdmin); - accessGroups$: Observable = this.userProfile$.pipe( - map((profile) => (profile ? profile.accessGroups : [])), - ); + //attachments$ = this.store.select(selectCurrentAttachments); + // datasetWithout$ = this.store.select(selectCurrentDatasetWithoutFileInfo); + // userProfile$ = this.store.select(selectProfile); + // isAdmin$ = this.store.select(selectIsAdmin); + // accessGroups$: Observable = this.userProfile$.pipe( + // map((profile) => (profile ? profile.accessGroups : [])), + // ); private subscriptions: Subscription[] = []; constructor(public appConfigService: AppConfigService, - private http: HttpClient, - private route: ActivatedRoute, - private router: Router, private store: Store, - private fb: FormBuilder) { } + // private http: HttpClient, + // private route: ActivatedRoute, + // private router: Router, + private fb: FormBuilder + ) { } ngOnInit() { + console.log('init OneDep') this.form = this.fb.group({ datasetName: new FormControl("", [Validators.required]), description: new FormControl("", [Validators.required]), keywords: this.fb.array([]), }); - - this.subscriptions.push( - this.store.select(selectCurrentDataset).subscribe((dataset) => { - this.dataset = dataset; - console.log(dataset); - if (this.dataset) { - combineLatest([this.accessGroups$, this.isAdmin$]).subscribe( - ([groups, isAdmin]) => { - this.editingAllowed = - groups.indexOf(this.dataset.ownerGroup) !== -1 || isAdmin; - }, - ); - } - }), - ); + this.store.select(selectCurrentDataset).subscribe((dataset) => { + this.dataset = dataset; + }); } - -} \ No newline at end of file +} From 7dc30a3c9f72eac390558a8f4692eabfabbcdad3 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Mon, 21 Oct 2024 10:56:04 +0200 Subject: [PATCH 023/245] choose em method --- .../dataset-detail.component.html | 15 +- .../dataset-detail.component.scss | 5 +- src/app/datasets/onedep/onedep.component.html | 225 ++++++++++++++++-- src/app/datasets/onedep/onedep.component.ts | 43 ++-- 4 files changed, 244 insertions(+), 44 deletions(-) diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.html b/src/app/datasets/dataset-detail/dataset-detail.component.html index ba5b6e7f24..dd98f6e24f 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.html +++ b/src/app/datasets/dataset-detail/dataset-detail.component.html @@ -12,27 +12,18 @@ Jupyter Hub
- - +
EMPIAR diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.scss b/src/app/datasets/dataset-detail/dataset-detail.component.scss index efb4f1bdce..4ceac42898 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.scss +++ b/src/app/datasets/dataset-detail/dataset-detail.component.scss @@ -52,8 +52,9 @@ mat-card { margin: 1em 0 0 1em; } .emexport-button { - margin: 1em 0 0 3em; - background-color: hsla(185, 43%, 45%, 0.458); + margin: 1em 0 0 1em; + background-color: hsla(185, 43%, 45%, 0.858); + color: white; } .public-toggle { margin: 1em 1em 0 0; diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index 822153b384..db0a8ec7a6 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -1,26 +1,221 @@ -

OneDep Component is working!

- - + +
assignment
- Begin deposition + General information
- + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ keyword }} + + + +
Name{{ dataset.datasetName || "-" }}
Description + +
PID + {{ dataset.pid }} +
Type{{dataset.type }}
Creation Time{{ dataset.creationTime | date: "yyyy-MM-dd + HH:mm" }}
Keywords
+
+
-
+ + +
+ blur_linear +
+ Method Information: +
+ + + + + + + experimental method + + + {{ method.viewValue }} + + + + + + + + + + + + + + + + - + + + + + + + + + + + +
Choose Electron Microscopy + Method
Are you deposing coordinates with this + submission (for PDB)? + + + Yes + + + No + + +
Has an associated map been deposited to EMDB? + + + + Yes + + + No + + +
EMDB Identifier + + EMDB ID + + +
Is this a composite map? + + + Yes + + + No + + +
+
+
+ +

showValue()

+ + + +

- + \ No newline at end of file diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index b1f0a3e4e7..76f308fe17 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from "@angular/core"; +import { Component, OnInit, ChangeDetectorRef } from "@angular/core"; import { AppConfigService, HelpMessages } from "app-config.service"; import { HttpClient } from '@angular/common/http'; import { ActivatedRoute, Router } from '@angular/router'; @@ -16,7 +16,12 @@ import { } from "state-management/selectors/datasets.selectors"; import { Subscription } from "rxjs"; +import { string } from "mathjs"; +interface EmMethod { + value: string; + viewValue:string; +} @Component({ selector: 'onedep', @@ -27,35 +32,43 @@ export class OneDepComponent implements OnInit { appConfig = this.appConfigService.getConfig(); dataset: Dataset | undefined; - cd$ = this.store.select(selectCurrentDataset); form: FormGroup; - //attachments$ = this.store.select(selectCurrentAttachments); - // datasetWithout$ = this.store.select(selectCurrentDatasetWithoutFileInfo); - // userProfile$ = this.store.select(selectProfile); - // isAdmin$ = this.store.select(selectIsAdmin); - // accessGroups$: Observable = this.userProfile$.pipe( - // map((profile) => (profile ? profile.accessGroups : [])), - // ); private subscriptions: Subscription[] = []; + showAssociatedMapQuestion: boolean = false; + methodsList: EmMethod[] = [ + {value:'helical', viewValue: 'Helical'}, + {value:'single-particle', viewValue:'Single Particle'}, + {value:'subtomogram-averaging',viewValue: 'Subtomogram Averaging'}, + {value:'tomogram', viewValue: 'Tomogram'}, + {value:'electron-cristallography', viewValue:'Electron Crystallography'}, + ]; + emMethod: string; constructor(public appConfigService: AppConfigService, private store: Store, // private http: HttpClient, // private route: ActivatedRoute, // private router: Router, - private fb: FormBuilder + private fb: FormBuilder, ) { } ngOnInit() { - console.log('init OneDep') + this.store.select(selectCurrentDataset).subscribe((dataset) => { + this.dataset = dataset; + }); this.form = this.fb.group({ datasetName: new FormControl("", [Validators.required]), description: new FormControl("", [Validators.required]), keywords: this.fb.array([]), - }); - this.store.select(selectCurrentDataset).subscribe((dataset) => { - this.dataset = dataset; - }); + deposingCoordinates:new FormControl(true), + associatedMap: new FormControl(false), + compositeMap:new FormControl(false), + emdbId:new FormControl(false), + + }) + } + showValue(){ + console.log(this.form['deposingCoordinates']) } } From 3de3fef7e7c8dee05db258eccf47dfb836374450 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Thu, 31 Oct 2024 17:55:14 +0100 Subject: [PATCH 024/245] prepared dields for OneDep export --- .../dataset-detail.component.html | 19 +- .../dataset-detail.component.ts | 5 + src/app/datasets/onedep/onedep.component.html | 382 ++++++++++++------ src/app/datasets/onedep/onedep.component.scss | 24 +- src/app/datasets/onedep/onedep.component.ts | 87 +++- 5 files changed, 387 insertions(+), 130 deletions(-) diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.html b/src/app/datasets/dataset-detail/dataset-detail.component.html index dd98f6e24f..c5c6d58335 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.html +++ b/src/app/datasets/dataset-detail/dataset-detail.component.html @@ -16,19 +16,20 @@ mat-raised-button id="onedepBtn" class="emexport-button" + *ngIf="hasOpenEMKeyword()" (click)="onOneDepClick()" > OneDep - +
Description - + @@ -40,8 +39,7 @@ Keywords - + {{ keyword }} @@ -50,7 +48,7 @@ - +
blur_linear @@ -61,73 +59,53 @@ - - + experimental method - - - {{ method.viewValue }} - - + + + {{ method.viewValue }} + + - + - - - + - + - +
Choose Electron Microscopy + Choose Electron Microscopy Method
Are you deposing coordinates with this - submission (for PDB)?Are you deposing coordinates with this + submission? (for PDB) - - - Yes + + + Yes + + + No - - No -
Has an associated map been deposited to EMDB? - - - Yes + + + Yes + + + No - - No -
EMDB Identifier - + EMDB ID @@ -136,85 +114,263 @@
Is this a composite map?Is this a composite map? - - Yes - - - No - + + Yes + + + No +
+ -

showValue()

- - + + Main Map + +

+ + + + +
+ attach_file +
+ + + + +
+

Selected File: {{ selectedFile['pathToMainMap'].name }} +

+
+
+ + + + + Half Map (1) + +
+ + + + +
+ attach_file +
+ + + + +
+

Selected File: {{ selectedFile['pathToHalfMap1'].name }} +

+
+
+ + + + + Half Map (2) + +
+ + + + +
+ attach_file +
+ + + + +
+

Selected File: {{ selectedFile['pathToHalfMap2'].name }} +

+
+
+ + + + + + Mask Map + +
+ + + + +
+ attach_file +
+ + + + +
+

Selected File: {{ selectedFile['pathToHalfMask'].name }} +

+
+
+ + + + + Additional Map + +
+ + + + +
+ attach_file +
+ + + + +
+

Selected File: {{ selectedFile['pathToAdditionalMap'].name + }}

+
+
+ + + + + + Coordinates + +
+ + + + +
+ attach_file +
+ + + + +
+

Selected File: {{ selectedFile['pathToCoordinates'].name + }}

+
+
+ + + + + + Public Image + +
+ + + + +
+ attach_file +
+ + + + +
+

Selected File: {{ selectedFile['pathToImage'].name }}

+
+
+ + + + + + FSC-XML + +
+ + + + +
+ attach_file +
+ + + + +
+

Selected File: {{ selectedFile['pathToFSC'].name }}

+
- - - - - - - + + + + + + + + -
--> + +
diff --git a/src/app/datasets/onedep/onedep.component.scss b/src/app/datasets/onedep/onedep.component.scss index b862d7d649..71d65ed58e 100644 --- a/src/app/datasets/onedep/onedep.component.scss +++ b/src/app/datasets/onedep/onedep.component.scss @@ -12,6 +12,9 @@ mat-card { table { th { + .questionnaire{ + min-width: 20rem; + } min-width: 10rem; padding-right: 0.5rem; text-align: left; @@ -30,10 +33,29 @@ mat-card { } } - + .EMDB-ID{ + width: 15%; + } + .method-name{ + width: 25%; + } + .file-types{ + width:10%; + } .full-width { width: 100%; } + .fileChooser{ + margin-left: 0px; + } + .fileName { + margin-left: 10px; + white-space: nowrap; + } + .submitDep{ + background-color: blueviolet; + } } + } \ No newline at end of file diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index 76f308fe17..16434c83f5 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -36,6 +36,14 @@ export class OneDepComponent implements OnInit { private subscriptions: Subscription[] = []; showAssociatedMapQuestion: boolean = false; + connectedDepositionBackend: string = ''; + connectedDepositionBackendVersion: string = ''; + connectingToDepositionBackend: boolean = false; + lastUsedDepositionBackends: string[] = []; + forwardDepositionBackend: string = ''; + errorMessage: string = ''; + + methodsList: EmMethod[] = [ {value:'helical', viewValue: 'Helical'}, {value:'single-particle', viewValue:'Single Particle'}, @@ -43,12 +51,15 @@ export class OneDepComponent implements OnInit { {value:'tomogram', viewValue: 'Tomogram'}, {value:'electron-cristallography', viewValue:'Electron Crystallography'}, ]; - emMethod: string; + + selectedFile: { [key: string]: File | null } = {}; + + constructor(public appConfigService: AppConfigService, private store: Store, - // private http: HttpClient, - // private route: ActivatedRoute, - // private router: Router, + private http: HttpClient, + private route: ActivatedRoute, + private router: Router, private fb: FormBuilder, ) { } @@ -61,14 +72,76 @@ export class OneDepComponent implements OnInit { datasetName: new FormControl("", [Validators.required]), description: new FormControl("", [Validators.required]), keywords: this.fb.array([]), + emMethod: new FormControl(""), deposingCoordinates:new FormControl(true), associatedMap: new FormControl(false), compositeMap:new FormControl(false), - emdbId:new FormControl(false), + emdbId:new FormControl(""), + pathToMainMap: new FormControl(""), + pathToHalfMap1: new FormControl(""), + pathToHalfMap2: new FormControl(""), + pathToMask: new FormControl(""), + pathToAdditionalMap: new FormControl(""), + pathToCoordinates: new FormControl(""), + pathToImage: new FormControl(""), + pathToCif: new FormControl(""), + pathToFSC: new FormControl(""), }) + + this.connectingToDepositionBackend = true; + // Get the GET parameter 'backendUrl' from the URL + // this.route.queryParams.subscribe(params => { + // const backendUrl = params['backendUrl']; + // if (backendUrl) { + // this.connectToDepositionBackend(backendUrl); + // } + // else { + // this.connectingToDepositionBackend = false; + // } + // }); + } + + connectToDepositionBackend(): boolean { + var DepositionBackendUrl = "http://localhost:8080" + let DepositionBackendUrlCleaned = DepositionBackendUrl.slice(); + // Check if last symbol is a slash and add version endpoint + if (!DepositionBackendUrlCleaned.endsWith('/')) { + DepositionBackendUrlCleaned += '/'; + } + + let DepositionBackendUrlVersion = DepositionBackendUrlCleaned; + + // Try to connect to the facility backend/version to check if it is available + console.log('Connecting to facility backend: ' + DepositionBackendUrlVersion); + this.http.get(DepositionBackendUrlVersion).subscribe( + response => { + console.log('Connected to facility backend', response); + // If the connection is successful, store the connected facility backend URL + this.connectedDepositionBackend = DepositionBackendUrlCleaned; + this.connectingToDepositionBackend = false; + this.connectedDepositionBackendVersion = response['version']; + }, + error => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}
`; + console.error('Request failed', error); + this.connectedDepositionBackend = ''; + this.connectingToDepositionBackend = false; + } + ); + + return true; + } + + onFileSelected(event: Event, controlName: string) { + const input = event.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + this.selectedFile[controlName] = input.files[0]; + this.form.get(controlName)?.setValue(this.selectedFile[controlName].name); + } } - showValue(){ - console.log(this.form['deposingCoordinates']) + onDepositClick(){ + const formData = this.form.value; } + } From 94bdd298f63b24c51d453018b84322fa0f1caeca Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Fri, 1 Nov 2024 16:34:27 +0100 Subject: [PATCH 025/245] connect to OneDep backend when going to onedep --- .../dataset-detail.component.ts | 45 +++++++++++++++++ src/app/datasets/onedep/onedep.component.html | 4 +- src/app/datasets/onedep/onedep.component.ts | 48 +++++-------------- 3 files changed, 57 insertions(+), 40 deletions(-) diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.ts b/src/app/datasets/dataset-detail/dataset-detail.component.ts index 3a3db36fdf..30f76fa823 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.ts +++ b/src/app/datasets/dataset-detail/dataset-detail.component.ts @@ -7,6 +7,7 @@ import { MatDialog } from "@angular/material/dialog"; import { DialogComponent } from "shared/modules/dialog/dialog.component"; import { combineLatest, fromEvent, Observable, Subscription } from "rxjs"; import { Store } from "@ngrx/store"; +import { HttpClient } from '@angular/common/http'; import { showMessageAction } from "state-management/actions/user.actions"; import { @@ -90,6 +91,14 @@ export class DatasetDetailComponent editEnabled = false; show = false; + + connectedDepositionBackend: string = ''; + connectedDepositionBackendVersion: string = ''; + connectingToDepositionBackend: boolean = false; + lastUsedDepositionBackends: string[] = []; + forwardDepositionBackend: string = ''; + errorMessage: string = ''; + @Output() emClick = new EventEmitter(); readonly separatorKeyCodes: number[] = [ENTER, COMMA, SPACE]; @@ -100,11 +109,14 @@ export class DatasetDetailComponent private attachmentService: AttachmentService, public dialog: MatDialog, private store: Store, + private http: HttpClient, private router: Router, private fb: FormBuilder, ) {} ngOnInit() { + this.connectingToDepositionBackend = true; + this.form = this.fb.group({ datasetName: new FormControl("", [Validators.required]), description: new FormControl("", [Validators.required]), @@ -324,4 +336,37 @@ export class DatasetDetailComponent this.router.navigateByUrl("/datasets/" + id + "/empiar"); } + + connectToDepositionBackend(): boolean { + var DepositionBackendUrl = "http://localhost:8080" + let DepositionBackendUrlCleaned = DepositionBackendUrl.slice(); + // Check if last symbol is a slash and add version endpoint + if (!DepositionBackendUrlCleaned.endsWith('/')) { + DepositionBackendUrlCleaned += '/'; + } + + let DepositionBackendUrlVersion = DepositionBackendUrlCleaned; + + // Try to connect to the facility backend/version to check if it is available + console.log('Connecting to facility backend: ' + DepositionBackendUrlVersion); + this.http.get(DepositionBackendUrlVersion).subscribe( + response => { + console.log('Connected to facility backend', response); + // If the connection is successful, store the connected facility backend URL + this.connectedDepositionBackend = DepositionBackendUrlCleaned; + this.connectingToDepositionBackend = false; + this.connectedDepositionBackendVersion = response['version']; + }, + error => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}
`; + console.error('Request failed', error); + this.connectedDepositionBackend = ''; + this.connectingToDepositionBackend = false; + } + ); + + return true; + } + } + diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index 7148f5362c..62bf4c1668 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -360,9 +360,7 @@ - + diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index 16434c83f5..bb9f31e052 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -36,12 +36,12 @@ export class OneDepComponent implements OnInit { private subscriptions: Subscription[] = []; showAssociatedMapQuestion: boolean = false; - connectedDepositionBackend: string = ''; - connectedDepositionBackendVersion: string = ''; - connectingToDepositionBackend: boolean = false; - lastUsedDepositionBackends: string[] = []; - forwardDepositionBackend: string = ''; - errorMessage: string = ''; + // connectedDepositionBackend: string = ''; + // connectedDepositionBackendVersion: string = ''; + // connectingToDepositionBackend: boolean = false; + // lastUsedDepositionBackends: string[] = []; + // forwardDepositionBackend: string = ''; + // errorMessage: string = ''; methodsList: EmMethod[] = [ @@ -89,7 +89,7 @@ export class OneDepComponent implements OnInit { pathToFSC: new FormControl(""), }) - this.connectingToDepositionBackend = true; + // this.connectingToDepositionBackend = true; // Get the GET parameter 'backendUrl' from the URL // this.route.queryParams.subscribe(params => { // const backendUrl = params['backendUrl']; @@ -102,36 +102,6 @@ export class OneDepComponent implements OnInit { // }); } - connectToDepositionBackend(): boolean { - var DepositionBackendUrl = "http://localhost:8080" - let DepositionBackendUrlCleaned = DepositionBackendUrl.slice(); - // Check if last symbol is a slash and add version endpoint - if (!DepositionBackendUrlCleaned.endsWith('/')) { - DepositionBackendUrlCleaned += '/'; - } - - let DepositionBackendUrlVersion = DepositionBackendUrlCleaned; - - // Try to connect to the facility backend/version to check if it is available - console.log('Connecting to facility backend: ' + DepositionBackendUrlVersion); - this.http.get(DepositionBackendUrlVersion).subscribe( - response => { - console.log('Connected to facility backend', response); - // If the connection is successful, store the connected facility backend URL - this.connectedDepositionBackend = DepositionBackendUrlCleaned; - this.connectingToDepositionBackend = false; - this.connectedDepositionBackendVersion = response['version']; - }, - error => { - this.errorMessage += `${new Date().toLocaleString()}: ${error.message}
`; - console.error('Request failed', error); - this.connectedDepositionBackend = ''; - this.connectingToDepositionBackend = false; - } - ); - - return true; - } onFileSelected(event: Event, controlName: string) { const input = event.target as HTMLInputElement; @@ -142,6 +112,10 @@ export class OneDepComponent implements OnInit { } onDepositClick(){ const formData = this.form.value; + // need to properly catch the dataset details + console.log(this.dataset) + //return this.http.post(this.backendUrl, formData); + } } From f3a2ed404461104fbcdb6aaadfa02d46695544a0 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Mon, 4 Nov 2024 17:56:56 +0100 Subject: [PATCH 026/245] adding restrictions to deposition types --- .../dataset-detail.component.ts | 14 +- src/app/datasets/onedep/onedep.component.html | 66 +++---- src/app/datasets/onedep/onedep.component.ts | 184 ++++++++++-------- src/app/datasets/onedep/types/methods.enum.ts | 39 ++++ 4 files changed, 185 insertions(+), 118 deletions(-) create mode 100644 src/app/datasets/onedep/types/methods.enum.ts diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.ts b/src/app/datasets/dataset-detail/dataset-detail.component.ts index 30f76fa823..67f706479c 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.ts +++ b/src/app/datasets/dataset-detail/dataset-detail.component.ts @@ -66,8 +66,7 @@ import { AttachmentService } from "shared/services/attachment.service"; standalone: false, }) export class DatasetDetailComponent - implements OnInit, OnDestroy, EditableComponent -{ + implements OnInit, OnDestroy, EditableComponent { private subscriptions: Subscription[] = []; private _hasUnsavedChanges = false; form: FormGroup; @@ -112,7 +111,7 @@ export class DatasetDetailComponent private http: HttpClient, private router: Router, private fb: FormBuilder, - ) {} + ) { } ngOnInit() { this.connectingToDepositionBackend = true; @@ -171,7 +170,6 @@ export class DatasetDetailComponent } }), ); - console.log("the keywords:" , this.dataset.keywords); } onEditModeEnable() { @@ -331,7 +329,7 @@ export class DatasetDetailComponent openAttachment(encoded: string) { this.attachmentService.openAttachment(encoded); } - onEMPIARclick(){ + onEMPIARclick() { const id = encodeURIComponent(this.dataset.pid); this.router.navigateByUrl("/datasets/" + id + "/empiar"); } @@ -345,13 +343,13 @@ export class DatasetDetailComponent DepositionBackendUrlCleaned += '/'; } - let DepositionBackendUrlVersion = DepositionBackendUrlCleaned; + let DepositionBackendUrlVersion = DepositionBackendUrlCleaned + 'version'; // Try to connect to the facility backend/version to check if it is available - console.log('Connecting to facility backend: ' + DepositionBackendUrlVersion); + console.log('Connecting to OneDep backend: ' + DepositionBackendUrlVersion); this.http.get(DepositionBackendUrlVersion).subscribe( response => { - console.log('Connected to facility backend', response); + console.log('Connected to OneDep backend', response); // If the connection is successful, store the connected facility backend URL this.connectedDepositionBackend = DepositionBackendUrlCleaned; this.connectingToDepositionBackend = false; diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index 62bf4c1668..e784b686a7 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -64,7 +64,7 @@ experimental method - + {{ method.viewValue }} @@ -151,16 +151,16 @@ Choose File -
+
attach_file
- -
-

Selected File: {{ selectedFile['pathToMainMap'].name }} +

+

Selected File: {{ selectedFile['mainMap'].name }}

@@ -178,16 +178,16 @@ Choose File -
+
attach_file
- -
-

Selected File: {{ selectedFile['pathToHalfMap1'].name }} +

+

Selected File: {{ selectedFile['halfMap1'].name }}

@@ -205,16 +205,16 @@ Choose File -
+
attach_file
- -
-

Selected File: {{ selectedFile['pathToHalfMap2'].name }} +

+

Selected File: {{ selectedFile['halfMap2'].name }}

@@ -233,16 +233,16 @@ Choose File -
+
attach_file
- -
-

Selected File: {{ selectedFile['pathToHalfMask'].name }} +

+

Selected File: {{ selectedFile['mask'].name }}

@@ -260,17 +260,17 @@ Choose File -
+
attach_file
-
-

Selected File: {{ selectedFile['pathToAdditionalMap'].name +

+

Selected File: {{ selectedFile['addMap'].name }}

@@ -289,16 +289,16 @@ Choose File -
+
attach_file
+ (change)="onFileSelected($event, 'coordinates')" style="display: none;" /> -
-

Selected File: {{ selectedFile['pathToCoordinates'].name +

+

Selected File: {{ selectedFile['coordinates'].name }}

@@ -317,16 +317,16 @@ Choose File -
+
attach_file
- -
-

Selected File: {{ selectedFile['pathToImage'].name }}

+
+

Selected File: {{ selectedFile['image'].name }}

@@ -344,16 +344,16 @@ Choose File -
+
attach_file
- -
-

Selected File: {{ selectedFile['pathToFSC'].name }}

+
+

Selected File: {{ selectedFile['fsc'].name }}

diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index bb9f31e052..1cbb0ed074 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -14,14 +14,10 @@ import { selectCurrentDataset, } from "state-management/selectors/datasets.selectors"; - +import { MethodsList, Experiment, OneDepFile } from "./types/methods.enum" import { Subscription } from "rxjs"; import { string } from "mathjs"; -interface EmMethod { - value: string; - viewValue:string; -} @Component({ selector: 'onedep', @@ -35,24 +31,9 @@ export class OneDepComponent implements OnInit { form: FormGroup; private subscriptions: Subscription[] = []; showAssociatedMapQuestion: boolean = false; - - // connectedDepositionBackend: string = ''; - // connectedDepositionBackendVersion: string = ''; - // connectingToDepositionBackend: boolean = false; - // lastUsedDepositionBackends: string[] = []; - // forwardDepositionBackend: string = ''; - // errorMessage: string = ''; - - - methodsList: EmMethod[] = [ - {value:'helical', viewValue: 'Helical'}, - {value:'single-particle', viewValue:'Single Particle'}, - {value:'subtomogram-averaging',viewValue: 'Subtomogram Averaging'}, - {value:'tomogram', viewValue: 'Tomogram'}, - {value:'electron-cristallography', viewValue:'Electron Crystallography'}, - ]; - - selectedFile: { [key: string]: File | null } = {}; + methodsList = MethodsList; + experiment = Experiment; + selectedFile: { [key: string]: File | null } = {}; constructor(public appConfigService: AppConfigService, @@ -61,61 +42,110 @@ export class OneDepComponent implements OnInit { private route: ActivatedRoute, private router: Router, private fb: FormBuilder, - ) { } - - - ngOnInit() { - this.store.select(selectCurrentDataset).subscribe((dataset) => { - this.dataset = dataset; + ) { } + + + ngOnInit() { + this.store.select(selectCurrentDataset).subscribe((dataset) => { + this.dataset = dataset; + }); + this.form = this.fb.group({ + datasetName: this.dataset.datasetName, + description: this.dataset.description, + keywords: this.fb.array(this.dataset.keywords), + metadata: this.dataset.scientificMetadata, + emMethod: new FormControl(""), + deposingCoordinates: new FormControl(true), + associatedMap: new FormControl(false), + compositeMap: new FormControl(false), + emdbId: new FormControl(""), + + mainMap: { + name: "", + type: "vo-map", + pathToFile: "", + contour: 0.0, + details: "", + }, + halfMap1: { + name: "", + type: "half-map", + pathToFile: "", + contour: 0.0, + details: "", + }, + halfMap2: { + name: "", + type: "half-map", + pathToFile: "", + contour: 0.0, + details: "", + }, + mask: { + name: "", + type: "mask-map", + pathToFile: "", + contour: 0.0, + details: "", + }, + addMap: { + name: "", + type: "add-map", + pathToFile: "", + contour: 0.0, + details: "", + }, + coordinates: { + name: "", + type: "co-cif", + pathToFile: "", + details: "", + }, + image: { + name: "", + type: "img-emdb", + pathToFile: "", + details: "", + }, + // pathToCif: { --> should be extracted from this.dataset.scientificMetadata + // name: "", + // type: "undef", + // pathToFile: "", + // details: "", + // }, + fsc: { + name: "", + type: "fsc-xml", + pathToFile: "", + details: "", + }, + }) + } + + + onFileSelected(event: Event, controlName: string) { + const input = event.target as HTMLInputElement; + console.log(input); + if (input.files && input.files.length > 0) { + this.selectedFile[controlName] = input.files[0]; + this.form.get(controlName)?.setValue({ + ...this.form.get(controlName)?.value, + pathToFile: this.selectedFile[controlName].name }); - this.form = this.fb.group({ - datasetName: new FormControl("", [Validators.required]), - description: new FormControl("", [Validators.required]), - keywords: this.fb.array([]), - emMethod: new FormControl(""), - deposingCoordinates:new FormControl(true), - associatedMap: new FormControl(false), - compositeMap:new FormControl(false), - emdbId:new FormControl(""), - - pathToMainMap: new FormControl(""), - pathToHalfMap1: new FormControl(""), - pathToHalfMap2: new FormControl(""), - pathToMask: new FormControl(""), - pathToAdditionalMap: new FormControl(""), - pathToCoordinates: new FormControl(""), - pathToImage: new FormControl(""), - pathToCif: new FormControl(""), - pathToFSC: new FormControl(""), - }) - - // this.connectingToDepositionBackend = true; - // Get the GET parameter 'backendUrl' from the URL - // this.route.queryParams.subscribe(params => { - // const backendUrl = params['backendUrl']; - // if (backendUrl) { - // this.connectToDepositionBackend(backendUrl); - // } - // else { - // this.connectingToDepositionBackend = false; - // } - // }); } - - - onFileSelected(event: Event, controlName: string) { - const input = event.target as HTMLInputElement; - if (input.files && input.files.length > 0) { - this.selectedFile[controlName] = input.files[0]; - this.form.get(controlName)?.setValue(this.selectedFile[controlName].name); + } + onDepositClick() { + const formData = this.form.value; + // need to properly catch the dataset details + console.log("creating deposition", formData) + this.http.post("http://localhost:8080/onedep", formData).subscribe( + response => { + console.log('created deposition in OneDep', response); + }, + error => { + console.error('Request failed esf', error); } - } - onDepositClick(){ - const formData = this.form.value; - // need to properly catch the dataset details - console.log(this.dataset) - //return this.http.post(this.backendUrl, formData); - - } - + ); + } + } diff --git a/src/app/datasets/onedep/types/methods.enum.ts b/src/app/datasets/onedep/types/methods.enum.ts new file mode 100644 index 0000000000..0432087812 --- /dev/null +++ b/src/app/datasets/onedep/types/methods.enum.ts @@ -0,0 +1,39 @@ +export enum EmType { + Helical = "helical", + SingleParticle = "single-particle", + SubtomogramAveraging = "subtomogram-averaging", + Tomogram = "tomogram", + ElectronCristallography = "electron-cristallography" +}; +interface EmMethod { + value: EmType; + viewValue:string; +} + + +export const MethodsList: EmMethod[] = [ + {value:EmType.Helical, viewValue: 'Helical'}, + {value:EmType.SingleParticle, viewValue:'Single Particle'}, + {value:EmType.SubtomogramAveraging,viewValue: 'Subtomogram Averaging'}, + {value:EmType.Tomogram, viewValue: 'Tomogram'}, + {value:EmType.ElectronCristallography, viewValue:'Electron Crystallography'}, + ]; +interface OneDepExperiment { + type: string; + subtype?: string; +} + +export const Experiment: { [e in EmType]: OneDepExperiment } = { + [EmType.Helical]: { type: "em", subtype: "helical" }, + [EmType.SingleParticle]: { type: "em", subtype: "single" }, + [EmType.SubtomogramAveraging]: { type: "em", subtype: "subtomogram" }, + [EmType.Tomogram]: { type: "em", subtype: "tomography" }, + [EmType.ElectronCristallography]: { type: "ec" } +}; +export interface OneDepFile{ + name:File, + type:string, + pathToFile:string, + contour?: number, + details?: string, +} \ No newline at end of file From 78f1fae1138b384b73e174c791d75e3fcadec6f5 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Tue, 5 Nov 2024 10:15:39 +0100 Subject: [PATCH 027/245] refactoring --- src/app/datasets/onedep/onedep.component.html | 64 ++++++------ src/app/datasets/onedep/onedep.component.ts | 78 +++------------ src/app/datasets/onedep/types/methods.enum.ts | 99 ++++++++++++++++--- 3 files changed, 128 insertions(+), 113 deletions(-) diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index e784b686a7..77003ab2df 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -151,16 +151,16 @@ Choose File -
+
attach_file
- -
-

Selected File: {{ selectedFile['mainMap'].name }} +

+

Selected File: {{ selectedFile[emFile.MainMap].name }}

@@ -178,16 +178,16 @@ Choose File -
+
attach_file
- -
-

Selected File: {{ selectedFile['halfMap1'].name }} +

+

Selected File: {{ selectedFile[emFile.HalfMap1].name }}

@@ -205,16 +205,16 @@ Choose File -
+
attach_file
- -
-

Selected File: {{ selectedFile['halfMap2'].name }} +

+

Selected File: {{ selectedFile[emFile.HalfMap2].name }}

@@ -233,16 +233,16 @@ Choose File -
+
attach_file
- -
-

Selected File: {{ selectedFile['mask'].name }} +

+

Selected File: {{ selectedFile[emFile.MaskMap].name }}

@@ -260,17 +260,17 @@ Choose File -
+
attach_file
-
-

Selected File: {{ selectedFile['addMap'].name +

+

Selected File: {{ selectedFile[emFile.AddMap].name }}

@@ -289,16 +289,16 @@ Choose File -
+
attach_file
+ (change)="onFileSelected($event, emFile.Coordinates)" style="display: none;" /> -
-

Selected File: {{ selectedFile['coordinates'].name +

+

Selected File: {{ selectedFile[emFile.Coordinates].name }}

@@ -317,16 +317,16 @@ Choose File -
+
attach_file
- -
-

Selected File: {{ selectedFile['image'].name }}

+
+

Selected File: {{ selectedFile[emFile.Image].name }}

@@ -344,16 +344,16 @@ Choose File -
+
attach_file
- -
-

Selected File: {{ selectedFile['fsc'].name }}

+
+

Selected File: {{ selectedFile[emFile.FSC].name }}

diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index 1cbb0ed074..3d506a8416 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -14,7 +14,7 @@ import { selectCurrentDataset, } from "state-management/selectors/datasets.selectors"; -import { MethodsList, Experiment, OneDepFile } from "./types/methods.enum" +import { MethodsList, Experiment, EmFile, EmFiles } from "./types/methods.enum" import { Subscription } from "rxjs"; import { string } from "mathjs"; @@ -34,7 +34,8 @@ export class OneDepComponent implements OnInit { methodsList = MethodsList; experiment = Experiment; selectedFile: { [key: string]: File | null } = {}; - + emFile = EmFile; + files = EmFiles; constructor(public appConfigService: AppConfigService, private store: Store, @@ -59,81 +60,24 @@ export class OneDepComponent implements OnInit { associatedMap: new FormControl(false), compositeMap: new FormControl(false), emdbId: new FormControl(""), - - mainMap: { - name: "", - type: "vo-map", - pathToFile: "", - contour: 0.0, - details: "", - }, - halfMap1: { - name: "", - type: "half-map", - pathToFile: "", - contour: 0.0, - details: "", - }, - halfMap2: { - name: "", - type: "half-map", - pathToFile: "", - contour: 0.0, - details: "", - }, - mask: { - name: "", - type: "mask-map", - pathToFile: "", - contour: 0.0, - details: "", - }, - addMap: { - name: "", - type: "add-map", - pathToFile: "", - contour: 0.0, - details: "", - }, - coordinates: { - name: "", - type: "co-cif", - pathToFile: "", - details: "", - }, - image: { - name: "", - type: "img-emdb", - pathToFile: "", - details: "", - }, - // pathToCif: { --> should be extracted from this.dataset.scientificMetadata - // name: "", - // type: "undef", - // pathToFile: "", - // details: "", - // }, - fsc: { - name: "", - type: "fsc-xml", - pathToFile: "", - details: "", - }, + // files: this.fb.group({}), + }) } onFileSelected(event: Event, controlName: string) { const input = event.target as HTMLInputElement; - console.log(input); + if (input.files && input.files.length > 0) { this.selectedFile[controlName] = input.files[0]; - this.form.get(controlName)?.setValue({ - ...this.form.get(controlName)?.value, - pathToFile: this.selectedFile[controlName].name - }); + this.files[controlName].file = this.selectedFile[controlName]; + this.files[controlName].name = this.selectedFile[controlName].name; } + console.log(this.files); } + + onDepositClick() { const formData = this.form.value; // need to properly catch the dataset details diff --git a/src/app/datasets/onedep/types/methods.enum.ts b/src/app/datasets/onedep/types/methods.enum.ts index 0432087812..5cefb55be2 100644 --- a/src/app/datasets/onedep/types/methods.enum.ts +++ b/src/app/datasets/onedep/types/methods.enum.ts @@ -5,19 +5,89 @@ export enum EmType { Tomogram = "tomogram", ElectronCristallography = "electron-cristallography" }; + +export enum EmFile { + MainMap = 'vo-map', + HalfMap1 = 'half-map1', + HalfMap2 = 'half-map2', + MaskMap = 'mask-map', + AddMap = 'add-map', + Coordinates = 'co-cif', + Image = 'img-emdb', + FSC = 'fsc-xml', + +}; + + +export const EmFiles: { [f in EmFile]: OneDepFile } = { + [EmFile.MainMap]: { + name: "", + type: "vo-map", + file: null, + contour: 0.0, + details: "", + }, + [EmFile.HalfMap1]: { + name: "", + type: "half-map", + file: null, + contour: 0.0, + details: "", + }, + [EmFile.HalfMap2]: { + name: "", + type: "half-map", + file: null, + contour: 0.0, + details: "", + }, + [EmFile.MaskMap]: { + name: "", + type: "mask-map", + file: null, + contour: 0.0, + details: "", + }, + [EmFile.AddMap]: { + name: "", + type: "add-map", + file: null, + contour: 0.0, + details: "", + }, + [EmFile.Coordinates]: { + name: "", + type: "co-cif", + file: null, + details: "", + }, + [EmFile.Image]: { + name: "", + type: "img-emdb", + file: null, + details: "", + }, + [EmFile.FSC]: { + name: "", + type: "fsc-xml", + file: null, + details: "", + }, +}; + interface EmMethod { value: EmType; - viewValue:string; + viewValue: string; } export const MethodsList: EmMethod[] = [ - {value:EmType.Helical, viewValue: 'Helical'}, - {value:EmType.SingleParticle, viewValue:'Single Particle'}, - {value:EmType.SubtomogramAveraging,viewValue: 'Subtomogram Averaging'}, - {value:EmType.Tomogram, viewValue: 'Tomogram'}, - {value:EmType.ElectronCristallography, viewValue:'Electron Crystallography'}, - ]; + { value: EmType.Helical, viewValue: 'Helical' }, + { value: EmType.SingleParticle, viewValue: 'Single Particle' }, + { value: EmType.SubtomogramAveraging, viewValue: 'Subtomogram Averaging' }, + { value: EmType.Tomogram, viewValue: 'Tomogram' }, + { value: EmType.ElectronCristallography, viewValue: 'Electron Crystallography' }, +]; interface OneDepExperiment { type: string; subtype?: string; @@ -30,10 +100,11 @@ export const Experiment: { [e in EmType]: OneDepExperiment } = { [EmType.Tomogram]: { type: "em", subtype: "tomography" }, [EmType.ElectronCristallography]: { type: "ec" } }; -export interface OneDepFile{ - name:File, - type:string, - pathToFile:string, - contour?: number, - details?: string, -} \ No newline at end of file +export interface OneDepFile { + name: string, + type: string, + file: File, + contour?: number, + details?: string, +} + From a766839209c371dc7bce018240b72c4098429749 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Tue, 5 Nov 2024 17:31:21 +0100 Subject: [PATCH 028/245] refactoring wip: experiments not passed correctly --- src/app/datasets/onedep/onedep.component.html | 267 +++--------------- src/app/datasets/onedep/onedep.component.scss | 187 ++++++++---- src/app/datasets/onedep/onedep.component.ts | 180 ++++++++++-- src/app/datasets/onedep/types/methods.enum.ts | 8 +- 4 files changed, 330 insertions(+), 312 deletions(-) diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index 77003ab2df..5a8b368d58 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -64,7 +64,7 @@ experimental method - + {{ method.viewValue }} @@ -138,234 +138,47 @@ Choose files for deposition - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Main Map -
- - - - -
- attach_file -
- - - - -
-

Selected File: {{ selectedFile[emFile.MainMap].name }} -

-
-
-
Half Map (1) -
- - - - -
- attach_file -
- - - - -
-

Selected File: {{ selectedFile[emFile.HalfMap1].name }} -

-
-
-
Half Map (2) -
- - - - -
- attach_file -
- - - - -
-

Selected File: {{ selectedFile[emFile.HalfMap2].name }} -

-
+ + + {{ fileType.header }} + + +
+ +
+ attach_file
-
Mask Map -
- - - - -
- attach_file -
- - - - -
-

Selected File: {{ selectedFile[emFile.MaskMap].name }} -

-
+ +
+

Selected File: {{ selectedFile[fileType.key].name }}

-
Additional Map -
- - - - -
- attach_file -
- - - - -
-

Selected File: {{ selectedFile[emFile.AddMap].name - }}

-
-
-
Coordinates -
- - - - -
- attach_file -
- - - - -
-

Selected File: {{ selectedFile[emFile.Coordinates].name - }}

-
-
-
Public Image -
- - - - -
- attach_file -
- - - - -
-

Selected File: {{ selectedFile[emFile.Image].name }}

-
-
-
FSC-XML -
- - - - -
- attach_file -
- - - - -
-

Selected File: {{ selectedFile[emFile.FSC].name }}

-
-
-
+
+ +
+ + + Contour Level + + + + + Details + + +
+ + + + + diff --git a/src/app/datasets/onedep/onedep.component.scss b/src/app/datasets/onedep/onedep.component.scss index 71d65ed58e..617a0715ae 100644 --- a/src/app/datasets/onedep/onedep.component.scss +++ b/src/app/datasets/onedep/onedep.component.scss @@ -1,61 +1,146 @@ mat-card { - margin: 1em; - - .section-icon { - height: auto !important; - width: auto !important; - - mat-icon { - vertical-align: middle; - } + margin: 1em; + + .section-icon { + height: auto !important; + width: auto !important; + + mat-icon { + vertical-align: middle; } - - table { - th { - .questionnaire{ - min-width: 20rem; - } - min-width: 10rem; - padding-right: 0.5rem; - text-align: left; + } + + table { + th { + .questionnaire { + min-width: 20rem; } - - td { - width: 100%; - padding: 0.5rem 0; - - .sample-edit { - padding-left: 0.5rem; - - mat-icon { - font-size: medium; - } + + min-width: 10rem; + padding-right: 0.5rem; + text-align: left; + } + + td { + width: 100%; + padding: 0.5rem 0; + + .sample-edit { + padding-left: 0.5rem; + + mat-icon { + font-size: medium; } - - } - .EMDB-ID{ - width: 15%; - } - .method-name{ - width: 25%; - } - .file-types{ - width:10%; - } - .full-width { - width: 100%; - } - .fileChooser{ - margin-left: 0px; } + } + + .EMDB-ID { + width: 15%; + } + + .method-name { + width: 25%; + } + + .file-types { + width: 10%; + } + + .full-width { + width: 100%; + } + } + + .fileCard { + width: 80%; + margin: 10px 0 10px 0; + border: 1px solid #ddd; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); + + mat-card-header { + background-color: #B3D9AC; + height: 26px; + display: flex; // Use flexbox to align items + align-items: center; // Center content vertically + padding: 0 16px; // Optional: adjust padding as needed + } + mat-card-title{ + font-size: 16px; + } + + .file-selection-container { + display: flex; // Use flexbox for layout + align-items: center; // Center items vertically + gap: 10px; // Space between items + + .fileChooser { + background-color: #CFE7CB; + color: #333; + margin: 5px 0 0 0; // Reset any margin + } .fileName { - margin-left: 10px; - white-space: nowrap; - } - .submitDep{ - background-color: blueviolet; + font-size: 14px; // Adjust the font size as needed + color: #333; // Adjust the text color if needed } } + + mat-card-content { + display: flex; + flex-direction: column; + gap: 5px; + + .fileChooser { + margin: 3px auto; + } + + .input-container { + display: flex; // Use flexbox for layout + align-items: flex-end; + gap: 10px; // Space between the fields + + .contour-level { + flex: 0 0 20%; // Set to take 20% of the width + min-width: 100px; // Optional: set a minimum width for usability + + /* Chrome, Safari, Edge, Opera */ + input[matinput]::-webkit-outer-spin-button, + input[matinput]::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + + /* Firefox */ + input[matinput][type=number] { + -moz-appearance: textfield; + } + } + + .details { + flex: 1; // Allow this field to take the remaining space + min-width: 200px; // Optional: set a minimum width for usability + mat-form-field { + textarea { + max-height: calc(5 * 1.5em); // 5 lines max height + overflow-y: hidden; // Hide overflow + resize: none; // Disable resizing + line-height: 1.5; // Set line height + + &:focus { + height: auto; // Allow height to grow as needed + } + } + } + } + + mat-form-field { + width: 100%; // Ensure fields take full width of their flex container + margin-bottom: 0; + } + } + } + } + .submitDep { + background-color: #B3D9AC; } - \ No newline at end of file +} \ No newline at end of file diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index 3d506a8416..ac61cf2f6f 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -1,22 +1,23 @@ -import { Component, OnInit, ChangeDetectorRef } from "@angular/core"; -import { AppConfigService, HelpMessages } from "app-config.service"; +import { Component, OnInit, ViewChild, ElementRef } from "@angular/core"; +import { AppConfigService } from "app-config.service"; import { HttpClient } from '@angular/common/http'; import { ActivatedRoute, Router } from '@angular/router'; import { FormBuilder, FormControl, FormGroup, - Validators, + FormArray, } from "@angular/forms"; import { Store } from "@ngrx/store"; import { Dataset } from "shared/sdk/models"; import { - selectCurrentDataset, } from "state-management/selectors/datasets.selectors"; -import { MethodsList, Experiment, EmFile, EmFiles } from "./types/methods.enum" -import { Subscription } from "rxjs"; -import { string } from "mathjs"; +import {selectCurrentUser +} from "state-management/selectors/user.selectors"; +import { User } from "shared/sdk"; +import { MethodsList, OneDepExperiment, Experiments, EmFile, EmFiles } from "./types/methods.enum" +import { Subscription, fromEvent } from "rxjs"; @Component({ @@ -25,17 +26,34 @@ import { string } from "mathjs"; styleUrls: ['./onedep.component.scss'] }) export class OneDepComponent implements OnInit { + private subscriptions: Subscription[] = []; + private _hasUnsavedChanges = false; appConfig = this.appConfigService.getConfig(); dataset: Dataset | undefined; + user: User | undefined; form: FormGroup; - private subscriptions: Subscription[] = []; showAssociatedMapQuestion: boolean = false; methodsList = MethodsList; - experiment = Experiment; + experiments = Experiments; + experiment: OneDepExperiment selectedFile: { [key: string]: File | null } = {}; emFile = EmFile; files = EmFiles; + detailsOverflow: string = 'hidden'; + + @ViewChild('fileInput') fileInput: ElementRef | undefined; + + fileTypes = [ + { header: 'Main Map', key: this.emFile.MainMap }, + { header: 'Half Map (1)', key: this.emFile.HalfMap1 }, + { header: 'Half Map (2)', key: this.emFile.HalfMap2 }, + { header: 'Mask Map', key: this.emFile.MaskMap }, + { header: 'Additional Map', key: this.emFile.AddMap }, + { header: 'Coordinates', key: this.emFile.Coordinates }, + { header: 'Public Image', key: this.emFile.Image }, + { header: 'FSC-XML', key: this.emFile.FSC }, + ]; constructor(public appConfigService: AppConfigService, private store: Store, @@ -50,46 +68,148 @@ export class OneDepComponent implements OnInit { this.store.select(selectCurrentDataset).subscribe((dataset) => { this.dataset = dataset; }); + this.subscriptions.push( + this.store.select(selectCurrentUser).subscribe((user) => { + if (user) { + this.user = user; + } + }), + ); + // Prevent user from reloading page if there are unsave changes + this.subscriptions.push( + fromEvent(window, "beforeunload").subscribe((event) => { + if (this.hasUnsavedChanges()) { + event.preventDefault(); + } + }), + ); this.form = this.fb.group({ - datasetName: this.dataset.datasetName, - description: this.dataset.description, - keywords: this.fb.array(this.dataset.keywords), metadata: this.dataset.scientificMetadata, + experiments: this.fb.array([]), emMethod: new FormControl(""), deposingCoordinates: new FormControl(true), associatedMap: new FormControl(false), compositeMap: new FormControl(false), emdbId: new FormControl(""), - // files: this.fb.group({}), - - }) + files: this.fb.array([]), + email: this.user.email + }) + } + + hasUnsavedChanges() { + return this._hasUnsavedChanges; + } + onHasUnsavedChanges($event: boolean) { + this._hasUnsavedChanges = $event; } + autoGrow(event: Event): void { + const textarea = event.target as HTMLTextAreaElement; + const lineHeight = parseInt(getComputedStyle(textarea).lineHeight, 10); + const maxLines = 5; - onFileSelected(event: Event, controlName: string) { - const input = event.target as HTMLInputElement; + // Reset height to auto to calculate scrollHeight + textarea.style.height = 'auto'; + + // Set the height based on the scrollHeight but limit it + const newHeight = Math.min(textarea.scrollHeight, lineHeight * maxLines); + textarea.style.height = `${newHeight}px`; + // Update overflow property based on height + this.detailsOverflow = textarea.scrollHeight > newHeight ? 'auto' : 'hidden'; + } + onChooseFile(fileInput: HTMLInputElement): void { + fileInput.click(); + } + onFileSelected(event: Event, controlName: EmFile) { + const input = event.target as HTMLInputElement; if (input.files && input.files.length > 0) { this.selectedFile[controlName] = input.files[0]; this.files[controlName].file = this.selectedFile[controlName]; this.files[controlName].name = this.selectedFile[controlName].name; } - console.log(this.files); } + updateContourLevel(event: Event, controlName: EmFile) { + const input = (event.target as HTMLInputElement).value.trim(); + const normalizedInput = input.replace(',', '.'); + const parsedValue = parseFloat(normalizedInput); + if (!isNaN(parsedValue)) { + this.files[controlName].contour = parsedValue; + } else { + console.warn('Invalid number format:', input); + } + } + updateDetails(event: Event, controlName: EmFile) { + const textarea = event.target as HTMLTextAreaElement; // Cast to HTMLTextAreaElement + const value = textarea.value; + if (this.files[controlName]) { + this.files[controlName].details = value; + } + } + onDepositClick() { + // const filesArray = this.form.get('files') as FormArray; + // filesArray.clear(); + // for (const key in this.files) { + // if (this.files[key].file) { // e.g coordinates or add-map might not be present + // filesArray.push(new FormControl(this.files[key])); + // } + // } + + // const expArray = this.form.get('experiments') as FormArray; + // expArray.clear(); + // expArray.push(new FormControl(this.form.get('emMethod'))); + // const formDataToSend = { + // email: this.form.value.email, + // experiments: this.form.value.experiments, + // metadata: this.form.value.metadata, + // // emdbId: this.form.value.emdbId, + // files: this.form.value.files.map(file => file.file), + // }; + - onDepositClick() { - const formData = this.form.value; - // need to properly catch the dataset details - console.log("creating deposition", formData) - this.http.post("http://localhost:8080/onedep", formData).subscribe( - response => { - console.log('created deposition in OneDep', response); - }, - error => { - console.error('Request failed esf', error); + // // const formData = this.form.value; + // // need to properly catch the dataset details + // console.log("creating deposition", formDataToSend); + // console.log(JSON.stringify(formDataToSend)); + + const filesArray = this.form.get('files') as FormArray; + filesArray.clear(); + const formData = new FormData(); + + // Append the email + formData.append('email', this.form.value.email); + + // Append metadata + formData.append('metadata', JSON.stringify(this.form.value.metadata)); + + // Append experiments + + const experiments = this.form.value.experiments.map(exp => { + return { + type: exp.type, // adjust based on your experiment structure + subtype: exp.subtype // adjust based on your experiment structure + }; + }); + formData.append('experiments', JSON.stringify(experiments)); + + // Append files + for (const key in this.files) { + if (this.files[key].file) { // e.g coordinates or add-map might not be present + formData.append('files', this.files[key].file); } - ); - } + } + + console.log("Creating deposition", formData); + console.log('Files to send:', this.files); + this.http.post("http://localhost:8080/onedep", formData).subscribe( + response => { + console.log('Created deposition in OneDep', response); + }, + error => { + console.error('Request failed', error); + } +); + } } diff --git a/src/app/datasets/onedep/types/methods.enum.ts b/src/app/datasets/onedep/types/methods.enum.ts index 5cefb55be2..fee0239705 100644 --- a/src/app/datasets/onedep/types/methods.enum.ts +++ b/src/app/datasets/onedep/types/methods.enum.ts @@ -15,10 +15,8 @@ export enum EmFile { Coordinates = 'co-cif', Image = 'img-emdb', FSC = 'fsc-xml', - }; - export const EmFiles: { [f in EmFile]: OneDepFile } = { [EmFile.MainMap]: { name: "", @@ -88,12 +86,14 @@ export const MethodsList: EmMethod[] = [ { value: EmType.Tomogram, viewValue: 'Tomogram' }, { value: EmType.ElectronCristallography, viewValue: 'Electron Crystallography' }, ]; -interface OneDepExperiment { + + +export interface OneDepExperiment { type: string; subtype?: string; } -export const Experiment: { [e in EmType]: OneDepExperiment } = { +export const Experiments: { [e in EmType]: OneDepExperiment } = { [EmType.Helical]: { type: "em", subtype: "helical" }, [EmType.SingleParticle]: { type: "em", subtype: "single" }, [EmType.SubtomogramAveraging]: { type: "em", subtype: "subtomogram" }, From 66acf3866f54b648d453b3261701611857cb892d Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Wed, 6 Nov 2024 17:48:42 +0100 Subject: [PATCH 029/245] pass all forms to backend, need input for orcid --- src/app/datasets/onedep/onedep.component.html | 2 +- src/app/datasets/onedep/onedep.component.ts | 94 ++++++------------- 2 files changed, 30 insertions(+), 66 deletions(-) diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index 5a8b368d58..ca292ea3ab 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -64,7 +64,7 @@ experimental method - + {{ method.viewValue }} diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index ac61cf2f6f..d47c5b0f06 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, ViewChild, ElementRef } from "@angular/core"; import { AppConfigService } from "app-config.service"; -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; import { ActivatedRoute, Router } from '@angular/router'; import { FormBuilder, @@ -13,7 +13,8 @@ import { Dataset } from "shared/sdk/models"; import { selectCurrentDataset, } from "state-management/selectors/datasets.selectors"; -import {selectCurrentUser +import { + selectCurrentUser } from "state-management/selectors/user.selectors"; import { User } from "shared/sdk"; import { MethodsList, OneDepExperiment, Experiments, EmFile, EmFiles } from "./types/methods.enum" @@ -35,7 +36,7 @@ export class OneDepComponent implements OnInit { form: FormGroup; showAssociatedMapQuestion: boolean = false; methodsList = MethodsList; - experiments = Experiments; + // experiments = Experiments; experiment: OneDepExperiment selectedFile: { [key: string]: File | null } = {}; emFile = EmFile; @@ -91,9 +92,8 @@ export class OneDepComponent implements OnInit { associatedMap: new FormControl(false), compositeMap: new FormControl(false), emdbId: new FormControl(""), - files: this.fb.array([]), email: this.user.email - }) + }) } hasUnsavedChanges() { @@ -147,69 +147,33 @@ export class OneDepComponent implements OnInit { } } onDepositClick() { - // const filesArray = this.form.get('files') as FormArray; - // filesArray.clear(); - // for (const key in this.files) { - // if (this.files[key].file) { // e.g coordinates or add-map might not be present - // filesArray.push(new FormControl(this.files[key])); - // } - // } - - // const expArray = this.form.get('experiments') as FormArray; - // expArray.clear(); - // expArray.push(new FormControl(this.form.get('emMethod'))); - - // const formDataToSend = { - // email: this.form.value.email, - // experiments: this.form.value.experiments, - // metadata: this.form.value.metadata, - // // emdbId: this.form.value.emdbId, - // files: this.form.value.files.map(file => file.file), - // }; - - - // // const formData = this.form.value; - // // need to properly catch the dataset details - // console.log("creating deposition", formDataToSend); - // console.log(JSON.stringify(formDataToSend)); - - const filesArray = this.form.get('files') as FormArray; - filesArray.clear(); - const formData = new FormData(); - - // Append the email - formData.append('email', this.form.value.email); - - // Append metadata - formData.append('metadata', JSON.stringify(this.form.value.metadata)); - - // Append experiments - - const experiments = this.form.value.experiments.map(exp => { - return { - type: exp.type, // adjust based on your experiment structure - subtype: exp.subtype // adjust based on your experiment structure - }; - }); - formData.append('experiments', JSON.stringify(experiments)); - - // Append files - for (const key in this.files) { - if (this.files[key].file) { // e.g coordinates or add-map might not be present - formData.append('files', this.files[key].file); + const formDataToSend = new FormData(); + formDataToSend.append('email', this.form.value.email); + formDataToSend.append('metadata', JSON.stringify(this.form.value.metadata)); + formDataToSend.append('experiments', this.form.value.emMethod); + // emdbId: this.form.value.emdbId, + + const fileMeta = Object.entries(this.files).reduce((acc, [key, file]) => { + if (file.file) { + formDataToSend.append('file', file.file); + acc[file.name] = { type: file.type, contour: file.contour, details: file.details }; } - } + return acc; + }, {}); + formDataToSend.append('fileMetadata', JSON.stringify(fileMeta)); - console.log("Creating deposition", formData); - console.log('Files to send:', this.files); - this.http.post("http://localhost:8080/onedep", formData).subscribe( - response => { + console.log("Creating deposition", formDataToSend); + this.http.post("http://localhost:8080/onedep", formDataToSend, { + headers: { } + }).subscribe( + response => { console.log('Created deposition in OneDep', response); - }, - error => { - console.error('Request failed', error); - } -); + }, + error => { + console.error('Request failed', error.error); + } + ); + } } From 212fea69b3bb0945ba0f124365e6a1481345af84 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Mon, 11 Nov 2024 15:21:05 +0100 Subject: [PATCH 030/245] wip: send data to onedep api --- src/app/datasets/onedep/onedep.component.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index d47c5b0f06..ad3e6059f4 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -152,20 +152,21 @@ export class OneDepComponent implements OnInit { formDataToSend.append('metadata', JSON.stringify(this.form.value.metadata)); formDataToSend.append('experiments', this.form.value.emMethod); // emdbId: this.form.value.emdbId, + var fileMetadata = [] - const fileMeta = Object.entries(this.files).reduce((acc, [key, file]) => { - if (file.file) { - formDataToSend.append('file', file.file); - acc[file.name] = { type: file.type, contour: file.contour, details: file.details }; + for (const key in this.files) { + if (this.files[key].file) { + formDataToSend.append('file', this.files[key].file); + fileMetadata.push({ name: this.files[key].name, type: this.files[key].type, contour: this.files[key].contour, details: this.files[key].details }); } - return acc; - }, {}); - formDataToSend.append('fileMetadata', JSON.stringify(fileMeta)); + } + console.log(fileMetadata); + formDataToSend.append('fileMetadata', JSON.stringify(fileMetadata)); console.log("Creating deposition", formDataToSend); this.http.post("http://localhost:8080/onedep", formDataToSend, { - headers: { } + headers: {} }).subscribe( response => { console.log('Created deposition in OneDep', response); From d1baed81db3b8eb861f109fffc2e53214b85adcd Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Thu, 21 Nov 2024 16:12:49 +0000 Subject: [PATCH 031/245] add contour level propagates --- src/app/datasets/onedep/onedep.component.html | 9 +- src/app/datasets/onedep/onedep.component.ts | 16 +- src/assets/config.json | 164 +++++++----------- 3 files changed, 78 insertions(+), 111 deletions(-) diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index ca292ea3ab..0d9cc8efe9 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -160,9 +160,12 @@ Contour Level - diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index ad3e6059f4..ba92fbc1fa 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -129,6 +129,20 @@ export class OneDepComponent implements OnInit { this.files[controlName].name = this.selectedFile[controlName].name; } } + updateContourLevelMain(event: Event) { + const input = (event.target as HTMLInputElement).value.trim(); + const normalizedInput = input.replace(',', '.'); + const parsedValue = parseFloat(normalizedInput); + if (!isNaN(parsedValue)) { + [EmFile.MainMap, EmFile.HalfMap1, EmFile.HalfMap2].forEach((key) => { + if (this.files[key]) { + this.files[key].contour = parsedValue; + } + }); + } else { + console.warn('Invalid number format:', input); + } + } updateContourLevel(event: Event, controlName: EmFile) { const input = (event.target as HTMLInputElement).value.trim(); const normalizedInput = input.replace(',', '.'); @@ -160,11 +174,9 @@ export class OneDepComponent implements OnInit { fileMetadata.push({ name: this.files[key].name, type: this.files[key].type, contour: this.files[key].contour, details: this.files[key].details }); } } - console.log(fileMetadata); formDataToSend.append('fileMetadata', JSON.stringify(fileMetadata)); - console.log("Creating deposition", formDataToSend); this.http.post("http://localhost:8080/onedep", formDataToSend, { headers: {} }).subscribe( diff --git a/src/assets/config.json b/src/assets/config.json index 96518ad9d4..a71270c0ee 100644 --- a/src/assets/config.json +++ b/src/assets/config.json @@ -1,119 +1,79 @@ { - "accessTokenPrefix": "Bearer ", - "addDatasetEnabled": true, + "addDatasetEnabled": false, "archiveWorkflowEnabled": false, "datasetReduceEnabled": true, - "datasetJsonScientificMetadata": true, "editDatasetSampleEnabled": true, "editMetadataEnabled": true, - "editPublishedData": false, - "addSampleEnabled": true, - "externalAuthEndpoint": "/auth/msad", - "facility": "Local", - "siteIcon": "site-header-logo.png", - "loginFacilityLabel": "ESS", - "loginLdapLabel": "Ldap", - "loginLocalLabel": "Local", - "loginFacilityEnabled": true, - "loginLdapEnabled": false, - "loginLocalEnabled": true, + "editPublishedData": true, + "editSampleEnabled": true, + "facility": "SAMPLE-SITE", "fileColorEnabled": true, "fileDownloadEnabled": true, "gettingStarted": null, "ingestManual": null, - "jobsEnabled": true, + "jobsEnabled": false, "jsonMetadataEnabled": true, - "jupyterHubUrl": "https://jupyterhub.esss.lu.se/", - "landingPage": "doi.ess.eu/detail/", + "jupyterHubUrl": "", + "landingPage": "", "lbBaseURL": "http://127.0.0.1:3000", - "logbookEnabled": true, - "loginFormEnabled": true, - "maxDirectDownloadSize": 5000000000, - "metadataPreviewEnabled": true, - "metadataStructure": "", - "multipleDownloadAction": "http://localhost:3012/zip", - "multipleDownloadEnabled": true, - "oAuth2Endpoints": [ + "localColumns": [ { - "authURL": "api/v3/auth/oidc", - "displayText": "ESS One Identity" - } - ], - "policiesEnabled": true, - "retrieveDestinations": [], - "riotBaseUrl": "http://scichat.swap.ess.eu", - "scienceSearchEnabled": false, - "scienceSearchUnitsEnabled": true, - "searchPublicDataEnabled": true, - "searchSamples": true, - "sftpHost": "login.esss.dk", - "shareEnabled": true, - "shoppingCartEnabled": true, - "shoppingCartOnHeader": true, - "tableSciDataEnabled": true, - "datasetDetailsShowMissingProposalId": false, - "notificationInterceptorEnabled": true, - "datafilesActionsEnabled": true, - "datafilesActions": [ + "name": "datasetName", + "order": 1, + "type": "standard", + "enabled": true + }, { - "id": "eed8efec-4354-11ef-a3b5-d75573a5d37f", - "order": 4, - "label": "Download All", - "files": "all", - "mat_icon": "download", - "type": "form", - "url": "https://www.scicat.info/download/all", - "target": "_blank", - "enabled": "#SizeLimit", - "authorization": ["#datasetAccess", "#datasetPublic"] + "name": "type", + "order": 2, + "type": "standard", + "enabled": true }, { - "id": "3072fafc-4363-11ef-b9f9-ebf568222d26", + "name": "creationTime", "order": 3, - "label": "Download Selected", - "files": "selected", - "mat_icon": "download", - "type": "form", - "url": "https://www.scicat.info/download/selected", - "target": "_blank", - "enabled": "#Selected && #SizeLimit", - "authorization": ["#datasetAccess", "#datasetPublic"] + "type": "standard", + "enabled": true }, { - "id": "4f974f0e-4364-11ef-9c63-03d19f813f4e", - "order": 2, - "label": "Notebook All", - "files": "all", - "icon": "/assets/icons/jupyter_logo.png", - "type": "form", - "url": "https://www.scicat.info/notebook/all", - "target": "_blank", - "authorization": ["#datasetAccess", "#datasetPublic"] + "name": "proposalId", + "order": 6, + "type": "standard", + "enabled": true }, { - "id": "fa3ce6ee-482d-11ef-95e9-ff2c80dd50bd", - "order": 1, - "label": "Notebook Selected", - "files": "selected", - "icon": "/assets/icons/jupyter_logo.png", - "type": "form", - "url": "https://www.scicat.info/notebook/selected", - "target": "_blank", - "enabled": "#Selected", - "authorization": ["#datasetAccess", "#datasetPublic"] + "name": "image", + "order": 7, + "type": "standard", + "enabled": true + }, + { + "name": "sourceFolder", + "order": 8, + "type": "standard", + "enabled": true } ], - "labelMaps": { - "filters": { - "LocationFilter": "Location", - "PidFilter": "Pid", - "GroupFilter": "Group", - "TypeFilter": "Type", - "KeywordFilter": "Keyword", - "DateRangeFilter": "Start Date - End Date", - "TextFilter": "Text" - } - }, + "logbookEnabled": false, + "loginFormEnabled": true, + "oAuth2Endpoints": [], + "maxDirectDownloadSize": 5000000000, + "metadataPreviewEnabled": true, + "metadataStructure": "tree", + "multipleDownloadAction": "", + "multipleDownloadEnabled": false, + "policiesEnabled": true, + "retrieveDestinations": [], + "riotBaseUrl": "", + "scienceSearchEnabled": true, + "scienceSearchUnitsEnabled": true, + "searchPublicDataEnabled": true, + "searchSamples": true, + "sftpHost": "", + "shareEnabled": false, + "shoppingCartEnabled": false, + "shoppingCartOnHeader": false, + "tableSciDataEnabled": true, "defaultDatasetsListSettings": { "columns": [ { @@ -194,16 +154,8 @@ "type": "standard", "enabled": false } - ], - "filters": [ - { "LocationFilter": true }, - { "PidFilter": true }, - { "GroupFilter": true }, - { "TypeFilter": true }, - { "KeywordFilter": true }, - { "DateRangeFilter": true }, - { "TextFilter": true } - ], - "conditions": [] - } + ] + }, + "accessTokenPrefix": "Bearer ", + "externalAuthEndpoint": "/api/v3/auth/ldap" } From 8bb84d824de46fa71da72ce3279a307aef7d3d5d Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Mon, 25 Nov 2024 16:54:21 +0000 Subject: [PATCH 032/245] add list of ORCID IDs, token not passed yet --- src/app/datasets/datasets.module.ts | 2 + src/app/datasets/onedep/onedep.component.html | 145 ++++++++++++++---- src/app/datasets/onedep/onedep.component.scss | 79 ++++++++-- src/app/datasets/onedep/onedep.component.ts | 21 ++- src/app/datasets/onedep/onedep.directive.ts | 38 +++++ 5 files changed, 241 insertions(+), 44 deletions(-) create mode 100644 src/app/datasets/onedep/onedep.directive.ts diff --git a/src/app/datasets/datasets.module.ts b/src/app/datasets/datasets.module.ts index 8f8d905a14..2c007619cd 100644 --- a/src/app/datasets/datasets.module.ts +++ b/src/app/datasets/datasets.module.ts @@ -91,6 +91,7 @@ import { FiltersModule } from "shared/modules/filters/filters.module"; import { userReducer } from "state-management/reducers/user.reducer"; import { MatSnackBarModule } from "@angular/material/snack-bar"; import { OneDepComponent } from "./onedep/onedep.component"; +import { OrcidFormatterDirective } from "./onedep/onedep.directive"; @NgModule({ imports: [ @@ -181,6 +182,7 @@ import { OneDepComponent } from "./onedep/onedep.component"; DatafilesActionComponent, DatasetsFilterSettingsComponent, OneDepComponent, + OrcidFormatterDirective, ], providers: [ ArchivingService, diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index 0d9cc8efe9..99c0aa40e5 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -18,7 +18,8 @@ Description - + @@ -39,7 +40,8 @@ Keywords - + {{ keyword }} @@ -63,8 +65,10 @@ Method experimental method - - + + {{ method.viewValue }} @@ -73,28 +77,36 @@ - Are you deposing coordinates with this + Are you deposing + coordinates with this submission? (for PDB) - - + + Yes - + No - + Has an associated map been deposited to EMDB? - - + + Yes - + No @@ -114,13 +126,16 @@ - Is this a composite map? + Is this a composite map? + - + Yes - + No @@ -129,57 +144,123 @@ + + +
+ assignment_ind +
+ Administrative +
+ +
+ +
+

Obtain OneDep Token

+

Add instruction how to get it

+ + Token + + +
+ + +
+

Enter 16-digit ORCID iD

+

Owners of these ORCIDs iDs are + allowed to access this deposition.

+
+ +
+ +
+ + Enter 16-digits ORCID iD + + +
+
+
+ +
+
+
+
+ + +
- folder + attachment
Choose files for deposition
- + - {{ fileType.header }} + {{ fileType.header + }}
-
attach_file
- -
-

Selected File: {{ selectedFile[fileType.key].name }}

+ +
+

Selected File: {{ + selectedFile[fileType.key].name }}

- - + + Contour Level - + (input)="fileType.key === 'vo-map' ? updateContourLevelMain($event) : updateContourLevel($event, fileType.key)" /> Details - +
- diff --git a/src/app/datasets/onedep/onedep.component.scss b/src/app/datasets/onedep/onedep.component.scss index 617a0715ae..ad555adaff 100644 --- a/src/app/datasets/onedep/onedep.component.scss +++ b/src/app/datasets/onedep/onedep.component.scss @@ -10,6 +10,62 @@ mat-card { } } + .card-container { + display: flex; + justify-content: space-between; + align-items: start; + padding: 16px; + } + + .card-left { + flex: 1; + margin-right: 16px; + } + + .card-right { + flex: 1; + display: flex; + flex-direction: column; + /* Stack fields vertically */ + } + + + .file-header { + display: flex; + margin-bottom: 16px; + } + + .instruction { + color: #555; + } + + .token-field { + width: 100%; + height: 20px; + margin-bottom: 8px; + margin-top: 0; + } + + .data-field { + width: 30%; + height: 20px; + margin-bottom: 40px; + } + + .add-field-btn { + margin-top: 16px; + color: rgba(0, 0, 0, 0.6); + transition: color 0.3s ease; + } + + .add-field-btn:hover { + color: rgba(0, 0, 0, 0.9); + } + + .add-field-btn mat-icon { + font-size: 24px; + } + table { th { .questionnaire { @@ -64,7 +120,8 @@ mat-card { align-items: center; // Center content vertically padding: 0 16px; // Optional: adjust padding as needed } - mat-card-title{ + + mat-card-title { font-size: 16px; } @@ -77,7 +134,8 @@ mat-card { background-color: #CFE7CB; color: #333; margin: 5px 0 0 0; // Reset any margin - } + } + .fileName { font-size: 14px; // Adjust the font size as needed color: #333; // Adjust the text color if needed @@ -95,37 +153,37 @@ mat-card { .input-container { display: flex; // Use flexbox for layout - align-items: flex-end; + align-items: flex-end; gap: 10px; // Space between the fields .contour-level { flex: 0 0 20%; // Set to take 20% of the width min-width: 100px; // Optional: set a minimum width for usability - + /* Chrome, Safari, Edge, Opera */ input[matinput]::-webkit-outer-spin-button, input[matinput]::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; + -webkit-appearance: none; + margin: 0; } - + /* Firefox */ input[matinput][type=number] { - -moz-appearance: textfield; + -moz-appearance: textfield; } } .details { flex: 1; // Allow this field to take the remaining space min-width: 200px; // Optional: set a minimum width for usability - + mat-form-field { textarea { max-height: calc(5 * 1.5em); // 5 lines max height overflow-y: hidden; // Hide overflow resize: none; // Disable resizing line-height: 1.5; // Set line height - + &:focus { height: auto; // Allow height to grow as needed } @@ -140,6 +198,7 @@ mat-card { } } } + .submitDep { background-color: #B3D9AC; } diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index ba92fbc1fa..5cea0d5a7d 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -7,6 +7,7 @@ import { FormControl, FormGroup, FormArray, + Validators, } from "@angular/forms"; import { Store } from "@ngrx/store"; import { Dataset } from "shared/sdk/models"; @@ -43,6 +44,7 @@ export class OneDepComponent implements OnInit { files = EmFiles; detailsOverflow: string = 'hidden'; + @ViewChild('fileInput') fileInput: ElementRef | undefined; fileTypes = [ @@ -92,16 +94,31 @@ export class OneDepComponent implements OnInit { associatedMap: new FormControl(false), compositeMap: new FormControl(false), emdbId: new FormControl(""), - email: this.user.email + email: this.user.email, + jwtToken: new FormControl(""), + orcid: this.fb.array([]), }) } + get orcidArray(): FormArray { + return this.form.get('orcid') as FormArray; + } + hasUnsavedChanges() { return this._hasUnsavedChanges; } onHasUnsavedChanges($event: boolean) { this._hasUnsavedChanges = $event; } + addOrcidField(): void { + const orcidField = this.fb.group({ + orcidId: ['', [Validators.required, Validators.pattern(/^(\d{4}-){3}\d{4}$/)]], + }); + this.orcidArray.push(orcidField); + } + removeOrcidField(index: number): void { + this.orcidArray.removeAt(index); + } autoGrow(event: Event): void { const textarea = event.target as HTMLTextAreaElement; @@ -163,6 +180,7 @@ export class OneDepComponent implements OnInit { onDepositClick() { const formDataToSend = new FormData(); formDataToSend.append('email', this.form.value.email); + formDataToSend.append('orcidIds', this.form.value.orcidArray); formDataToSend.append('metadata', JSON.stringify(this.form.value.metadata)); formDataToSend.append('experiments', this.form.value.emMethod); // emdbId: this.form.value.emdbId, @@ -176,7 +194,6 @@ export class OneDepComponent implements OnInit { } formDataToSend.append('fileMetadata', JSON.stringify(fileMetadata)); - this.http.post("http://localhost:8080/onedep", formDataToSend, { headers: {} }).subscribe( diff --git a/src/app/datasets/onedep/onedep.directive.ts b/src/app/datasets/onedep/onedep.directive.ts new file mode 100644 index 0000000000..d54b0a2502 --- /dev/null +++ b/src/app/datasets/onedep/onedep.directive.ts @@ -0,0 +1,38 @@ +import { Directive, HostListener, ElementRef, OnInit } from "@angular/core"; + +@Directive({ selector: "[orcidFormatter]" }) +export class OrcidFormatterDirective { + + private readonly maxRawLength = 16; + + constructor(private el: ElementRef) {} + + @HostListener('input', ['$event']) + onInput(event: InputEvent): void { + const inputElement = this.el.nativeElement as HTMLInputElement; + + // Remove all existing dashes and limit to the max length + const rawValue = inputElement.value.replace(/-/g, '').slice(0, this.maxRawLength); + + // Format with dashes + const formattedValue = this.formatWithDashes(rawValue); + + // Update the input's visible value + inputElement.value = formattedValue; + + // Preserve the cursor position + const cursorPosition = this.getAdjustedCursorPosition(rawValue, inputElement.selectionStart || 0); + inputElement.setSelectionRange(cursorPosition, cursorPosition); + } + + private formatWithDashes(value: string): string { + return value.match(/.{1,4}/g)?.join('-') || value; + } + + private getAdjustedCursorPosition(rawValue: string, originalPosition: number): number { + const rawCursorPosition = rawValue.slice(0, originalPosition).length; + const dashCountBeforeCursor = Math.floor(rawCursorPosition / 4); + return rawCursorPosition + dashCountBeforeCursor; + } + +} \ No newline at end of file From 0692b18ba4b28d9e9168c5d776c0b2f019430b81 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Tue, 26 Nov 2024 14:49:47 +0000 Subject: [PATCH 033/245] wip: token and orcid entries working; need to refactor files --- src/app/datasets/onedep/onedep.component.html | 175 +++++++++++------- src/app/datasets/onedep/onedep.component.scss | 21 +++ src/app/datasets/onedep/onedep.component.ts | 11 +- 3 files changed, 132 insertions(+), 75 deletions(-) diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index 99c0aa40e5..c96828f1d7 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -65,13 +65,19 @@ Method experimental method - {{ method.viewValue }} + +
+ You must specify the experimental method +
@@ -82,13 +88,17 @@ submission? (for PDB) - + Yes - + No @@ -100,12 +110,16 @@ - + Yes - No @@ -114,12 +128,15 @@ - + EMDB Identifier EMDB ID - + @@ -129,13 +146,17 @@ Is this a composite map? - + Yes - + No @@ -144,7 +165,8 @@ - + +
assignment_ind @@ -157,11 +179,14 @@

Obtain OneDep Token

Add instruction how to get it

- Token -
@@ -171,30 +196,45 @@

Obtain OneDep Token

Enter 16-digit ORCID iD

Owners of these ORCIDs iDs are allowed to access this deposition.

- +
- Enter 16-digits ORCID iD - + + +
- - + +
+ @@ -208,55 +248,52 @@

Enter 16-digit ORCID iD

Choose files for deposition - - - {{ fileType.header - }} - - -
- -
- attach_file -
- -
-

Selected File: {{ - selectedFile[fileType.key].name }}

+ + + + {{ fileType.header }} + + +
+ +
+ attach_file +
+ +
+

Selected File: {{ selectedFile[fileType.key].name }}

+
-
- -
- - - Contour Level - + +
+ + + Contour Level + + + + + + + + Details + - - - Details - - -
- +
+ + + -
- -
- -
- -
- + - -
@@ -248,33 +226,43 @@

Enter 16-digit ORCID iD

Choose files for deposition - - + + - {{ fileType.header }} + {{ fileType.header + }}
-
attach_file
- -
-

Selected File: {{ selectedFile[fileType.key].name }}

+ +
+

Selected File: {{ + selectedFile[fileType.key].name }} +

- +
- - + + Contour Level - @@ -286,14 +274,18 @@

Enter 16-digit ORCID iD

Details - +
- +
experimental method - + {{ method.viewValue }} +
@@ -141,13 +145,14 @@

Choose Electron Microscopy - + Are you deposing coordinates with this submission? (for PDB) + formControlName="deposingCoordinates" + (change)="onPDB($event)"> Yes @@ -160,7 +165,7 @@

Choose Electron Microscopy + *ngIf="form.value['emMethod'] && form.get('deposingCoordinates')?.value === 'true'"> Has an associated map been deposited to EMDB? @@ -180,7 +185,7 @@

Choose Electron Microscopy + *ngIf="form.value['emMethod'] && form.get('associatedMap').value === 'true' && form.get('deposingCoordinates').value === 'true'"> EMDB Identifier @@ -192,7 +197,7 @@

Choose Electron Microscopy - + Is this a composite map? @@ -211,13 +216,13 @@

Choose Electron Microscopy

- + --> - +
@@ -229,10 +234,10 @@

Choose Electron Microscopy + *ngIf="fileType.emName !== 'co-cif' || form.get('deposingCoordinates').value === 'true'"> - {{ fileType.header - }} + {{ fileType.nameFE}} * +
@@ -241,30 +246,30 @@

Choose Electron Microscopy (click)="onChooseFile(fileInput)"> Choose File -
+
attach_file
-

Selected File: {{ - selectedFile[fileType.key].name }} + selectedFile[fileType.emName].name }}

+ *ngIf="fileType.contour !== undefined"> Contour Level + [value]="fileType.contour || ''" + (input)="fileType.emName === 'vo-map' ? updateContourLevelMain($event) : updateContourLevel($event, fileType.emName)" /> @@ -277,7 +282,7 @@

Choose Electron Microscopy diff --git a/src/app/datasets/onedep/onedep.component.scss b/src/app/datasets/onedep/onedep.component.scss index 5f2bd34f19..8b16ccae5d 100644 --- a/src/app/datasets/onedep/onedep.component.scss +++ b/src/app/datasets/onedep/onedep.component.scss @@ -61,7 +61,7 @@ mat-card { .remove-field-btn { position: absolute; top: 50%; - right: -30%; + right: -40%; transform: translateY(-50%); margin-left: 8px; } diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index 68ada646ef..050444632d 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -18,7 +18,7 @@ import { selectCurrentUser } from "state-management/selectors/user.selectors"; import { User } from "shared/sdk"; -import { MethodsList, OneDepExperiment, Experiments, EmFile, EmFiles } from "./types/methods.enum" +import { MethodsList, OneDepExperiment, EmFile, DepositionFiles } from "./types/methods.enum" import { Subscription, fromEvent } from "rxjs"; @@ -40,23 +40,12 @@ export class OneDepComponent implements OnInit { experiment: OneDepExperiment selectedFile: { [key: string]: File | null } = {}; emFile = EmFile; - files = EmFiles; + fileTypes: DepositionFiles[]; detailsOverflow: string = 'hidden'; @ViewChild('fileInput') fileInput: ElementRef | undefined; - fileTypes = [ - { header: 'Main Map', key: this.emFile.MainMap }, - { header: 'Half Map (1)', key: this.emFile.HalfMap1 }, - { header: 'Half Map (2)', key: this.emFile.HalfMap2 }, - { header: 'Mask Map', key: this.emFile.MaskMap }, - { header: 'Additional Map', key: this.emFile.AddMap }, - { header: 'Coordinates', key: this.emFile.Coordinates }, - { header: 'Public Image', key: this.emFile.Image }, - { header: 'FSC-XML', key: this.emFile.FSC }, - ]; - constructor(public appConfigService: AppConfigService, private store: Store, private http: HttpClient, @@ -95,31 +84,74 @@ export class OneDepComponent implements OnInit { associatedMap: new FormControl(null, Validators.required), compositeMap: new FormControl(null, Validators.required), emdbId: new FormControl(""), - orcid: this.fb.array(['']), + orcid: this.fb.array([ + this.fb.group({ + orcidId: ['', [Validators.required, Validators.pattern(/^(\d{4}-){3}\d{4}$/)]], + }) + ]), }) } - - get orcidArray(): FormArray { - return this.form.get('orcid') as FormArray; - } - hasUnsavedChanges() { return this._hasUnsavedChanges; } onHasUnsavedChanges($event: boolean) { this._hasUnsavedChanges = $event; } - addOrcidField(): void { + orcidArray(): FormArray { + return this.form.get('orcid') as FormArray; + } + addOrcidField() { const orcidField = this.fb.group({ orcidId: ['', [Validators.required, Validators.pattern(/^(\d{4}-){3}\d{4}$/)]], }); - this.orcidArray.push(orcidField); + this.orcidArray().push(orcidField); + } + removeOrcidField(index: number) { + if (this.orcidArray().length > 1) { + this.orcidArray().removeAt(index); + } + } + onMethodChange() { + this.fileTypes = this.methodsList.find(mL => mL.value === this.form.value['emMethod']).files; + console.log("files", this.fileTypes) + this.fileTypes.forEach((fT) => { + if (fT.emName === this.emFile.MainMap || fT.emName === this.emFile.Image) { + fT.required = true; + }else{ + fT.required = false; + } + }); + switch (this.form.value['emMethod']) { + case 'helical': + this.fileTypes.forEach((fT) => { + if (fT.emName === this.emFile.HalfMap1 || fT.emName === this.emFile.HalfMap2) { + fT.required = true; + } + }); + break; + case "single-particle": + this.fileTypes.forEach((fT) => { + if (fT.emName === this.emFile.HalfMap1 || fT.emName === this.emFile.HalfMap2 || fT.emName === this.emFile.MaskMap) { + fT.required = true; + } + }); + break; + } + } - removeOrcidField(index: number): void { - this.orcidArray.removeAt(index); + + onPDB(event: any) { + const input = event.value; + if (input === 'true') { + this.fileTypes.forEach((fT) => { + if (fT.emName === this.emFile.Coordinates ) { + fT.required = true; + } + }); + } } - autoGrow(event: Event): void { + autoGrow(event: Event) { const textarea = event.target as HTMLTextAreaElement; const lineHeight = parseInt(getComputedStyle(textarea).lineHeight, 10); const maxLines = 5; @@ -134,25 +166,38 @@ export class OneDepComponent implements OnInit { // Update overflow property based on height this.detailsOverflow = textarea.scrollHeight > newHeight ? 'auto' : 'hidden'; } - onChooseFile(fileInput: HTMLInputElement): void { + onChooseFile(fileInput: HTMLInputElement) { fileInput.click(); } onFileSelected(event: Event, controlName: EmFile) { const input = event.target as HTMLInputElement; if (input.files && input.files.length > 0) { this.selectedFile[controlName] = input.files[0]; - this.files[controlName].file = this.selectedFile[controlName]; - this.files[controlName].name = this.selectedFile[controlName].name; + this.fileTypes.forEach((fT) => { + if (fT.emName === controlName) { + fT.file = this.selectedFile[controlName]; + fT.fileName = this.selectedFile[controlName].name; + } + }); } } + isRequired(controlName: string): boolean { + let value: boolean; + this.fileTypes.forEach((fT) => { + if (fT.emName === controlName) { + value = fT.required; + } + }); + return value; + } updateContourLevelMain(event: Event) { const input = (event.target as HTMLInputElement).value.trim(); const normalizedInput = input.replace(',', '.'); const parsedValue = parseFloat(normalizedInput); if (!isNaN(parsedValue)) { - [EmFile.MainMap, EmFile.HalfMap1, EmFile.HalfMap2].forEach((key) => { - if (this.files[key]) { - this.files[key].contour = parsedValue; + this.fileTypes.forEach((fT) => { + if (fT.emName === EmFile.MainMap || fT.emName === EmFile.HalfMap1 || fT.emName === EmFile.HalfMap2) { + fT.contour = parsedValue; } }); } else { @@ -164,7 +209,11 @@ export class OneDepComponent implements OnInit { const normalizedInput = input.replace(',', '.'); const parsedValue = parseFloat(normalizedInput); if (!isNaN(parsedValue)) { - this.files[controlName].contour = parsedValue; + this.fileTypes.forEach((fT) => { + if (fT.emName === controlName) { + fT.contour = parsedValue; + } + }); } else { console.warn('Invalid number format:', input); } @@ -172,10 +221,14 @@ export class OneDepComponent implements OnInit { updateDetails(event: Event, controlName: EmFile) { const textarea = event.target as HTMLTextAreaElement; // Cast to HTMLTextAreaElement const value = textarea.value; - if (this.files[controlName]) { - this.files[controlName].details = value; - } + this.fileTypes.forEach((fT) => { + if (fT.emName === controlName) { + fT.details = value; + } + }); + } + onDepositClick() { const formDataToSend = new FormData(); formDataToSend.append('email', this.form.value.email); @@ -185,12 +238,13 @@ export class OneDepComponent implements OnInit { // emdbId: this.form.value.emdbId, var fileMetadata = [] - for (const key in this.files) { - if (this.files[key].file) { - formDataToSend.append('file', this.files[key].file); - fileMetadata.push({ name: this.files[key].name, type: this.files[key].type, contour: this.files[key].contour, details: this.files[key].details }); + // for (const fI in this.fileTypes) { + this.fileTypes.forEach((fT) => { + if (fT.file) { + formDataToSend.append('file', fT.file); + fileMetadata.push({ name: fT.fileName, type: fT.type, contour: fT.contour, details: fT.details }); } - } + }); formDataToSend.append('fileMetadata', JSON.stringify(fileMetadata)); this.http.post("http://localhost:8080/onedep", formDataToSend, { headers: {} @@ -205,34 +259,34 @@ export class OneDepComponent implements OnInit { } - onCreateClick(){ - let bearer = 'Bearer ' + this.form.value['jwtToken']; - const headers = new HttpHeaders() - .append( - 'Content-Type', - 'application/json' - ) - .append( - 'Authorization', - bearer - ); + // onCreateClick() { + // let bearer = 'Bearer ' + this.form.value['jwtToken']; + // const headers = new HttpHeaders() + // .append( + // 'Content-Type', + // 'application/json' + // ) + // .append( + // 'Authorization', + // bearer + // ); - const body=JSON.stringify( - { - "email": "sofya.laskina@epfl.ch", - "users": ["0009-0003-3665-5367"], - "country": "United States", - "experiments": [Experiments[this.form.value.emMethod]], - } - ); + // const body = JSON.stringify( + // { + // "email": "sofya.laskina@epfl.ch", + // "users": ["0009-0003-3665-5367"], + // "country": "United States", + // "experiments": [Experiments[this.form.value.emMethod]], + // } + // ); - this.http - .post('https://onedep-depui-test.wwpdb.org/deposition/api/v1/depositions/new', body, { - headers: headers, - }) - .subscribe((res) => console.log(res)); + // this.http + // .post('https://onedep-depui-test.wwpdb.org/deposition/api/v1/depositions/new', body, { + // headers: headers, + // }) + // .subscribe((res) => console.log(res)); - } + // } } diff --git a/src/app/datasets/onedep/types/methods.enum.ts b/src/app/datasets/onedep/types/methods.enum.ts index fee0239705..008503e18d 100644 --- a/src/app/datasets/onedep/types/methods.enum.ts +++ b/src/app/datasets/onedep/types/methods.enum.ts @@ -15,96 +15,218 @@ export enum EmFile { Coordinates = 'co-cif', Image = 'img-emdb', FSC = 'fsc-xml', + LayerLines = "layer-lines", + StructureFactors = "xs-cif", + MTZ = "xs-mtz", }; -export const EmFiles: { [f in EmFile]: OneDepFile } = { - [EmFile.MainMap]: { - name: "", - type: "vo-map", - file: null, - contour: 0.0, - details: "", - }, - [EmFile.HalfMap1]: { - name: "", - type: "half-map", - file: null, - contour: 0.0, - details: "", - }, - [EmFile.HalfMap2]: { - name: "", - type: "half-map", - file: null, - contour: 0.0, - details: "", - }, - [EmFile.MaskMap]: { - name: "", - type: "mask-map", - file: null, - contour: 0.0, - details: "", - }, - [EmFile.AddMap]: { - name: "", - type: "add-map", - file: null, - contour: 0.0, - details: "", - }, - [EmFile.Coordinates]: { - name: "", - type: "co-cif", - file: null, - details: "", - }, - [EmFile.Image]: { - name: "", - type: "img-emdb", - file: null, - details: "", - }, - [EmFile.FSC]: { - name: "", - type: "fsc-xml", - file: null, - details: "", - }, -}; - -interface EmMethod { - value: EmType; - viewValue: string; -} - - -export const MethodsList: EmMethod[] = [ - { value: EmType.Helical, viewValue: 'Helical' }, - { value: EmType.SingleParticle, viewValue: 'Single Particle' }, - { value: EmType.SubtomogramAveraging, viewValue: 'Subtomogram Averaging' }, - { value: EmType.Tomogram, viewValue: 'Tomogram' }, - { value: EmType.ElectronCristallography, viewValue: 'Electron Crystallography' }, -]; - - export interface OneDepExperiment { type: string; subtype?: string; } -export const Experiments: { [e in EmType]: OneDepExperiment } = { - [EmType.Helical]: { type: "em", subtype: "helical" }, - [EmType.SingleParticle]: { type: "em", subtype: "single" }, - [EmType.SubtomogramAveraging]: { type: "em", subtype: "subtomogram" }, - [EmType.Tomogram]: { type: "em", subtype: "tomography" }, - [EmType.ElectronCristallography]: { type: "ec" } -}; -export interface OneDepFile { - name: string, +export interface DepositionFiles { + emName: EmFile; + nameFE: string; type: string, + fileName: string, file: File, contour?: number, details?: string, + required: boolean, +} + +interface EmMethod { + value: EmType; + viewValue: string; + experiment: OneDepExperiment; + files: DepositionFiles[]; +} + +export const DepositionImage: DepositionFiles ={ + emName: EmFile.Image, + nameFE: 'Public Image', + type: "img-emdb", + fileName: "", + file: null, + details: "", + required: false, +} +export const DepositionMainMap:DepositionFiles ={ + emName: EmFile.MainMap, + nameFE: 'Main Map', + type: "vo-map", + fileName: "", + file: null, + contour: 0.0, + details: "", + required: false, +} +export const DepositionHalfMap1:DepositionFiles ={ + emName: EmFile.HalfMap1, + nameFE: 'Half Map (1)', + type: "half-map", + fileName: "", + file: null, + contour: 0.0, + details: "", + required: false, +} +export const DepositionHalfMap2:DepositionFiles ={ + emName: EmFile.HalfMap2, + nameFE: 'Half Map (2)', + type: "half-map", + fileName: "", + file: null, + contour: 0.0, + details: "", + required: false, +} +export const DepositionMaskMap:DepositionFiles ={ + emName: EmFile.MaskMap, + nameFE: 'Mask Map', + type: "mask-map", + fileName: "", + file: null, + contour: 0.0, + details: "", + required: false, +} +export const DepositionAddMap:DepositionFiles ={ + emName: EmFile.AddMap, + nameFE: 'Additional Map', + type: "add-map", + fileName: "", + file: null, + contour: 0.0, + details: "", + required: false, +} +export const DepositionFSC:DepositionFiles ={ + emName: EmFile.FSC, + nameFE: 'FSC-XML', + type: "fsc-xml", + fileName: "", + file: null, + details: "", + required: false, } +export const DepositionLayerLines:DepositionFiles ={ + emName: EmFile.LayerLines, + nameFE: 'Other: Layer Lines Data ', + type: "layer-lines", + fileName: "", + file: null, + details: "", + required: false, +} +export const DepositionCoordinates: DepositionFiles = { + emName: EmFile.Coordinates, + nameFE: 'Coordinates', + type: "co-cif", + fileName: "", + file: null, + details: "", + required: false, +} +export const DepositionStructureFactors: DepositionFiles = { + emName: EmFile.StructureFactors, + nameFE: 'Structure Factors', + type: "xs-cif", + fileName: "", + file: null, + details: "", + required: false, +} +export const DepositionMTZ: DepositionFiles = { + emName: EmFile.MTZ, + nameFE: 'MTZ', + type: "xs-mtz", + fileName: "", + file: null, + details: "", + required: false, +} + +export const MethodsList: EmMethod[] = [ + { + value: EmType.Helical, + viewValue: 'Helical', + experiment: { type: "em", subtype: "helical" }, + files: [ + DepositionMainMap, + DepositionMaskMap, + DepositionHalfMap1, + DepositionHalfMap2, + DepositionAddMap, + DepositionCoordinates, + DepositionImage, + DepositionFSC, + DepositionLayerLines, + ], + }, + { + value: EmType.SingleParticle, + viewValue: 'Single Particle', + experiment: { type: "em", subtype: "single" }, + files: [ + DepositionMainMap, + DepositionMaskMap, + DepositionHalfMap1, + DepositionHalfMap2, + DepositionAddMap, + DepositionCoordinates, + DepositionImage, + DepositionFSC, + DepositionLayerLines, + ], + }, + { + value: EmType.SubtomogramAveraging, + viewValue: 'Subtomogram Averaging', + experiment: { type: "em", subtype: "subtomogram" }, + files:[ + DepositionMainMap, + DepositionMaskMap, + DepositionHalfMap1, + DepositionHalfMap2, + DepositionAddMap, + DepositionCoordinates, + DepositionImage, + DepositionFSC, + DepositionLayerLines, + ], + }, + { + value: EmType.Tomogram, + viewValue: 'Tomogram', + experiment: { type: "em", subtype: "tomography" }, + files: [ + DepositionMainMap, + DepositionMaskMap, + DepositionAddMap, + DepositionImage, + DepositionFSC, + DepositionLayerLines, + ], + }, + { + value: EmType.ElectronCristallography, + viewValue: 'Electron Crystallography', + experiment: { type: "ec" }, + files: [ + DepositionMainMap, + DepositionMaskMap, + DepositionHalfMap1, + DepositionHalfMap2, + DepositionStructureFactors, + DepositionMTZ, + DepositionAddMap, + DepositionCoordinates, + DepositionImage, + DepositionFSC, + DepositionLayerLines, + ], + }, +]; \ No newline at end of file From 2986ab1c6f164c47b9932118b1592982dff012e2 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Tue, 3 Dec 2024 16:27:02 +0000 Subject: [PATCH 036/245] specify many add maps --- src/app/datasets/onedep/onedep.component.html | 35 ++++++++--- src/app/datasets/onedep/onedep.component.scss | 4 ++ src/app/datasets/onedep/onedep.component.ts | 60 ++++++++++++++++++- src/app/datasets/onedep/types/methods.enum.ts | 30 +++++----- 4 files changed, 106 insertions(+), 23 deletions(-) diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index 54aecdb542..abb7adc38a 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -249,15 +249,23 @@

Choose Electron Microscopy
attach_file
+ +
+ attach_file +
-
+

Selected File: {{ selectedFile[fileType.emName]?.name }}

+
+
-

Selected File: {{ - selectedFile[fileType.emName].name }} -

+

Selected File: {{ selectedFile['add-map-' + fileType.id]?.name }}

+

@@ -269,7 +277,11 @@

Choose Electron Microscopy + (input)="fileType.emName === 'vo-map' ? updateContourLevelMain($event) : + fileType.emName === 'add-map' ? updateContourLevelAddMap($event, fileType.id) : + updateContourLevel($event, fileType.emName)" /> + + @@ -282,13 +294,22 @@

Choose Electron Microscopy

+
+ +
diff --git a/src/app/datasets/onedep/onedep.component.scss b/src/app/datasets/onedep/onedep.component.scss index 8b16ccae5d..2118ed5cd3 100644 --- a/src/app/datasets/onedep/onedep.component.scss +++ b/src/app/datasets/onedep/onedep.component.scss @@ -64,6 +64,7 @@ mat-card { right: -40%; transform: translateY(-50%); margin-left: 8px; + color: rgba(0, 0, 0, 0.6); } .add-field-btn { @@ -72,6 +73,9 @@ mat-card { transition: color 0.3s ease; } + button[mat-icon-button]:hover { + background-color: rgba(56, 178, 73, 0.1); /* Subtle background highlight on hover */ + } .add-field-btn:hover { color: rgba(0, 0, 0, 0.9); } diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index 050444632d..ffbbcd77ec 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -18,7 +18,7 @@ import { selectCurrentUser } from "state-management/selectors/user.selectors"; import { User } from "shared/sdk"; -import { MethodsList, OneDepExperiment, EmFile, DepositionFiles } from "./types/methods.enum" +import { MethodsList, OneDepExperiment, EmFile, DepositionFiles, DepositionAddMap } from "./types/methods.enum" import { Subscription, fromEvent } from "rxjs"; @@ -42,6 +42,7 @@ export class OneDepComponent implements OnInit { emFile = EmFile; fileTypes: DepositionFiles[]; detailsOverflow: string = 'hidden'; + additionalMaps = 0; @ViewChild('fileInput') fileInput: ElementRef | undefined; @@ -113,7 +114,6 @@ export class OneDepComponent implements OnInit { } onMethodChange() { this.fileTypes = this.methodsList.find(mL => mL.value === this.form.value['emMethod']).files; - console.log("files", this.fileTypes) this.fileTypes.forEach((fT) => { if (fT.emName === this.emFile.MainMap || fT.emName === this.emFile.Image) { fT.required = true; @@ -181,6 +181,19 @@ export class OneDepComponent implements OnInit { }); } } + onFileAddMapSelected(event: Event, id: number) { + const input = event.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + // Use the ID to store the file uniquely for each "add-map" + this.selectedFile[`add-map-${id}`] = input.files[0]; + this.fileTypes.forEach((fT) => { + if (fT.emName === this.emFile.AddMap && fT.id === id) { + fT.file = this.selectedFile[`add-map-${id}`]; + fT.fileName = this.selectedFile[`add-map-${id}`].name; + } + }); + } +} isRequired(controlName: string): boolean { let value: boolean; this.fileTypes.forEach((fT) => { @@ -204,6 +217,20 @@ export class OneDepComponent implements OnInit { console.warn('Invalid number format:', input); } } + updateContourLevelAddMap(event: Event, id: number) { + const input = (event.target as HTMLInputElement).value.trim(); + const normalizedInput = input.replace(',', '.'); + const parsedValue = parseFloat(normalizedInput); + if (!isNaN(parsedValue)) { + this.fileTypes.forEach((fT) => { + if (fT.emName === this.emFile.AddMap && fT.id === id) { + fT.contour = parsedValue; + } + }); + } else { + console.warn('Invalid number format:', input); + } + } updateContourLevel(event: Event, controlName: EmFile) { const input = (event.target as HTMLInputElement).value.trim(); const normalizedInput = input.replace(',', '.'); @@ -228,6 +255,35 @@ export class OneDepComponent implements OnInit { }); } + updateDetailsAddMap(event: Event, id:number) { + const textarea = event.target as HTMLTextAreaElement; // Cast to HTMLTextAreaElement + const value = textarea.value; + this.fileTypes.forEach((fT) => { + if (fT.emName === this.emFile.AddMap && fT.id === id) { + fT.details = value; + } + }); + + } + addMap(){ + const nextId = this.fileTypes + .filter(file => file.emName === EmFile.AddMap) + .reduce((maxId, file) => (file.id > maxId ? file.id : maxId), 0) + 1; + + const newMap: DepositionFiles = { + emName: EmFile.AddMap, + id: nextId, + nameFE: 'Additional Map ( ' + (nextId+1).toString() + ' )', + type: "add-map", + fileName: "", + file: null, + contour: 0.0, + details: "", + required: false, + }; + + this.fileTypes.push(newMap); + } onDepositClick() { const formDataToSend = new FormData(); diff --git a/src/app/datasets/onedep/types/methods.enum.ts b/src/app/datasets/onedep/types/methods.enum.ts index 008503e18d..388d089b3f 100644 --- a/src/app/datasets/onedep/types/methods.enum.ts +++ b/src/app/datasets/onedep/types/methods.enum.ts @@ -27,6 +27,7 @@ export interface OneDepExperiment { export interface DepositionFiles { emName: EmFile; + id?: number, nameFE: string; type: string, fileName: string, @@ -94,6 +95,7 @@ export const DepositionMaskMap:DepositionFiles ={ } export const DepositionAddMap:DepositionFiles ={ emName: EmFile.AddMap, + id:0, nameFE: 'Additional Map', type: "add-map", fileName: "", @@ -155,15 +157,15 @@ export const MethodsList: EmMethod[] = [ viewValue: 'Helical', experiment: { type: "em", subtype: "helical" }, files: [ + DepositionImage, + DepositionCoordinates, DepositionMainMap, DepositionMaskMap, DepositionHalfMap1, DepositionHalfMap2, - DepositionAddMap, - DepositionCoordinates, - DepositionImage, DepositionFSC, DepositionLayerLines, + DepositionAddMap, ], }, { @@ -171,15 +173,15 @@ export const MethodsList: EmMethod[] = [ viewValue: 'Single Particle', experiment: { type: "em", subtype: "single" }, files: [ + DepositionImage, + DepositionCoordinates, DepositionMainMap, DepositionMaskMap, DepositionHalfMap1, DepositionHalfMap2, - DepositionAddMap, - DepositionCoordinates, - DepositionImage, DepositionFSC, DepositionLayerLines, + DepositionAddMap, ], }, { @@ -187,15 +189,15 @@ export const MethodsList: EmMethod[] = [ viewValue: 'Subtomogram Averaging', experiment: { type: "em", subtype: "subtomogram" }, files:[ + DepositionImage, + DepositionCoordinates, DepositionMainMap, DepositionMaskMap, DepositionHalfMap1, DepositionHalfMap2, - DepositionAddMap, - DepositionCoordinates, - DepositionImage, DepositionFSC, DepositionLayerLines, + DepositionAddMap, ], }, { @@ -203,12 +205,12 @@ export const MethodsList: EmMethod[] = [ viewValue: 'Tomogram', experiment: { type: "em", subtype: "tomography" }, files: [ + DepositionImage, DepositionMainMap, DepositionMaskMap, - DepositionAddMap, - DepositionImage, DepositionFSC, DepositionLayerLines, + DepositionAddMap, ], }, { @@ -216,17 +218,17 @@ export const MethodsList: EmMethod[] = [ viewValue: 'Electron Crystallography', experiment: { type: "ec" }, files: [ + DepositionImage, + DepositionCoordinates, DepositionMainMap, DepositionMaskMap, DepositionHalfMap1, DepositionHalfMap2, DepositionStructureFactors, DepositionMTZ, - DepositionAddMap, - DepositionCoordinates, - DepositionImage, DepositionFSC, DepositionLayerLines, + DepositionAddMap, ], }, ]; \ No newline at end of file From 6b8f2e67178b5b1bd0536a2cfca20a50f9350179 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Wed, 4 Dec 2024 16:43:00 +0000 Subject: [PATCH 037/245] restruct sending requests --- src/app/datasets/onedep/onedep.component.html | 3 +- src/app/datasets/onedep/onedep.component.ts | 135 +++++++++++------- src/app/datasets/onedep/types/methods.enum.ts | 10 -- 3 files changed, 85 insertions(+), 63 deletions(-) diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index abb7adc38a..b02eaa1013 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -275,7 +275,8 @@

Choose Electron Microscopy class="contour-level"> Contour Level 1) { + if (this.orcidArray().length > 1) { this.orcidArray().removeAt(index); } } @@ -117,7 +115,7 @@ export class OneDepComponent implements OnInit { this.fileTypes.forEach((fT) => { if (fT.emName === this.emFile.MainMap || fT.emName === this.emFile.Image) { fT.required = true; - }else{ + } else { fT.required = false; } }); @@ -144,7 +142,7 @@ export class OneDepComponent implements OnInit { const input = event.value; if (input === 'true') { this.fileTypes.forEach((fT) => { - if (fT.emName === this.emFile.Coordinates ) { + if (fT.emName === this.emFile.Coordinates) { fT.required = true; } }); @@ -184,16 +182,16 @@ export class OneDepComponent implements OnInit { onFileAddMapSelected(event: Event, id: number) { const input = event.target as HTMLInputElement; if (input.files && input.files.length > 0) { - // Use the ID to store the file uniquely for each "add-map" - this.selectedFile[`add-map-${id}`] = input.files[0]; - this.fileTypes.forEach((fT) => { - if (fT.emName === this.emFile.AddMap && fT.id === id) { - fT.file = this.selectedFile[`add-map-${id}`]; - fT.fileName = this.selectedFile[`add-map-${id}`].name; - } - }); + // Use the ID to store the file uniquely for each "add-map" + this.selectedFile[`add-map-${id}`] = input.files[0]; + this.fileTypes.forEach((fT) => { + if (fT.emName === this.emFile.AddMap && fT.id === id) { + fT.file = this.selectedFile[`add-map-${id}`]; + fT.fileName = this.selectedFile[`add-map-${id}`].name; + } + }); } -} + } isRequired(controlName: string): boolean { let value: boolean; this.fileTypes.forEach((fT) => { @@ -255,7 +253,7 @@ export class OneDepComponent implements OnInit { }); } - updateDetailsAddMap(event: Event, id:number) { + updateDetailsAddMap(event: Event, id: number) { const textarea = event.target as HTMLTextAreaElement; // Cast to HTMLTextAreaElement const value = textarea.value; this.fileTypes.forEach((fT) => { @@ -265,53 +263,86 @@ export class OneDepComponent implements OnInit { }); } - addMap(){ + addMap() { const nextId = this.fileTypes - .filter(file => file.emName === EmFile.AddMap) - .reduce((maxId, file) => (file.id > maxId ? file.id : maxId), 0) + 1; + .filter(file => file.emName === EmFile.AddMap) + .reduce((maxId, file) => (file.id > maxId ? file.id : maxId), 0) + 1; const newMap: DepositionFiles = { - emName: EmFile.AddMap, - id: nextId, - nameFE: 'Additional Map ( ' + (nextId+1).toString() + ' )', - type: "add-map", - fileName: "", - file: null, - contour: 0.0, - details: "", - required: false, + emName: EmFile.AddMap, + id: nextId, + nameFE: 'Additional Map ( ' + (nextId + 1).toString() + ' )', + type: "add-map", + fileName: "", + file: null, + contour: 0.0, + details: "", + required: false, }; this.fileTypes.push(newMap); } - + sendFollowUpRequests(depID: string, form: FormData) { + this.http.post("http://localhost:8080/onedep/${depID}", form ).subscribe({ + next: (res) => console.log('Uploaded File and Metadata', res), + error: (error) => console.error('Could not upload File and Metadata', error), + }); + } onDepositClick() { - const formDataToSend = new FormData(); - formDataToSend.append('email', this.form.value.email); - formDataToSend.append('orcidIds', this.form.value.orcidArray); - formDataToSend.append('metadata', JSON.stringify(this.form.value.metadata)); - formDataToSend.append('experiments', this.form.value.emMethod); - // emdbId: this.form.value.emdbId, - var fileMetadata = [] - - // for (const fI in this.fileTypes) { - this.fileTypes.forEach((fT) => { - if (fT.file) { - formDataToSend.append('file', fT.file); - fileMetadata.push({ name: fT.fileName, type: fT.type, contour: fT.contour, details: fT.details }); + // Create a deposition + console.log(this.orcidArray().value.map(item => item.orcidId)); + const body = JSON.stringify( + { + "email": "sofya.laskina@epfl.ch", // for now + "orcidIds": this.orcidArray().value.map(item => item.orcidId), + "country": "United States", + "method": this.form.value.emMethod, + "jwtToken": this.form.value.jwtToken, } - }); - formDataToSend.append('fileMetadata', JSON.stringify(fileMetadata)); - this.http.post("http://localhost:8080/onedep", formDataToSend, { + ); + let depID: string; + this.http.post("http://localhost:8080/onedep", body, { headers: {} - }).subscribe( - response => { - console.log('Created deposition in OneDep', response); + }).subscribe({ + next: (response: any) => { + depID = response.depID; // Update the outer variable + console.log('Created deposition in OneDep', depID); + + // Call subsequent requests + this.fileTypes.forEach((fT) => { + if (fT.file) { + const formDataFile = new FormData() + formDataFile.append('jwtToken', this.form.value.jwtToken ) + formDataFile.append('file', fT.file); + formDataFile.append('fileMetadata', JSON.stringify({ name: fT.fileName, type: fT.type, contour: fT.contour, details: fT.details })) + console.log(formDataFile) + this.sendFollowUpRequests(depID, formDataFile); + } + }); + }, - error => { - console.error('Request failed', error.error); - } - ); + error: (error) => console.error('Request failed', error.error), + }); + + depID + // const formDataFile = new FormData(); + // formDataToSend.append('jwtToken', this.form.value.jwtToken); + // formDataToSend.append('metadata', JSON.stringify(this.form.value.metadata)); + + // var fileMetadata = [] + // // for (const fI in this.fileTypes) { + // + // formDataToSend.append('fileMetadata', JSON.stringify(fileMetadata)); + // this.http.post("http://localhost:8080/onedep", formDataToSend, { + // headers: {} + // }).subscribe( + // response => { + // console.log('Created deposition in OneDep', response); + // }, + // error => { + // console.error('Request failed', error.error); + // } + // ); } diff --git a/src/app/datasets/onedep/types/methods.enum.ts b/src/app/datasets/onedep/types/methods.enum.ts index 388d089b3f..ab39c9a87e 100644 --- a/src/app/datasets/onedep/types/methods.enum.ts +++ b/src/app/datasets/onedep/types/methods.enum.ts @@ -20,10 +20,6 @@ export enum EmFile { MTZ = "xs-mtz", }; -export interface OneDepExperiment { - type: string; - subtype?: string; -} export interface DepositionFiles { emName: EmFile; @@ -40,7 +36,6 @@ export interface DepositionFiles { interface EmMethod { value: EmType; viewValue: string; - experiment: OneDepExperiment; files: DepositionFiles[]; } @@ -155,7 +150,6 @@ export const MethodsList: EmMethod[] = [ { value: EmType.Helical, viewValue: 'Helical', - experiment: { type: "em", subtype: "helical" }, files: [ DepositionImage, DepositionCoordinates, @@ -171,7 +165,6 @@ export const MethodsList: EmMethod[] = [ { value: EmType.SingleParticle, viewValue: 'Single Particle', - experiment: { type: "em", subtype: "single" }, files: [ DepositionImage, DepositionCoordinates, @@ -187,7 +180,6 @@ export const MethodsList: EmMethod[] = [ { value: EmType.SubtomogramAveraging, viewValue: 'Subtomogram Averaging', - experiment: { type: "em", subtype: "subtomogram" }, files:[ DepositionImage, DepositionCoordinates, @@ -203,7 +195,6 @@ export const MethodsList: EmMethod[] = [ { value: EmType.Tomogram, viewValue: 'Tomogram', - experiment: { type: "em", subtype: "tomography" }, files: [ DepositionImage, DepositionMainMap, @@ -216,7 +207,6 @@ export const MethodsList: EmMethod[] = [ { value: EmType.ElectronCristallography, viewValue: 'Electron Crystallography', - experiment: { type: "ec" }, files: [ DepositionImage, DepositionCoordinates, From 2b9750c718c5f03eaef402e6c08c54f85511e395 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Thu, 5 Dec 2024 10:03:02 +0000 Subject: [PATCH 038/245] communication with OneDep works --- src/app/datasets/onedep/onedep.component.ts | 82 ++++++++++++++------- 1 file changed, 54 insertions(+), 28 deletions(-) diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index a85036002c..aece4d008c 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -282,15 +282,29 @@ export class OneDepComponent implements OnInit { this.fileTypes.push(newMap); } - sendFollowUpRequests(depID: string, form: FormData) { - this.http.post("http://localhost:8080/onedep/${depID}", form ).subscribe({ - next: (res) => console.log('Uploaded File and Metadata', res), + sendFile(depID: string, form: FormData) { + this.http.post("http://localhost:8080/onedep/" + depID + "/file", form).subscribe({ + next: (res) => console.log('Uploaded', this.emFile[form.get("fileMetadata")["type"]],res), error: (error) => console.error('Could not upload File and Metadata', error), }); } + sendCoordFile(depID: string, form: FormData) { + this.http.post("http://localhost:8080/onedep/" + depID + "/pdb", form).subscribe({ + next: (res) => console.log('Uploaded Coordinates and Metadata', res), + error: (error) => console.error('Could not upload Coordinates and Metadata', error), + }); + } + sendMetadata(depID: string, body: string) { + // missing token! + this.http.post("http://localhost:8080/onedep/" + depID + "/metadata", body, { + headers: { 'Content-Type': 'application/json' }, + }).subscribe({ + next: (res) => console.log('Uploaded Metadata', res), + error: (error) => console.error('Could not upload Metadata', error), + }); + } onDepositClick() { // Create a deposition - console.log(this.orcidArray().value.map(item => item.orcidId)); const body = JSON.stringify( { "email": "sofya.laskina@epfl.ch", // for now @@ -301,33 +315,45 @@ export class OneDepComponent implements OnInit { } ); let depID: string; - this.http.post("http://localhost:8080/onedep", body, { - headers: {} - }).subscribe({ - next: (response: any) => { - depID = response.depID; // Update the outer variable - console.log('Created deposition in OneDep', depID); - - // Call subsequent requests - this.fileTypes.forEach((fT) => { - if (fT.file) { - const formDataFile = new FormData() - formDataFile.append('jwtToken', this.form.value.jwtToken ) - formDataFile.append('file', fT.file); - formDataFile.append('fileMetadata', JSON.stringify({ name: fT.fileName, type: fT.type, contour: fT.contour, details: fT.details })) - console.log(formDataFile) - this.sendFollowUpRequests(depID, formDataFile); - } - }); - - }, - error: (error) => console.error('Request failed', error.error), + let metadataAdded = false; + depID = "D_800043"; + // this.http.post("http://localhost:8080/onedep", body, { + // headers: { 'Content-Type': 'application/json' }, + // }).subscribe({ + // next: (response: any) => { + // depID = response.depID; // Update the outer variable + // console.log('Created deposition in OneDep', depID); + + // // Call subsequent requests + this.fileTypes.forEach((fT) => { + if (fT.file) { + const formDataFile = new FormData() + formDataFile.append('jwtToken', this.form.value.jwtToken) + formDataFile.append('file', fT.file); + formDataFile.append('fileMetadata', JSON.stringify({ name: fT.fileName, type: fT.type, contour: fT.contour, details: fT.details })) + if (fT.emName === this.emFile.Coordinates) { + formDataFile.append('metadata', JSON.stringify(this.form.value.metadata)); + this.sendCoordFile(depID, formDataFile); + metadataAdded = true + } else { + this.sendFile(depID, formDataFile); + } + } }); - - depID + if (! metadataAdded){ + const metadataBody = JSON.stringify({ + metadata: this.form.value.metadata, + jwtToken: this.form.value.jwtToken , + }); + this.sendMetadata(depID, metadataBody); + } + // }, + // error: (error) => console.error('Request failed', error.error), + // }); + // const formDataFile = new FormData(); // formDataToSend.append('jwtToken', this.form.value.jwtToken); - // formDataToSend.append('metadata', JSON.stringify(this.form.value.metadata)); + // // var fileMetadata = [] // // for (const fI in this.fileTypes) { From 18f5cf0a0dbd002bb7274414abd77d0c4bd7ba08 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Mon, 9 Dec 2024 09:27:49 +0000 Subject: [PATCH 039/245] ORCID field of same size no matter window size --- src/app/datasets/onedep/onedep.component.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/datasets/onedep/onedep.component.scss b/src/app/datasets/onedep/onedep.component.scss index 2118ed5cd3..b81300843a 100644 --- a/src/app/datasets/onedep/onedep.component.scss +++ b/src/app/datasets/onedep/onedep.component.scss @@ -181,7 +181,11 @@ mat-card { .fileChooser { margin: 3px auto; } - + .data-field { + width: 350px; /* Fixed width to accommodate 16 digits, 3 dashes, and button */ + margin-bottom: 40px; + position: relative; /* Necessary for child positioning */ + } .input-container { display: flex; // Use flexbox for layout align-items: flex-end; From d2ed3818ce6832882ba16080c0c5706271880a5f Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Mon, 9 Dec 2024 16:52:20 +0000 Subject: [PATCH 040/245] added comments to input file on hover --- src/app/datasets/onedep/onedep.component.html | 11 +++---- src/app/datasets/onedep/onedep.component.scss | 29 +++++++------------ src/app/datasets/onedep/onedep.component.ts | 1 + src/app/datasets/onedep/types/methods.enum.ts | 9 ++++++ 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index b02eaa1013..85dbf9397e 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -1,7 +1,7 @@
-
-
+
+
@@ -97,8 +97,9 @@

Enter 16-digit ORCID iD

maxlength="19" placeholder="xxxx-xxxx-xxxx-xxxx" formControlName="orcidId" - orcidFormatter> - +

Enter 16-digit ORCID iD

diff --git a/src/app/datasets/onedep/onedep.component.scss b/src/app/datasets/onedep/onedep.component.scss index d1efd128dc..1884b5fec2 100644 --- a/src/app/datasets/onedep/onedep.component.scss +++ b/src/app/datasets/onedep/onedep.component.scss @@ -1,6 +1,5 @@ mat-card { margin: 1em; - .section-icon { height: auto !important; width: auto !important; @@ -16,21 +15,31 @@ mat-card { align-items: flex-start; gap: 16px; padding: 16px; - margin-bottom: 16px; + margin-bottom: 20px; } - .card-left { - flex: 1; - } + .card-left, + .card-middle, + .card-right { + flex: 0 0 33%; + display: flex; + flex-direction: column; + gap: 1rem; + box-sizing: border-box; + padding: 1rem; - .card-middle { - flex: 1; } - .card-right { - flex: 1; + .card-left { + overflow-y: auto; /* Allows content to scroll if it overflows */ + } + h2 { + font-size: 1.2rem; + margin-top: 0; + } + h2.password{ + margin-top:3rem; } - .file-header { display: flex; margin-bottom: 16px; @@ -38,14 +47,31 @@ mat-card { .instruction { color: #555; + width: 80%; } .token-field { - width: 70%; + width: 80%; height: 20px; min-width: 12rem; margin-bottom: 8px; margin-top: 0; + box-sizing: border-box + } + .token-field + textarea { + width: 100%; + max-height: calc(3 * 1.5em); // Allow up to 5 lines of text + overflow-y: auto; // Show scrollbar only when needed + resize: none; // Prevent manual resizing + box-sizing: border-box; // Padding doesn't affect the size + line-height: 1.5; // Match text line height + border: none; // Remove textarea border + padding: 0; // Remove padding + + } + .password-field{ + width:40%; } .remove-field-btn { @@ -205,7 +231,7 @@ mat-card { mat-form-field { textarea { - max-height: calc(5 * 1.5em); // 5 lines max height + max-height: calc(3 * 1.5em); // 5 lines max height overflow-y: hidden; // Hide overflow resize: none; // Disable resizing line-height: 1.5; // Set line height diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index 34c1b2232f..0557ace15d 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -1,7 +1,7 @@ -import { Component, OnInit, ViewChild, ElementRef } from "@angular/core"; +import { Component, OnInit, ViewChild, ElementRef, OnDestroy } from "@angular/core"; import { AppConfigService } from "app-config.service"; -import { HttpClient, HttpHeaders } from '@angular/common/http'; -import { ActivatedRoute, Router } from '@angular/router'; +import { HttpClient } from '@angular/common/http'; +import { Router } from '@angular/router'; import { FormBuilder, FormControl, @@ -19,7 +19,7 @@ import { selectCurrentUser } from "state-management/selectors/user.selectors"; import { User } from "shared/sdk"; -import { MethodsList, EmFile, DepositionFiles, DepositionAddMap } from "./types/methods.enum" +import { MethodsList, EmFile, DepositionFiles } from "./types/methods.enum" import { Subscription, fromEvent } from "rxjs"; @@ -28,7 +28,7 @@ import { Subscription, fromEvent } from "rxjs"; templateUrl: './onedep.component.html', styleUrls: ['./onedep.component.scss'] }) -export class OneDepComponent implements OnInit { +export class OneDepComponent implements OnInit, OnDestroy { private subscriptions: Subscription[] = []; private _hasUnsavedChanges = false; @@ -43,6 +43,7 @@ export class OneDepComponent implements OnInit { fileTypes: DepositionFiles[]; detailsOverflow: string = 'hidden'; additionalMaps = 0; + showPassword = false; @ViewChild('fileInput') fileInput: ElementRef | undefined; @@ -50,7 +51,6 @@ export class OneDepComponent implements OnInit { constructor(public appConfigService: AppConfigService, private store: Store, private http: HttpClient, - private route: ActivatedRoute, private router: Router, private fb: FormBuilder, ) { } @@ -78,6 +78,7 @@ export class OneDepComponent implements OnInit { this.form = this.fb.group({ email: this.user.email, jwtToken: new FormControl(""), + password: new FormControl(), metadata: this.dataset.scientificMetadata, emMethod: new FormControl(""), deposingCoordinates: new FormControl(null, Validators.required), @@ -91,12 +92,21 @@ export class OneDepComponent implements OnInit { ]), }) } + + ngOnDestroy() { + this.subscriptions.forEach((subscription) => { + subscription.unsubscribe(); + }); + } hasUnsavedChanges() { return this._hasUnsavedChanges; } onHasUnsavedChanges($event: boolean) { this._hasUnsavedChanges = $event; } + togglePasswordVisibility() { + this.showPassword = !this.showPassword; + } orcidArray(): FormArray { return this.form.get('orcid') as FormArray; } @@ -140,6 +150,7 @@ export class OneDepComponent implements OnInit { } onPDB(event: any) { + console.log(this.form.get('password')) const input = event.value; if (input === 'true') { this.fileTypes.forEach((fT) => { @@ -153,7 +164,7 @@ export class OneDepComponent implements OnInit { autoGrow(event: Event) { const textarea = event.target as HTMLTextAreaElement; const lineHeight = parseInt(getComputedStyle(textarea).lineHeight, 10); - const maxLines = 5; + const maxLines = 3; // Reset height to auto to calculate scrollHeight textarea.style.height = 'auto'; @@ -304,15 +315,29 @@ export class OneDepComponent implements OnInit { } onDepositClick() { // Create a deposition - const body = JSON.stringify( - { - "email": "sofya.laskina@epfl.ch", // for now - "orcidIds": this.orcidArray().value.map(item => item.orcidId), - "country": "United States", - "method": this.form.value.emMethod, - "jwtToken": this.form.value.jwtToken, - } - ); + let body: string; + if (this.form.value.password){ + body = JSON.stringify( + { + "email": "sofya.laskina@epfl.ch", // for now + "orcidIds": this.orcidArray().value.map(item => item.orcidId), + "country": "United States", + "method": this.form.value.emMethod, + "jwtToken": this.form.value.jwtToken, + "password": this.form.value.password, + } + ); + }else{ + body = JSON.stringify( + { + "email": "sofya.laskina@epfl.ch", // for now + "orcidIds": this.orcidArray().value.map(item => item.orcidId), + "country": "United States", + "method": this.form.value.emMethod, + "jwtToken": this.form.value.jwtToken, + } + ); + } let depID: string; let metadataAdded = false; this.http.post("http://localhost:8080/onedep", body, { @@ -322,7 +347,7 @@ export class OneDepComponent implements OnInit { depID = response.depID; // Update the outer variable console.log('Created deposition in OneDep', depID); - // // Call subsequent requests + // Call subsequent requests this.fileTypes.forEach((fT) => { if (fT.file) { const formDataFile = new FormData() @@ -348,57 +373,7 @@ export class OneDepComponent implements OnInit { }, error: (error) => console.error('Request failed', error.error), }); - - // const formDataFile = new FormData(); - // formDataToSend.append('jwtToken', this.form.value.jwtToken); - // - - // var fileMetadata = [] - // // for (const fI in this.fileTypes) { - // - // formDataToSend.append('fileMetadata', JSON.stringify(fileMetadata)); - // this.http.post("http://localhost:8080/onedep", formDataToSend, { - // headers: {} - // }).subscribe( - // response => { - // console.log('Created deposition in OneDep', response); - // }, - // error => { - // console.error('Request failed', error.error); - // } - // ); - } - - // onCreateClick() { - // let bearer = 'Bearer ' + this.form.value['jwtToken']; - // const headers = new HttpHeaders() - // .append( - // 'Content-Type', - // 'application/json' - // ) - // .append( - // 'Authorization', - // bearer - // ); - - // const body = JSON.stringify( - // { - // "email": "sofya.laskina@epfl.ch", - // "users": ["0009-0003-3665-5367"], - // "country": "United States", - // "experiments": [Experiments[this.form.value.emMethod]], - // } - // ); - - // this.http - // .post('https://onedep-depui-test.wwpdb.org/deposition/api/v1/depositions/new', body, { - // headers: headers, - // }) - // .subscribe((res) => console.log(res)); - - // } - } From 8a8f5e303629cb9bd4331f0b7912651295cc1573 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Mon, 16 Dec 2024 20:32:22 +0000 Subject: [PATCH 044/245] onedep component can be turned off --- .eslintrc.json | 2 +- CI/e2e/frontend.config.e2e.json | 2 +- src/app/app-config.service.ts | 2 + ...ataset-details-dashboard.routing.module.ts | 13 + .../datasets.routing.module.ts | 5 - src/app/app-routing/service.guard.ts | 3 + .../dataset-detail.component.html | 18 - .../dataset-detail.component.scss | 5 - .../dataset-detail.component.ts | 67 +-- .../dataset-details-dashboard.component.ts | 11 + src/app/datasets/onedep/onedep.component.ts | 319 ++++++++----- src/app/datasets/onedep/onedep.directive.ts | 22 +- src/app/datasets/onedep/types/methods.enum.ts | 447 +++++++++--------- src/app/empiar/empiar.component.html | 4 - src/app/empiar/empiar.component.scss | 0 src/app/empiar/empiar.component.ts | 18 - src/assets/config.json | 4 +- 17 files changed, 487 insertions(+), 455 deletions(-) delete mode 100644 src/app/empiar/empiar.component.html delete mode 100644 src/app/empiar/empiar.component.scss delete mode 100644 src/app/empiar/empiar.component.ts diff --git a/.eslintrc.json b/.eslintrc.json index 2f22b56243..862f21e381 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -9,7 +9,7 @@ "plugins": ["@typescript-eslint/eslint-plugin"], "overrides": [ { - "files": ["*.ts"], + "files": ["onedep*.ts"], "parserOptions": { "project": ["tsconfig.json"], "createDefaultProgram": true diff --git a/CI/e2e/frontend.config.e2e.json b/CI/e2e/frontend.config.e2e.json index da87bca32d..633624b991 100644 --- a/CI/e2e/frontend.config.e2e.json +++ b/CI/e2e/frontend.config.e2e.json @@ -2,7 +2,7 @@ "accessTokenPrefix": "Bearer ", "addDatasetEnabled": false, "archiveWorkflowEnabled": false, - "datasetReduceEnabled": false, + "datasetReduceEnabled": true, "datasetJsonScientificMetadata": true, "editDatasetSampleEnabled": true, "editMetadataEnabled": true, diff --git a/src/app/app-config.service.ts b/src/app/app-config.service.ts index 7dec6effdf..ea5714c926 100644 --- a/src/app/app-config.service.ts +++ b/src/app/app-config.service.ts @@ -41,6 +41,7 @@ export interface AppConfig { datasetJsonScientificMetadata: boolean; datasetReduceEnabled: boolean; datasetDetailsShowMissingProposalId: boolean; + datasetOneDepIntegration: boolean; datafilesActionsEnabled: boolean; datafilesActions: any[]; editDatasetSampleEnabled: boolean; @@ -64,6 +65,7 @@ export interface AppConfig { jupyterHubUrl: string | null; landingPage: string | null; lbBaseURL: string; + depositorURL: string; localColumns?: TableColumn[]; // localColumns is deprecated and should be removed in the future logbookEnabled: boolean; loginFormEnabled: boolean; diff --git a/src/app/app-routing/lazy/dataset-details-dashboard-routing/dataset-details-dashboard.routing.module.ts b/src/app/app-routing/lazy/dataset-details-dashboard-routing/dataset-details-dashboard.routing.module.ts index 0f3f81e434..1bf09da064 100644 --- a/src/app/app-routing/lazy/dataset-details-dashboard-routing/dataset-details-dashboard.routing.module.ts +++ b/src/app/app-routing/lazy/dataset-details-dashboard-routing/dataset-details-dashboard.routing.module.ts @@ -11,6 +11,7 @@ import { DatasetDetailComponent } from "datasets/dataset-detail/dataset-detail.c import { DatasetFileUploaderComponent } from "datasets/dataset-file-uploader/dataset-file-uploader.component"; import { DatasetLifecycleComponent } from "datasets/dataset-lifecycle/dataset-lifecycle.component"; import { ReduceComponent } from "datasets/reduce/reduce.component"; +import { OneDepComponent } from "datasets/onedep/onedep.component"; import { RelatedDatasetsComponent } from "datasets/related-datasets/related-datasets.component"; import { LogbooksDashboardComponent } from "logbooks/logbooks-dashboard/logbooks-dashboard.component"; const routes: Routes = [ @@ -71,6 +72,18 @@ const routes: Routes = [ component: AdminTabComponent, canActivate: [AuthGuard, AdminGuard], }, + { + path: "onedep", + canActivate: [ServiceGuard], + children: [ + { + path: "", + component: OneDepComponent, + canActivate: [AuthGuard], + }, + ], + data: { service: "onedep" }, + }, ]; @NgModule({ imports: [RouterModule.forChild(routes)], 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 14feaf6945..d1637dbd59 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 @@ -6,7 +6,6 @@ import { DashboardComponent } from "datasets/dashboard/dashboard.component"; import { DatablocksComponent } from "datasets/datablocks-table/datablocks-table.component"; import { DatasetDetailsDashboardComponent } from "datasets/dataset-details-dashboard/dataset-details-dashboard.component"; import { PublishComponent } from "datasets/publish/publish.component"; -import { OneDepComponent } from "datasets/onedep/onedep.component"; const routes: Routes = [ { @@ -36,10 +35,6 @@ const routes: Routes = [ component: DatablocksComponent, canActivate: [AuthGuard], }, - { - path: ":id/onedep", - component: OneDepComponent, - }, ]; @NgModule({ imports: [RouterModule.forChild(routes)], diff --git a/src/app/app-routing/service.guard.ts b/src/app/app-routing/service.guard.ts index fddb5db54b..5f3ec55dce 100644 --- a/src/app/app-routing/service.guard.ts +++ b/src/app/app-routing/service.guard.ts @@ -31,6 +31,9 @@ export class ServiceGuard implements CanActivate { case "reduce": shouldActivate = this.appConfig.datasetReduceEnabled; break; + case "onedep": + shouldActivate = this.appConfig.datasetOneDepIntegration; + break; } if (!shouldActivate) { this.router.navigate(["/404"], { diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.html b/src/app/datasets/dataset-detail/dataset-detail.component.html index c5c6d58335..0df999dc2e 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.html +++ b/src/app/datasets/dataset-detail/dataset-detail.component.html @@ -12,24 +12,6 @@ Jupyter Hub
- -
(); - readonly separatorKeyCodes: number[] = [ENTER, COMMA, SPACE]; constructor( @@ -111,11 +110,9 @@ export class DatasetDetailComponent private http: HttpClient, private router: Router, private fb: FormBuilder, - ) { } + ) {} ngOnInit() { - this.connectingToDepositionBackend = true; - this.form = this.fb.group({ datasetName: new FormControl("", [Validators.required]), description: new FormControl("", [Validators.required]), @@ -329,42 +326,4 @@ export class DatasetDetailComponent openAttachment(encoded: string) { this.attachmentService.openAttachment(encoded); } - onEMPIARclick() { - const id = encodeURIComponent(this.dataset.pid); - this.router.navigateByUrl("/datasets/" + id + "/empiar"); - } - - - connectToDepositionBackend(): boolean { - var DepositionBackendUrl = "http://localhost:8080" - let DepositionBackendUrlCleaned = DepositionBackendUrl.slice(); - // Check if last symbol is a slash and add version endpoint - if (!DepositionBackendUrlCleaned.endsWith('/')) { - DepositionBackendUrlCleaned += '/'; - } - - let DepositionBackendUrlVersion = DepositionBackendUrlCleaned + 'version'; - - // Try to connect to the facility backend/version to check if it is available - console.log('Connecting to OneDep backend: ' + DepositionBackendUrlVersion); - this.http.get(DepositionBackendUrlVersion).subscribe( - response => { - console.log('Connected to OneDep backend', response); - // If the connection is successful, store the connected facility backend URL - this.connectedDepositionBackend = DepositionBackendUrlCleaned; - this.connectingToDepositionBackend = false; - this.connectedDepositionBackendVersion = response['version']; - }, - error => { - this.errorMessage += `${new Date().toLocaleString()}: ${error.message}
`; - console.error('Request failed', error); - this.connectedDepositionBackend = ''; - this.connectingToDepositionBackend = false; - } - ); - - return true; - } - } - diff --git a/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.ts b/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.ts index c857094866..9a8326095f 100644 --- a/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.ts +++ b/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.ts @@ -62,6 +62,7 @@ enum TAB { attachments = "Attachments", admin = "Admin", lifecycle = "Lifecycle", + onedep = "OneDep", } @Component({ selector: "dataset-details-dashboard", @@ -104,6 +105,7 @@ export class DatasetDetailsDashboardComponent [TAB.logbook]: { action: fetchDatasetLogbookAction, loaded: false }, [TAB.attachments]: { action: fetchAttachmentsAction, loaded: false }, [TAB.admin]: { action: fetchDatablocksAction, loaded: false }, + [TAB.onedep]: { action: fetchDatasetAction, loaded: false }, }; userProfile$ = this.store.select(selectProfile); isAdmin$ = this.store.select(selectIsAdmin); @@ -146,6 +148,9 @@ export class DatasetDetailsDashboardComponent const hasAccessToLogbook = isInOwnerGroup || this.dataset.accessGroups.some((g) => groups.includes(g)); + const hasOpenEMKeyword = this.dataset.keywords.some( + (k) => k.toLowerCase() === "openem", + ); this.navLinks = [ { location: "./", @@ -208,6 +213,12 @@ export class DatasetDetailsDashboardComponent icon: "settings", enabled: isLoggedIn && isAdmin, }, + { + location: "./onedep", + label: TAB.onedep, + icon: "file_upload", + enabled: isLoggedIn && hasOpenEMKeyword, + }, ]; }) .unsubscribe(); diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index 0557ace15d..df0ad54227 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -1,7 +1,13 @@ -import { Component, OnInit, ViewChild, ElementRef, OnDestroy } from "@angular/core"; -import { AppConfigService } from "app-config.service"; -import { HttpClient } from '@angular/common/http'; -import { Router } from '@angular/router'; +import { + Component, + OnInit, + ViewChild, + ElementRef, + OnDestroy, +} from "@angular/core"; +import { AppConfigService, AppConfig } from "app-config.service"; +import { HttpClient } from "@angular/common/http"; +import { Router } from "@angular/router"; import { FormBuilder, FormControl, @@ -12,51 +18,59 @@ import { import { Store } from "@ngrx/store"; import { Dataset } from "shared/sdk/models"; -import { - selectCurrentDataset, -} from "state-management/selectors/datasets.selectors"; -import { - selectCurrentUser -} from "state-management/selectors/user.selectors"; +import { selectCurrentDataset } from "state-management/selectors/datasets.selectors"; +import { selectCurrentUser } from "state-management/selectors/user.selectors"; import { User } from "shared/sdk"; -import { MethodsList, EmFile, DepositionFiles } from "./types/methods.enum" +import { methodsList, EmFile, DepositionFiles } from "./types/methods.enum"; import { Subscription, fromEvent } from "rxjs"; - @Component({ - selector: 'onedep', - templateUrl: './onedep.component.html', - styleUrls: ['./onedep.component.scss'] + selector: "onedep", + templateUrl: "./onedep.component.html", + styleUrls: ["./onedep.component.scss"], }) export class OneDepComponent implements OnInit, OnDestroy { private subscriptions: Subscription[] = []; private _hasUnsavedChanges = false; - appConfig = this.appConfigService.getConfig(); + config: AppConfig; + dataset: Dataset | undefined; user: User | undefined; form: FormGroup; - showAssociatedMapQuestion: boolean = false; - methodsList = MethodsList; + showAssociatedMapQuestion = false; + methodsList = methodsList; selectedFile: { [key: string]: File | null } = {}; emFile = EmFile; fileTypes: DepositionFiles[]; - detailsOverflow: string = 'hidden'; + detailsOverflow = "hidden"; additionalMaps = 0; showPassword = false; + connectedDepositionBackend = ""; + connectedDepositionBackendVersion = ""; + connectingToDepositionBackend = false; + lastUsedDepositionBackends: string[] = []; + forwardDepositionBackend = ""; + errorMessage = ""; - @ViewChild('fileInput') fileInput: ElementRef | undefined; + @ViewChild("fileInput") fileInput: ElementRef | undefined; - constructor(public appConfigService: AppConfigService, + constructor( + public appConfigService: AppConfigService, private store: Store, private http: HttpClient, private router: Router, private fb: FormBuilder, - ) { } - + ) { + this.config = this.appConfigService.getConfig(); + } ngOnInit() { + // connect to the depositor + this.connectingToDepositionBackend = true; + this.connectToDepositionBackend(); + this.store.select(selectCurrentDataset).subscribe((dataset) => { this.dataset = dataset; }); @@ -87,10 +101,13 @@ export class OneDepComponent implements OnInit, OnDestroy { emdbId: new FormControl(""), orcid: this.fb.array([ this.fb.group({ - orcidId: ['', [Validators.required, Validators.pattern(/^(\d{4}-){3}\d{4}$/)]], - }) + orcidId: [ + "", + [Validators.required, Validators.pattern(/^(\d{4}-){3}\d{4}$/)], + ], + }), ]), - }) + }); } ngOnDestroy() { @@ -108,11 +125,14 @@ export class OneDepComponent implements OnInit, OnDestroy { this.showPassword = !this.showPassword; } orcidArray(): FormArray { - return this.form.get('orcid') as FormArray; + return this.form.get("orcid") as FormArray; } addOrcidField() { const orcidField = this.fb.group({ - orcidId: ['', [Validators.required, Validators.pattern(/^(\d{4}-){3}\d{4}$/)]], + orcidId: [ + "", + [Validators.required, Validators.pattern(/^(\d{4}-){3}\d{4}$/)], + ], }); this.orcidArray().push(orcidField); } @@ -122,37 +142,48 @@ export class OneDepComponent implements OnInit, OnDestroy { } } onMethodChange() { - this.fileTypes = this.methodsList.find(mL => mL.value === this.form.value['emMethod']).files; + this.fileTypes = this.methodsList.find( + (mL) => mL.value === this.form.value["emMethod"], + ).files; this.fileTypes.forEach((fT) => { - if (fT.emName === this.emFile.MainMap || fT.emName === this.emFile.Image) { + if ( + fT.emName === this.emFile.MainMap || + fT.emName === this.emFile.Image + ) { fT.required = true; } else { fT.required = false; } }); - switch (this.form.value['emMethod']) { - case 'helical': + switch (this.form.value["emMethod"]) { + case "helical": this.fileTypes.forEach((fT) => { - if (fT.emName === this.emFile.HalfMap1 || fT.emName === this.emFile.HalfMap2) { + if ( + fT.emName === this.emFile.HalfMap1 || + fT.emName === this.emFile.HalfMap2 + ) { fT.required = true; } }); break; case "single-particle": this.fileTypes.forEach((fT) => { - if (fT.emName === this.emFile.HalfMap1 || fT.emName === this.emFile.HalfMap2 || fT.emName === this.emFile.MaskMap) { + if ( + fT.emName === this.emFile.HalfMap1 || + fT.emName === this.emFile.HalfMap2 || + fT.emName === this.emFile.MaskMap + ) { fT.required = true; } }); break; } - } - onPDB(event: any) { - console.log(this.form.get('password')) + onPDB(event: any) { // FIXME specify type + console.log(this.form.get("password")); const input = event.value; - if (input === 'true') { + if (input === "true") { this.fileTypes.forEach((fT) => { if (fT.emName === this.emFile.Coordinates) { fT.required = true; @@ -167,14 +198,15 @@ export class OneDepComponent implements OnInit, OnDestroy { const maxLines = 3; // Reset height to auto to calculate scrollHeight - textarea.style.height = 'auto'; + textarea.style.height = "auto"; // Set the height based on the scrollHeight but limit it const newHeight = Math.min(textarea.scrollHeight, lineHeight * maxLines); textarea.style.height = `${newHeight}px`; // Update overflow property based on height - this.detailsOverflow = textarea.scrollHeight > newHeight ? 'auto' : 'hidden'; + this.detailsOverflow = + textarea.scrollHeight > newHeight ? "auto" : "hidden"; } onChooseFile(fileInput: HTMLInputElement) { fileInput.click(); @@ -215,21 +247,25 @@ export class OneDepComponent implements OnInit, OnDestroy { } updateContourLevelMain(event: Event) { const input = (event.target as HTMLInputElement).value.trim(); - const normalizedInput = input.replace(',', '.'); + const normalizedInput = input.replace(",", "."); const parsedValue = parseFloat(normalizedInput); if (!isNaN(parsedValue)) { this.fileTypes.forEach((fT) => { - if (fT.emName === EmFile.MainMap || fT.emName === EmFile.HalfMap1 || fT.emName === EmFile.HalfMap2) { + if ( + fT.emName === EmFile.MainMap || + fT.emName === EmFile.HalfMap1 || + fT.emName === EmFile.HalfMap2 + ) { fT.contour = parsedValue; } }); } else { - console.warn('Invalid number format:', input); + console.warn("Invalid number format:", input); } } updateContourLevelAddMap(event: Event, id: number) { const input = (event.target as HTMLInputElement).value.trim(); - const normalizedInput = input.replace(',', '.'); + const normalizedInput = input.replace(",", "."); const parsedValue = parseFloat(normalizedInput); if (!isNaN(parsedValue)) { this.fileTypes.forEach((fT) => { @@ -238,12 +274,12 @@ export class OneDepComponent implements OnInit, OnDestroy { } }); } else { - console.warn('Invalid number format:', input); + console.warn("Invalid number format:", input); } } updateContourLevel(event: Event, controlName: EmFile) { const input = (event.target as HTMLInputElement).value.trim(); - const normalizedInput = input.replace(',', '.'); + const normalizedInput = input.replace(",", "."); const parsedValue = parseFloat(normalizedInput); if (!isNaN(parsedValue)) { this.fileTypes.forEach((fT) => { @@ -252,7 +288,7 @@ export class OneDepComponent implements OnInit, OnDestroy { } }); } else { - console.warn('Invalid number format:', input); + console.warn("Invalid number format:", input); } } updateDetails(event: Event, controlName: EmFile) { @@ -263,7 +299,6 @@ export class OneDepComponent implements OnInit, OnDestroy { fT.details = value; } }); - } updateDetailsAddMap(event: Event, id: number) { const textarea = event.target as HTMLTextAreaElement; // Cast to HTMLTextAreaElement @@ -273,17 +308,17 @@ export class OneDepComponent implements OnInit, OnDestroy { fT.details = value; } }); - } addMap() { - const nextId = this.fileTypes - .filter(file => file.emName === EmFile.AddMap) - .reduce((maxId, file) => (file.id > maxId ? file.id : maxId), 0) + 1; + const nextId = + this.fileTypes + .filter((file) => file.emName === EmFile.AddMap) + .reduce((maxId, file) => (file.id > maxId ? file.id : maxId), 0) + 1; const newMap: DepositionFiles = { emName: EmFile.AddMap, id: nextId, - nameFE: 'Additional Map ( ' + (nextId + 1).toString() + ' )', + nameFE: "Additional Map ( " + (nextId + 1).toString() + " )", type: "add-map", fileName: "", file: null, @@ -295,85 +330,133 @@ export class OneDepComponent implements OnInit, OnDestroy { this.fileTypes.push(newMap); } sendFile(depID: string, form: FormData, fileType: string) { - this.http.post("http://localhost:8080/onedep/" + depID + "/file", form).subscribe({ - next: (res) => console.log('Uploaded', fileType, res), - error: (error) => console.error('Could not upload File and Metadata', error), - }); + this.http + .post("http://localhost:8080/onedep/" + depID + "/file", form) + .subscribe({ + next: (res) => console.log("Uploaded", fileType, res), + error: (error) => + console.error("Could not upload File and Metadata", error), + }); } sendCoordFile(depID: string, form: FormData) { - this.http.post("http://localhost:8080/onedep/" + depID + "/pdb", form).subscribe({ - next: (res) => console.log('Uploaded Coordinates and Metadata', res), - error: (error) => console.error('Could not upload Coordinates and Metadata', error), - }); + this.http + .post("http://localhost:8080/onedep/" + depID + "/pdb", form) + .subscribe({ + next: (res) => console.log("Uploaded Coordinates and Metadata", res), + error: (error) => + console.error("Could not upload Coordinates and Metadata", error), + }); } sendMetadata(depID: string, form: FormData) { // missing token! - this.http.post("http://localhost:8080/onedep/" + depID + "/metadata", form).subscribe({ - next: (res) => console.log('Uploaded Metadata', res), - error: (error) => console.error('Could not upload Metadata', error), - }); + this.http + .post("http://localhost:8080/onedep/" + depID + "/metadata", form) + .subscribe({ + next: (res) => console.log("Uploaded Metadata", res), + error: (error) => console.error("Could not upload Metadata", error), + }); } onDepositClick() { // Create a deposition - let body: string; - if (this.form.value.password){ - body = JSON.stringify( - { - "email": "sofya.laskina@epfl.ch", // for now - "orcidIds": this.orcidArray().value.map(item => item.orcidId), - "country": "United States", - "method": this.form.value.emMethod, - "jwtToken": this.form.value.jwtToken, - "password": this.form.value.password, - } - ); - }else{ - body = JSON.stringify( - { - "email": "sofya.laskina@epfl.ch", // for now - "orcidIds": this.orcidArray().value.map(item => item.orcidId), - "country": "United States", - "method": this.form.value.emMethod, - "jwtToken": this.form.value.jwtToken, - } - ); + let body: string; + if (this.form.value.password) { + body = JSON.stringify({ + email: "sofya.laskina@epfl.ch", // for now + orcidIds: this.orcidArray().value.map((item) => item.orcidId), + country: "United States", + method: this.form.value.emMethod, + jwtToken: this.form.value.jwtToken, + password: this.form.value.password, + }); + } else { + body = JSON.stringify({ + email: "sofya.laskina@epfl.ch", // for now + orcidIds: this.orcidArray().value.map((item) => item.orcidId), + country: "United States", + method: this.form.value.emMethod, + jwtToken: this.form.value.jwtToken, + }); } let depID: string; let metadataAdded = false; - this.http.post("http://localhost:8080/onedep", body, { - headers: { 'Content-Type': 'application/json' }, - }).subscribe({ - next: (response: any) => { - depID = response.depID; // Update the outer variable - console.log('Created deposition in OneDep', depID); + this.http + .post("http://localhost:8080/onedep", body, { + headers: { "Content-Type": "application/json" }, + }) + .subscribe({ + next: (response: any) => { // FIX ME specify type + depID = response.depID; // Update the outer variable + console.log("Created deposition in OneDep", depID); - // Call subsequent requests - this.fileTypes.forEach((fT) => { - if (fT.file) { - const formDataFile = new FormData() - formDataFile.append('jwtToken', this.form.value.jwtToken) - formDataFile.append('file', fT.file); - if (fT.emName === this.emFile.Coordinates) { - formDataFile.append('scientificMetadata', JSON.stringify(this.form.value.metadata)); - this.sendCoordFile(depID, formDataFile); - metadataAdded = true - } else { - formDataFile.append('fileMetadata', JSON.stringify({ name: fT.fileName, type: fT.type, contour: fT.contour, details: fT.details })) - this.sendFile(depID, formDataFile, fT.type); - } - } - }); - if (! metadataAdded){ - const formDataFile = new FormData() + // Call subsequent requests + this.fileTypes.forEach((fT) => { + if (fT.file) { + const formDataFile = new FormData(); + formDataFile.append("jwtToken", this.form.value.jwtToken); + formDataFile.append("file", fT.file); + if (fT.emName === this.emFile.Coordinates) { + formDataFile.append( + "scientificMetadata", + JSON.stringify(this.form.value.metadata), + ); + this.sendCoordFile(depID, formDataFile); + metadataAdded = true; + } else { + formDataFile.append( + "fileMetadata", + JSON.stringify({ + name: fT.fileName, + type: fT.type, + contour: fT.contour, + details: fT.details, + }), + ); + this.sendFile(depID, formDataFile, fT.type); + } + } + }); + if (!metadataAdded) { + const formDataFile = new FormData(); - formDataFile.append('jwtToken', this.form.value.jwtToken) - formDataFile.append('scientificMetadata', JSON.stringify(this.form.value.metadata)); - this.sendMetadata(depID, formDataFile); + formDataFile.append("jwtToken", this.form.value.jwtToken); + formDataFile.append( + "scientificMetadata", + JSON.stringify(this.form.value.metadata), + ); + this.sendMetadata(depID, formDataFile); + } + }, + error: (error) => console.error("Request failed", error.error), + }); + } + connectToDepositionBackend(): boolean { + const depositionBackendUrl = this.config.depositorURL; + let depositionBackendUrlCleaned = depositionBackendUrl.slice(); + // Check if last symbol is a slash and add version endpoint + if (!depositionBackendUrlCleaned.endsWith("/")) { + depositionBackendUrlCleaned += "/"; } + + const depositionBackendUrlVersion = depositionBackendUrlCleaned + "version"; + + // Try to connect to the facility backend/version to check if it is available + console.log("Connecting to OneDep backend: " + depositionBackendUrlVersion); + this.http.get(depositionBackendUrlVersion).subscribe( + (response) => { + console.log("Connected to OneDep backend", response); + // If the connection is successful, store the connected facility backend URL + this.connectedDepositionBackend = depositionBackendUrlCleaned; + this.connectingToDepositionBackend = false; + this.connectedDepositionBackendVersion = response["version"]; }, - error: (error) => console.error('Request failed', error.error), - }); + (error) => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}
`; + console.error("Request failed", error); + this.connectedDepositionBackend = ""; + this.connectingToDepositionBackend = false; + }, + ); + + return true; } } - - diff --git a/src/app/datasets/onedep/onedep.directive.ts b/src/app/datasets/onedep/onedep.directive.ts index d54b0a2502..7928e65520 100644 --- a/src/app/datasets/onedep/onedep.directive.ts +++ b/src/app/datasets/onedep/onedep.directive.ts @@ -2,17 +2,18 @@ import { Directive, HostListener, ElementRef, OnInit } from "@angular/core"; @Directive({ selector: "[orcidFormatter]" }) export class OrcidFormatterDirective { - private readonly maxRawLength = 16; constructor(private el: ElementRef) {} - @HostListener('input', ['$event']) + @HostListener("input", ["$event"]) onInput(event: InputEvent): void { const inputElement = this.el.nativeElement as HTMLInputElement; // Remove all existing dashes and limit to the max length - const rawValue = inputElement.value.replace(/-/g, '').slice(0, this.maxRawLength); + const rawValue = inputElement.value + .replace(/-/g, "") + .slice(0, this.maxRawLength); // Format with dashes const formattedValue = this.formatWithDashes(rawValue); @@ -21,18 +22,23 @@ export class OrcidFormatterDirective { inputElement.value = formattedValue; // Preserve the cursor position - const cursorPosition = this.getAdjustedCursorPosition(rawValue, inputElement.selectionStart || 0); + const cursorPosition = this.getAdjustedCursorPosition( + rawValue, + inputElement.selectionStart || 0, + ); inputElement.setSelectionRange(cursorPosition, cursorPosition); } private formatWithDashes(value: string): string { - return value.match(/.{1,4}/g)?.join('-') || value; + return value.match(/.{1,4}/g)?.join("-") || value; } - private getAdjustedCursorPosition(rawValue: string, originalPosition: number): number { + private getAdjustedCursorPosition( + rawValue: string, + originalPosition: number, + ): number { const rawCursorPosition = rawValue.slice(0, originalPosition).length; const dashCountBeforeCursor = Math.floor(rawCursorPosition / 4); return rawCursorPosition + dashCountBeforeCursor; } - -} \ No newline at end of file +} diff --git a/src/app/datasets/onedep/types/methods.enum.ts b/src/app/datasets/onedep/types/methods.enum.ts index 971433d610..e5748c9f09 100644 --- a/src/app/datasets/onedep/types/methods.enum.ts +++ b/src/app/datasets/onedep/types/methods.enum.ts @@ -1,233 +1,236 @@ export enum EmType { - Helical = "helical", - SingleParticle = "single-particle", - SubtomogramAveraging = "subtomogram-averaging", - Tomogram = "tomogram", - ElectronCristallography = "electron-cristallography" -}; + Helical = "helical", + SingleParticle = "single-particle", + SubtomogramAveraging = "subtomogram-averaging", + Tomogram = "tomogram", + ElectronCristallography = "electron-cristallography", +} export enum EmFile { - MainMap = 'vo-map', - HalfMap1 = 'half-map1', - HalfMap2 = 'half-map2', - MaskMap = 'mask-map', - AddMap = 'add-map', - Coordinates = 'co-cif', - Image = 'img-emdb', - FSC = 'fsc-xml', - LayerLines = "layer-lines", - StructureFactors = "xs-cif", - MTZ = "xs-mtz", -}; - + MainMap = "vo-map", + HalfMap1 = "half-map1", + HalfMap2 = "half-map2", + MaskMap = "mask-map", + AddMap = "add-map", + Coordinates = "co-cif", + Image = "img-emdb", + FSC = "fsc-xml", + LayerLines = "layer-lines", + StructureFactors = "xs-cif", + MTZ = "xs-mtz", +} export interface DepositionFiles { - emName: EmFile; - id?: number, - nameFE: string; - type: string, - fileName: string, - file: File, - contour?: number, - details?: string, - required: boolean, - explanation?: string, + emName: EmFile; + id?: number; + nameFE: string; + type: string; + fileName: string; + file: File; + contour?: number; + details?: string; + required: boolean; + explanation?: string; } interface EmMethod { - value: EmType; - viewValue: string; - files: DepositionFiles[]; -} - -export const DepositionImage: DepositionFiles ={ - emName: EmFile.Image, - nameFE: 'Public Image', - type: "img-emdb", - fileName: "", - file: null, - details: "", - required: false, - explanation: "Image of the map (500 x 500 pixels in .jpg, .png, etc. format)", -} -export const DepositionMainMap:DepositionFiles ={ - emName: EmFile.MainMap, - nameFE: 'Main Map', - type: "vo-map", - fileName: "", - file: null, - contour: 0.0, - details: "", - required: false, - explanation: "Primary map (.mrc or .ccp4 format, may use gzip or bzip2 compression) along with recommended contour level", -} -export const DepositionHalfMap1:DepositionFiles ={ - emName: EmFile.HalfMap1, - nameFE: 'Half Map (1)', - type: "half-map", - fileName: "", - file: null, - contour: 0.0, - details: "", - required: false, - explanation:"Half maps (as used for FSC calculation; two maps must be uploaded)", -} -export const DepositionHalfMap2:DepositionFiles ={ - emName: EmFile.HalfMap2, - nameFE: 'Half Map (2)', - type: "half-map", - fileName: "", - file: null, - contour: 0.0, - details: "", - required: false, - explanation:"Half maps (as used for FSC calculation; two maps must be uploaded)", -} -export const DepositionMaskMap:DepositionFiles ={ - emName: EmFile.MaskMap, - nameFE: 'Mask Map', - type: "mask-map", - fileName: "", - file: null, - contour: 0.0, - details: "", - required: false, - explanation:"Primary/raw map mask, segmentation/focused refinement mask and half-map mask", -} -export const DepositionAddMap:DepositionFiles ={ - emName: EmFile.AddMap, - id:0, - nameFE: 'Additional Map', - type: "add-map", - fileName: "", - file: null, - contour: 0.0, - details: "", - required: false, - explanation:"Difference maps, maps showing alternative conformations and/or compositions, maps with differential processing (e.g. filtering, sharpening and masking)", -} -export const DepositionFSC:DepositionFiles ={ - emName: EmFile.FSC, - nameFE: 'FSC-XML', - type: "fsc-xml", - fileName: "", - file: null, - details: "", - required: false, - explanation: "Half-map FSC, Map-model FSC, Cross-validation FSCs", -} -export const DepositionLayerLines:DepositionFiles ={ - emName: EmFile.LayerLines, - nameFE: 'Other: Layer Lines Data ', - type: "layer-lines", - fileName: "", - file: null, - details: "", - required: false, -} -export const DepositionCoordinates: DepositionFiles = { - emName: EmFile.Coordinates, - nameFE: 'Coordinates', - type: "co-cif", - fileName: "", - file: null, - details: "", - required: false, - explanation: "mmCIF or PDB format", -} -export const DepositionStructureFactors: DepositionFiles = { - emName: EmFile.StructureFactors, - nameFE: 'Structure Factors', - type: "xs-cif", - fileName: "", - file: null, - details: "", - required: false, -} -export const DepositionMTZ: DepositionFiles = { - emName: EmFile.MTZ, - nameFE: 'MTZ', - type: "xs-mtz", - fileName: "", - file: null, - details: "", - required: false, + value: EmType; + viewValue: string; + files: DepositionFiles[]; } +export const depositionImage: DepositionFiles = { + emName: EmFile.Image, + nameFE: "Public Image", + type: "img-emdb", + fileName: "", + file: null, + details: "", + required: false, + explanation: "Image of the map (500 x 500 pixels in .jpg, .png, etc. format)", +}; +export const depositionMainMap: DepositionFiles = { + emName: EmFile.MainMap, + nameFE: "Main Map", + type: "vo-map", + fileName: "", + file: null, + contour: 0.0, + details: "", + required: false, + explanation: + "Primary map (.mrc or .ccp4 format, may use gzip or bzip2 compression) along with recommended contour level", +}; +export const depositionHalfMap1: DepositionFiles = { + emName: EmFile.HalfMap1, + nameFE: "Half Map (1)", + type: "half-map", + fileName: "", + file: null, + contour: 0.0, + details: "", + required: false, + explanation: + "Half maps (as used for FSC calculation; two maps must be uploaded)", +}; +export const depositionHalfMap2: DepositionFiles = { + emName: EmFile.HalfMap2, + nameFE: "Half Map (2)", + type: "half-map", + fileName: "", + file: null, + contour: 0.0, + details: "", + required: false, + explanation: + "Half maps (as used for FSC calculation; two maps must be uploaded)", +}; +export const depositionMaskMap: DepositionFiles = { + emName: EmFile.MaskMap, + nameFE: "Mask Map", + type: "mask-map", + fileName: "", + file: null, + contour: 0.0, + details: "", + required: false, + explanation: + "Primary/raw map mask, segmentation/focused refinement mask and half-map mask", +}; +export const depositionAddMap: DepositionFiles = { + emName: EmFile.AddMap, + id: 0, + nameFE: "Additional Map", + type: "add-map", + fileName: "", + file: null, + contour: 0.0, + details: "", + required: false, + explanation: + "Difference maps, maps showing alternative conformations and/or compositions, maps with differential processing (e.g. filtering, sharpening and masking)", +}; +export const depositionFSC: DepositionFiles = { + emName: EmFile.FSC, + nameFE: "FSC-XML", + type: "fsc-xml", + fileName: "", + file: null, + details: "", + required: false, + explanation: "Half-map FSC, Map-model FSC, Cross-validation FSCs", +}; +export const depositionLayerLines: DepositionFiles = { + emName: EmFile.LayerLines, + nameFE: "Other: Layer Lines Data ", + type: "layer-lines", + fileName: "", + file: null, + details: "", + required: false, +}; +export const depositionCoordinates: DepositionFiles = { + emName: EmFile.Coordinates, + nameFE: "Coordinates", + type: "co-cif", + fileName: "", + file: null, + details: "", + required: false, + explanation: "mmCIF or PDB format", +}; +export const depositionStructureFactors: DepositionFiles = { + emName: EmFile.StructureFactors, + nameFE: "Structure Factors", + type: "xs-cif", + fileName: "", + file: null, + details: "", + required: false, +}; +export const depositionMTZ: DepositionFiles = { + emName: EmFile.MTZ, + nameFE: "MTZ", + type: "xs-mtz", + fileName: "", + file: null, + details: "", + required: false, +}; -export const MethodsList: EmMethod[] = [ - { - value: EmType.Helical, - viewValue: 'Helical', - files: [ - DepositionImage, - DepositionCoordinates, - DepositionMainMap, - DepositionMaskMap, - DepositionHalfMap1, - DepositionHalfMap2, - DepositionFSC, - DepositionLayerLines, - DepositionAddMap, - ], - }, - { - value: EmType.SingleParticle, - viewValue: 'Single Particle', - files: [ - DepositionImage, - DepositionCoordinates, - DepositionMainMap, - DepositionMaskMap, - DepositionHalfMap1, - DepositionHalfMap2, - DepositionFSC, - DepositionLayerLines, - DepositionAddMap, - ], - }, - { - value: EmType.SubtomogramAveraging, - viewValue: 'Subtomogram Averaging', - files:[ - DepositionImage, - DepositionCoordinates, - DepositionMainMap, - DepositionMaskMap, - DepositionHalfMap1, - DepositionHalfMap2, - DepositionFSC, - DepositionLayerLines, - DepositionAddMap, - ], - }, - { - value: EmType.Tomogram, - viewValue: 'Tomogram', - files: [ - DepositionImage, - DepositionMainMap, - DepositionMaskMap, - DepositionFSC, - DepositionLayerLines, - DepositionAddMap, - ], - }, - { - value: EmType.ElectronCristallography, - viewValue: 'Electron Crystallography', - files: [ - DepositionImage, - DepositionCoordinates, - DepositionMainMap, - DepositionMaskMap, - DepositionHalfMap1, - DepositionHalfMap2, - DepositionStructureFactors, - DepositionMTZ, - DepositionFSC, - DepositionLayerLines, - DepositionAddMap, - ], - }, -]; \ No newline at end of file +export const methodsList: EmMethod[] = [ + { + value: EmType.Helical, + viewValue: "Helical", + files: [ + depositionImage, + depositionCoordinates, + depositionMainMap, + depositionMaskMap, + depositionHalfMap1, + depositionHalfMap2, + depositionFSC, + depositionLayerLines, + depositionAddMap, + ], + }, + { + value: EmType.SingleParticle, + viewValue: "Single Particle", + files: [ + depositionImage, + depositionCoordinates, + depositionMainMap, + depositionMaskMap, + depositionHalfMap1, + depositionHalfMap2, + depositionFSC, + depositionLayerLines, + depositionAddMap, + ], + }, + { + value: EmType.SubtomogramAveraging, + viewValue: "Subtomogram Averaging", + files: [ + depositionImage, + depositionCoordinates, + depositionMainMap, + depositionMaskMap, + depositionHalfMap1, + depositionHalfMap2, + depositionFSC, + depositionLayerLines, + depositionAddMap, + ], + }, + { + value: EmType.Tomogram, + viewValue: "Tomogram", + files: [ + depositionImage, + depositionMainMap, + depositionMaskMap, + depositionFSC, + depositionLayerLines, + depositionAddMap, + ], + }, + { + value: EmType.ElectronCristallography, + viewValue: "Electron Crystallography", + files: [ + depositionImage, + depositionCoordinates, + depositionMainMap, + depositionMaskMap, + depositionHalfMap1, + depositionHalfMap2, + depositionStructureFactors, + depositionMTZ, + depositionFSC, + depositionLayerLines, + depositionAddMap, + ], + }, +]; diff --git a/src/app/empiar/empiar.component.html b/src/app/empiar/empiar.component.html deleted file mode 100644 index 7fe6bedc3a..0000000000 --- a/src/app/empiar/empiar.component.html +++ /dev/null @@ -1,4 +0,0 @@ - -
-

EMPIAR here!

-
\ No newline at end of file diff --git a/src/app/empiar/empiar.component.scss b/src/app/empiar/empiar.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/app/empiar/empiar.component.ts b/src/app/empiar/empiar.component.ts deleted file mode 100644 index ae9b3eba27..0000000000 --- a/src/app/empiar/empiar.component.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Component, OnInit, ViewChild } from "@angular/core"; -import { AppConfigService, HelpMessages } from "app-config.service"; -import { HttpClient } from '@angular/common/http'; -import { ActivatedRoute, Router } from '@angular/router'; - -@Component({ - selector: 'empiar', - templateUrl: './empiar.component.html', - styleUrls: ['./empiar.component.scss'] -}) -export class EmpiarComponent implements OnInit { - - empiar : boolean; - - ngOnInit() { - this.empiar = true; - } -} \ No newline at end of file diff --git a/src/assets/config.json b/src/assets/config.json index a71270c0ee..b5c7e35a6a 100644 --- a/src/assets/config.json +++ b/src/assets/config.json @@ -1,7 +1,8 @@ { "addDatasetEnabled": false, "archiveWorkflowEnabled": false, - "datasetReduceEnabled": true, + "datasetReduceEnabled": false, + "datasetOneDepIntegration": true, "editDatasetSampleEnabled": true, "editMetadataEnabled": true, "editPublishedData": true, @@ -16,6 +17,7 @@ "jupyterHubUrl": "", "landingPage": "", "lbBaseURL": "http://127.0.0.1:3000", + "depositorURL": "http://127.0.0.1:8080", "localColumns": [ { "name": "datasetName", From 8a93987c01f93d2d108acb1762d60852e12cb9c4 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Tue, 17 Dec 2024 11:15:47 +0000 Subject: [PATCH 045/245] option to download files --- src/app/datasets/onedep/onedep.component.html | 4 + src/app/datasets/onedep/onedep.component.ts | 74 +++++++++++++++++-- src/app/datasets/onedep/types/methods.enum.ts | 1 + 3 files changed, 71 insertions(+), 8 deletions(-) diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index c8e12289ab..163cf8ab6a 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -334,6 +334,10 @@

Choose Electron Microscopy (click)="onDepositClick()"> Start Deposition + diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index df0ad54227..65f2cbbf8b 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -5,6 +5,7 @@ import { ElementRef, OnDestroy, } from "@angular/core"; +import { MatRadioChange } from "@angular/material/radio"; import { AppConfigService, AppConfig } from "app-config.service"; import { HttpClient } from "@angular/common/http"; import { Router } from "@angular/router"; @@ -180,8 +181,7 @@ export class OneDepComponent implements OnInit, OnDestroy { } } - onPDB(event: any) { // FIXME specify type - console.log(this.form.get("password")); + onPDB(event: MatRadioChange) { const input = event.value; if (input === "true") { this.fileTypes.forEach((fT) => { @@ -331,7 +331,7 @@ export class OneDepComponent implements OnInit, OnDestroy { } sendFile(depID: string, form: FormData, fileType: string) { this.http - .post("http://localhost:8080/onedep/" + depID + "/file", form) + .post(this.connectedDepositionBackend + "onedep/" + depID + "/file", form) .subscribe({ next: (res) => console.log("Uploaded", fileType, res), error: (error) => @@ -340,7 +340,7 @@ export class OneDepComponent implements OnInit, OnDestroy { } sendCoordFile(depID: string, form: FormData) { this.http - .post("http://localhost:8080/onedep/" + depID + "/pdb", form) + .post(this.connectedDepositionBackend + "onedep/" + depID + "/pdb", form) .subscribe({ next: (res) => console.log("Uploaded Coordinates and Metadata", res), error: (error) => @@ -350,7 +350,10 @@ export class OneDepComponent implements OnInit, OnDestroy { sendMetadata(depID: string, form: FormData) { // missing token! this.http - .post("http://localhost:8080/onedep/" + depID + "/metadata", form) + .post( + this.connectedDepositionBackend + "onedep/" + depID + "/metadata", + form, + ) .subscribe({ next: (res) => console.log("Uploaded Metadata", res), error: (error) => console.error("Could not upload Metadata", error), @@ -379,13 +382,17 @@ export class OneDepComponent implements OnInit, OnDestroy { } let depID: string; let metadataAdded = false; + + interface OneDepCreate { + depID: string; + } this.http - .post("http://localhost:8080/onedep", body, { + .post(this.connectedDepositionBackend + "onedep", body, { headers: { "Content-Type": "application/json" }, }) .subscribe({ - next: (response: any) => { // FIX ME specify type - depID = response.depID; // Update the outer variable + next: (response: OneDepCreate) => { + depID = response.depID; console.log("Created deposition in OneDep", depID); // Call subsequent requests @@ -429,6 +436,57 @@ export class OneDepComponent implements OnInit, OnDestroy { error: (error) => console.error("Request failed", error.error), }); } + onDownloadClick() { + console.log("download data"); + if (this.form.value.deposingCoordinates === true) { + const formDataFile = new FormData(); + const fT = this.fileTypes.find( + (fileType) => fileType.emName === this.emFile.Coordinates, + ); + formDataFile.append("file", fT.file); + formDataFile.append( + "scientificMetadata", + JSON.stringify(this.form.value.metadata), + ); + this.http + .post(this.connectedDepositionBackend + "onedep/pdb", formDataFile, { + responseType: "blob", + }) + .subscribe({ + next: (response: Blob) => { + this.triggerDownload(response, "coordinatesWithMmetadata.cif"); + }, + error: (error) => { + console.error("Error downloading file from onedep/pdb", error); + }, + }); + } else { + const body = JSON.stringify(this.form.value.metadata); + this.http + .post(this.connectedDepositionBackend + "onedep/metadata", body, { + headers: { "Content-Type": "application/json" }, + responseType: "blob", + }) + .subscribe({ + next: (response: Blob) => { + this.triggerDownload(response, "metadata.cif"); + }, + error: (error) => { + console.error("Error downloading file from onedep/metadata", error); + }, + }); + } + } + triggerDownload(response: Blob, filename: string) { + const downloadUrl = window.URL.createObjectURL(response); + const a = document.createElement("a"); + a.href = downloadUrl; + a.download = filename; // Set the file name here + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + } + connectToDepositionBackend(): boolean { const depositionBackendUrl = this.config.depositorURL; let depositionBackendUrlCleaned = depositionBackendUrl.slice(); diff --git a/src/app/datasets/onedep/types/methods.enum.ts b/src/app/datasets/onedep/types/methods.enum.ts index e5748c9f09..f2c8b8bdfa 100644 --- a/src/app/datasets/onedep/types/methods.enum.ts +++ b/src/app/datasets/onedep/types/methods.enum.ts @@ -234,3 +234,4 @@ export const methodsList: EmMethod[] = [ ], }, ]; + From 2ce4ad137a94e45fc95a419d7e1bc45f1d85d3ea Mon Sep 17 00:00:00 2001 From: martintrajanovski Date: Fri, 15 Nov 2024 12:41:23 +0100 Subject: [PATCH 046/245] add some improvements and reduce number of requests for proposal details page --- .../effects/proposals.effects.ts | 49 +++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/src/app/state-management/effects/proposals.effects.ts b/src/app/state-management/effects/proposals.effects.ts index 216eae775f..dfba4fb676 100644 --- a/src/app/state-management/effects/proposals.effects.ts +++ b/src/app/state-management/effects/proposals.effects.ts @@ -1,10 +1,6 @@ import { Injectable } from "@angular/core"; import { Actions, createEffect, ofType, concatLatestFrom } from "@ngrx/effects"; -import { - DatasetsService, - ProposalClass, - ProposalsService, -} from "@scicatproject/scicat-sdk-ts"; +import { DatasetApi, ProposalApi, Proposal, Dataset } from "shared/sdk"; import { Action, Store } from "@ngrx/store"; import * as fromActions from "state-management/actions/proposals.actions"; import { @@ -13,6 +9,7 @@ import { } from "state-management/selectors/proposals.selectors"; import { map, mergeMap, catchError, switchMap, filter } from "rxjs/operators"; import { ObservableInput, of } from "rxjs"; +import { ObservableInput, of } from "rxjs"; import { loadingAction, loadingCompleteAction, @@ -66,6 +63,12 @@ export class ProposalEffects { ); }); + fetchProposal$ = this.createProposalFetchEffect( + fromActions.fetchProposalAction.type, + fromActions.fetchProposalCompleteAction, + fromActions.fetchProposalFailedAction, + fromActions.fetchProposalAccessFailedAction, + ); fetchProposal$ = this.createProposalFetchEffect( fromActions.fetchProposalAction.type, fromActions.fetchProposalCompleteAction, @@ -73,6 +76,12 @@ export class ProposalEffects { fromActions.fetchProposalAccessFailedAction, ); + fetchParentProposal$ = this.createProposalFetchEffect( + fromActions.fetchParentProposalAction.type, + fromActions.fetchParentProposalCompleteAction, + fromActions.fetchParentProposalFailedAction, + fromActions.fetchParentProposalAccessFailedAction, + ); fetchParentProposal$ = this.createProposalFetchEffect( fromActions.fetchParentProposalAction.type, fromActions.fetchParentProposalCompleteAction, @@ -262,28 +271,28 @@ export class ProposalEffects { private createProposalFetchEffect( triggerAction: string, - completeAction: (props: { proposal: ProposalClass }) => Action, + completeAction: (props: { proposal: Proposal }) => Action, failedAction: () => Action, accessFailedAction: () => Action, ) { return createEffect(() => { return this.actions$.pipe( ofType(triggerAction), - switchMap>(({ proposalId }) => - this.proposalsService - .proposalsControllerFindByIdAccess(proposalId) - .pipe( - filter((permission) => permission.canAccess), - switchMap(() => - this.proposalsService - .proposalsControllerFindById(proposalId) - .pipe( - map((proposal) => completeAction({ proposal })), - catchError(() => of(failedAction())), - ), - ), - catchError(() => of(accessFailedAction())), + switchMap>(({ proposalId }) => + this.proposalApi.findByIdAccess(encodeURIComponent(proposalId)).pipe( + filter( + (permission: { canAccess: boolean }) => permission.canAccess, ), + switchMap(() => + this.proposalApi + .findById(encodeURIComponent(proposalId)) + .pipe( + map((proposal) => completeAction({ proposal })), + catchError(() => of(failedAction())), + ), + ), + catchError(() => of(accessFailedAction())), + ), ), ); }); From 151892a82ceaa18759be11b13d8e56d1d69f4215 Mon Sep 17 00:00:00 2001 From: Jay Date: Fri, 29 Nov 2024 14:30:40 +0100 Subject: [PATCH 047/245] fix: optimize condition editing logic in DatasetsFilterSettingsComponent (#1673) * fix: optimize condition editing logic in DatasetsFilterSettingsComponent * if user creates duplicated condition, do nothing * add snackbar notification for duplicate condition in DatasetsFilterSettingsComponent * remove unused import * remove panelClass from snackBar * added e2e test for the change --- src/app/datasets/datasets.module.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/datasets/datasets.module.ts b/src/app/datasets/datasets.module.ts index 2c007619cd..7b11366efa 100644 --- a/src/app/datasets/datasets.module.ts +++ b/src/app/datasets/datasets.module.ts @@ -92,6 +92,7 @@ import { userReducer } from "state-management/reducers/user.reducer"; import { MatSnackBarModule } from "@angular/material/snack-bar"; import { OneDepComponent } from "./onedep/onedep.component"; import { OrcidFormatterDirective } from "./onedep/onedep.directive"; +import { MatSnackBarModule } from "@angular/material/snack-bar"; @NgModule({ imports: [ From c2548b8f05afcae710f13d4348b4046b2cc9450e Mon Sep 17 00:00:00 2001 From: Max Novelli Date: Wed, 4 Dec 2024 11:13:31 +0100 Subject: [PATCH 048/245] BREAKING CHANGE: new sdk release (#1658) * feat: add the new auth service to prepare for the new sdk * try to fix some ai-bot review suggestions * add the note for the good review suggestion from ai-bot * remove old sdk and adjust types against the new one * fix more types and issues against the new sdk * finalize type error fixes * remove prefix * add the new sdk generation script for local development * start fixing TODOs after newly generated sdk * fixed sdk local generation for linux * update the sdk package version and fix some more types * detect the OS and use the right current directory path * improve types and fix more TODOs * improve types and fix TODOs after backend improvements * finalize TODOs and FIXMEs fixes and type improvements with the new sdk * fix some sourcery-ai comments * fix some of the last TODOs * adapted sdk generation to unix environment * ignore the @scicatproject that is generated with the sdk * start fixing tests with the new sdk * add needed stub classes and fix some more tests * continue fixing unit tests * try to fix e2e tests and revert some changes that need more attention for now * changes to just run the tests * use latest sdk * update package-lock file * fixing unit tests * fix more unit tests * continue fixing tests * update the sdk * fix last e2e test * fix thumbnail unit tests * revert some change * finalize fixing unit tests * revert the backend image changes after the tests pass * add some improvements in the mocked objects for unit tests based on ai bot suggestion * remove encodeURIComponent in the effects as it seems redundant * fix test files after some changes * try to use mock objects as much as possible * update the sdk version * update package-lock file * update the sdk to latest * BREAKING CHANGE: new sdk release --------- Co-authored-by: martintrajanovski Co-authored-by: Jay --- .../dataset-detail.component.ts | 10 +----- .../effects/proposals.effects.ts | 36 ++++++++++--------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.ts b/src/app/datasets/dataset-detail/dataset-detail.component.ts index 17b7985915..eca8887ea8 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.ts +++ b/src/app/datasets/dataset-detail/dataset-detail.component.ts @@ -1,12 +1,4 @@ -import { - Component, - OnInit, - Output, - EventEmitter, - OnDestroy, - Inject, -} from "@angular/core"; -import { Dataset, Proposal, Sample } from "shared/sdk/models"; +import { Component, OnInit, OnDestroy, Inject } from "@angular/core"; import { ENTER, COMMA, SPACE } from "@angular/cdk/keycodes"; import { MatChipInputEvent } from "@angular/material/chips"; diff --git a/src/app/state-management/effects/proposals.effects.ts b/src/app/state-management/effects/proposals.effects.ts index dfba4fb676..150f79dcdb 100644 --- a/src/app/state-management/effects/proposals.effects.ts +++ b/src/app/state-management/effects/proposals.effects.ts @@ -1,6 +1,10 @@ import { Injectable } from "@angular/core"; import { Actions, createEffect, ofType, concatLatestFrom } from "@ngrx/effects"; -import { DatasetApi, ProposalApi, Proposal, Dataset } from "shared/sdk"; +import { + DatasetsService, + ProposalClass, + ProposalsService, +} from "@scicatproject/scicat-sdk-ts"; import { Action, Store } from "@ngrx/store"; import * as fromActions from "state-management/actions/proposals.actions"; import { @@ -271,28 +275,28 @@ export class ProposalEffects { private createProposalFetchEffect( triggerAction: string, - completeAction: (props: { proposal: Proposal }) => Action, + completeAction: (props: { proposal: ProposalClass }) => Action, failedAction: () => Action, accessFailedAction: () => Action, ) { return createEffect(() => { return this.actions$.pipe( ofType(triggerAction), - switchMap>(({ proposalId }) => - this.proposalApi.findByIdAccess(encodeURIComponent(proposalId)).pipe( - filter( - (permission: { canAccess: boolean }) => permission.canAccess, - ), - switchMap(() => - this.proposalApi - .findById(encodeURIComponent(proposalId)) - .pipe( - map((proposal) => completeAction({ proposal })), - catchError(() => of(failedAction())), - ), + switchMap>(({ proposalId }) => + this.proposalsService + .proposalsControllerFindByIdAccess(proposalId) + .pipe( + filter((permission) => permission.canAccess), + switchMap(() => + this.proposalsService + .proposalsControllerFindById(proposalId) + .pipe( + map((proposal) => completeAction({ proposal })), + catchError(() => of(failedAction())), + ), + ), + catchError(() => of(accessFailedAction())), ), - catchError(() => of(accessFailedAction())), - ), ), ); }); From 2889aaddeb08844e2ca7e628a0026737660b68fa Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Wed, 16 Oct 2024 17:09:54 +0200 Subject: [PATCH 049/245] OneDep from dataset --- .../datasets-routing/datasets.routing.module.ts | 5 +++++ .../dataset-detail.component.html | 17 +++++++++++++++++ .../dataset-detail.component.scss | 4 ++++ .../dataset-detail/dataset-detail.component.ts | 6 ++++++ src/app/datasets/datasets.module.ts | 1 + src/app/empiar/empiar.component.scss | 0 6 files changed, 33 insertions(+) create mode 100644 src/app/empiar/empiar.component.scss 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 d1637dbd59..14feaf6945 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 @@ -6,6 +6,7 @@ import { DashboardComponent } from "datasets/dashboard/dashboard.component"; import { DatablocksComponent } from "datasets/datablocks-table/datablocks-table.component"; import { DatasetDetailsDashboardComponent } from "datasets/dataset-details-dashboard/dataset-details-dashboard.component"; import { PublishComponent } from "datasets/publish/publish.component"; +import { OneDepComponent } from "datasets/onedep/onedep.component"; const routes: Routes = [ { @@ -35,6 +36,10 @@ const routes: Routes = [ component: DatablocksComponent, canActivate: [AuthGuard], }, + { + path: ":id/onedep", + component: OneDepComponent, + }, ]; @NgModule({ imports: [RouterModule.forChild(routes)], diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.html b/src/app/datasets/dataset-detail/dataset-detail.component.html index 0df999dc2e..f9c575b4f7 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.html +++ b/src/app/datasets/dataset-detail/dataset-detail.component.html @@ -21,6 +21,23 @@ Public

+ +
diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.scss b/src/app/datasets/dataset-detail/dataset-detail.component.scss index 14d17981ed..5eb33c71ce 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.scss +++ b/src/app/datasets/dataset-detail/dataset-detail.component.scss @@ -51,6 +51,10 @@ mat-card { .jupyter-button { margin: 1em 0 0 1em; } +.emexport-button { + margin: 1em 0 0 3em; + color: hsla(185, 43%, 45%, 0.458); +} .public-toggle { margin: 1em 1em 0 0; float: right; diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.ts b/src/app/datasets/dataset-detail/dataset-detail.component.ts index eca8887ea8..3a0e3b0616 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.ts +++ b/src/app/datasets/dataset-detail/dataset-detail.component.ts @@ -318,4 +318,10 @@ export class DatasetDetailComponent openAttachment(encoded: string) { this.attachmentService.openAttachment(encoded); } + + onOneDepClick() { + const id = encodeURIComponent(this.dataset.pid); + this.router.navigateByUrl("/datasets/" + id + "/onedep"); + } + } diff --git a/src/app/datasets/datasets.module.ts b/src/app/datasets/datasets.module.ts index 7b11366efa..9f23144951 100644 --- a/src/app/datasets/datasets.module.ts +++ b/src/app/datasets/datasets.module.ts @@ -93,6 +93,7 @@ import { MatSnackBarModule } from "@angular/material/snack-bar"; import { OneDepComponent } from "./onedep/onedep.component"; import { OrcidFormatterDirective } from "./onedep/onedep.directive"; import { MatSnackBarModule } from "@angular/material/snack-bar"; +import { OneDepComponent } from "./onedep/onedep.component"; @NgModule({ imports: [ diff --git a/src/app/empiar/empiar.component.scss b/src/app/empiar/empiar.component.scss new file mode 100644 index 0000000000..e69de29bb2 From a0682c00cdefd53b66b3fc4416a82de8d69f92fa Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Thu, 17 Oct 2024 17:56:02 +0200 Subject: [PATCH 050/245] OneDep component recieves dataset - turned off cleaning in dataset-details dashboard onDestroy --- .../dataset-detail.component.html | 31 ++++++++++++------- .../dataset-detail.component.scss | 2 +- .../dataset-detail.component.ts | 6 +++- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.html b/src/app/datasets/dataset-detail/dataset-detail.component.html index f9c575b4f7..ba5b6e7f24 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.html +++ b/src/app/datasets/dataset-detail/dataset-detail.component.html @@ -12,23 +12,23 @@ Jupyter Hub
-
- - Public - -
- + --> + +
+ + Public + +
diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.scss b/src/app/datasets/dataset-detail/dataset-detail.component.scss index 5eb33c71ce..efb4f1bdce 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.scss +++ b/src/app/datasets/dataset-detail/dataset-detail.component.scss @@ -53,7 +53,7 @@ mat-card { } .emexport-button { margin: 1em 0 0 3em; - color: hsla(185, 43%, 45%, 0.458); + background-color: hsla(185, 43%, 45%, 0.458); } .public-toggle { margin: 1em 1em 0 0; diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.ts b/src/app/datasets/dataset-detail/dataset-detail.component.ts index 3a0e3b0616..3da1109e8b 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.ts +++ b/src/app/datasets/dataset-detail/dataset-detail.component.ts @@ -1,4 +1,5 @@ -import { Component, OnInit, OnDestroy, Inject } from "@angular/core"; +import { Component, OnInit, Output, EventEmitter, OnDestroy, Inject } from "@angular/core"; +import { Dataset, Proposal, Sample } from "shared/sdk/models"; import { ENTER, COMMA, SPACE } from "@angular/cdk/keycodes"; import { MatChipInputEvent } from "@angular/material/chips"; @@ -320,8 +321,11 @@ export class DatasetDetailComponent } onOneDepClick() { + console.log('started one dep click'); const id = encodeURIComponent(this.dataset.pid); this.router.navigateByUrl("/datasets/" + id + "/onedep"); + console.log("my datset in the details:", this.dataset); + // this.store.dispatch(selectDatasetAction({ dataset: this.dataset })); } } From deb7666b4e0a2abb0b5243889f085f7b120e755b Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Thu, 31 Oct 2024 17:55:14 +0100 Subject: [PATCH 051/245] prepared dields for OneDep export --- .../dataset-detail/dataset-detail.component.html | 1 + .../dataset-detail/dataset-detail.component.ts | 12 ++++++++++++ src/app/datasets/onedep/onedep.component.html | 6 ++---- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.html b/src/app/datasets/dataset-detail/dataset-detail.component.html index ba5b6e7f24..aabc10c2d6 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.html +++ b/src/app/datasets/dataset-detail/dataset-detail.component.html @@ -16,6 +16,7 @@ mat-raised-button id="onedepBtn" class="emexport-button" + *ngIf="hasOpenEMKeyword()" (click)="onOneDepClick()" > OneDep diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.ts b/src/app/datasets/dataset-detail/dataset-detail.component.ts index 3da1109e8b..9b801fd7de 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.ts +++ b/src/app/datasets/dataset-detail/dataset-detail.component.ts @@ -160,6 +160,7 @@ export class DatasetDetailComponent } }), ); + console.log("the keywords:" , this.dataset.keywords); } onEditModeEnable() { @@ -319,6 +320,13 @@ export class DatasetDetailComponent openAttachment(encoded: string) { this.attachmentService.openAttachment(encoded); } + + hasOpenEMKeyword(): boolean { + const keywordsArray = this.dataset.keywords; + return keywordsArray.some((keyword: string) => + keyword.toLowerCase() === 'openem' + ); + } onOneDepClick() { console.log('started one dep click'); @@ -327,5 +335,9 @@ export class DatasetDetailComponent console.log("my datset in the details:", this.dataset); // this.store.dispatch(selectDatasetAction({ dataset: this.dataset })); } + onEMPIARclick(){ + const id = encodeURIComponent(this.dataset.pid); + this.router.navigateByUrl("/datasets/" + id + "/empiar"); + } } diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index 163cf8ab6a..294e0d39fa 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -18,8 +18,7 @@ Description - + @@ -40,8 +39,7 @@ Keywords - + {{ keyword }} From 3e78908ac47701d4ae476a9f69f7f5d28db44f5b Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Fri, 1 Nov 2024 16:34:27 +0100 Subject: [PATCH 052/245] connect to OneDep backend when going to onedep --- .../dataset-detail.component.ts | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.ts b/src/app/datasets/dataset-detail/dataset-detail.component.ts index 9b801fd7de..58830f8c25 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.ts +++ b/src/app/datasets/dataset-detail/dataset-detail.component.ts @@ -92,6 +92,16 @@ export class DatasetDetailComponent editEnabled = false; show = false; + + connectedDepositionBackend: string = ''; + connectedDepositionBackendVersion: string = ''; + connectingToDepositionBackend: boolean = false; + lastUsedDepositionBackends: string[] = []; + forwardDepositionBackend: string = ''; + errorMessage: string = ''; + + @Output() emClick = new EventEmitter(); + readonly separatorKeyCodes: number[] = [ENTER, COMMA, SPACE]; constructor( @@ -101,11 +111,14 @@ export class DatasetDetailComponent public dialog: MatDialog, private store: Store, private http: HttpClient, + private http: HttpClient, private router: Router, private fb: FormBuilder, ) {} ngOnInit() { + this.connectingToDepositionBackend = true; + this.form = this.fb.group({ datasetName: new FormControl("", [Validators.required]), description: new FormControl("", [Validators.required]), @@ -340,4 +353,37 @@ export class DatasetDetailComponent this.router.navigateByUrl("/datasets/" + id + "/empiar"); } + + connectToDepositionBackend(): boolean { + var DepositionBackendUrl = "http://localhost:8080" + let DepositionBackendUrlCleaned = DepositionBackendUrl.slice(); + // Check if last symbol is a slash and add version endpoint + if (!DepositionBackendUrlCleaned.endsWith('/')) { + DepositionBackendUrlCleaned += '/'; + } + + let DepositionBackendUrlVersion = DepositionBackendUrlCleaned; + + // Try to connect to the facility backend/version to check if it is available + console.log('Connecting to facility backend: ' + DepositionBackendUrlVersion); + this.http.get(DepositionBackendUrlVersion).subscribe( + response => { + console.log('Connected to facility backend', response); + // If the connection is successful, store the connected facility backend URL + this.connectedDepositionBackend = DepositionBackendUrlCleaned; + this.connectingToDepositionBackend = false; + this.connectedDepositionBackendVersion = response['version']; + }, + error => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}
`; + console.error('Request failed', error); + this.connectedDepositionBackend = ''; + this.connectingToDepositionBackend = false; + } + ); + + return true; + } + } + From 5010076cf6731db11ca04c8ae8622d4f9ff59c55 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Mon, 4 Nov 2024 17:56:56 +0100 Subject: [PATCH 053/245] adding restrictions to deposition types --- .../dataset-detail/dataset-detail.component.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.ts b/src/app/datasets/dataset-detail/dataset-detail.component.ts index 58830f8c25..2f47246783 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.ts +++ b/src/app/datasets/dataset-detail/dataset-detail.component.ts @@ -67,8 +67,7 @@ import { AttachmentService } from "shared/services/attachment.service"; standalone: false, }) export class DatasetDetailComponent - implements OnInit, OnDestroy, EditableComponent -{ + implements OnInit, OnDestroy, EditableComponent { private subscriptions: Subscription[] = []; private _hasUnsavedChanges = false; form: FormGroup; @@ -114,7 +113,7 @@ export class DatasetDetailComponent private http: HttpClient, private router: Router, private fb: FormBuilder, - ) {} + ) { } ngOnInit() { this.connectingToDepositionBackend = true; @@ -173,7 +172,6 @@ export class DatasetDetailComponent } }), ); - console.log("the keywords:" , this.dataset.keywords); } onEditModeEnable() { @@ -344,11 +342,12 @@ export class DatasetDetailComponent onOneDepClick() { console.log('started one dep click'); const id = encodeURIComponent(this.dataset.pid); + this.connectToDepositionBackend(); this.router.navigateByUrl("/datasets/" + id + "/onedep"); console.log("my datset in the details:", this.dataset); // this.store.dispatch(selectDatasetAction({ dataset: this.dataset })); } - onEMPIARclick(){ + onEMPIARclick() { const id = encodeURIComponent(this.dataset.pid); this.router.navigateByUrl("/datasets/" + id + "/empiar"); } @@ -362,13 +361,13 @@ export class DatasetDetailComponent DepositionBackendUrlCleaned += '/'; } - let DepositionBackendUrlVersion = DepositionBackendUrlCleaned; + let DepositionBackendUrlVersion = DepositionBackendUrlCleaned + 'version'; // Try to connect to the facility backend/version to check if it is available - console.log('Connecting to facility backend: ' + DepositionBackendUrlVersion); + console.log('Connecting to OneDep backend: ' + DepositionBackendUrlVersion); this.http.get(DepositionBackendUrlVersion).subscribe( response => { - console.log('Connected to facility backend', response); + console.log('Connected to OneDep backend', response); // If the connection is successful, store the connected facility backend URL this.connectedDepositionBackend = DepositionBackendUrlCleaned; this.connectingToDepositionBackend = false; From 32ab70dab3923457fec4df13a1375d3ecde60bbf Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Mon, 25 Nov 2024 16:54:21 +0000 Subject: [PATCH 054/245] add list of ORCID IDs, token not passed yet --- src/app/datasets/datasets.module.ts | 1 + src/app/datasets/onedep/onedep.component.html | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/datasets/datasets.module.ts b/src/app/datasets/datasets.module.ts index 9f23144951..d1d05db717 100644 --- a/src/app/datasets/datasets.module.ts +++ b/src/app/datasets/datasets.module.ts @@ -94,6 +94,7 @@ import { OneDepComponent } from "./onedep/onedep.component"; import { OrcidFormatterDirective } from "./onedep/onedep.directive"; import { MatSnackBarModule } from "@angular/material/snack-bar"; import { OneDepComponent } from "./onedep/onedep.component"; +import { OrcidFormatterDirective } from "./onedep/onedep.directive"; @NgModule({ imports: [ diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index 294e0d39fa..163cf8ab6a 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -18,7 +18,8 @@ Description - + @@ -39,7 +40,8 @@ Keywords - + {{ keyword }} From 3260b87fa1594a4e62b19bd2fa973219f7498c56 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Mon, 16 Dec 2024 20:32:22 +0000 Subject: [PATCH 055/245] onedep component can be turned off --- .../datasets.routing.module.ts | 5 ---- .../dataset-detail.component.scss | 4 --- .../dataset-detail.component.ts | 26 ++++++++----------- src/app/empiar/empiar.component.scss | 0 4 files changed, 11 insertions(+), 24 deletions(-) delete mode 100644 src/app/empiar/empiar.component.scss 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 14feaf6945..d1637dbd59 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 @@ -6,7 +6,6 @@ import { DashboardComponent } from "datasets/dashboard/dashboard.component"; import { DatablocksComponent } from "datasets/datablocks-table/datablocks-table.component"; import { DatasetDetailsDashboardComponent } from "datasets/dataset-details-dashboard/dataset-details-dashboard.component"; import { PublishComponent } from "datasets/publish/publish.component"; -import { OneDepComponent } from "datasets/onedep/onedep.component"; const routes: Routes = [ { @@ -36,10 +35,6 @@ const routes: Routes = [ component: DatablocksComponent, canActivate: [AuthGuard], }, - { - path: ":id/onedep", - component: OneDepComponent, - }, ]; @NgModule({ imports: [RouterModule.forChild(routes)], diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.scss b/src/app/datasets/dataset-detail/dataset-detail.component.scss index efb4f1bdce..14d17981ed 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.scss +++ b/src/app/datasets/dataset-detail/dataset-detail.component.scss @@ -51,10 +51,6 @@ mat-card { .jupyter-button { margin: 1em 0 0 1em; } -.emexport-button { - margin: 1em 0 0 3em; - background-color: hsla(185, 43%, 45%, 0.458); -} .public-toggle { margin: 1em 1em 0 0; float: right; diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.ts b/src/app/datasets/dataset-detail/dataset-detail.component.ts index 2f47246783..308b7f684f 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.ts +++ b/src/app/datasets/dataset-detail/dataset-detail.component.ts @@ -1,4 +1,11 @@ -import { Component, OnInit, Output, EventEmitter, OnDestroy, Inject } from "@angular/core"; +import { + Component, + OnInit, + Output, + EventEmitter, + OnDestroy, + Inject, +} from "@angular/core"; import { Dataset, Proposal, Sample } from "shared/sdk/models"; import { ENTER, COMMA, SPACE } from "@angular/cdk/keycodes"; import { MatChipInputEvent } from "@angular/material/chips"; @@ -67,7 +74,8 @@ import { AttachmentService } from "shared/services/attachment.service"; standalone: false, }) export class DatasetDetailComponent - implements OnInit, OnDestroy, EditableComponent { + implements OnInit, OnDestroy, EditableComponent +{ private subscriptions: Subscription[] = []; private _hasUnsavedChanges = false; form: FormGroup; @@ -91,16 +99,6 @@ export class DatasetDetailComponent editEnabled = false; show = false; - - connectedDepositionBackend: string = ''; - connectedDepositionBackendVersion: string = ''; - connectingToDepositionBackend: boolean = false; - lastUsedDepositionBackends: string[] = []; - forwardDepositionBackend: string = ''; - errorMessage: string = ''; - - @Output() emClick = new EventEmitter(); - readonly separatorKeyCodes: number[] = [ENTER, COMMA, SPACE]; constructor( @@ -113,11 +111,9 @@ export class DatasetDetailComponent private http: HttpClient, private router: Router, private fb: FormBuilder, - ) { } + ) {} ngOnInit() { - this.connectingToDepositionBackend = true; - this.form = this.fb.group({ datasetName: new FormControl("", [Validators.required]), description: new FormControl("", [Validators.required]), diff --git a/src/app/empiar/empiar.component.scss b/src/app/empiar/empiar.component.scss deleted file mode 100644 index e69de29bb2..0000000000 From c3d30af3aab32cd4c855dcdb620aaf8673dafcc7 Mon Sep 17 00:00:00 2001 From: Spencer Bliven Date: Tue, 17 Dec 2024 15:42:10 +0100 Subject: [PATCH 056/245] Frontend updates to match the backend release-jobs branch This commit rebases and squashes #1585 (7d2a87208d1b780683026e0a769be03e9e4b156c). It contains the following commits: - update job view to match release-jobs - update job schema and timestamp fields - update jobs-detail page - fix testing and linting --- .../datasets/admin-tab/admin-tab.component.ts | 16 +- src/app/datasets/archiving.service.spec.ts | 13 +- src/app/datasets/archiving.service.ts | 8 +- .../datafiles-action.component.spec.ts | 4 +- .../datasets/datafiles/datafiles.component.ts | 13 +- .../jobs-dashboard-new.component.ts | 19 +- .../jobs-dashboard.component.spec.ts | 4 +- .../jobs-dashboard.component.ts | 21 +- .../jobs-detail/jobs-detail.component.html | 203 +++++++++++------- .../jobs-detail/jobs-detail.component.scss | 33 +-- .../shared-table/_shared-table-theme.scss | 1 + src/app/shared/sdk/models/Job.ts | 149 +++++++++++++ .../effects/jobs.effects.spec.ts | 9 +- .../reducers/jobs.reducer.spec.ts | 9 +- .../selectors/jobs.selectors.spec.ts | 15 +- 15 files changed, 358 insertions(+), 159 deletions(-) create mode 100644 src/app/shared/sdk/models/Job.ts diff --git a/src/app/datasets/admin-tab/admin-tab.component.ts b/src/app/datasets/admin-tab/admin-tab.component.ts index 19b77893c0..e612e490a1 100644 --- a/src/app/datasets/admin-tab/admin-tab.component.ts +++ b/src/app/datasets/admin-tab/admin-tab.component.ts @@ -50,23 +50,23 @@ export class AdminTabComponent implements OnInit, OnDestroy { const job: CreateJobDto = { emailJobInitiator: user.email, type: "reset", + createdBy: user.username, + createdAt: new Date(), datasetList: [], jobParams: {}, }; - job.jobParams["username"] = user.username; - const fileObj: FileObject = { - pid: "", - files: [], - }; + const fileList: string[] = []; - fileObj.pid = this.dataset["pid"]; if (this.dataset["datablocks"]) { this.dataset["datablocks"].map((d) => { fileList.push(d["archiveId"]); }); } - fileObj.files = fileList; - job.datasetList = [fileObj]; + const fileObj: FileObject = { + pid: this.dataset["pid"], + files: fileList, + }; + job.jobParams.datasetList = [fileObj]; this.store.dispatch(submitJobAction({ job })); } }); diff --git a/src/app/datasets/archiving.service.spec.ts b/src/app/datasets/archiving.service.spec.ts index 8d15983323..683992c735 100644 --- a/src/app/datasets/archiving.service.spec.ts +++ b/src/app/datasets/archiving.service.spec.ts @@ -70,10 +70,9 @@ describe("ArchivingService", () => { destinationPath, ); - // expect(job).toBeInstanceOf(Job); - expect(job["emailJobInitiator"]).toEqual("test@email.com"); - expect(job["jobParams"]["username"]).toEqual("testName"); - expect(job["datasetList"]).toEqual(datasetList); + expect(job).toBeInstanceOf(Job); + expect(job["createdBy"]).toEqual("testName"); + expect(job["jobParams"]["datasetList"]).toEqual(datasetList); expect(job["type"]).toEqual("archive"); }); }); @@ -104,9 +103,9 @@ describe("ArchivingService", () => { })); const archive = true; const job = createMock({ - jobParams: { username: user.username }, - emailJobInitiator: user.email, - datasetList, + jobParams: { datasetList }, + createdBy: user.username, + createdAt: new Date(), type: "archive", executionTime: "", jobResultObject: {}, diff --git a/src/app/datasets/archiving.service.ts b/src/app/datasets/archiving.service.ts index fcf68c4a12..99cfb094f2 100644 --- a/src/app/datasets/archiving.service.ts +++ b/src/app/datasets/archiving.service.ts @@ -30,7 +30,7 @@ export class ArchivingService { ) { const extra = archive ? {} : destinationPath; const jobParams = { - username: user.username, + datasetIds: datasets.map((dataset) => dataset.pid), ...extra, }; @@ -40,12 +40,8 @@ export class ArchivingService { const data = { jobParams, - emailJobInitiator: user.email, + createdBy: user.username, // Revise this, files == []...? See earlier version of this method in dataset-table component for context - datasetList: datasets.map((dataset) => ({ - pid: dataset.pid, - files: [], - })), type: archive ? "archive" : "retrieve", }; diff --git a/src/app/datasets/datafiles-actions/datafiles-action.component.spec.ts b/src/app/datasets/datafiles-actions/datafiles-action.component.spec.ts index 46e362e2b5..0919c32cc0 100644 --- a/src/app/datasets/datafiles-actions/datafiles-action.component.spec.ts +++ b/src/app/datasets/datafiles-actions/datafiles-action.component.spec.ts @@ -185,7 +185,7 @@ describe("1000: DatafilesActionComponent", () => { beforeEach(() => { fixture = TestBed.createComponent(DatafilesActionComponent); component = fixture.componentInstance; - component.files = structuredClone(actionFiles); + component.files = JSON.parse(JSON.stringify(actionFiles)); component.actionConfig = actionsConfig[0]; component.actionDataset = actionDataset; component.maxFileSize = lowerMaxFileSizeLimit; @@ -484,7 +484,7 @@ describe("1000: DatafilesActionComponent", () => { component.maxFileSize = lowerMaxFileSizeLimit; break; } - component.files = structuredClone(actionFiles); + component.files = JSON.parse(JSON.stringify(actionFiles)); switch (selectedFiles) { case selectedFilesType.file1: component.files[0].selected = true; diff --git a/src/app/datasets/datafiles/datafiles.component.ts b/src/app/datasets/datafiles/datafiles.component.ts index d807d09145..91da0d4b1e 100644 --- a/src/app/datasets/datafiles/datafiles.component.ts +++ b/src/app/datasets/datafiles/datafiles.component.ts @@ -304,15 +304,12 @@ export class DatafilesComponent if (email) { this.getSelectedFiles(); const data = { - emailJobInitiator: email, - creationTime: new Date(), + createdBy: email, + createdAt: new Date(), type: "public", - datasetList: [ - { - pid: this.datasetPid, - files: this.getSelectedFiles(), - }, - ], + jobParams: { + datasetIds: [this.datasetPid], + }, }; this.store.dispatch(submitJobAction({ job: data })); } diff --git a/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.ts b/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.ts index 611ee66cec..11dee7b735 100644 --- a/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.ts +++ b/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.ts @@ -4,6 +4,7 @@ import { Component, OnDestroy, } from "@angular/core"; +import { Router } from "@angular/router"; import { SciCatDataSource } from "../../shared/services/scicat.datasource"; import { ScicatDataService } from "../../shared/services/scicat-data-service"; import { ExportExcelService } from "../../shared/services/export-excel.service"; @@ -30,11 +31,11 @@ export class JobsDashboardNewComponent implements OnDestroy, AfterViewChecked { hideOrder: 0, }, { - id: "emailJobInitiator", - label: "Initiator", + id: "createdBy", + label: "Creator", icon: "person", canSort: true, - matchMode: "contains", + matchMode: "is", hideOrder: 1, }, { @@ -46,7 +47,7 @@ export class JobsDashboardNewComponent implements OnDestroy, AfterViewChecked { hideOrder: 2, }, { - id: "creationTime", + id: "createdAt", icon: "schedule", label: "Created at local time", format: "date medium ", @@ -64,10 +65,9 @@ export class JobsDashboardNewComponent implements OnDestroy, AfterViewChecked { hideOrder: 4, }, { - id: "jobStatusMessage", + id: "statusCode", icon: "traffic", label: "Status", - format: "json", canSort: true, matchMode: "contains", hideOrder: 5, @@ -102,6 +102,7 @@ export class JobsDashboardNewComponent implements OnDestroy, AfterViewChecked { private cdRef: ChangeDetectorRef, private dataService: ScicatDataService, private exportService: ExportExcelService, + private router: Router, ) { this.dataSource = new SciCatDataSource( this.appConfigService, @@ -119,10 +120,8 @@ export class JobsDashboardNewComponent implements OnDestroy, AfterViewChecked { this.dataSource.disconnectExportData(); } - onRowClick(job: JobsTableData) { - // currently deactivated, no extra data available - /* console.log("Row clicked:", job); + onRowClick(job: Job) { const id = encodeURIComponent(job.id); - this.router.navigateByUrl("/user/jobs/" + id); */ + this.router.navigateByUrl("/user/jobs/" + id); } } diff --git a/src/app/jobs/jobs-dashboard/jobs-dashboard.component.spec.ts b/src/app/jobs/jobs-dashboard/jobs-dashboard.component.spec.ts index 0084ef2f6c..4e50368af3 100644 --- a/src/app/jobs/jobs-dashboard/jobs-dashboard.component.spec.ts +++ b/src/app/jobs/jobs-dashboard/jobs-dashboard.component.spec.ts @@ -100,8 +100,8 @@ describe("JobsDashboardComponent", () => { dispatchSpy = spyOn(store, "dispatch"); const mode = JobViewMode.myJobs; - component.email = "test@email.com"; - const viewMode = { emailJobInitiator: component.email }; + component.username = "testName"; + const viewMode = { createdBy: component.username }; component.onModeChange(mode); expect(dispatchSpy).toHaveBeenCalledTimes(1); diff --git a/src/app/jobs/jobs-dashboard/jobs-dashboard.component.ts b/src/app/jobs/jobs-dashboard/jobs-dashboard.component.ts index 2033ad245a..3a8b41ff3a 100644 --- a/src/app/jobs/jobs-dashboard/jobs-dashboard.component.ts +++ b/src/app/jobs/jobs-dashboard/jobs-dashboard.component.ts @@ -48,7 +48,7 @@ export class JobsDashboardComponent implements OnInit, OnDestroy { jobs: JobsTableData[] = []; profile: any; - email = ""; + username = ""; subscriptions: Subscription[] = []; @@ -96,13 +96,10 @@ export class JobsDashboardComponent implements OnInit, OnDestroy { if (jobs) { tableData = jobs.map((job) => ({ id: job._id, - initiator: job.emailJobInitiator, + initiator: job.createdBy, type: job.type, - createdAt: this.datePipe.transform( - job.creationTime, - "yyyy-MM-dd HH:mm", - ), - statusMessage: job.jobStatusMessage, + createdAt: this.datePipe.transform(job.createdAt, "yyyy-MM-dd HH:mm"), + statusMessage: job.statusMessage, })); } return tableData; @@ -129,7 +126,7 @@ export class JobsDashboardComponent implements OnInit, OnDestroy { break; } case JobViewMode.myJobs: { - viewMode = { emailJobInitiator: this.email }; + viewMode = { createdBy: this.username }; break; } default: { @@ -154,11 +151,11 @@ export class JobsDashboardComponent implements OnInit, OnDestroy { // map column names back to original names switch (event.active) { case "statusMessage": { - event.active = "jobStatusMessage"; + event.active = "statusMessage"; break; } case "initiator": { - event.active = "emailJobInitiator"; + event.active = "createdBy"; break; } default: { @@ -181,13 +178,13 @@ export class JobsDashboardComponent implements OnInit, OnDestroy { this.subscriptions.push( this.store.select(selectCurrentUser).subscribe((current) => { if (current) { - this.email = current.email; + this.username = current.username; if (!current.realm) { this.store.select(selectProfile).subscribe((profile) => { if (profile) { this.profile = profile; - this.email = profile.email; + this.username = profile.username; } this.onModeChange(JobViewMode.myJobs); }); diff --git a/src/app/jobs/jobs-detail/jobs-detail.component.html b/src/app/jobs/jobs-detail/jobs-detail.component.html index ac46789303..42b3234ba6 100644 --- a/src/app/jobs/jobs-detail/jobs-detail.component.html +++ b/src/app/jobs/jobs-detail/jobs-detail.component.html @@ -1,73 +1,132 @@ -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- {{ daSet.pid | json }} +
+
+ + +
+ description
-
- - - - - - - - -
- mail - Email Job Initiator - {{ job.emailJobInitiator }}
- bubble_chart - Type - {{ value }}
- brightness_high - Creation Time - {{ value | date: "yyyy-MM-dd HH:mm" }}
- gavel - Execution Time - {{ value | date: "yyyy-MM-dd HH:mm" }}
- settings - Job Params - {{ value | json }}
- markunread - Date Of Last Message - {{ value | date: "yyyy-MM-dd HH:mm" }}
- folder - Dataset List -
- calendar_today - Created At - {{ value | date: "yyyy-MM-dd HH:mm" }}
- calendar_today - Updated At - {{ value | date: "yyyy-MM-dd HH:mm" }}
-
-
-
+ General Information + + + + + + + + + + + + + + + +
ID{{ value }}
Type{{ value }}
Created At{{ value | date: "yyyy-MM-dd HH:mm" }}
+
+ + + +
+ person +
+ Users and Ownership +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Created By{{ value }}
Contact Email{{ value }}
Owner User{{ value }}
Owner Group{{ value }}
Access Groups{{ value }}
Updated By{{ value }}
+
+
+ + +
+ analytics +
+ Status +
+ + + + + + + + + + + + + + +
Status Code{{ value }}
Status Message{{ value }}
Updated At{{ value }}
+
+
+ + +
+ library_books +
+ Parameters and Configuration +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Job Parameters{{ value | json }}
Configuration Version{{ value }}
Job Result Object{{ value | json }}
Datasets ValidationNo
Datasets ValidationYes
Is PublishedNo
Is PublishedYes
+
+
+
+

diff --git a/src/app/jobs/jobs-detail/jobs-detail.component.scss b/src/app/jobs/jobs-detail/jobs-detail.component.scss index 2687093ee7..2d1be484f3 100644 --- a/src/app/jobs/jobs-detail/jobs-detail.component.scss +++ b/src/app/jobs/jobs-detail/jobs-detail.component.scss @@ -1,20 +1,25 @@ -.job-detail { - mat-card { - margin: 1em; +mat-card { + margin: 1em; - table { - td { - padding: 0.3em; - } + .section-icon { + height: auto !important; + width: auto !important; - th { - text-align: left; - padding-right: 0.5em; + mat-icon { + vertical-align: middle; + } + } + + table { + th { + min-width: 10rem; + padding-right: 0.5rem; + text-align: left; + } - mat-icon { - vertical-align: middle; - } - } + td { + width: 100%; + padding: 0.5rem 0; } } } diff --git a/src/app/shared/modules/shared-table/_shared-table-theme.scss b/src/app/shared/modules/shared-table/_shared-table-theme.scss index 4124c88a0c..735cd85127 100644 --- a/src/app/shared/modules/shared-table/_shared-table-theme.scss +++ b/src/app/shared/modules/shared-table/_shared-table-theme.scss @@ -31,6 +31,7 @@ mat-row:hover { background-color: mat.get-color-from-palette($hover, "lighter"); + cursor: pointer; } .mat-form-field-appearance-outline { diff --git a/src/app/shared/sdk/models/Job.ts b/src/app/shared/sdk/models/Job.ts new file mode 100644 index 0000000000..778938bec9 --- /dev/null +++ b/src/app/shared/sdk/models/Job.ts @@ -0,0 +1,149 @@ +/* eslint-disable */ + +declare var Object: any; +export interface JobInterface { + id?: string; + ownerUser?: string; + type?: string; + statusCode?: string; + statusMessage?: string; + jobParams?: any; + datasetsValidation?: boolean; + contactEmail?: string; + configVersion?: string; + jobResultObject?: any; + createdBy?: string; + updatedBy?: string; + createdAt?: Date; + updatedAt?: Date; + ownerGroup?: string; + accessGroups?: any; + isPublished?: boolean; +} + +export class Job implements JobInterface { + "id": string; + "ownerUser": string; + "type": string; + "statusCode": string; + "statusMessage": string; + "jobParams": any; + "datasetsValidation": boolean; + "contactEmail": string; + "configVersion": string; + "jobResultObject": any; + "createdBy": string; + "updatedBy": string; + "createdAt": Date; + "updatedAt": Date; + "ownerGroup": string; + "accessGroups": any; + "isPublished": boolean; + + constructor(data?: JobInterface) { + Object.assign(this, data); + } + /** + * The name of the model represented by this $resource, + * i.e. `Job`. + */ + public static getModelName() { + return "Job"; + } + /** + * @method factory + * @author Jonathan Casarrubias + * @license MIT + * This method creates an instance of Job for dynamic purposes. + **/ + public static factory(data: JobInterface): Job { + return new Job(data); + } + /** + * @method getModelDefinition + * @author Julien Ledun + * @license MIT + * This method returns an object that represents some of the model + * definitions. + **/ + public static getModelDefinition() { + return { + name: "Job", + plural: "Jobs", + path: "Jobs", + idName: "id", + properties: { + id: { + name: "id", + type: "string", + }, + ownerUser: { + name: "ownerUser", + type: "string", + }, + type: { + name: "type", + type: "string", + default: "retrieve", + }, + statusCode: { + name: "statusCode", + type: "string", + }, + statusMessage: { + name: "statusMessage", + type: "string", + }, + jobParams: { + name: "jobParams", + type: "any", + }, + datasetsValidation: { + name: "datasetsValidation", + type: "boolean", + }, + contactEmail: { + name: "contactEmail", + type: "string", + }, + configVersion: { + name: "configVersion", + type: "string", + }, + jobResultObject: { + name: "jobResultObject", + type: "any", + }, + createdBy: { + name: "createdBy", + type: "string", + }, + updatedBy: { + name: "updatedBy", + type: "string", + }, + createdAt: { + name: "createdAt", + type: "Date", + }, + updatedAt: { + name: "updatedAt", + type: "Date", + }, + ownerGroup: { + name: "ownerGroup", + type: "string", + }, + accessGroups: { + name: "accessGroups", + type: "any", + }, + isPublished: { + name: "isPublished", + type: "boolean", + }, + }, + relations: {}, + }; + } +} diff --git a/src/app/state-management/effects/jobs.effects.spec.ts b/src/app/state-management/effects/jobs.effects.spec.ts index 1390740160..b4e673e373 100644 --- a/src/app/state-management/effects/jobs.effects.spec.ts +++ b/src/app/state-management/effects/jobs.effects.spec.ts @@ -24,12 +24,11 @@ import { createMock } from "shared/MockStubs"; const job = createMock({ _id: "testId", id: "testId", - emailJobInitiator: "test@email.com", + createdBy: "testName", type: "archive", - datasetList: [], - creationTime: "", - executionTime: "", - jobParams: {}, + jobParams: { + datasetList: [], + }, jobResultObject: {}, jobStatusMessage: "", ownerGroup: "", diff --git a/src/app/state-management/reducers/jobs.reducer.spec.ts b/src/app/state-management/reducers/jobs.reducer.spec.ts index 4a0157e02d..8eb3420209 100644 --- a/src/app/state-management/reducers/jobs.reducer.spec.ts +++ b/src/app/state-management/reducers/jobs.reducer.spec.ts @@ -7,12 +7,11 @@ import { JobClass } from "@scicatproject/scicat-sdk-ts"; const job = createMock({ _id: "testId", id: "testId", - emailJobInitiator: "test@email.com", + createdBy: "testName", type: "archive", - datasetList: [], - creationTime: "", - executionTime: "", - jobParams: {}, + jobParams: { + datasetList: [] + }, jobResultObject: {}, jobStatusMessage: "", ownerGroup: "", diff --git a/src/app/state-management/selectors/jobs.selectors.spec.ts b/src/app/state-management/selectors/jobs.selectors.spec.ts index 7f866bd6fd..a2c3098b4e 100644 --- a/src/app/state-management/selectors/jobs.selectors.spec.ts +++ b/src/app/state-management/selectors/jobs.selectors.spec.ts @@ -4,18 +4,17 @@ import { JobsState } from "../state/jobs.store"; import { createMock } from "shared/MockStubs"; import { JobClass } from "@scicatproject/scicat-sdk-ts"; -const job = createMock({ - emailJobInitiator: "test@email.com", +const data: JobInterface = { + _id: "testId", + id: "testId", + createdBy: "testName", type: "archive", - _id: "", - id: "", - creationTime: "", - executionTime: "", - jobParams: {}, + jobParams: { + datasetList: [], + }, jobResultObject: {}, jobStatusMessage: "", ownerGroup: "", - datasetList: [], }); const jobFilters = { From 5f372b007afe438d28afff898568fd5289b5bce5 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Tue, 17 Dec 2024 16:59:37 +0000 Subject: [PATCH 057/245] just to be safe --- src/app/datasets/onedep/onedep.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index 163cf8ab6a..4887573eef 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -18,7 +18,7 @@ Description - From 19b59bd85087ec9061c33aa172f1de6f4d302864 Mon Sep 17 00:00:00 2001 From: Despina Date: Wed, 18 Dec 2024 09:35:00 +0100 Subject: [PATCH 058/245] solving conflicts --- .../datasets/admin-tab/admin-tab.component.ts | 12 +++++------- src/app/datasets/archiving.service.spec.ts | 8 ++++---- src/app/datasets/archiving.service.ts | 5 ++++- .../datasets/datafiles/datafiles.component.ts | 14 ++++++++++---- .../jobs-dashboard-new.component.ts | 11 ++--------- .../jobs-dashboard.component.spec.ts | 3 ++- .../jobs-dashboard/jobs-dashboard.component.ts | 5 +++-- .../jobs/jobs-detail/jobs-detail.component.ts | 4 +++- src/app/shared/MockStubs.ts | 3 ++- src/app/shared/sdk/models/Job.ts | 16 ++++++++-------- src/app/state-management/actions/jobs.actions.ts | 9 +++++---- .../effects/jobs.effects.spec.ts | 10 ++++------ src/app/state-management/effects/jobs.effects.ts | 12 +++++++++--- .../reducers/jobs.reducer.spec.ts | 12 +++++------- .../selectors/jobs.selectors.spec.ts | 9 +++------ src/app/state-management/state/jobs.store.ts | 5 +++-- 16 files changed, 72 insertions(+), 66 deletions(-) diff --git a/src/app/datasets/admin-tab/admin-tab.component.ts b/src/app/datasets/admin-tab/admin-tab.component.ts index e612e490a1..878cfdf0dc 100644 --- a/src/app/datasets/admin-tab/admin-tab.component.ts +++ b/src/app/datasets/admin-tab/admin-tab.component.ts @@ -17,6 +17,7 @@ import { selectIsAdmin, selectIsLoading, } from "state-management/selectors/user.selectors"; +import { Job } from "shared/sdk/models/Job"; @Component({ selector: "app-admin-tab", @@ -47,15 +48,12 @@ export class AdminTabComponent implements OnInit, OnDestroy { .pipe(take(1)) .subscribe((user) => { if (user && this.dataset) { - const job: CreateJobDto = { - emailJobInitiator: user.email, - type: "reset", + const job = new Job({ createdBy: user.username, - createdAt: new Date(), - datasetList: [], + createdAt: new Date().toDateString(), + type: "reset", jobParams: {}, - }; - + }); const fileList: string[] = []; if (this.dataset["datablocks"]) { this.dataset["datablocks"].map((d) => { diff --git a/src/app/datasets/archiving.service.spec.ts b/src/app/datasets/archiving.service.spec.ts index 683992c735..0ad6e1a77b 100644 --- a/src/app/datasets/archiving.service.spec.ts +++ b/src/app/datasets/archiving.service.spec.ts @@ -11,6 +11,7 @@ import { JobsState } from "state-management/state/jobs.store"; import { ArchivingService } from "./archiving.service"; import { createMock, mockDataset } from "shared/MockStubs"; import { CreateJobDto, ReturnedUserDto } from "@scicatproject/scicat-sdk-ts"; +import { Job } from "shared/sdk/models/Job"; describe("ArchivingService", () => { let service: ArchivingService; @@ -102,14 +103,13 @@ describe("ArchivingService", () => { files: [], })); const archive = true; - const job = createMock({ + const job = createMock({ jobParams: { datasetList }, createdBy: user.username, - createdAt: new Date(), + createdAt: new Date().toDateString(), type: "archive", - executionTime: "", jobResultObject: {}, - jobStatusMessage: "", + statusMessage: "", }); const createJobSpy = spyOn( service, diff --git a/src/app/datasets/archiving.service.ts b/src/app/datasets/archiving.service.ts index 99cfb094f2..eb4374a44d 100644 --- a/src/app/datasets/archiving.service.ts +++ b/src/app/datasets/archiving.service.ts @@ -30,7 +30,10 @@ export class ArchivingService { ) { const extra = archive ? {} : destinationPath; const jobParams = { - datasetIds: datasets.map((dataset) => dataset.pid), + datasetList: datasets.map((dataset) => ({ + pid: dataset.pid, + files: [], + })), ...extra, }; diff --git a/src/app/datasets/datafiles/datafiles.component.ts b/src/app/datasets/datafiles/datafiles.component.ts index 91da0d4b1e..de9f38d88e 100644 --- a/src/app/datasets/datafiles/datafiles.component.ts +++ b/src/app/datasets/datafiles/datafiles.component.ts @@ -33,6 +33,7 @@ import { NgForm } from "@angular/forms"; import { DataFiles_File } from "./datafiles.interfaces"; import { ActionDataset } from "datasets/datafiles-actions/datafiles-action.interfaces"; import { AuthService } from "shared/services/auth/auth.service"; +import { Job } from "shared/sdk/models/Job"; @Component({ selector: "datafiles", @@ -302,16 +303,21 @@ export class DatafilesComponent }); dialogRef.afterClosed().subscribe((email) => { if (email) { - this.getSelectedFiles(); + const selectedFiles = this.getSelectedFiles(); const data = { createdBy: email, - createdAt: new Date(), + createdAt: new Date().toDateString(), type: "public", jobParams: { - datasetIds: [this.datasetPid], + datasetList: [ + { + pid: this.datasetPid, + files: selectedFiles, + }, + ], }, }; - this.store.dispatch(submitJobAction({ job: data })); + this.store.dispatch(submitJobAction({ job: data as Job })); } }); } diff --git a/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.ts b/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.ts index 11dee7b735..210586cfa1 100644 --- a/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.ts +++ b/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.ts @@ -11,6 +11,7 @@ import { ExportExcelService } from "../../shared/services/export-excel.service"; import { Column } from "shared/modules/shared-table/shared-table.module"; import { AppConfigService } from "app-config.service"; import { JobsTableData } from "jobs/jobs-dashboard/jobs-dashboard.component"; +import { Job } from "shared/sdk/models/Job"; @Component({ selector: "app-jobs-new-dashboard", @@ -72,21 +73,13 @@ export class JobsDashboardNewComponent implements OnDestroy, AfterViewChecked { matchMode: "contains", hideOrder: 5, }, - { - id: "datasetList", - icon: "list", - label: "Datasets", - format: "json", - canSort: true, - hideOrder: 6, - }, { id: "jobResultObject", icon: "work_outline", label: "Result", format: "json", canSort: true, - hideOrder: 7, + hideOrder: 6, }, ]; diff --git a/src/app/jobs/jobs-dashboard/jobs-dashboard.component.spec.ts b/src/app/jobs/jobs-dashboard/jobs-dashboard.component.spec.ts index 4e50368af3..ee5315f7e7 100644 --- a/src/app/jobs/jobs-dashboard/jobs-dashboard.component.spec.ts +++ b/src/app/jobs/jobs-dashboard/jobs-dashboard.component.spec.ts @@ -24,6 +24,7 @@ import { MatCardModule } from "@angular/material/card"; import { MatIconModule } from "@angular/material/icon"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { JobClass } from "@scicatproject/scicat-sdk-ts"; +import { JobInterface } from "shared/sdk/models/Job"; describe("JobsDashboardComponent", () => { let component: JobsDashboardComponent; @@ -144,7 +145,7 @@ describe("JobsDashboardComponent", () => { describe("#onRowClick()", () => { it("should navigate to a job", () => { - const job = createMock({ id: "test" }); + const job = createMock({ id: "test" }); component.onRowClick({ ...job, initiator: "", diff --git a/src/app/jobs/jobs-dashboard/jobs-dashboard.component.ts b/src/app/jobs/jobs-dashboard/jobs-dashboard.component.ts index 3a8b41ff3a..8c374bfecf 100644 --- a/src/app/jobs/jobs-dashboard/jobs-dashboard.component.ts +++ b/src/app/jobs/jobs-dashboard/jobs-dashboard.component.ts @@ -27,6 +27,7 @@ import { selectCurrentUser, selectProfile, } from "state-management/selectors/user.selectors"; +import { Job, JobInterface } from "shared/sdk/models/Job"; export interface JobsTableData { id: string; @@ -91,11 +92,11 @@ export class JobsDashboardComponent implements OnInit, OnDestroy { ); } - formatTableData(jobs: JobClass[]): JobsTableData[] { + formatTableData(jobs: JobInterface[]): JobsTableData[] { let tableData: JobsTableData[] = []; if (jobs) { tableData = jobs.map((job) => ({ - id: job._id, + id: job.id, initiator: job.createdBy, type: job.type, createdAt: this.datePipe.transform(job.createdAt, "yyyy-MM-dd HH:mm"), diff --git a/src/app/jobs/jobs-detail/jobs-detail.component.ts b/src/app/jobs/jobs-detail/jobs-detail.component.ts index 910febb145..8142f27e90 100644 --- a/src/app/jobs/jobs-detail/jobs-detail.component.ts +++ b/src/app/jobs/jobs-detail/jobs-detail.component.ts @@ -5,6 +5,7 @@ import { ActivatedRoute } from "@angular/router"; import { selectCurrentJob } from "state-management/selectors/jobs.selectors"; import { Observable, Subscription } from "rxjs"; import { JobClass } from "@scicatproject/scicat-sdk-ts"; +import { JobInterface } from "shared/sdk/models/Job"; @Component({ selector: "app-jobs-detail", @@ -14,7 +15,8 @@ import { JobClass } from "@scicatproject/scicat-sdk-ts"; export class JobsDetailComponent implements OnInit, OnDestroy { // TODO: We should extract the response dto with the right properties instead of using the schema for ApiResponse in the backend job$ = this.store.select(selectCurrentJob) as Observable< - JobClass & { createdAt: string; updatedAt: string } + // JobClass & { createdAt: string; updatedAt: string } + JobInterface >; routeSubscription: Subscription = new Subscription(); diff --git a/src/app/shared/MockStubs.ts b/src/app/shared/MockStubs.ts index 3060e7ce8c..600de0b6b0 100644 --- a/src/app/shared/MockStubs.ts +++ b/src/app/shared/MockStubs.ts @@ -22,6 +22,7 @@ import { ReturnedUserDto, } from "@scicatproject/scicat-sdk-ts"; import { SDKToken } from "./services/auth/auth.service"; +import { JobInterface } from "./sdk/models/Job"; export class MockUserApi { getCurrentId() { @@ -325,7 +326,7 @@ export const mockAttachment = createMock({}); export const mockSample = createMock({}); export const mockProposal = createMock({}); export const mockInstrument = createMock({}); -export const mockJob = createMock({}); +export const mockJob = createMock({}); export const mockLogbook = createMock({}); export const mockPolicy = createMock({}); export const mockPublishedData = createMock({}); diff --git a/src/app/shared/sdk/models/Job.ts b/src/app/shared/sdk/models/Job.ts index 778938bec9..ba6e9ad142 100644 --- a/src/app/shared/sdk/models/Job.ts +++ b/src/app/shared/sdk/models/Job.ts @@ -4,18 +4,18 @@ declare var Object: any; export interface JobInterface { id?: string; ownerUser?: string; - type?: string; + type: string; statusCode?: string; statusMessage?: string; - jobParams?: any; + jobParams: any; datasetsValidation?: boolean; contactEmail?: string; configVersion?: string; jobResultObject?: any; createdBy?: string; updatedBy?: string; - createdAt?: Date; - updatedAt?: Date; + createdAt?: string; + updatedAt?: string; ownerGroup?: string; accessGroups?: any; isPublished?: boolean; @@ -34,8 +34,8 @@ export class Job implements JobInterface { "jobResultObject": any; "createdBy": string; "updatedBy": string; - "createdAt": Date; - "updatedAt": Date; + "createdAt": string; + "updatedAt": string; "ownerGroup": string; "accessGroups": any; "isPublished": boolean; @@ -124,11 +124,11 @@ export class Job implements JobInterface { }, createdAt: { name: "createdAt", - type: "Date", + type: "string", }, updatedAt: { name: "updatedAt", - type: "Date", + type: "string", }, ownerGroup: { name: "ownerGroup", diff --git a/src/app/state-management/actions/jobs.actions.ts b/src/app/state-management/actions/jobs.actions.ts index 993a057778..fdeecbd8a0 100644 --- a/src/app/state-management/actions/jobs.actions.ts +++ b/src/app/state-management/actions/jobs.actions.ts @@ -1,10 +1,11 @@ import { createAction, props } from "@ngrx/store"; import { CreateJobDto, JobClass } from "@scicatproject/scicat-sdk-ts"; +import { Job, JobInterface } from "shared/sdk/models/Job"; export const fetchJobsAction = createAction("[Job] Fetch Jobs"); export const fetchJobsCompleteAction = createAction( "[Job] Fetch Jobs Complete", - props<{ jobs: JobClass[] }>(), + props<{ jobs: JobInterface[] }>(), ); export const fetchJobsFailedAction = createAction("[Job] Fetch Jobs Failed"); @@ -21,17 +22,17 @@ export const fetchJobAction = createAction( ); export const fetchJobCompleteAction = createAction( "[Job] Fetch Job Complete", - props<{ job: JobClass }>(), + props<{ job: JobInterface }>(), ); export const fetchJobFailedAction = createAction("[Job] Fetch Job Failed"); export const submitJobAction = createAction( "[Job] Submit Job", - props<{ job: CreateJobDto }>(), + props<{ job: Job }>(), ); export const submitJobCompleteAction = createAction( "[Job] Submit Job Complete", - props<{ job: JobClass }>(), + props<{ job: JobInterface }>(), ); export const submitJobFailedAction = createAction( "[Job] Submit Job Failed", diff --git a/src/app/state-management/effects/jobs.effects.spec.ts b/src/app/state-management/effects/jobs.effects.spec.ts index b4e673e373..ac902ae6a5 100644 --- a/src/app/state-management/effects/jobs.effects.spec.ts +++ b/src/app/state-management/effects/jobs.effects.spec.ts @@ -20,19 +20,17 @@ import { } from "@scicatproject/scicat-sdk-ts"; import { TestObservable } from "jasmine-marbles/src/test-observables"; import { createMock } from "shared/MockStubs"; +import { Job, JobInterface } from "shared/sdk/models/Job"; -const job = createMock({ - _id: "testId", +const data: JobInterface = { id: "testId", createdBy: "testName", type: "archive", jobParams: { datasetList: [], }, - jobResultObject: {}, - jobStatusMessage: "", - ownerGroup: "", -}); +}; +const job = new Job(data); describe("JobEffects", () => { let actions: TestObservable; diff --git a/src/app/state-management/effects/jobs.effects.ts b/src/app/state-management/effects/jobs.effects.ts index e1e32fb2fc..b8e139bf90 100644 --- a/src/app/state-management/effects/jobs.effects.ts +++ b/src/app/state-management/effects/jobs.effects.ts @@ -17,6 +17,8 @@ import { loadingCompleteAction, updateUserSettingsAction, } from "state-management/actions/user.actions"; +import { JobInterface } from "shared/sdk/models/Job"; +import { datasets } from "state-management/selectors"; @Injectable() export class JobEffects { @@ -58,7 +60,7 @@ export class JobEffects { ofType(fromActions.fetchJobAction), switchMap(({ jobId }) => this.jobsService.jobsControllerFindOne(jobId).pipe( - map((job: JobClass) => fromActions.fetchJobCompleteAction({ job })), + map((job: JobInterface) => fromActions.fetchJobCompleteAction({ job })), catchError(() => of(fromActions.fetchJobFailedAction())), ), ), @@ -69,8 +71,12 @@ export class JobEffects { return this.actions$.pipe( ofType(fromActions.submitJobAction), switchMap(({ job }) => - this.jobsService.jobsControllerCreate(job).pipe( - map((res) => fromActions.submitJobCompleteAction({ job: res })), + this.jobsService.jobsControllerCreate({ + ...job, + emailJobInitiator: job.createdBy, + datasetList: job.jobParams.datasetList, + } as CreateJobDto).pipe( + map((res) => fromActions.submitJobCompleteAction({ job: res as JobInterface })), catchError((err) => of(fromActions.submitJobFailedAction({ err }))), ), ), diff --git a/src/app/state-management/reducers/jobs.reducer.spec.ts b/src/app/state-management/reducers/jobs.reducer.spec.ts index 8eb3420209..80981a7e7c 100644 --- a/src/app/state-management/reducers/jobs.reducer.spec.ts +++ b/src/app/state-management/reducers/jobs.reducer.spec.ts @@ -3,19 +3,17 @@ import * as fromActions from "../actions/jobs.actions"; import { initialJobsState } from "state-management/state/jobs.store"; import { createMock } from "shared/MockStubs"; import { JobClass } from "@scicatproject/scicat-sdk-ts"; +import { Job, JobInterface } from "shared/sdk/models/Job"; -const job = createMock({ - _id: "testId", +const data: JobInterface = { id: "testId", createdBy: "testName", type: "archive", jobParams: { - datasetList: [] + datasetList: [], }, - jobResultObject: {}, - jobStatusMessage: "", - ownerGroup: "", -}); +}; +const job = new Job(data); describe("jobsReducer", () => { describe("on fetchJobsCompleteAction", () => { diff --git a/src/app/state-management/selectors/jobs.selectors.spec.ts b/src/app/state-management/selectors/jobs.selectors.spec.ts index a2c3098b4e..040366483a 100644 --- a/src/app/state-management/selectors/jobs.selectors.spec.ts +++ b/src/app/state-management/selectors/jobs.selectors.spec.ts @@ -1,21 +1,18 @@ import * as fromSelectors from "./jobs.selectors"; - import { JobsState } from "../state/jobs.store"; import { createMock } from "shared/MockStubs"; import { JobClass } from "@scicatproject/scicat-sdk-ts"; +import { Job, JobInterface } from "shared/sdk/models/Job"; const data: JobInterface = { - _id: "testId", id: "testId", createdBy: "testName", type: "archive", jobParams: { datasetList: [], }, - jobResultObject: {}, - jobStatusMessage: "", - ownerGroup: "", -}); +}; +const job = new Job(data); const jobFilters = { mode: null, diff --git a/src/app/state-management/state/jobs.store.ts b/src/app/state-management/state/jobs.store.ts index 365e0ede3b..d9e891e835 100644 --- a/src/app/state-management/state/jobs.store.ts +++ b/src/app/state-management/state/jobs.store.ts @@ -1,9 +1,10 @@ import { JobClass } from "@scicatproject/scicat-sdk-ts"; +import { JobInterface } from "shared/sdk/models/Job"; import { JobFilters } from "state-management/models"; export interface JobsState { - jobs: JobClass[]; - currentJob: JobClass | undefined; + jobs: JobInterface[]; + currentJob: JobInterface | undefined; totalCount: number; From 5900467228ff236cb1cfe6a1f13d74204b32d5c9 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Wed, 18 Dec 2024 09:05:26 +0000 Subject: [PATCH 059/245] cleanup messes after rebase --- .eslintrc.json | 2 +- .../app-header/app-header.component.html | 11 +- src/app/app-routing/app-routing.module.ts | 7 - .../ingestor.feature.module.ts | 8 - .../ingestor.routing.module.ts | 15 -- src/app/datasets/archiving.service.ts | 8 +- .../datafiles-action.component.spec.ts | 4 +- .../datasets/datafiles/datafiles.component.ts | 13 +- .../dataset-detail.component.html | 27 --- .../dataset-detail.component.ts | 71 +----- .../dataset-details-dashboard.component.ts | 2 +- src/app/datasets/datasets.module.ts | 3 - .../ingestor-metadata-editor.component.html | 4 - .../ingestor-metadata-editor.component.scss | 3 - .../ingestor-metadata-editor.component.ts | 18 -- src/app/ingestor/ingestor.module.ts | 33 --- .../ingestor/ingestor-api-endpoints.ts | 7 - .../ingestor/ingestor/ingestor.component.html | 105 --------- .../ingestor/ingestor/ingestor.component.scss | 16 -- .../ingestor/ingestor.component.spec.ts | 24 --- .../ingestor/ingestor/ingestor.component.ts | 153 ------------- .../jobs-dashboard-new.component.ts | 23 +- .../jobs-dashboard.component.spec.ts | 3 +- .../jobs-dashboard.component.ts | 19 +- .../jobs-detail/jobs-detail.component.html | 203 +++++++----------- .../jobs-detail/jobs-detail.component.scss | 35 ++- .../shared-table/_shared-table-theme.scss | 1 - .../effects/proposals.effects.ts | 13 -- 28 files changed, 136 insertions(+), 695 deletions(-) delete mode 100644 src/app/app-routing/lazy/ingestor-routing/ingestor.feature.module.ts delete mode 100644 src/app/app-routing/lazy/ingestor-routing/ingestor.routing.module.ts delete mode 100644 src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.html delete mode 100644 src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.scss delete mode 100644 src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts delete mode 100644 src/app/ingestor/ingestor.module.ts delete mode 100644 src/app/ingestor/ingestor/ingestor-api-endpoints.ts delete mode 100644 src/app/ingestor/ingestor/ingestor.component.html delete mode 100644 src/app/ingestor/ingestor/ingestor.component.scss delete mode 100644 src/app/ingestor/ingestor/ingestor.component.spec.ts delete mode 100644 src/app/ingestor/ingestor/ingestor.component.ts diff --git a/.eslintrc.json b/.eslintrc.json index 862f21e381..2f22b56243 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -9,7 +9,7 @@ "plugins": ["@typescript-eslint/eslint-plugin"], "overrides": [ { - "files": ["onedep*.ts"], + "files": ["*.ts"], "parserOptions": { "project": ["tsconfig.json"], "createDefaultProgram": true diff --git a/src/app/_layout/app-header/app-header.component.html b/src/app/_layout/app-header/app-header.component.html index dced69ad42..b844e86598 100644 --- a/src/app/_layout/app-header/app-header.component.html +++ b/src/app/_layout/app-header/app-header.component.html @@ -67,16 +67,7 @@

{{ status }}

- - -
- - cloud_upload - Ingestor (OpenEM) -
-
- +
diff --git a/src/app/app-routing/app-routing.module.ts b/src/app/app-routing/app-routing.module.ts index 2f95ca27e5..3310f2c053 100644 --- a/src/app/app-routing/app-routing.module.ts +++ b/src/app/app-routing/app-routing.module.ts @@ -105,13 +105,6 @@ export const routes: Routes = [ (m) => m.HelpFeatureModule, ), }, - { - path: "ingestor", - loadChildren: () => - import("./lazy/ingestor-routing/ingestor.feature.module").then( - (m) => m.IngestorFeatureModule, - ), - }, { path: "logbooks", loadChildren: () => diff --git a/src/app/app-routing/lazy/ingestor-routing/ingestor.feature.module.ts b/src/app/app-routing/lazy/ingestor-routing/ingestor.feature.module.ts deleted file mode 100644 index 8796c9c34e..0000000000 --- a/src/app/app-routing/lazy/ingestor-routing/ingestor.feature.module.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { NgModule } from "@angular/core"; -import { IngestorRoutingModule } from "./ingestor.routing.module"; -import { IngestorModule } from "ingestor/ingestor.module"; - -@NgModule({ - imports: [IngestorModule, IngestorRoutingModule], -}) -export class IngestorFeatureModule {} diff --git a/src/app/app-routing/lazy/ingestor-routing/ingestor.routing.module.ts b/src/app/app-routing/lazy/ingestor-routing/ingestor.routing.module.ts deleted file mode 100644 index c1a5b047d5..0000000000 --- a/src/app/app-routing/lazy/ingestor-routing/ingestor.routing.module.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { NgModule } from "@angular/core"; -import { RouterModule, Routes } from "@angular/router"; -import { IngestorComponent } from "ingestor/ingestor/ingestor.component"; - -const routes: Routes = [ - { - path: "", - component: IngestorComponent, - }, -]; -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule], -}) -export class IngestorRoutingModule {} diff --git a/src/app/datasets/archiving.service.ts b/src/app/datasets/archiving.service.ts index 99cfb094f2..fcf68c4a12 100644 --- a/src/app/datasets/archiving.service.ts +++ b/src/app/datasets/archiving.service.ts @@ -30,7 +30,7 @@ export class ArchivingService { ) { const extra = archive ? {} : destinationPath; const jobParams = { - datasetIds: datasets.map((dataset) => dataset.pid), + username: user.username, ...extra, }; @@ -40,8 +40,12 @@ export class ArchivingService { const data = { jobParams, - createdBy: user.username, + emailJobInitiator: user.email, // Revise this, files == []...? See earlier version of this method in dataset-table component for context + datasetList: datasets.map((dataset) => ({ + pid: dataset.pid, + files: [], + })), type: archive ? "archive" : "retrieve", }; diff --git a/src/app/datasets/datafiles-actions/datafiles-action.component.spec.ts b/src/app/datasets/datafiles-actions/datafiles-action.component.spec.ts index 0919c32cc0..46e362e2b5 100644 --- a/src/app/datasets/datafiles-actions/datafiles-action.component.spec.ts +++ b/src/app/datasets/datafiles-actions/datafiles-action.component.spec.ts @@ -185,7 +185,7 @@ describe("1000: DatafilesActionComponent", () => { beforeEach(() => { fixture = TestBed.createComponent(DatafilesActionComponent); component = fixture.componentInstance; - component.files = JSON.parse(JSON.stringify(actionFiles)); + component.files = structuredClone(actionFiles); component.actionConfig = actionsConfig[0]; component.actionDataset = actionDataset; component.maxFileSize = lowerMaxFileSizeLimit; @@ -484,7 +484,7 @@ describe("1000: DatafilesActionComponent", () => { component.maxFileSize = lowerMaxFileSizeLimit; break; } - component.files = JSON.parse(JSON.stringify(actionFiles)); + component.files = structuredClone(actionFiles); switch (selectedFiles) { case selectedFilesType.file1: component.files[0].selected = true; diff --git a/src/app/datasets/datafiles/datafiles.component.ts b/src/app/datasets/datafiles/datafiles.component.ts index 91da0d4b1e..d807d09145 100644 --- a/src/app/datasets/datafiles/datafiles.component.ts +++ b/src/app/datasets/datafiles/datafiles.component.ts @@ -304,12 +304,15 @@ export class DatafilesComponent if (email) { this.getSelectedFiles(); const data = { - createdBy: email, - createdAt: new Date(), + emailJobInitiator: email, + creationTime: new Date(), type: "public", - jobParams: { - datasetIds: [this.datasetPid], - }, + datasetList: [ + { + pid: this.datasetPid, + files: this.getSelectedFiles(), + }, + ], }; this.store.dispatch(submitJobAction({ job: data })); } diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.html b/src/app/datasets/dataset-detail/dataset-detail.component.html index aabc10c2d6..0df999dc2e 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.html +++ b/src/app/datasets/dataset-detail/dataset-detail.component.html @@ -12,33 +12,6 @@ Jupyter Hub

- - -
- keyword.toLowerCase() === 'openem' - ); - } - - onOneDepClick() { - console.log('started one dep click'); - const id = encodeURIComponent(this.dataset.pid); - this.connectToDepositionBackend(); - this.router.navigateByUrl("/datasets/" + id + "/onedep"); - console.log("my datset in the details:", this.dataset); - // this.store.dispatch(selectDatasetAction({ dataset: this.dataset })); - } - onEMPIARclick() { - const id = encodeURIComponent(this.dataset.pid); - this.router.navigateByUrl("/datasets/" + id + "/empiar"); - } - - - connectToDepositionBackend(): boolean { - var DepositionBackendUrl = "http://localhost:8080" - let DepositionBackendUrlCleaned = DepositionBackendUrl.slice(); - // Check if last symbol is a slash and add version endpoint - if (!DepositionBackendUrlCleaned.endsWith('/')) { - DepositionBackendUrlCleaned += '/'; - } - - let DepositionBackendUrlVersion = DepositionBackendUrlCleaned + 'version'; - - // Try to connect to the facility backend/version to check if it is available - console.log('Connecting to OneDep backend: ' + DepositionBackendUrlVersion); - this.http.get(DepositionBackendUrlVersion).subscribe( - response => { - console.log('Connected to OneDep backend', response); - // If the connection is successful, store the connected facility backend URL - this.connectedDepositionBackend = DepositionBackendUrlCleaned; - this.connectingToDepositionBackend = false; - this.connectedDepositionBackendVersion = response['version']; - }, - error => { - this.errorMessage += `${new Date().toLocaleString()}: ${error.message}
`; - console.error('Request failed', error); - this.connectedDepositionBackend = ''; - this.connectingToDepositionBackend = false; - } - ); - - return true; - } - -} - +} \ No newline at end of file diff --git a/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.ts b/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.ts index 9a8326095f..ae48fc5457 100644 --- a/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.ts +++ b/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.ts @@ -300,7 +300,7 @@ export class DatasetDetailsDashboardComponent } ngOnDestroy() { - //this.store.dispatch(clearCurrentDatasetStateAction()); + this.store.dispatch(clearCurrentDatasetStateAction()); this.store.dispatch(clearCurrentProposalStateAction()); this.store.dispatch(clearCurrentSampleStateAction()); this.subscriptions.forEach((subscription) => { diff --git a/src/app/datasets/datasets.module.ts b/src/app/datasets/datasets.module.ts index d1d05db717..2c007619cd 100644 --- a/src/app/datasets/datasets.module.ts +++ b/src/app/datasets/datasets.module.ts @@ -92,9 +92,6 @@ import { userReducer } from "state-management/reducers/user.reducer"; import { MatSnackBarModule } from "@angular/material/snack-bar"; import { OneDepComponent } from "./onedep/onedep.component"; import { OrcidFormatterDirective } from "./onedep/onedep.directive"; -import { MatSnackBarModule } from "@angular/material/snack-bar"; -import { OneDepComponent } from "./onedep/onedep.component"; -import { OrcidFormatterDirective } from "./onedep/onedep.directive"; @NgModule({ imports: [ diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.html b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.html deleted file mode 100644 index 2e2b11f4ef..0000000000 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.html +++ /dev/null @@ -1,4 +0,0 @@ - \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.scss b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.scss deleted file mode 100644 index 89fe8050d3..0000000000 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -.ingestor-metadata-editor { - width: 100%; -} \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts deleted file mode 100644 index 19b7d76c4a..0000000000 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Component, EventEmitter, Output } from '@angular/core'; - -@Component({ - selector: 'app-metadata-editor', - templateUrl: './ingestor-metadata-editor.component.html', - styleUrls: ['./ingestor-metadata-editor.component.scss'] -}) -export class IngestorMetadataEditorComponent { - metadata: string = ''; - - // Optional: EventEmitter, um Änderungen an der Metadata zu melden - @Output() metadataChange = new EventEmitter(); - - onMetadataChange(newMetadata: string) { - this.metadata = newMetadata; - this.metadataChange.emit(this.metadata); - } -} \ No newline at end of file diff --git a/src/app/ingestor/ingestor.module.ts b/src/app/ingestor/ingestor.module.ts deleted file mode 100644 index bd04008153..0000000000 --- a/src/app/ingestor/ingestor.module.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { NgModule } from "@angular/core"; -import { CommonModule } from "@angular/common"; -import { IngestorComponent } from "./ingestor/ingestor.component"; -import { MatCardModule } from "@angular/material/card"; -import { RouterModule } from "@angular/router"; -import { IngestorMetadataEditorComponent } from "./ingestor-metadata-editor/ingestor-metadata-editor.component"; -import { MatButtonModule } from "@angular/material/button"; -import { MatFormFieldModule } from "@angular/material/form-field"; -import { MatInputModule } from "@angular/material/input"; -import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; -import { FormsModule } from "@angular/forms"; -import { MatListModule } from '@angular/material/list'; -import { MatIconModule } from '@angular/material/icon'; - -@NgModule({ - declarations: [ - IngestorComponent, - IngestorMetadataEditorComponent - ], - imports: [ - CommonModule, - MatCardModule, - FormsModule, - MatFormFieldModule, - MatInputModule, - MatButtonModule, - MatProgressSpinnerModule, - RouterModule, - MatListModule, - MatIconModule - ], -}) -export class IngestorModule { } diff --git a/src/app/ingestor/ingestor/ingestor-api-endpoints.ts b/src/app/ingestor/ingestor/ingestor-api-endpoints.ts deleted file mode 100644 index aa0ee1e755..0000000000 --- a/src/app/ingestor/ingestor/ingestor-api-endpoints.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const INGESTOR_API_ENDPOINTS_V1 = { - DATASET: "dataset", - TRANSFER: "transfer", - OTHER: { - VERSION: 'version', - }, -}; diff --git a/src/app/ingestor/ingestor/ingestor.component.html b/src/app/ingestor/ingestor/ingestor.component.html deleted file mode 100644 index a61b5ca5ba..0000000000 --- a/src/app/ingestor/ingestor/ingestor.component.html +++ /dev/null @@ -1,105 +0,0 @@ -

- - - Ingestor-Connection - - -

- -
-
- -

No Backend connected

- -

Please provide a valid Backend URL

- - - - - -
- -
- -
- - - - - Backend URL - {{ connectedFacilityBackend }} change - - - Connection Status - Connected - - - Version - {{ connectedFacilityBackendVersion }} - - - -
- - -

- -

- - - Ingest Dataset - - -

-
- -
- -
- - -

- -

- - - Return Value - - -

-
-
-

{{returnValue}}

-
- - -

- -

- - - Error message - - - -

-

-
- - -

\ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor.component.scss b/src/app/ingestor/ingestor/ingestor.component.scss deleted file mode 100644 index 96cdeec1ba..0000000000 --- a/src/app/ingestor/ingestor/ingestor.component.scss +++ /dev/null @@ -1,16 +0,0 @@ -.ingestor-vertical-layout { - display: flex; - flex-direction: column; - gap: 1em; -} - -/* src/app/ingestor/ingestor.component.scss */ -.ingestor-mixed-header { - display: flex; - justify-content: space-between; - align-items: center; -} - -.ingestor-close-button { - margin-left: auto; -} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor.component.spec.ts b/src/app/ingestor/ingestor/ingestor.component.spec.ts deleted file mode 100644 index 52186b1077..0000000000 --- a/src/app/ingestor/ingestor/ingestor.component.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { IngestorComponent } from './ingestor.component'; - -describe('IngestorComponent', () => { - let component: IngestorComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ IngestorComponent ] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(IngestorComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); \ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor.component.ts b/src/app/ingestor/ingestor/ingestor.component.ts deleted file mode 100644 index 99cf89e0f0..0000000000 --- a/src/app/ingestor/ingestor/ingestor.component.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { Component, OnInit, ViewChild } from "@angular/core"; -import { AppConfigService, HelpMessages } from "app-config.service"; -import { HttpClient } from '@angular/common/http'; -import { IngestorMetadataEditorComponent } from '../ingestor-metadata-editor/ingestor-metadata-editor.component'; -import { ActivatedRoute, Router } from '@angular/router'; -import { INGESTOR_API_ENDPOINTS_V1 } from "./ingestor-api-endpoints"; - -@Component({ - selector: "ingestor", - templateUrl: "./ingestor.component.html", - styleUrls: ["./ingestor.component.scss"], -}) -export class IngestorComponent implements OnInit { - - @ViewChild(IngestorMetadataEditorComponent) metadataEditor: IngestorMetadataEditorComponent; - - appConfig = this.appConfigService.getConfig(); - facility: string | null = null; - ingestManual: string | null = null; - gettingStarted: string | null = null; - shoppingCartEnabled = false; - helpMessages: HelpMessages; - - filePath: string = ''; - loading: boolean = false; - forwardFacilityBackend: string = ''; - - connectedFacilityBackend: string = ''; - connectedFacilityBackendVersion: string = ''; - connectingToFacilityBackend: boolean = false; - lastUsedFacilityBackends: string[] = []; - - errorMessage: string = ''; - returnValue: string = ''; - - constructor(public appConfigService: AppConfigService, private http: HttpClient, private route: ActivatedRoute, private router: Router) { } - - ngOnInit() { - this.facility = this.appConfig.facility; - this.ingestManual = this.appConfig.ingestManual; - this.helpMessages = new HelpMessages( - this.appConfig.helpMessages?.gettingStarted, - this.appConfig.helpMessages?.ingestManual, - ); - this.gettingStarted = this.appConfig.gettingStarted; - this.connectingToFacilityBackend = true; - this.lastUsedFacilityBackends = this.loadLastUsedFacilityBackends(); - // Get the GET parameter 'backendUrl' from the URL - this.route.queryParams.subscribe(params => { - const backendUrl = params['backendUrl']; - if (backendUrl) { - this.connectToFacilityBackend(backendUrl); - } - else { - this.connectingToFacilityBackend = false; - } - }); - } - - connectToFacilityBackend(facilityBackendUrl: string): boolean { - let facilityBackendUrlCleaned = facilityBackendUrl.slice(); - // Check if last symbol is a slash and add version endpoint - if (!facilityBackendUrlCleaned.endsWith('/')) { - facilityBackendUrlCleaned += '/'; - } - - let facilityBackendUrlVersion = facilityBackendUrlCleaned + INGESTOR_API_ENDPOINTS_V1.OTHER.VERSION; - - // Try to connect to the facility backend/version to check if it is available - console.log('Connecting to facility backend: ' + facilityBackendUrlVersion); - this.http.get(facilityBackendUrlVersion).subscribe( - response => { - console.log('Connected to facility backend', response); - // If the connection is successful, store the connected facility backend URL - this.connectedFacilityBackend = facilityBackendUrlCleaned; - this.connectingToFacilityBackend = false; - this.connectedFacilityBackendVersion = response['version']; - }, - error => { - this.errorMessage += `${new Date().toLocaleString()}: ${error.message}
`; - console.error('Request failed', error); - this.connectedFacilityBackend = ''; - this.connectingToFacilityBackend = false; - this.lastUsedFacilityBackends = this.loadLastUsedFacilityBackends(); - } - ); - - return true; - } - - upload() { - this.loading = true; - this.returnValue = ''; - const payload = { - filePath: this.filePath, - metaData: this.metadataEditor.metadata - }; - - console.log('Uploading', payload); - - this.http.post(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.DATASET, payload).subscribe( - response => { - console.log('Upload successful', response); - this.returnValue = JSON.stringify(response); - this.loading = false; - }, - error => { - this.errorMessage += `${new Date().toLocaleString()}: ${error.message}]
`; - console.error('Upload failed', error); - this.loading = false; - } - ); - } - - forwardToIngestorPage() { - if (this.forwardFacilityBackend) { - this.connectingToFacilityBackend = true; - - // If current route is equal to the forward route, the router will not navigate to the new route - if (this.connectedFacilityBackend === this.forwardFacilityBackend) { - this.connectToFacilityBackend(this.forwardFacilityBackend); - return; - } - - this.router.navigate(['/ingestor'], { queryParams: { backendUrl: this.forwardFacilityBackend } }); - } - } - - disconnectIngestor() { - this.returnValue = ''; - this.connectedFacilityBackend = ''; - // Remove the GET parameter 'backendUrl' from the URL - this.router.navigate(['/ingestor']); - } - - // Helper functions - selectFacilityBackend(facilityBackend: string) { - this.forwardFacilityBackend = facilityBackend; - } - - loadLastUsedFacilityBackends(): string[] { - // Load the list from the local Storage - const lastUsedFacilityBackends = '["http://localhost:8000", "http://localhost:8888"]'; - if (lastUsedFacilityBackends) { - return JSON.parse(lastUsedFacilityBackends); - } - return []; - } - - clearErrorMessage(): void { - this.errorMessage = ''; - } -} \ No newline at end of file diff --git a/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.ts b/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.ts index fb6689b8c8..611ee66cec 100644 --- a/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.ts +++ b/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.ts @@ -4,7 +4,6 @@ import { Component, OnDestroy, } from "@angular/core"; -import { Router } from "@angular/router"; import { SciCatDataSource } from "../../shared/services/scicat.datasource"; import { ScicatDataService } from "../../shared/services/scicat-data-service"; import { ExportExcelService } from "../../shared/services/export-excel.service"; @@ -31,11 +30,11 @@ export class JobsDashboardNewComponent implements OnDestroy, AfterViewChecked { hideOrder: 0, }, { - id: "createdBy", - label: "Creator", + id: "emailJobInitiator", + label: "Initiator", icon: "person", canSort: true, - matchMode: "is", + matchMode: "contains", hideOrder: 1, }, { @@ -47,7 +46,7 @@ export class JobsDashboardNewComponent implements OnDestroy, AfterViewChecked { hideOrder: 2, }, { - id: "createdAt", + id: "creationTime", icon: "schedule", label: "Created at local time", format: "date medium ", @@ -65,13 +64,22 @@ export class JobsDashboardNewComponent implements OnDestroy, AfterViewChecked { hideOrder: 4, }, { - id: "statusCode", + id: "jobStatusMessage", icon: "traffic", label: "Status", + format: "json", canSort: true, matchMode: "contains", hideOrder: 5, }, + { + id: "datasetList", + icon: "list", + label: "Datasets", + format: "json", + canSort: true, + hideOrder: 6, + }, { id: "jobResultObject", icon: "work_outline", @@ -94,7 +102,6 @@ export class JobsDashboardNewComponent implements OnDestroy, AfterViewChecked { private cdRef: ChangeDetectorRef, private dataService: ScicatDataService, private exportService: ExportExcelService, - private router: Router, ) { this.dataSource = new SciCatDataSource( this.appConfigService, @@ -118,4 +125,4 @@ export class JobsDashboardNewComponent implements OnDestroy, AfterViewChecked { const id = encodeURIComponent(job.id); this.router.navigateByUrl("/user/jobs/" + id); */ } -} \ No newline at end of file +} diff --git a/src/app/jobs/jobs-dashboard/jobs-dashboard.component.spec.ts b/src/app/jobs/jobs-dashboard/jobs-dashboard.component.spec.ts index 4e50368af3..f3c32b5117 100644 --- a/src/app/jobs/jobs-dashboard/jobs-dashboard.component.spec.ts +++ b/src/app/jobs/jobs-dashboard/jobs-dashboard.component.spec.ts @@ -100,7 +100,8 @@ describe("JobsDashboardComponent", () => { dispatchSpy = spyOn(store, "dispatch"); const mode = JobViewMode.myJobs; - component.username = "testName"; + component.email = "test@email.com"; + const viewMode = { emailJobInitiator: component.email }; const viewMode = { createdBy: component.username }; component.onModeChange(mode); diff --git a/src/app/jobs/jobs-dashboard/jobs-dashboard.component.ts b/src/app/jobs/jobs-dashboard/jobs-dashboard.component.ts index cc1ae02dd7..2033ad245a 100644 --- a/src/app/jobs/jobs-dashboard/jobs-dashboard.component.ts +++ b/src/app/jobs/jobs-dashboard/jobs-dashboard.component.ts @@ -48,7 +48,7 @@ export class JobsDashboardComponent implements OnInit, OnDestroy { jobs: JobsTableData[] = []; profile: any; - username = ""; + email = ""; subscriptions: Subscription[] = []; @@ -98,8 +98,11 @@ export class JobsDashboardComponent implements OnInit, OnDestroy { id: job._id, initiator: job.emailJobInitiator, type: job.type, - createdAt: this.datePipe.transform(job.createdAt, "yyyy-MM-dd HH:mm"), - statusMessage: job.statusMessage, + createdAt: this.datePipe.transform( + job.creationTime, + "yyyy-MM-dd HH:mm", + ), + statusMessage: job.jobStatusMessage, })); } return tableData; @@ -126,7 +129,7 @@ export class JobsDashboardComponent implements OnInit, OnDestroy { break; } case JobViewMode.myJobs: { - viewMode = { createdBy: this.username }; + viewMode = { emailJobInitiator: this.email }; break; } default: { @@ -151,11 +154,11 @@ export class JobsDashboardComponent implements OnInit, OnDestroy { // map column names back to original names switch (event.active) { case "statusMessage": { - event.active = "statusMessage"; + event.active = "jobStatusMessage"; break; } case "initiator": { - event.active = "createdBy"; + event.active = "emailJobInitiator"; break; } default: { @@ -178,13 +181,13 @@ export class JobsDashboardComponent implements OnInit, OnDestroy { this.subscriptions.push( this.store.select(selectCurrentUser).subscribe((current) => { if (current) { - this.username = current.username; + this.email = current.email; if (!current.realm) { this.store.select(selectProfile).subscribe((profile) => { if (profile) { this.profile = profile; - this.username = profile.username; + this.email = profile.email; } this.onModeChange(JobViewMode.myJobs); }); diff --git a/src/app/jobs/jobs-detail/jobs-detail.component.html b/src/app/jobs/jobs-detail/jobs-detail.component.html index 42b3234ba6..a41a60800d 100644 --- a/src/app/jobs/jobs-detail/jobs-detail.component.html +++ b/src/app/jobs/jobs-detail/jobs-detail.component.html @@ -1,132 +1,73 @@ -
-
- - -
- description +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {{ daSet.pid | json }}
- General Information - - -
+ mail + Email Job Initiator + {{ job.emailJobInitiator }}
+ bubble_chart + Type + {{ value }}
+ brightness_high + Creation Time + {{ value | date: "yyyy-MM-dd HH:mm" }}
+ gavel + Execution Time + {{ value | date: "yyyy-MM-dd HH:mm" }}
+ settings + Job Params + {{ value | json }}
+ markunread + Date Of Last Message + {{ value | date: "yyyy-MM-dd HH:mm" }}
+ folder + Dataset List +
- - - - - - - - - - - - -
ID{{ value }}
Type{{ value }}
Created At{{ value | date: "yyyy-MM-dd HH:mm" }}
-
-
- - -
- person -
- Users and Ownership -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Created By{{ value }}
Contact Email{{ value }}
Owner User{{ value }}
Owner Group{{ value }}
Access Groups{{ value }}
Updated By{{ value }}
-
-
- - -
- analytics -
- Status -
- - - - - - - - - - - - - - -
Status Code{{ value }}
Status Message{{ value }}
Updated At{{ value }}
-
-
- - -
- library_books -
- Parameters and Configuration -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Job Parameters{{ value | json }}
Configuration Version{{ value }}
Job Result Object{{ value | json }}
Datasets ValidationNo
Datasets ValidationYes
Is PublishedNo
Is PublishedYes
-
-
-
-
+ + + + calendar_today + Created At + + {{ value | date: "yyyy-MM-dd HH:mm" }} + + + + calendar_today + Updated At + + {{ value | date: "yyyy-MM-dd HH:mm" }} + + + +
+
\ No newline at end of file diff --git a/src/app/jobs/jobs-detail/jobs-detail.component.scss b/src/app/jobs/jobs-detail/jobs-detail.component.scss index 2d1be484f3..8ce5c5b903 100644 --- a/src/app/jobs/jobs-detail/jobs-detail.component.scss +++ b/src/app/jobs/jobs-detail/jobs-detail.component.scss @@ -1,25 +1,20 @@ -mat-card { - margin: 1em; +.job-detail { + mat-card { + margin: 1em; - .section-icon { - height: auto !important; - width: auto !important; + table { + td { + padding: 0.3em; + } - mat-icon { - vertical-align: middle; - } - } - - table { - th { - min-width: 10rem; - padding-right: 0.5rem; - text-align: left; - } + th { + text-align: left; + padding-right: 0.5em; - td { - width: 100%; - padding: 0.5rem 0; + mat-icon { + vertical-align: middle; + } + } } } -} +} \ No newline at end of file diff --git a/src/app/shared/modules/shared-table/_shared-table-theme.scss b/src/app/shared/modules/shared-table/_shared-table-theme.scss index 735cd85127..4124c88a0c 100644 --- a/src/app/shared/modules/shared-table/_shared-table-theme.scss +++ b/src/app/shared/modules/shared-table/_shared-table-theme.scss @@ -31,7 +31,6 @@ mat-row:hover { background-color: mat.get-color-from-palette($hover, "lighter"); - cursor: pointer; } .mat-form-field-appearance-outline { diff --git a/src/app/state-management/effects/proposals.effects.ts b/src/app/state-management/effects/proposals.effects.ts index 150f79dcdb..216eae775f 100644 --- a/src/app/state-management/effects/proposals.effects.ts +++ b/src/app/state-management/effects/proposals.effects.ts @@ -13,7 +13,6 @@ import { } from "state-management/selectors/proposals.selectors"; import { map, mergeMap, catchError, switchMap, filter } from "rxjs/operators"; import { ObservableInput, of } from "rxjs"; -import { ObservableInput, of } from "rxjs"; import { loadingAction, loadingCompleteAction, @@ -67,12 +66,6 @@ export class ProposalEffects { ); }); - fetchProposal$ = this.createProposalFetchEffect( - fromActions.fetchProposalAction.type, - fromActions.fetchProposalCompleteAction, - fromActions.fetchProposalFailedAction, - fromActions.fetchProposalAccessFailedAction, - ); fetchProposal$ = this.createProposalFetchEffect( fromActions.fetchProposalAction.type, fromActions.fetchProposalCompleteAction, @@ -80,12 +73,6 @@ export class ProposalEffects { fromActions.fetchProposalAccessFailedAction, ); - fetchParentProposal$ = this.createProposalFetchEffect( - fromActions.fetchParentProposalAction.type, - fromActions.fetchParentProposalCompleteAction, - fromActions.fetchParentProposalFailedAction, - fromActions.fetchParentProposalAccessFailedAction, - ); fetchParentProposal$ = this.createProposalFetchEffect( fromActions.fetchParentProposalAction.type, fromActions.fetchParentProposalCompleteAction, From b94ba2e85d5abb4450e2666b1d6b75033a8e364e Mon Sep 17 00:00:00 2001 From: Despina Date: Wed, 18 Dec 2024 13:13:21 +0100 Subject: [PATCH 060/245] create JobsServiceV4 --- src/app/app.module.ts | 2 + .../datasets/admin-tab/admin-tab.component.ts | 5 +- src/app/datasets/archiving.service.spec.ts | 2 +- .../share-dialog.component.spec.ts | 2 + .../jobs-dashboard.component.spec.ts | 1 - .../jobs-dashboard.component.ts | 3 +- .../jobs/jobs-detail/jobs-detail.component.ts | 4 +- src/app/shared/MockStubs.ts | 1 - src/app/shared/sdk/apis/JobsService.ts | 59 +++++++++++++++++++ .../actions/jobs.actions.spec.ts | 4 +- .../state-management/actions/jobs.actions.ts | 1 - .../effects/jobs.effects.spec.ts | 24 ++++---- .../state-management/effects/jobs.effects.ts | 17 ++---- .../reducers/jobs.reducer.spec.ts | 2 - .../selectors/jobs.selectors.spec.ts | 2 - src/app/state-management/state/jobs.store.ts | 1 - 16 files changed, 86 insertions(+), 44 deletions(-) create mode 100644 src/app/shared/sdk/apis/JobsService.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 093937260a..73a9c0b923 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -28,6 +28,7 @@ import { SnackbarInterceptor } from "shared/interceptors/snackbar.interceptor"; import { AuthService } from "shared/services/auth/auth.service"; import { InternalStorage, SDKStorage } from "shared/services/auth/base.storage"; import { CookieService } from "ngx-cookie-service"; +import { JobsServiceV4 } from 'shared/sdk/apis/JobsService'; const appConfigInitializerFn = (appConfig: AppConfigService) => { return () => appConfig.loadAppConfig(); @@ -81,6 +82,7 @@ const apiConfigurationFn = ( exports: [MatNativeDateModule], providers: [ AppConfigService, + JobsServiceV4, { provide: APP_INITIALIZER, useFactory: appConfigInitializerFn, diff --git a/src/app/datasets/admin-tab/admin-tab.component.ts b/src/app/datasets/admin-tab/admin-tab.component.ts index 878cfdf0dc..123910ced4 100644 --- a/src/app/datasets/admin-tab/admin-tab.component.ts +++ b/src/app/datasets/admin-tab/admin-tab.component.ts @@ -3,10 +3,7 @@ import { Store } from "@ngrx/store"; import { FileObject } from "datasets/dataset-details-dashboard/dataset-details-dashboard.component"; import { Subscription } from "rxjs"; import { take } from "rxjs/operators"; -import { - CreateJobDto, - OutputDatasetObsoleteDto, -} from "@scicatproject/scicat-sdk-ts"; +import { OutputDatasetObsoleteDto } from "@scicatproject/scicat-sdk-ts"; import { submitJobAction } from "state-management/actions/jobs.actions"; import { selectCurrentDatablocks, diff --git a/src/app/datasets/archiving.service.spec.ts b/src/app/datasets/archiving.service.spec.ts index 0ad6e1a77b..0f6824a6bf 100644 --- a/src/app/datasets/archiving.service.spec.ts +++ b/src/app/datasets/archiving.service.spec.ts @@ -10,7 +10,7 @@ import { import { JobsState } from "state-management/state/jobs.store"; import { ArchivingService } from "./archiving.service"; import { createMock, mockDataset } from "shared/MockStubs"; -import { CreateJobDto, ReturnedUserDto } from "@scicatproject/scicat-sdk-ts"; +import { ReturnedUserDto } from "@scicatproject/scicat-sdk-ts"; import { Job } from "shared/sdk/models/Job"; describe("ArchivingService", () => { diff --git a/src/app/datasets/share-dialog/share-dialog.component.spec.ts b/src/app/datasets/share-dialog/share-dialog.component.spec.ts index 9acc15820f..701a6f3a46 100644 --- a/src/app/datasets/share-dialog/share-dialog.component.spec.ts +++ b/src/app/datasets/share-dialog/share-dialog.component.spec.ts @@ -43,6 +43,7 @@ import { AuthService } from "shared/services/auth/auth.service"; import { InternalStorage } from "shared/services/auth/base.storage"; import { cold } from "jasmine-marbles"; import { of } from "rxjs"; +import { JobsServiceV4 } from "shared/sdk/apis/JobsService"; const data = { infoMessage: "", @@ -77,6 +78,7 @@ describe("ShareDialogComponent", () => { { provide: UsersService, useClass: MockUserApi }, { provide: InstrumentsService, useValue: {} }, { provide: JobsService, useValue: {} }, + { provide: JobsServiceV4, useValue: {} }, { provide: ProposalsService, useValue: {} }, { provide: SamplesService, useValue: {} }, { provide: PublishedDataService, useClass: MockPublishedDataApi }, diff --git a/src/app/jobs/jobs-dashboard/jobs-dashboard.component.spec.ts b/src/app/jobs/jobs-dashboard/jobs-dashboard.component.spec.ts index ee5315f7e7..31efaf81ca 100644 --- a/src/app/jobs/jobs-dashboard/jobs-dashboard.component.spec.ts +++ b/src/app/jobs/jobs-dashboard/jobs-dashboard.component.spec.ts @@ -23,7 +23,6 @@ import { FlexLayoutModule } from "@ngbracket/ngx-layout"; import { MatCardModule } from "@angular/material/card"; import { MatIconModule } from "@angular/material/icon"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; -import { JobClass } from "@scicatproject/scicat-sdk-ts"; import { JobInterface } from "shared/sdk/models/Job"; describe("JobsDashboardComponent", () => { diff --git a/src/app/jobs/jobs-dashboard/jobs-dashboard.component.ts b/src/app/jobs/jobs-dashboard/jobs-dashboard.component.ts index 8c374bfecf..8eeb995db0 100644 --- a/src/app/jobs/jobs-dashboard/jobs-dashboard.component.ts +++ b/src/app/jobs/jobs-dashboard/jobs-dashboard.component.ts @@ -1,7 +1,6 @@ import { Component, OnInit, OnDestroy } from "@angular/core"; import { Router } from "@angular/router"; -import { Store } from "@ngrx/store"; -import { JobClass } from "@scicatproject/scicat-sdk-ts"; +import { Store } from "@ngrx/store";; import { Subscription } from "rxjs"; import { selectJobs, diff --git a/src/app/jobs/jobs-detail/jobs-detail.component.ts b/src/app/jobs/jobs-detail/jobs-detail.component.ts index 8142f27e90..a803b8fa64 100644 --- a/src/app/jobs/jobs-detail/jobs-detail.component.ts +++ b/src/app/jobs/jobs-detail/jobs-detail.component.ts @@ -4,7 +4,6 @@ import { Store } from "@ngrx/store"; import { ActivatedRoute } from "@angular/router"; import { selectCurrentJob } from "state-management/selectors/jobs.selectors"; import { Observable, Subscription } from "rxjs"; -import { JobClass } from "@scicatproject/scicat-sdk-ts"; import { JobInterface } from "shared/sdk/models/Job"; @Component({ @@ -15,8 +14,7 @@ import { JobInterface } from "shared/sdk/models/Job"; export class JobsDetailComponent implements OnInit, OnDestroy { // TODO: We should extract the response dto with the right properties instead of using the schema for ApiResponse in the backend job$ = this.store.select(selectCurrentJob) as Observable< - // JobClass & { createdAt: string; updatedAt: string } - JobInterface + JobInterface // sdk JobClass & { createdAt: string; updatedAt: string } >; routeSubscription: Subscription = new Subscription(); diff --git a/src/app/shared/MockStubs.ts b/src/app/shared/MockStubs.ts index 600de0b6b0..88e7f89502 100644 --- a/src/app/shared/MockStubs.ts +++ b/src/app/shared/MockStubs.ts @@ -12,7 +12,6 @@ import { DataFiles_File } from "datasets/datafiles/datafiles.interfaces"; import { Attachment, Instrument, - JobClass, OutputDatasetObsoleteDto, ProposalClass, PublishedData, diff --git a/src/app/shared/sdk/apis/JobsService.ts b/src/app/shared/sdk/apis/JobsService.ts new file mode 100644 index 0000000000..0dd46a908e --- /dev/null +++ b/src/app/shared/sdk/apis/JobsService.ts @@ -0,0 +1,59 @@ +import { Injectable } from "@angular/core"; +import { HttpClient, HttpHeaders, HttpResponse, HttpEvent, HttpParameterCodec, HttpContext } from "@angular/common/http"; +import { Observable } from "rxjs"; +import { Configuration } from "@scicatproject/scicat-sdk-ts"; +import { Job, JobInterface } from "../models/Job"; + +interface JobsServiceInterfaceV4 { + defaultHeaders: HttpHeaders; + configuration: Configuration; + /** + * + * + * @param createJobDto + */ + jobsControllerCreateV4(createJobDto: Job, extraHttpRequestParams?: any): Observable; +} + +@Injectable({ + providedIn: "root", +}) +export class JobsServiceV4 implements JobsServiceInterfaceV4 { + protected httpClient: HttpClient; + defaultHeaders: HttpHeaders; + configuration: Configuration; + encoder: HttpParameterCodec; + + constructor(httpClient: HttpClient, configuration: Configuration) { + this.httpClient = httpClient; + this.configuration = configuration; + }; + + jobsControllerCreateV4(createJobDto: Job, observe?: "body", reportProgress?: boolean, options?: { + httpHeaderAccept?: "application/json"; + context?: HttpContext; + }): Observable; + jobsControllerCreateV4(createJobDto: Job, observe?: "response", reportProgress?: boolean, options?: { + httpHeaderAccept?: "application/json"; + context?: HttpContext; + }): Observable>; + jobsControllerCreateV4(createJobDto: Job, observe?: "events", reportProgress?: boolean, options?: { + httpHeaderAccept?: "application/json"; + context?: HttpContext; + }): Observable>; + + jobsControllerCreateV4(createJobDto: Job, observe: any = "body", reportProgress: boolean = false, options: { + httpHeaderAccept?: "application/json"; + context?: HttpContext; + } = {}): Observable { + const headers = this.defaultHeaders; + const url = `${this.configuration.basePath}/jobs`; + + return this.httpClient.post(url, createJobDto, { + headers: headers, + observe: observe, + reportProgress: reportProgress, + ...options + }); + } +} diff --git a/src/app/state-management/actions/jobs.actions.spec.ts b/src/app/state-management/actions/jobs.actions.spec.ts index 216a5f85d1..00dcd5792a 100644 --- a/src/app/state-management/actions/jobs.actions.spec.ts +++ b/src/app/state-management/actions/jobs.actions.spec.ts @@ -1,4 +1,3 @@ -import { CreateJobDto } from "@scicatproject/scicat-sdk-ts"; import * as fromActions from "./jobs.actions"; import { mockJob as job } from "shared/MockStubs"; @@ -78,8 +77,7 @@ describe("Job Actions", () => { describe("submitJobAction", () => { it("should create an action", () => { - const newJob = { ...job } as CreateJobDto; - const action = fromActions.submitJobAction({ job: newJob }); + const action = fromActions.submitJobAction({ job }); expect({ ...action }).toEqual({ type: "[Job] Submit Job", job: newJob }); }); }); diff --git a/src/app/state-management/actions/jobs.actions.ts b/src/app/state-management/actions/jobs.actions.ts index fdeecbd8a0..4a5ca645d8 100644 --- a/src/app/state-management/actions/jobs.actions.ts +++ b/src/app/state-management/actions/jobs.actions.ts @@ -1,5 +1,4 @@ import { createAction, props } from "@ngrx/store"; -import { CreateJobDto, JobClass } from "@scicatproject/scicat-sdk-ts"; import { Job, JobInterface } from "shared/sdk/models/Job"; export const fetchJobsAction = createAction("[Job] Fetch Jobs"); diff --git a/src/app/state-management/effects/jobs.effects.spec.ts b/src/app/state-management/effects/jobs.effects.spec.ts index ac902ae6a5..93cbd25811 100644 --- a/src/app/state-management/effects/jobs.effects.spec.ts +++ b/src/app/state-management/effects/jobs.effects.spec.ts @@ -13,14 +13,10 @@ import { } from "state-management/actions/user.actions"; import { MessageType } from "state-management/models"; import { Type } from "@angular/core"; -import { - CreateJobDto, - JobClass, - JobsService, -} from "@scicatproject/scicat-sdk-ts"; +import { JobsService } from "@scicatproject/scicat-sdk-ts"; import { TestObservable } from "jasmine-marbles/src/test-observables"; -import { createMock } from "shared/MockStubs"; import { Job, JobInterface } from "shared/sdk/models/Job"; +import { JobsServiceV4 } from "shared/sdk/apis/JobsService"; const data: JobInterface = { id: "testId", @@ -36,6 +32,7 @@ describe("JobEffects", () => { let actions: TestObservable; let effects: JobEffects; let jobApi: jasmine.SpyObj; + let jobApiV4: jasmine.SpyObj; beforeEach(() => { TestBed.configureTestingModule({ @@ -53,11 +50,18 @@ describe("JobEffects", () => { "jobsControllerCreate", ]), }, + { + provide: JobsServiceV4, + useValue: jasmine.createSpyObj("jobApiV4", [ + "jobsControllerCreateV4", + ]), + }, ], }); effects = TestBed.inject(JobEffects); jobApi = injectedStub(JobsService); + jobApiV4 = injectedStub(JobsServiceV4); }); const injectedStub = (service: Type): jasmine.SpyObj => @@ -230,12 +234,12 @@ describe("JobEffects", () => { describe("submitJob$", () => { it("should result in a submitJobCompleteAction", () => { - const action = fromActions.submitJobAction({ job: job as CreateJobDto }); + const action = fromActions.submitJobAction({ job }); const outcome = fromActions.submitJobCompleteAction({ job }); actions = hot("-a", { a: action }); const response = cold("-a|", { a: job }); - jobApi.jobsControllerCreate.and.returnValue(response); + jobApiV4.jobsControllerCreateV4.and.returnValue(response); const expected = cold("--b", { b: outcome }); expect(effects.submitJob$).toBeObservable(expected); @@ -317,9 +321,7 @@ describe("JobEffects", () => { describe("ofType submitJobAction", () => { it("should dispatch a loadingAction", () => { - const action = fromActions.submitJobAction({ - job: job as CreateJobDto, - }); + const action = fromActions.submitJobAction({ job }); const outcome = loadingAction(); actions = hot("-a", { a: action }); diff --git a/src/app/state-management/effects/jobs.effects.ts b/src/app/state-management/effects/jobs.effects.ts index b8e139bf90..16251775d7 100644 --- a/src/app/state-management/effects/jobs.effects.ts +++ b/src/app/state-management/effects/jobs.effects.ts @@ -1,10 +1,6 @@ import { Injectable } from "@angular/core"; import { Actions, createEffect, ofType, concatLatestFrom } from "@ngrx/effects"; -import { - CreateJobDto, - JobClass, - JobsService, -} from "@scicatproject/scicat-sdk-ts"; +import { JobsService } from "@scicatproject/scicat-sdk-ts"; import { Store } from "@ngrx/store"; import { selectQueryParams } from "state-management/selectors/jobs.selectors"; import * as fromActions from "state-management/actions/jobs.actions"; @@ -17,8 +13,8 @@ import { loadingCompleteAction, updateUserSettingsAction, } from "state-management/actions/user.actions"; -import { JobInterface } from "shared/sdk/models/Job"; -import { datasets } from "state-management/selectors"; +import { Job, JobInterface } from "shared/sdk/models/Job"; +import { JobsServiceV4 } from "shared/sdk/apis/JobsService"; @Injectable() export class JobEffects { @@ -71,11 +67,7 @@ export class JobEffects { return this.actions$.pipe( ofType(fromActions.submitJobAction), switchMap(({ job }) => - this.jobsService.jobsControllerCreate({ - ...job, - emailJobInitiator: job.createdBy, - datasetList: job.jobParams.datasetList, - } as CreateJobDto).pipe( + this.jobsServiceV4.jobsControllerCreateV4(job as Job).pipe( map((res) => fromActions.submitJobCompleteAction({ job: res as JobInterface })), catchError((err) => of(fromActions.submitJobFailedAction({ err }))), ), @@ -141,6 +133,7 @@ export class JobEffects { constructor( private actions$: Actions, private jobsService: JobsService, + private jobsServiceV4: JobsServiceV4, private store: Store, ) {} } diff --git a/src/app/state-management/reducers/jobs.reducer.spec.ts b/src/app/state-management/reducers/jobs.reducer.spec.ts index 80981a7e7c..f9f5738dec 100644 --- a/src/app/state-management/reducers/jobs.reducer.spec.ts +++ b/src/app/state-management/reducers/jobs.reducer.spec.ts @@ -1,8 +1,6 @@ import { jobsReducer } from "./jobs.reducer"; import * as fromActions from "../actions/jobs.actions"; import { initialJobsState } from "state-management/state/jobs.store"; -import { createMock } from "shared/MockStubs"; -import { JobClass } from "@scicatproject/scicat-sdk-ts"; import { Job, JobInterface } from "shared/sdk/models/Job"; const data: JobInterface = { diff --git a/src/app/state-management/selectors/jobs.selectors.spec.ts b/src/app/state-management/selectors/jobs.selectors.spec.ts index 040366483a..3fdae792a2 100644 --- a/src/app/state-management/selectors/jobs.selectors.spec.ts +++ b/src/app/state-management/selectors/jobs.selectors.spec.ts @@ -1,7 +1,5 @@ import * as fromSelectors from "./jobs.selectors"; import { JobsState } from "../state/jobs.store"; -import { createMock } from "shared/MockStubs"; -import { JobClass } from "@scicatproject/scicat-sdk-ts"; import { Job, JobInterface } from "shared/sdk/models/Job"; const data: JobInterface = { diff --git a/src/app/state-management/state/jobs.store.ts b/src/app/state-management/state/jobs.store.ts index d9e891e835..cc5017089e 100644 --- a/src/app/state-management/state/jobs.store.ts +++ b/src/app/state-management/state/jobs.store.ts @@ -1,4 +1,3 @@ -import { JobClass } from "@scicatproject/scicat-sdk-ts"; import { JobInterface } from "shared/sdk/models/Job"; import { JobFilters } from "state-management/models"; From 0f989cdf0c833326132188e99ba01a9332295b95 Mon Sep 17 00:00:00 2001 From: Spencer Bliven Date: Wed, 18 Dec 2024 13:50:33 +0000 Subject: [PATCH 061/245] Remove createdBy from POST /job and fix endpoint --- src/app/shared/sdk/apis/JobsService.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/shared/sdk/apis/JobsService.ts b/src/app/shared/sdk/apis/JobsService.ts index 0dd46a908e..b39d7a0152 100644 --- a/src/app/shared/sdk/apis/JobsService.ts +++ b/src/app/shared/sdk/apis/JobsService.ts @@ -47,9 +47,13 @@ export class JobsServiceV4 implements JobsServiceInterfaceV4 { context?: HttpContext; } = {}): Observable { const headers = this.defaultHeaders; - const url = `${this.configuration.basePath}/jobs`; + const url = `${this.configuration.basePath}/api/v3/jobs`; - return this.httpClient.post(url, createJobDto, { + return this.httpClient.post(url, + { + ...createJobDto, + createdBy: undefined, + }, { headers: headers, observe: observe, reportProgress: reportProgress, From bd72c4d1ea7f37b2a2e70b621b364530bbbbd5c0 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Wed, 18 Dec 2024 14:14:43 +0000 Subject: [PATCH 062/245] frontend functional for alpha --- src/app/datasets/onedep/onedep.component.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index 65f2cbbf8b..aeee5e3e5f 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -18,10 +18,12 @@ import { } from "@angular/forms"; import { Store } from "@ngrx/store"; -import { Dataset } from "shared/sdk/models"; +import { + OutputDatasetObsoleteDto, + ReturnedUserDto, +} from "@scicatproject/scicat-sdk-ts"; import { selectCurrentDataset } from "state-management/selectors/datasets.selectors"; import { selectCurrentUser } from "state-management/selectors/user.selectors"; -import { User } from "shared/sdk"; import { methodsList, EmFile, DepositionFiles } from "./types/methods.enum"; import { Subscription, fromEvent } from "rxjs"; @@ -36,8 +38,8 @@ export class OneDepComponent implements OnInit, OnDestroy { config: AppConfig; - dataset: Dataset | undefined; - user: User | undefined; + dataset: OutputDatasetObsoleteDto | undefined; + user: ReturnedUserDto | undefined; form: FormGroup; showAssociatedMapQuestion = false; methodsList = methodsList; @@ -437,8 +439,7 @@ export class OneDepComponent implements OnInit, OnDestroy { }); } onDownloadClick() { - console.log("download data"); - if (this.form.value.deposingCoordinates === true) { + if (this.form.value.deposingCoordinates === "true") { const formDataFile = new FormData(); const fT = this.fileTypes.find( (fileType) => fileType.emName === this.emFile.Coordinates, From 96609a5242be9ebcc2bdd84c58aae77a8fa5b817 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Wed, 18 Dec 2024 15:10:55 +0000 Subject: [PATCH 063/245] new psi-deployment with openem changes rebased on master --- src/assets/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/config.json b/src/assets/config.json index 96518ad9d4..996dd1a935 100644 --- a/src/assets/config.json +++ b/src/assets/config.json @@ -1,7 +1,7 @@ { "accessTokenPrefix": "Bearer ", "addDatasetEnabled": true, - "archiveWorkflowEnabled": false, + "archiveWorkflowEnabled": true, "datasetReduceEnabled": true, "datasetJsonScientificMetadata": true, "editDatasetSampleEnabled": true, From 8c40aac7997c9030eeddae8dd8fd13baeee6ff4f Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Thu, 19 Dec 2024 08:49:29 +0000 Subject: [PATCH 064/245] remove email --- src/app/datasets/onedep/onedep.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index aeee5e3e5f..af13dc9f06 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -366,7 +366,7 @@ export class OneDepComponent implements OnInit, OnDestroy { let body: string; if (this.form.value.password) { body = JSON.stringify({ - email: "sofya.laskina@epfl.ch", // for now + email: this.form.value.email, orcidIds: this.orcidArray().value.map((item) => item.orcidId), country: "United States", method: this.form.value.emMethod, @@ -375,7 +375,7 @@ export class OneDepComponent implements OnInit, OnDestroy { }); } else { body = JSON.stringify({ - email: "sofya.laskina@epfl.ch", // for now + email: this.form.value.email, orcidIds: this.orcidArray().value.map((item) => item.orcidId), country: "United States", method: this.form.value.emMethod, From ad4ec208d831ee025e4db56afde4146674f25749 Mon Sep 17 00:00:00 2001 From: dwiessner-unibe Date: Thu, 19 Dec 2024 14:02:55 +0100 Subject: [PATCH 065/245] =?UTF-8?q?Update=20the=20=E2=80=9CRemote-Ingestor?= =?UTF-8?q?-OpenEM=E2=80=9D=20in=20the=20PSI=20Deployment=20OpenEM=20branc?= =?UTF-8?q?h=20(#1693)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Prepare ingestor frontend minimal ui * Update API * static list of backends * Remove unused file path input field in ingestor component * Extend ui and adjust api calls * Change back to localhost:3000 instead of backend.localhost * Change back the files to original state * fix ingestor endpoint * fix sonarcube issues * Prepare showing the transfer list * json-forms poc * frontend update - improved json form integration * Assignment of the input menus to the appropriate metadata * fix package.json for json-forms * Embedding ingestor backend part 1 * Embedding ingestor backend part 2 * Embedding ingestor backend part 3 --------- Co-authored-by: David Wiessner --- package.json | 2 + .../app-header/app-header.component.html | 10 +- src/app/app-routing/app-routing.module.ts | 7 + .../ingestor.feature.module.ts | 8 + .../ingestor.routing.module.ts | 15 + .../customRenderer/all-of-renderer.ts | 14 + .../customRenderer/any-of-renderer.ts | 70 +++ .../customRenderer/custom-renderers.ts | 20 + .../customRenderer/one-of-renderer.ts | 69 +++ .../ingestor-metadata-editor-helper.ts | 32 ++ .../ingestor-metadata-editor-schema_demo.ts | 483 ++++++++++++++++++ .../ingestor-metadata-editor.component.html | 4 + .../ingestor-metadata-editor.component.scss | 3 + .../ingestor-metadata-editor.component.ts | 28 + src/app/ingestor/ingestor.module.ts | 67 +++ .../ingestor/ingestor/_ingestor-theme.scss | 39 ++ ...estor.confirm-transfer-dialog.component.ts | 61 +++ .../ingestor.confirm-transfer-dialog.html | 22 + ...stor.dialog-stepper.component.component.ts | 10 + .../ingestor.dialog-stepper.component.css | 17 + .../ingestor.dialog-stepper.component.html | 16 + ...tor.extractor-metadata-dialog.component.ts | 62 +++ .../ingestor.extractor-metadata-dialog.html | 73 +++ .../ingestor.new-transfer-dialog.component.ts | 124 +++++ .../dialog/ingestor.new-transfer-dialog.html | 45 ++ ...ingestor.user-metadata-dialog.component.ts | 67 +++ .../dialog/ingestor.user-metadata-dialog.html | 67 +++ .../ingestor/ingestor-api-endpoints.ts | 17 + .../ingestor/ingestor.component-helper.ts | 104 ++++ .../ingestor/ingestor/ingestor.component.html | 144 ++++++ .../ingestor/ingestor/ingestor.component.scss | 62 +++ .../ingestor/ingestor.component.spec.ts | 24 + .../ingestor/ingestor/ingestor.component.ts | 282 ++++++++++ src/styles.scss | 2 + 34 files changed, 2069 insertions(+), 1 deletion(-) create mode 100644 src/app/app-routing/lazy/ingestor-routing/ingestor.feature.module.ts create mode 100644 src/app/app-routing/lazy/ingestor-routing/ingestor.routing.module.ts create mode 100644 src/app/ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer.ts create mode 100644 src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts create mode 100644 src/app/ingestor/ingestor-metadata-editor/customRenderer/custom-renderers.ts create mode 100644 src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts create mode 100644 src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts create mode 100644 src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schema_demo.ts create mode 100644 src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.html create mode 100644 src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.scss create mode 100644 src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts create mode 100644 src/app/ingestor/ingestor.module.ts create mode 100644 src/app/ingestor/ingestor/_ingestor-theme.scss create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.html create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.component.ts create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.css create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.html create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.html create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts create mode 100644 src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html create mode 100644 src/app/ingestor/ingestor/ingestor-api-endpoints.ts create mode 100644 src/app/ingestor/ingestor/ingestor.component-helper.ts create mode 100644 src/app/ingestor/ingestor/ingestor.component.html create mode 100644 src/app/ingestor/ingestor/ingestor.component.scss create mode 100644 src/app/ingestor/ingestor/ingestor.component.spec.ts create mode 100644 src/app/ingestor/ingestor/ingestor.component.ts diff --git a/package.json b/package.json index 91c79ee627..6404306d19 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,8 @@ "@angular/platform-server": "^16", "@angular/router": "^16", "@angular/service-worker": "^16", + "@jsonforms/angular": "3.2.*", + "@jsonforms/angular-material": "3.2.*", "@ngbracket/ngx-layout": "^16.0.0", "@ngrx/effects": "^16", "@ngrx/router-store": "^16", diff --git a/src/app/_layout/app-header/app-header.component.html b/src/app/_layout/app-header/app-header.component.html index b844e86598..88baab4099 100644 --- a/src/app/_layout/app-header/app-header.component.html +++ b/src/app/_layout/app-header/app-header.component.html @@ -67,7 +67,15 @@

{{ status }}

- + + +
+ + cloud_upload + Ingestor (OpenEM) +
+
diff --git a/src/app/app-routing/app-routing.module.ts b/src/app/app-routing/app-routing.module.ts index 3310f2c053..2f95ca27e5 100644 --- a/src/app/app-routing/app-routing.module.ts +++ b/src/app/app-routing/app-routing.module.ts @@ -105,6 +105,13 @@ export const routes: Routes = [ (m) => m.HelpFeatureModule, ), }, + { + path: "ingestor", + loadChildren: () => + import("./lazy/ingestor-routing/ingestor.feature.module").then( + (m) => m.IngestorFeatureModule, + ), + }, { path: "logbooks", loadChildren: () => diff --git a/src/app/app-routing/lazy/ingestor-routing/ingestor.feature.module.ts b/src/app/app-routing/lazy/ingestor-routing/ingestor.feature.module.ts new file mode 100644 index 0000000000..8796c9c34e --- /dev/null +++ b/src/app/app-routing/lazy/ingestor-routing/ingestor.feature.module.ts @@ -0,0 +1,8 @@ +import { NgModule } from "@angular/core"; +import { IngestorRoutingModule } from "./ingestor.routing.module"; +import { IngestorModule } from "ingestor/ingestor.module"; + +@NgModule({ + imports: [IngestorModule, IngestorRoutingModule], +}) +export class IngestorFeatureModule {} diff --git a/src/app/app-routing/lazy/ingestor-routing/ingestor.routing.module.ts b/src/app/app-routing/lazy/ingestor-routing/ingestor.routing.module.ts new file mode 100644 index 0000000000..c1a5b047d5 --- /dev/null +++ b/src/app/app-routing/lazy/ingestor-routing/ingestor.routing.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; +import { IngestorComponent } from "ingestor/ingestor/ingestor.component"; + +const routes: Routes = [ + { + path: "", + component: IngestorComponent, + }, +]; +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class IngestorRoutingModule {} diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer.ts new file mode 100644 index 0000000000..91085fbaf7 --- /dev/null +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; +import { JsonFormsControl } from '@jsonforms/angular'; + +@Component({ + selector: 'AllOfRenderer', + template: `
AllOf Renderer
` +}) +export class AllOfRenderer extends JsonFormsControl { + data: any[] = []; + + ngOnInit() { + this.data = this.uischema?.options?.items || []; + } +} diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts new file mode 100644 index 0000000000..530d762196 --- /dev/null +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts @@ -0,0 +1,70 @@ +import { Component } from '@angular/core'; +import { JsonFormsAngularService, JsonFormsControl } from '@jsonforms/angular'; +import { ControlProps, JsonSchema } from '@jsonforms/core'; +import { configuredRenderer } from '../ingestor-metadata-editor-helper'; + +@Component({ + selector: 'app-anyof-renderer', + template: ` +
+ {{anyOfTitle}} + + + +
+ +
+
+
+
+ ` +}) +export class AnyOfRenderer extends JsonFormsControl { + + dataAsString: string; + options: string[] = []; + anyOfTitle: string; + selectedTabIndex: number = 0; // default value + + rendererService: JsonFormsAngularService; + + defaultRenderer = configuredRenderer; + passedProps: ControlProps; + + constructor(service: JsonFormsAngularService) { + super(service); + this.rendererService = service; + } + + public mapAdditionalProps(props: ControlProps) { + this.passedProps = props; + this.anyOfTitle = props.label || 'AnyOf'; + this.options = props.schema.anyOf.map((option: any) => option.title || option.type || JSON.stringify(option)); + + if (this.options.includes("null") && !props.data) { + this.selectedTabIndex = this.options.indexOf("null"); + } + } + + public getTabSchema(tabOption: string): JsonSchema { + const selectedSchema = (this.passedProps.schema.anyOf as any).find((option: any) => option.title === tabOption || option.type === tabOption || JSON.stringify(option) === tabOption); + return selectedSchema; + } + + public onInnerJsonFormsChange(event: any) { + // Check if data is equal to the passedProps.data + if (event !== this.passedProps.data) { + const updatedData = this.rendererService.getState().jsonforms.core.data; + + // aktualisiere das aktuelle Datenobjekt + const pathSegments = this.passedProps.path.split('.'); + let current = updatedData; + for (let i = 0; i < pathSegments.length - 1; i++) { + current = current[pathSegments[i]]; + } + current[pathSegments[pathSegments.length - 1]] = event; + + this.rendererService.setData(updatedData); + } + } +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/custom-renderers.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/custom-renderers.ts new file mode 100644 index 0000000000..8b807f113b --- /dev/null +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/custom-renderers.ts @@ -0,0 +1,20 @@ +import { isAllOfControl, isAnyOfControl, isOneOfControl, JsonFormsRendererRegistryEntry } from '@jsonforms/core'; +import { OneOfRenderer } from 'ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer'; +import { AllOfRenderer } from 'ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer'; +import { AnyOfRenderer } from 'ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer'; +import { isRangeControl, RankedTester, rankWith } from '@jsonforms/core'; + +export const customRenderers: JsonFormsRendererRegistryEntry[] = [ + { + tester: rankWith(4, isOneOfControl), + renderer: OneOfRenderer + }, + { + tester: rankWith(4, isAllOfControl), + renderer: AllOfRenderer + }, + { + tester: rankWith(4, isAnyOfControl), + renderer: AnyOfRenderer + } +]; \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts new file mode 100644 index 0000000000..42665d5c62 --- /dev/null +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts @@ -0,0 +1,69 @@ +import { Component } from '@angular/core'; +import { JsonFormsAngularService, JsonFormsControl } from '@jsonforms/angular'; +import { ControlProps, JsonSchema } from '@jsonforms/core'; +import { configuredRenderer } from '../ingestor-metadata-editor-helper'; + +@Component({ + selector: 'app-oneof-component', + template: ` +
+

{{anyOfTitle}}

+ + + {{option}} + + +
+ +
+
+ ` +}) +export class OneOfRenderer extends JsonFormsControl { + + dataAsString: string; + options: string[] = []; + anyOfTitle: string; + selectedOption: string; + selectedAnyOption: JsonSchema; + + rendererService: JsonFormsAngularService; + + defaultRenderer = configuredRenderer; + passedProps: ControlProps; + + constructor(service: JsonFormsAngularService) { + super(service); + this.rendererService = service; + } + + public mapAdditionalProps(props: ControlProps) { + this.passedProps = props; + this.anyOfTitle = props.label || 'AnyOf'; + this.options = props.schema.anyOf.map((option: any) => option.title || option.type || JSON.stringify(option)); + if (!props.data) { + this.selectedOption = 'null'; // Auf "null" setzen, wenn die Daten leer sind + } + } + + public onOptionChange() { + this.selectedAnyOption = (this.passedProps.schema.anyOf as any).find((option: any) => option.title === this.selectedOption || option.type === this.selectedOption || JSON.stringify(option) === this.selectedOption); + } + + public onInnerJsonFormsChange(event: any) { + // Check if data is equal to the passedProps.data + if (event !== this.passedProps.data) { + const updatedData = this.rendererService.getState().jsonforms.core.data; + + // aktualisiere das aktuelle Datenobjekt + const pathSegments = this.passedProps.path.split('.'); + let current = updatedData; + for (let i = 0; i < pathSegments.length - 1; i++) { + current = current[pathSegments[i]]; + } + current[pathSegments[pathSegments.length - 1]] = event; + + this.rendererService.setData(updatedData); + } + } +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts new file mode 100644 index 0000000000..65c4d640d3 --- /dev/null +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts @@ -0,0 +1,32 @@ +import { angularMaterialRenderers } from "@jsonforms/angular-material"; +import { customRenderers } from "./customRenderer/custom-renderers"; + +export const configuredRenderer = [ + ...angularMaterialRenderers, + ...customRenderers, +]; + +export class IngestorMetadataEditorHelper { + // Resolve all $ref in a schema + static resolveRefs(schema: any, rootSchema: any): any { + if (schema === null || schema === undefined) { + return schema; + } + + if (schema.$ref) { + const refPath = schema.$ref.replace('#/', '').split('/'); + let ref = rootSchema; + refPath.forEach((part) => { + ref = ref[part]; + }); + return IngestorMetadataEditorHelper.resolveRefs(ref, rootSchema); + } else if (typeof schema === 'object') { + for (const key in schema) { + if (schema.hasOwnProperty(key)) { + schema[key] = IngestorMetadataEditorHelper.resolveRefs(schema[key], rootSchema); + } + } + } + return schema; + }; +}; \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schema_demo.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schema_demo.ts new file mode 100644 index 0000000000..5874d74d42 --- /dev/null +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schema_demo.ts @@ -0,0 +1,483 @@ +export const demo_organizational_schema = { + type: 'object', + properties: { + grants: { + type: 'array', + items: { + type: 'object', + properties: { + grant_name: { + type: 'string', + description: 'name of the grant', + }, + start_date: { + type: 'string', + format: 'date', + description: 'start date', + }, + end_date: { + type: 'string', + format: 'date', + description: 'end date', + }, + budget: { + type: 'number', + description: 'budget', + }, + project_id: { + type: 'string', + description: 'project id', + }, + country: { + type: 'string', + description: 'Country of the institution', + }, + }, + }, + description: 'List of grants associated with the project', + }, + authors: { + type: 'array', + items: { + type: 'object', + properties: { + first_name: { + type: 'string', + description: 'first name', + }, + work_status: { + type: 'boolean', + description: 'work status', + }, + email: { + type: 'string', + description: 'email', + pattern: '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$', + }, + work_phone: { + type: 'string', + description: 'work phone', + }, + name: { + type: 'string', + description: 'name', + }, + name_org: { + type: 'string', + description: 'Name of the organization', + }, + type_org: { + type: 'string', + description: 'Type of organization, academic, commercial, governmental, etc.', + enum: ['Academic', 'Commercial', 'Government', 'Other'], + }, + country: { + type: 'string', + description: 'Country of the institution', + }, + role: { + type: 'string', + description: 'Role of the author, for example principal investigator', + }, + orcid: { + type: 'string', + description: 'ORCID of the author, a type of unique identifier', + }, + }, + }, + description: 'List of authors associated with the project', + }, + funder: { + type: 'array', + items: { + type: 'object', + properties: { + funder_name: { + type: 'string', + description: 'funding organization/person.', + }, + type_org: { + type: 'string', + description: 'Type of organization, academic, commercial, governmental, etc.', + enum: ['Academic', 'Commercial', 'Government', 'Other'], + }, + country: { + type: 'string', + description: 'Country of the institution', + }, + }, + }, + description: 'Description of the project funding', + }, + }, + required: ['authors', 'funder'], +}; + +export const demo_acquisition_schema = { + type: 'object', + properties: { + nominal_defocus: { + type: 'object', + description: 'Target defocus set, min and max values in µm.', + }, + calibrated_defocus: { + type: 'object', + description: 'Machine estimated defocus, min and max values in µm. Has a tendency to be off.', + }, + nominal_magnification: { + type: 'integer', + description: 'Magnification level as indicated by the instrument, no unit', + }, + calibrated_magnification: { + type: 'integer', + description: 'Calculated magnification, no unit', + }, + holder: { + type: 'string', + description: 'Speciman holder model', + }, + holder_cryogen: { + type: 'string', + description: 'Type of cryogen used in the holder - if the holder is cooled seperately', + }, + temperature_range: { + type: 'object', + description: 'Temperature during data collection, in K with min and max values.', + }, + microscope_software: { + type: 'string', + description: 'Software used for instrument control', + }, + detector: { + type: 'string', + description: 'Make and model of the detector used', + }, + detector_mode: { + type: 'string', + description: 'Operating mode of the detector', + }, + dose_per_movie: { + type: 'object', + description: 'Average dose per image/movie/tilt - given in electrons per square Angstrom', + }, + energy_filter: { + type: 'object', + description: 'Whether an energy filter was used and its specifics.', + }, + image_size: { + type: 'object', + description: 'The size of the image in pixels, height and width given.', + }, + date_time: { + type: 'string', + description: 'Time and date of the data acquisition', + }, + exposure_time: { + type: 'object', + description: 'Time of data acquisition per movie/tilt - in s', + }, + cryogen: { + type: 'string', + description: 'Cryogen used in cooling the instrument and sample, usually nitrogen', + }, + frames_per_movie: { + type: 'integer', + description: 'Number of frames that on average constitute a full movie, can be a bit hard to define for some detectors', + }, + grids_imaged: { + type: 'integer', + description: 'Number of grids imaged for this project - here with qualifier during this data acquisition', + }, + images_generated: { + type: 'integer', + description: 'Number of images generated total for this data collection - might need a qualifier for tilt series to determine whether full series or individual tilts are counted', + }, + binning_camera: { + type: 'number', + description: 'Level of binning on the images applied during data collection', + }, + pixel_size: { + type: 'object', + description: 'Pixel size, in Angstrom', + }, + specialist_optics: { + type: 'object', + description: 'Any type of special optics, such as a phaseplate', + }, + beamshift: { + type: 'object', + description: 'Movement of the beam above the sample for data collection purposes that does not require movement of the stage. Given in mrad.', + }, + beamtilt: { + type: 'object', + description: 'Another way to move the beam above the sample for data collection purposes that does not require movement of the stage. Given in mrad.', + }, + imageshift: { + type: 'object', + description: 'Movement of the Beam below the image in order to shift the image on the detector. Given in µm.', + }, + beamtiltgroups: { + type: 'integer', + description: 'Number of Beamtilt groups present in this dataset - for optimized processing split dataset into groups of same tilt angle. Despite its name Beamshift is often used to achieve this result.', + }, + gainref_flip_rotate: { + type: 'string', + description: 'Whether and how you have to flip or rotate the gainref in order to align with your acquired images', + }, + }, + required: ['detector', 'dose_per_movie', 'date_time', 'binning_camera', 'pixel_size'], +}; + +export const demo_sample_schema = { + type: 'object', + properties: { + overall_molecule: { + type: 'object', + description: 'Description of the overall molecule', + properties: { + molecular_type: { + type: 'string', + description: 'Description of the overall molecular type, i.e., a complex', + }, + name_sample: { + type: 'string', + description: 'Name of the full sample', + }, + source: { + type: 'string', + description: 'Where the sample was taken from, i.e., natural host, recombinantly expressed, etc.', + }, + molecular_weight: { + type: 'object', + description: 'Molecular weight in Da', + }, + assembly: { + type: 'string', + description: 'What type of higher order structure your sample forms - if any.', + enum: ['FILAMENT', 'HELICAL ARRAY', 'PARTICLE'], + }, + }, + required: ['molecular_type', 'name_sample', 'source', 'assembly'], + }, + molecule: { + type: 'array', + items: { + type: 'object', + properties: { + name_mol: { + type: 'string', + description: 'Name of an individual molecule (often protein) in the sample', + }, + molecular_type: { + type: 'string', + description: 'Description of the overall molecular type, i.e., a complex', + }, + molecular_class: { + type: 'string', + description: 'Class of the molecule', + enum: ['Antibiotic', 'Carbohydrate', 'Chimera', 'None of these'], + }, + sequence: { + type: 'string', + description: 'Full sequence of the sample as in the data, i.e., cleaved tags should also be removed from sequence here', + }, + natural_source: { + type: 'string', + description: 'Scientific name of the natural host organism', + }, + taxonomy_id_source: { + type: 'string', + description: 'Taxonomy ID of the natural source organism', + }, + expression_system: { + type: 'string', + description: 'Scientific name of the organism used to produce the molecule of interest', + }, + taxonomy_id_expression: { + type: 'string', + description: 'Taxonomy ID of the expression system organism', + }, + gene_name: { + type: 'string', + description: 'Name of the gene of interest', + }, + }, + }, + required: ['name_mol', 'molecular_type', 'molecular_class', 'sequence', 'natural_source', 'taxonomy_id_source', 'expression_system', 'taxonomy_id_expression'], + }, + ligands: { + type: 'array', + items: { + type: 'object', + properties: { + present: { + type: 'boolean', + description: 'Whether the model contains any ligands', + }, + smiles: { + type: 'string', + description: 'Provide a valid SMILES string of your ligand', + }, + reference: { + type: 'string', + description: 'Link to a reference of your ligand, i.e., CCD, PubChem, etc.', + }, + }, + }, + description: 'List of ligands associated with the sample', + }, + specimen: { + type: 'object', + description: 'Description of the specimen', + properties: { + buffer: { + type: 'string', + description: 'Name/composition of the (chemical) sample buffer during grid preparation', + }, + concentration: { + type: 'object', + description: 'Concentration of the (supra)molecule in the sample, in mg/ml', + }, + ph: { + type: 'number', + description: 'pH of the sample buffer', + }, + vitrification: { + type: 'boolean', + description: 'Whether the sample was vitrified', + }, + vitrification_cryogen: { + type: 'string', + description: 'Which cryogen was used for vitrification', + }, + humidity: { + type: 'object', + description: 'Environmental humidity just before vitrification, in %', + }, + temperature: { + type: 'object', + description: 'Environmental temperature just before vitrification, in K', + minimum: 0.0, + }, + staining: { + type: 'boolean', + description: 'Whether the sample was stained', + }, + embedding: { + type: 'boolean', + description: 'Whether the sample was embedded', + }, + shadowing: { + type: 'boolean', + description: 'Whether the sample was shadowed', + }, + }, + required: ['ph', 'vitrification', 'vitrification_cryogen', 'staining', 'embedding', 'shadowing'], + }, + grid: { + type: 'object', + description: 'Description of the grid used', + properties: { + manufacturer: { + type: 'string', + description: 'Grid manufacturer', + }, + material: { + type: 'string', + description: 'Material out of which the grid is made', + }, + mesh: { + type: 'number', + description: 'Grid mesh in lines per inch', + }, + film_support: { + type: 'boolean', + description: 'Whether a support film was used', + }, + film_material: { + type: 'string', + description: 'Type of material the support film is made of', + }, + film_topology: { + type: 'string', + description: 'Topology of the support film', + }, + film_thickness: { + type: 'string', + description: 'Thickness of the support film', + }, + pretreatment_type: { + type: 'string', + description: 'Type of pretreatment of the grid, i.e., glow discharge', + }, + pretreatment_time: { + type: 'object', + description: 'Length of time of the pretreatment in s', + }, + pretreatment_pressure: { + type: 'object', + description: 'Pressure of the chamber during pretreatment, in Pa', + }, + pretreatment_atmosphere: { + type: 'string', + description: 'Atmospheric conditions in the chamber during pretreatment, i.e., addition of specific gases, etc.', + }, + }, + }, + }, + required: ['overall_molecule', 'molecule', 'specimen', 'grid'], +}; + +export const demo_instrument_schema = { + type: 'object', + properties: { + microscope: { + type: 'string', + description: 'Name/Type of the Microscope', + }, + illumination: { + type: 'string', + description: 'Mode of illumination used during data collection', + }, + imaging: { + type: 'string', + description: 'Mode of imaging used during data collection', + }, + electron_source: { + type: 'string', + description: 'Type of electron source used in the microscope, such as FEG', + }, + acceleration_voltage: { + type: 'object', + description: 'Voltage used for the electron acceleration, in kV', + }, + c2_aperture: { + type: 'object', + description: 'C2 aperture size used in data acquisition, in µm', + }, + cs: { + type: 'object', + description: 'Spherical aberration of the instrument, in mm', + }, + }, + required: ['microscope', 'illumination', 'imaging', 'electron_source', 'acceleration_voltage', 'cs'], +}; + +export const demo_scicatheader_schema = { + type: "object", + properties: { + datasetName: { type: "string" }, + description: { type: "string" }, + creationLocation: { type: "string" }, + dataFormat: { type: "string" }, + ownerGroup: { type: "string" }, + type: { type: "string" }, + license: { type: "string" }, + keywords: { + type: "array", + items: { type: "string" } + }, + scientificMetadata: { type: "string" } + }, + required: ["datasetName", "description", "creationLocation", "dataFormat", "ownerGroup", "type", "license", "keywords", "scientificMetadata"] +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.html b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.html new file mode 100644 index 0000000000..2e2b11f4ef --- /dev/null +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.html @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.scss b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.scss new file mode 100644 index 0000000000..89fe8050d3 --- /dev/null +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.scss @@ -0,0 +1,3 @@ +.ingestor-metadata-editor { + width: 100%; +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts new file mode 100644 index 0000000000..308a64143b --- /dev/null +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts @@ -0,0 +1,28 @@ +import { Component, EventEmitter, Output, Input } from '@angular/core'; +import { JsonSchema } from '@jsonforms/core'; +import { configuredRenderer } from './ingestor-metadata-editor-helper'; + +@Component({ + selector: 'app-metadata-editor', + template: ``, +}) + +export class IngestorMetadataEditorComponent { + @Input() data: Object; + @Input() schema: JsonSchema; + + @Output() dataChange = new EventEmitter(); + + get combinedRenderers() { + return configuredRenderer; + } + + onDataChange(event: any) { + this.dataChange.emit(event); + } +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor.module.ts b/src/app/ingestor/ingestor.module.ts new file mode 100644 index 0000000000..eb79e0e644 --- /dev/null +++ b/src/app/ingestor/ingestor.module.ts @@ -0,0 +1,67 @@ +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { IngestorComponent } from "./ingestor/ingestor.component"; +import { MatCardModule } from "@angular/material/card"; +import { RouterModule } from "@angular/router"; +import { IngestorMetadataEditorComponent } from "./ingestor-metadata-editor/ingestor-metadata-editor.component"; +import { MatButtonModule } from "@angular/material/button"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatInputModule } from "@angular/material/input"; +import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; +import { FormsModule } from "@angular/forms"; +import { MatListModule } from '@angular/material/list'; +import { MatIconModule } from '@angular/material/icon'; +import { MatTabsModule } from "@angular/material/tabs"; +import { MatTableModule } from "@angular/material/table"; +import { MatDialogModule } from "@angular/material/dialog"; +import { MatSelectModule } from "@angular/material/select"; +import { MatOptionModule } from "@angular/material/core"; +import { MatAutocompleteModule } from "@angular/material/autocomplete"; +import { IngestorNewTransferDialogComponent } from "./ingestor/dialog/ingestor.new-transfer-dialog.component"; +import { IngestorUserMetadataDialog } from "./ingestor/dialog/ingestor.user-metadata-dialog.component"; +import { JsonFormsModule } from '@jsonforms/angular'; +import { JsonFormsAngularMaterialModule } from "@jsonforms/angular-material"; +import { IngestorExtractorMetadataDialog } from "./ingestor/dialog/ingestor.extractor-metadata-dialog.component"; +import { IngestorConfirmTransferDialog } from "./ingestor/dialog/ingestor.confirm-transfer-dialog.component"; +import { MatStepperModule } from "@angular/material/stepper"; +import { IngestorDialogStepperComponent } from "./ingestor/dialog/ingestor.dialog-stepper.component.component"; +import { AnyOfRenderer } from "./ingestor-metadata-editor/customRenderer/any-of-renderer"; +import { OneOfRenderer } from "./ingestor-metadata-editor/customRenderer/one-of-renderer"; +import { MatRadioModule } from "@angular/material/radio"; + +@NgModule({ + declarations: [ + IngestorComponent, + IngestorMetadataEditorComponent, + IngestorNewTransferDialogComponent, + IngestorUserMetadataDialog, + IngestorExtractorMetadataDialog, + IngestorConfirmTransferDialog, + IngestorDialogStepperComponent, + AnyOfRenderer, + OneOfRenderer, + ], + imports: [ + CommonModule, + MatCardModule, + FormsModule, + MatFormFieldModule, + MatInputModule, + MatButtonModule, + MatProgressSpinnerModule, + RouterModule, + MatListModule, + MatIconModule, + MatTabsModule, + MatTableModule, + MatDialogModule, + MatSelectModule, + MatOptionModule, + MatStepperModule, + MatRadioModule, + MatAutocompleteModule, + JsonFormsModule, + JsonFormsAngularMaterialModule, + ], +}) +export class IngestorModule { } diff --git a/src/app/ingestor/ingestor/_ingestor-theme.scss b/src/app/ingestor/ingestor/_ingestor-theme.scss new file mode 100644 index 0000000000..39875ec92b --- /dev/null +++ b/src/app/ingestor/ingestor/_ingestor-theme.scss @@ -0,0 +1,39 @@ +@use "sass:map"; +@use "@angular/material" as mat; + +@mixin color($theme) { + $color-config: map-get($theme, "color"); + $primary: map-get($color-config, "primary"); + $header-1: map-get($color-config, "header-1"); + $header-2: map-get($color-config, "header-2"); + $header-3: map-get($color-config, "header-3"); + $accent: map-get($color-config, "accent"); + mat-card { + .scicat-header { + background-color: mat.get-color-from-palette($primary, "lighter"); + } + + .organizational-header { + background-color: mat.get-color-from-palette($header-1, "lighter"); + } + + .sample-header { + background-color: mat.get-color-from-palette($header-2, "lighter"); + } + + .instrument-header { + background-color: mat.get-color-from-palette($header-3, "lighter"); + } + + .acquisition-header { + background-color: mat.get-color-from-palette($accent, "lighter"); + } + } +} + +@mixin theme($theme) { + $color-config: mat.get-color-config($theme); + @if $color-config != null { + @include color($theme); + } +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts new file mode 100644 index 0000000000..52285fca6b --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts @@ -0,0 +1,61 @@ +import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; +import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { IDialogDataObject, IIngestionRequestInformation, IngestorHelper, ISciCatHeader } from '../ingestor.component-helper'; + +@Component({ + selector: 'ingestor.confirm-transfer-dialog', + templateUrl: 'ingestor.confirm-transfer-dialog.html', + changeDetection: ChangeDetectionStrategy.OnPush, + styleUrls: ['../ingestor.component.scss'], +}) + +export class IngestorConfirmTransferDialog { + createNewTransferData: IIngestionRequestInformation = IngestorHelper.createEmptyRequestInformation(); + provideMergeMetaData: string = ''; + backendURL: string = ''; + + constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: IDialogDataObject) { + this.createNewTransferData = data.createNewTransferData; + this.backendURL = data.backendURL; + } + + ngOnInit() { + this.provideMergeMetaData = this.createMetaDataString(); + } + + createMetaDataString(): string { + const space = 2; + const scicatMetadata: ISciCatHeader = { + datasetName: this.createNewTransferData.scicatHeader['datasetName'], + description: this.createNewTransferData.scicatHeader['description'], + creationLocation: this.createNewTransferData.scicatHeader['creationLocation'], + dataFormat: this.createNewTransferData.scicatHeader['dataFormat'], + ownerGroup: this.createNewTransferData.scicatHeader['ownerGroup'], + type: this.createNewTransferData.scicatHeader['type'], + license: this.createNewTransferData.scicatHeader['license'], + keywords: this.createNewTransferData.scicatHeader['keywords'], + filePath: this.createNewTransferData.scicatHeader['filePath'], + scientificMetadata: { + organizational: this.createNewTransferData.userMetaData['organizational'], + sample: this.createNewTransferData.userMetaData['sample'], + acquisition: this.createNewTransferData.extractorMetaData['acquisition'], + instrument: this.createNewTransferData.extractorMetaData['instrument'], + }, + }; + + return JSON.stringify(scicatMetadata, null, space); + } + + onClickBack(): void { + if (this.data && this.data.onClickNext) { + this.data.onClickNext(2); // Beispielwert für den Schritt + } + } + + onClickConfirm(): void { + if (this.data && this.data.onClickNext) { + this.createNewTransferData.mergedMetaDataString = this.provideMergeMetaData; + this.data.onClickNext(4); + } + } +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.html new file mode 100644 index 0000000000..52dfdddc6a --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.html @@ -0,0 +1,22 @@ +
+

+ Confirm transfer +

+ +
+ + + +

Confirm Metadata

+ + + + + +
+ + + + \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.component.ts new file mode 100644 index 0000000000..cda2927a18 --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.component.ts @@ -0,0 +1,10 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'ingestor-dialog-stepper', + templateUrl: './ingestor.dialog-stepper.component.html', + styleUrls: ['./ingestor.dialog-stepper.component.css'] +}) +export class IngestorDialogStepperComponent { + @Input() activeStep: number = 0; +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.css b/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.css new file mode 100644 index 0000000000..8e114ace88 --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.css @@ -0,0 +1,17 @@ +.stepper { + display: flex; + flex-direction: column; + align-items: center; +} + +.stepper div { + margin: 5px; +} + +.stepper div.active { + font-weight: bold; +} + +button { + margin: 5px; +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.html b/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.html new file mode 100644 index 0000000000..9902301bb8 --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.html @@ -0,0 +1,16 @@ +
+ + + Select your ingestion method + + + Fill out user-specific metadata + + + Correct dataset-specific metadata + + + Confirm inputs + + +
\ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts new file mode 100644 index 0000000000..97ba8e811d --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts @@ -0,0 +1,62 @@ +import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; +import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { JsonSchema } from '@jsonforms/core'; +import { IDialogDataObject, IIngestionRequestInformation, IngestorHelper } from '../ingestor.component-helper'; + +@Component({ + selector: 'ingestor.extractor-metadata-dialog', + templateUrl: 'ingestor.extractor-metadata-dialog.html', + styleUrls: ['../ingestor.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) + +export class IngestorExtractorMetadataDialog { + metadataSchemaInstrument: JsonSchema; + metadataSchemaAcquisition: JsonSchema; + createNewTransferData: IIngestionRequestInformation = IngestorHelper.createEmptyRequestInformation(); + + backendURL: string = ''; + extractorMetaDataReady: boolean = false; + extractorMetaDataError: boolean = false; + + isCardContentVisible = { + instrument: true, + acquisition: true + }; + + constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: IDialogDataObject) { + this.createNewTransferData = data.createNewTransferData; + this.backendURL = data.backendURL; + const instrumentSchema = this.createNewTransferData.selectedResolvedDecodedSchema.properties.instrument; + const acqusitionSchema = this.createNewTransferData.selectedResolvedDecodedSchema.properties.acquisition; + + this.metadataSchemaInstrument = instrumentSchema; + this.metadataSchemaAcquisition = acqusitionSchema; + this.extractorMetaDataReady = this.createNewTransferData.extractorMetaDataReady; + this.extractorMetaDataError = this.createNewTransferData.apiErrorInformation.metaDataExtraction; + } + + onClickBack(): void { + if (this.data && this.data.onClickNext) { + this.data.onClickNext(1); // Beispielwert für den Schritt + } + } + + onClickNext(): void { + if (this.data && this.data.onClickNext) { + this.data.onClickNext(3); // Beispielwert für den Schritt + } + } + + onDataChangeExtractorMetadataInstrument(event: Object) { + this.createNewTransferData.extractorMetaData['instrument'] = event; + } + + onDataChangeExtractorMetadataAcquisition(event: Object) { + this.createNewTransferData.extractorMetaData['acquisition'] = event; + } + + toggleCardContent(card: string): void { + this.isCardContentVisible[card] = !this.isCardContentVisible[card]; + } +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html new file mode 100644 index 0000000000..fe985f0d6a --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html @@ -0,0 +1,73 @@ +
+

+ Correct dataset-specific metadata +

+ +
+ + +
+ +
+ Wait for the Ingestor... +
+
+ +
+ +
+ + +
+ error +
+ The automatic metadata extraction was not successful. Please enter + the data manually. +
+
+
+
+
+ + +
+ biotech +
+ Instrument Information + + {{ isCardContentVisible.instrument ? 'expand_less' : 'expand_more' }} +
+ + + +
+ + +
+ category-search +
+ Acquisition Information + + {{ isCardContentVisible.acquisition ? 'expand_less' : 'expand_more' }} +
+ + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts new file mode 100644 index 0000000000..198b11fdfd --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts @@ -0,0 +1,124 @@ +import { ChangeDetectionStrategy, Component, Inject, OnInit } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; +import { HttpClient } from '@angular/common/http'; +import { INGESTOR_API_ENDPOINTS_V1 } from '../ingestor-api-endpoints'; +import { IDialogDataObject, IExtractionMethod, IIngestionRequestInformation, IngestorHelper } from '../ingestor.component-helper'; +import { IngestorMetadataEditorHelper } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; + +@Component({ + selector: 'ingestor.new-transfer-dialog', + templateUrl: 'ingestor.new-transfer-dialog.html', + changeDetection: ChangeDetectionStrategy.OnPush, + styleUrls: ['../ingestor.component.scss'] +}) + +export class IngestorNewTransferDialogComponent implements OnInit { + extractionMethods: IExtractionMethod[] = []; + availableFilePaths: string[] = []; + backendURL: string = ''; + extractionMethodsError: string = ''; + availableFilePathsError: string = ''; + + uiNextButtonReady: boolean = false; + + createNewTransferData: IIngestionRequestInformation = IngestorHelper.createEmptyRequestInformation(); + + constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: IDialogDataObject, private http: HttpClient) { + this.createNewTransferData = data.createNewTransferData; + this.backendURL = data.backendURL; + } + + ngOnInit(): void { + this.apiGetExtractionMethods(); + this.apiGetAvailableFilePaths(); + } + + set selectedPath(value: string) { + this.createNewTransferData.selectedPath = value; + this.validateNextButton(); + } + + get selectedPath(): string { + return this.createNewTransferData.selectedPath; + } + + set selectedMethod(value: IExtractionMethod) { + this.createNewTransferData.selectedMethod = value; + this.validateNextButton(); + } + + get selectedMethod(): IExtractionMethod { + return this.createNewTransferData.selectedMethod; + } + + apiGetExtractionMethods(): void { + this.http.get(this.backendURL + INGESTOR_API_ENDPOINTS_V1.EXTRACTOR).subscribe( + (response: any) => { + if (response.methods && response.methods.length > 0) { + this.extractionMethods = response.methods; + } + else { + this.extractionMethodsError = 'No extraction methods found.'; + } + }, + (error: any) => { + this.extractionMethodsError = error.message; + console.error(this.extractionMethodsError); + } + ); + } + + apiGetAvailableFilePaths(): void { + this.http.get(this.backendURL + INGESTOR_API_ENDPOINTS_V1.DATASET).subscribe( + (response: any) => { + if (response.datasets && response.datasets.length > 0) { + this.availableFilePaths = response.datasets; + } + else { + this.availableFilePathsError = 'No datasets found.'; + } + }, + (error: any) => { + this.availableFilePathsError = error.message; + console.error(this.availableFilePathsError); + } + ); + } + + generateExampleDataForSciCatHeader(): void { + this.data.createNewTransferData.scicatHeader['filePath'] = this.createNewTransferData.selectedPath; + this.data.createNewTransferData.scicatHeader['keywords'] = ['OpenEM']; + + const nameWithoutPath = this.createNewTransferData.selectedPath.split('/|\\')[-1] ?? this.createNewTransferData.selectedPath; + this.data.createNewTransferData.scicatHeader['datasetName'] = nameWithoutPath; + this.data.createNewTransferData.scicatHeader['license'] = 'MIT License'; + this.data.createNewTransferData.scicatHeader['type'] = 'raw'; + this.data.createNewTransferData.scicatHeader['dataFormat'] = 'root'; + this.data.createNewTransferData.scicatHeader['owner'] = 'User'; + } + + prepareSchemaForProcessing(): void { + const encodedSchema = this.createNewTransferData.selectedMethod.schema; + const decodedSchema = atob(encodedSchema); + const schema = JSON.parse(decodedSchema); + const resolvedSchema = IngestorMetadataEditorHelper.resolveRefs(schema, schema); + this.createNewTransferData.selectedResolvedDecodedSchema = resolvedSchema; + } + + onClickRetryRequests(): void { + this.apiGetExtractionMethods(); + this.apiGetAvailableFilePaths(); + } + + onClickNext(): void { + if (this.data && this.data.onClickNext) { + this.generateExampleDataForSciCatHeader(); + this.prepareSchemaForProcessing(); + this.data.onClickNext(1); // Open next dialog + } + } + + validateNextButton(): void { + this.uiNextButtonReady = !!this.createNewTransferData.selectedPath && !!this.createNewTransferData.selectedMethod?.name; + } +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.html new file mode 100644 index 0000000000..a80e8c4d8a --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.html @@ -0,0 +1,45 @@ +
+

+ Select your ingestion method +

+ +
+ + + +

Please select the dataset to be uploaded and the appropriate metadata extractor method.

+ +
+ + File Path + + + + {{ filePath }} + + + + {{ availableFilePathsError }} +
+ +
+ + Extraction Method + + + {{ method.name }} + + + + {{ extractionMethodsError }} +
+ +
+ + + + + \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts new file mode 100644 index 0000000000..427bacd801 --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts @@ -0,0 +1,67 @@ +import {ChangeDetectionStrategy, Component, Inject} from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; +import { IngestorMetadataEditorHelper } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; +import { JsonSchema } from '@jsonforms/core'; +import { IDialogDataObject, IIngestionRequestInformation, IngestorHelper, SciCatHeader_Schema } from '../ingestor.component-helper'; + +@Component({ + selector: 'ingestor.user-metadata-dialog', + templateUrl: 'ingestor.user-metadata-dialog.html', + changeDetection: ChangeDetectionStrategy.OnPush, + styleUrls: ['../ingestor.component.scss'], +}) + +export class IngestorUserMetadataDialog { + metadataSchemaOrganizational: JsonSchema; + metadataSchemaSample: JsonSchema; + scicatHeaderSchema: JsonSchema; + createNewTransferData: IIngestionRequestInformation = IngestorHelper.createEmptyRequestInformation(); + backendURL: string = ''; + + uiNextButtonReady: boolean = true; // Change to false when dev is ready + + isCardContentVisible = { + scicat: true, + organizational: true, + sample: true + }; + + constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: IDialogDataObject) { + this.createNewTransferData = data.createNewTransferData; + this.backendURL = data.backendURL; + const organizationalSchema = this.createNewTransferData.selectedResolvedDecodedSchema.properties.organizational; + const sampleSchema = this.createNewTransferData.selectedResolvedDecodedSchema.properties.sample; + + this.metadataSchemaOrganizational = organizationalSchema; + this.metadataSchemaSample = sampleSchema; + this.scicatHeaderSchema = SciCatHeader_Schema; + } + + onClickBack(): void { + if (this.data && this.data.onClickNext) { + this.data.onClickNext(0); // Beispielwert für den Schritt + } + } + + onClickNext(): void { + if (this.data && this.data.onClickNext) { + this.data.onClickNext(2); // Beispielwert für den Schritt + } + } + + onDataChangeUserMetadataOrganization(event: Object) { + this.createNewTransferData.userMetaData['organizational'] = event; + } + + onDataChangeUserMetadataSample(event: Object) { + this.createNewTransferData.userMetaData['sample'] = event; + } + + onDataChangeUserScicatHeader(event: Object) { + this.createNewTransferData.scicatHeader = event; + } + + toggleCardContent(card: string): void { + this.isCardContentVisible[card] = !this.isCardContentVisible[card]; + } +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html new file mode 100644 index 0000000000..39fbf41a49 --- /dev/null +++ b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html @@ -0,0 +1,67 @@ +
+

+ Fill out user-specific metadata +

+ +
+ + + +
+
+ + +
+ info +
+ SciCat Information + + {{ isCardContentVisible.scicat ? 'expand_less' : 'expand_more' }} +
+ + + +
+ + + +
+ person +
+ Organizational Information + + {{ isCardContentVisible.organizational ? 'expand_less' : 'expand_more' }} +
+ + + +
+ + + +
+ description +
+ Sample Information + + {{ isCardContentVisible.sample ? 'expand_less' : 'expand_more' }} +
+ + + +
+
+
+ +
+ + + + \ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor-api-endpoints.ts b/src/app/ingestor/ingestor/ingestor-api-endpoints.ts new file mode 100644 index 0000000000..df2d088330 --- /dev/null +++ b/src/app/ingestor/ingestor/ingestor-api-endpoints.ts @@ -0,0 +1,17 @@ +export const INGESTOR_API_ENDPOINTS_V1 = { + DATASET: "dataset", + TRANSFER: "transfer", + OTHER: { + VERSION: 'version', + }, + EXTRACTOR: 'extractor', +}; + +export interface IPostExtractorEndpoint { + filePath: string, + methodName: string, +} + +export interface IPostDatasetEndpoint { + metaData: string +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor.component-helper.ts b/src/app/ingestor/ingestor/ingestor.component-helper.ts new file mode 100644 index 0000000000..1d31525d57 --- /dev/null +++ b/src/app/ingestor/ingestor/ingestor.component-helper.ts @@ -0,0 +1,104 @@ +import { JsonSchema } from '@jsonforms/core'; + +export interface IExtractionMethod { + name: string; + schema: string; // Base64 encoded JSON schema +}; + +export interface IIngestionRequestInformation { + selectedPath: string; + selectedMethod: IExtractionMethod; + selectedResolvedDecodedSchema: JsonSchema; + scicatHeader: Object; + userMetaData: { + organizational: Object, + sample: Object, + }; + extractorMetaData: { + instrument: Object, + acquisition: Object, + }; + extractorMetaDataReady: boolean; + extractMetaDataRequested: boolean; + mergedMetaDataString: string; + + apiErrorInformation: { + metaDataExtraction: boolean; + } +} + +// There are many more... see DerivedDataset.ts +export interface ISciCatHeader { + datasetName: string; + description: string; + creationLocation: string; + dataFormat: string; + ownerGroup: string; + type: string; + license: string; + keywords: string[]; + filePath: string; + scientificMetadata: IScientificMetadata; +} + +export interface IScientificMetadata { + organizational: Object; + sample: Object; + acquisition: Object; + instrument: Object; +} + +export interface IDialogDataObject { + createNewTransferData: IIngestionRequestInformation; + backendURL: string; + onClickNext: (step: number) => void; +} + +export class IngestorHelper { + static createEmptyRequestInformation = (): IIngestionRequestInformation => { + return { + selectedPath: '', + selectedMethod: { name: '', schema: '' }, + selectedResolvedDecodedSchema: {}, + scicatHeader: {}, + userMetaData: { + organizational: {}, + sample: {}, + }, + extractorMetaData: { + instrument: {}, + acquisition: {}, + }, + extractorMetaDataReady: false, + extractMetaDataRequested: false, + mergedMetaDataString: '', + apiErrorInformation: { + metaDataExtraction: false, + }, + }; + }; + + static mergeUserAndExtractorMetadata(userMetadata: Object, extractorMetadata: Object, space: number): string { + return JSON.stringify({ ...userMetadata, ...extractorMetadata }, null, space); + }; +} + +export const SciCatHeader_Schema: JsonSchema = { + type: "object", + properties: { + datasetName: { type: "string" }, + description: { type: "string" }, + creationLocation: { type: "string" }, + dataFormat: { type: "string" }, + ownerGroup: { type: "string" }, + filePath: { type: "string", readOnly: true }, // disabled, because its selected in the first step + type: { type: "string" }, + license: { type: "string" }, + keywords: { + type: "array", + items: { type: "string" } + }, + // scientificMetadata: { type: "string" } ; is created during the ingestor process + }, + required: ["datasetName", "creationLocation", "dataFormat", "ownerGroup", "type", "license", "keywords", "scientificMetadata", "filePath"] +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor.component.html b/src/app/ingestor/ingestor/ingestor.component.html new file mode 100644 index 0000000000..187f6f2f76 --- /dev/null +++ b/src/app/ingestor/ingestor/ingestor.component.html @@ -0,0 +1,144 @@ +

+ + + Control center + + +

+ +
+
+ +

No Backend connected

+ +

Please provide a valid Backend URL

+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
ID {{element.transferId}} + Status {{element.status}} + + Action + + + +
+
+ + + + + Backend URL + {{ connectedFacilityBackend }} change + + + Connection Status + Connected + + + Version + {{ connectedFacilityBackendVersion + }} + + + +
+

+
+
+ + +

+ +

+ + + New transfer + + + + + +

+ +

+ + + Return Value + + +

+
+
+

{{returnValue}}

+
+ + +

+ +

+ + + Error message + + + +

+

+
+ + +

\ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor.component.scss b/src/app/ingestor/ingestor/ingestor.component.scss new file mode 100644 index 0000000000..a5c5dcad67 --- /dev/null +++ b/src/app/ingestor/ingestor/ingestor.component.scss @@ -0,0 +1,62 @@ +.ingestor-vertical-layout { + display: flex; + flex-direction: column; + gap: 1em; +} + +.ingestor-mixed-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.ingestor-close-button { + margin-left: auto; +} + +.form-full-width { + width: 100%; +} + +.metadata-preview { + height: 50vh !important; +} + +mat-card { + margin: 1em; + + .mat-mdc-card-content { + padding: 16px; + } + + .section-icon { + height: auto !important; + width: auto !important; + + mat-icon { + vertical-align: middle; + } + } +} + +.spinner-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; +} + +.spinner-text { + margin-top: 10px; + font-size: 16px; + text-align: center; +} + +.spacer { + flex: 1 1 auto; +} + +.error-message { + color: red; +} \ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor.component.spec.ts b/src/app/ingestor/ingestor/ingestor.component.spec.ts new file mode 100644 index 0000000000..52186b1077 --- /dev/null +++ b/src/app/ingestor/ingestor/ingestor.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { IngestorComponent } from './ingestor.component'; + +describe('IngestorComponent', () => { + let component: IngestorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ IngestorComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(IngestorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); \ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor.component.ts b/src/app/ingestor/ingestor/ingestor.component.ts new file mode 100644 index 0000000000..2b49f2f90a --- /dev/null +++ b/src/app/ingestor/ingestor/ingestor.component.ts @@ -0,0 +1,282 @@ +import { Component, inject, OnInit } from "@angular/core"; +import { AppConfigService } from "app-config.service"; +import { HttpClient } from '@angular/common/http'; +import { ActivatedRoute, Router } from '@angular/router'; +import { INGESTOR_API_ENDPOINTS_V1, IPostDatasetEndpoint, IPostExtractorEndpoint } from "./ingestor-api-endpoints"; +import { IngestorNewTransferDialogComponent } from "./dialog/ingestor.new-transfer-dialog.component"; +import { MatDialog } from "@angular/material/dialog"; +import { IngestorUserMetadataDialog } from "./dialog/ingestor.user-metadata-dialog.component"; +import { IngestorExtractorMetadataDialog } from "./dialog/ingestor.extractor-metadata-dialog.component"; +import { IngestorConfirmTransferDialog } from "./dialog/ingestor.confirm-transfer-dialog.component"; +import { IIngestionRequestInformation, IngestorHelper } from "./ingestor.component-helper"; + +interface ITransferDataListEntry { + transferId: string; + status: string; +} + +@Component({ + selector: "ingestor", + templateUrl: "./ingestor.component.html", + styleUrls: ["./ingestor.component.scss"], +}) +export class IngestorComponent implements OnInit { + readonly dialog = inject(MatDialog); + + filePath: string = ''; + loading: boolean = false; + forwardFacilityBackend: string = ''; + + connectedFacilityBackend: string = ''; + connectedFacilityBackendVersion: string = ''; + connectingToFacilityBackend: boolean = false; + + lastUsedFacilityBackends: string[] = []; + + transferDataSource: ITransferDataListEntry[] = []; // List of files to be transferred + displayedColumns: string[] = ['transferId', 'status', 'actions']; + + errorMessage: string = ''; + returnValue: string = ''; + + createNewTransferData: IIngestionRequestInformation = IngestorHelper.createEmptyRequestInformation(); + + constructor(public appConfigService: AppConfigService, private http: HttpClient, private route: ActivatedRoute, private router: Router) { } + + ngOnInit() { + this.connectingToFacilityBackend = true; + this.lastUsedFacilityBackends = this.loadLastUsedFacilityBackends(); + this.transferDataSource = []; + // Get the GET parameter 'backendUrl' from the URL + this.route.queryParams.subscribe(params => { + const backendUrl = params['backendUrl']; + if (backendUrl) { + this.apiConnectToFacilityBackend(backendUrl); + } + else { + this.connectingToFacilityBackend = false; + } + }); + } + + apiConnectToFacilityBackend(facilityBackendUrl: string): boolean { + let facilityBackendUrlCleaned = facilityBackendUrl.slice(); + // Check if last symbol is a slash and add version endpoint + if (!facilityBackendUrlCleaned.endsWith('/')) { + facilityBackendUrlCleaned += '/'; + } + + let facilityBackendUrlVersion = facilityBackendUrlCleaned + INGESTOR_API_ENDPOINTS_V1.OTHER.VERSION; + + // Try to connect to the facility backend/version to check if it is available + console.log('Connecting to facility backend: ' + facilityBackendUrlVersion); + this.http.get(facilityBackendUrlVersion).subscribe( + response => { + console.log('Connected to facility backend', response); + // If the connection is successful, store the connected facility backend URL + this.connectedFacilityBackend = facilityBackendUrlCleaned; + this.connectingToFacilityBackend = false; + this.connectedFacilityBackendVersion = response['version']; + }, + error => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}
`; + console.error('Request failed', error); + this.connectedFacilityBackend = ''; + this.connectingToFacilityBackend = false; + this.lastUsedFacilityBackends = this.loadLastUsedFacilityBackends(); + } + ); + + return true; + } + + apiGetTransferList(page: number, pageSize: number, transferId?: string): void { + const params: any = { + page: page.toString(), + pageSize: pageSize.toString(), + }; + if (transferId) { + params.transferId = transferId; + } + this.http.get(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.TRANSFER, { params }).subscribe( + response => { + console.log('Transfer list received', response); + this.transferDataSource = response['transfers']; + }, + error => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}]
`; + console.error('Request failed', error); + } + ); + } + + apiUpload() { + this.loading = true; + + const payload: IPostDatasetEndpoint = { + metaData: this.createNewTransferData.mergedMetaDataString, + }; + + this.http.post(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.DATASET, payload).subscribe( + response => { + console.log('Upload successfully started', response); + this.returnValue = JSON.stringify(response); + this.loading = false; + }, + error => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}]
`; + console.error('Upload failed', error); + this.loading = false; + } + ); + } + + async apiStartMetadataExtraction(): Promise { + this.createNewTransferData.apiErrorInformation.metaDataExtraction = false; + + if (this.createNewTransferData.extractMetaDataRequested) { + console.log(this.createNewTransferData.extractMetaDataRequested, ' already requested'); // Debugging + return false; + } + + this.createNewTransferData.extractorMetaDataReady = false; + this.createNewTransferData.extractMetaDataRequested = true; + + const payload: IPostExtractorEndpoint = { + filePath: this.createNewTransferData.selectedPath, + methodName: this.createNewTransferData.selectedMethod.name, + }; + + return new Promise((resolve) => { + this.http.post(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.EXTRACTOR, payload).subscribe( + response => { + console.log('Metadata extraction result', response); + this.createNewTransferData.extractorMetaData.instrument = (response as any).instrument ?? {}; + this.createNewTransferData.extractorMetaData.acquisition = (response as any).acquisition ?? {}; + this.createNewTransferData.extractorMetaDataReady = true; + resolve(true); + }, + error => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}]
`; + console.error('Metadata extraction failed', error); + this.createNewTransferData.extractorMetaDataReady = true; + this.createNewTransferData.apiErrorInformation.metaDataExtraction = true; + resolve(false); + } + ); + }); + } + + onClickForwardToIngestorPage() { + if (this.forwardFacilityBackend) { + this.connectingToFacilityBackend = true; + + // If current route is equal to the forward route, the router will not navigate to the new route + if (this.connectedFacilityBackend === this.forwardFacilityBackend) { + this.apiConnectToFacilityBackend(this.forwardFacilityBackend); + return; + } + + this.router.navigate(['/ingestor'], { queryParams: { backendUrl: this.forwardFacilityBackend } }); + } + } + + onClickDisconnectIngestor() { + this.returnValue = ''; + this.connectedFacilityBackend = ''; + // Remove the GET parameter 'backendUrl' from the URL + this.router.navigate(['/ingestor']); + } + + // Helper functions + onClickSelectFacilityBackend(facilityBackend: string) { + this.forwardFacilityBackend = facilityBackend; + } + + loadLastUsedFacilityBackends(): string[] { + // Load the list from the local Storage + const lastUsedFacilityBackends = '["http://localhost:8000", "http://localhost:8888"]'; + if (lastUsedFacilityBackends) { + return JSON.parse(lastUsedFacilityBackends); + } + return []; + } + + clearErrorMessage(): void { + this.errorMessage = ''; + } + + onClickAddIngestion(): void { + this.createNewTransferData = IngestorHelper.createEmptyRequestInformation(); + this.onClickNext(0); // Open first dialog to start the ingestion process + } + + onClickNext(step: number): void { + this.dialog.closeAll(); + + let dialogRef = null; + + switch (step) { + case 0: + this.createNewTransferData.extractMetaDataRequested = false; + this.createNewTransferData.extractorMetaDataReady = false; + dialogRef = this.dialog.open(IngestorNewTransferDialogComponent, { + data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData, backendURL: this.connectedFacilityBackend }, + disableClose: true + }); + + break; + case 1: + this.apiStartMetadataExtraction().then((response: boolean) => { + if (response) console.log('Metadata extraction finished'); + else console.error('Metadata extraction failed'); + }).catch(error => { + console.error('Metadata extraction error', error); + }); + + dialogRef = this.dialog.open(IngestorUserMetadataDialog, { + data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData, backendURL: this.connectedFacilityBackend }, + disableClose: true + }); + break; + case 2: + dialogRef = this.dialog.open(IngestorExtractorMetadataDialog, { + data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData, backendURL: this.connectedFacilityBackend }, + disableClose: true + }); + break; + case 3: + dialogRef = this.dialog.open(IngestorConfirmTransferDialog, { + data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData, backendURL: this.connectedFacilityBackend }, + disableClose: true + }); + break; + case 4: + this.apiUpload(); + break; + default: + console.error('Unknown step', step); + } + + // Error if the dialog reference is not set + if (dialogRef === null) return; + } + + onClickRefreshTransferList(): void { + this.apiGetTransferList(1, 100); + } + + onCancelTransfer(transferId: string) { + console.log('Cancel transfer', transferId); + this.http.delete(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.TRANSFER + '/' + transferId).subscribe( + response => { + console.log('Transfer cancelled', response); + this.apiGetTransferList(1, 100); + }, + error => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}
`; + console.error('Cancel transfer failed', error); + } + ); + } +} \ No newline at end of file diff --git a/src/styles.scss b/src/styles.scss index 9aee94e724..150dc3ab4c 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -29,6 +29,7 @@ @use "./app/shared/modules/shared-table/shared-table-theme" as shared-table; @use "./app/shared/modules/table/table-theme" as table; @use "./app/users/user-settings/user-settings-theme" as user-settings; +@use "./app/ingestor/ingestor/ingestor-theme" as ingestor; $my-custom-button-level: mat.define-typography-level( $font-weight: 400, @@ -221,6 +222,7 @@ $theme: map-merge( @include shared-table.theme($theme); @include table.theme($theme); @include user-settings.theme($theme); +@include ingestor.theme($theme); @include mat.button-density(0); @include mat.icon-button-density(0); From 3a9f1b0809220ab50b08305fba03d49efe752d30 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Thu, 19 Dec 2024 13:20:37 +0000 Subject: [PATCH 066/245] wip:adding effects --- src/app/datasets/datasets.module.ts | 5 + src/app/datasets/empiar/empiar.component.html | 1 + src/app/datasets/empiar/empiar.component.scss | 0 .../datasets/empiar/empiar.component.spec.ts | 21 +++ src/app/datasets/empiar/empiar.component.ts | 10 ++ src/app/datasets/onedep/onedep.component.ts | 16 +-- .../datasets/onedep/onedep.components.spec.ts | 21 +++ src/app/shared/sdk/apis/JobsService.ts | 9 +- .../sdk/apis/onedep-depositor.service.spec.ts | 16 +++ .../sdk/apis/onedep-depositor.service.ts | 43 +++++++ .../actions/onedep.actions.ts | 62 +++++++++ .../effects/onedep.effects.ts | 121 ++++++++++++++++++ 12 files changed, 316 insertions(+), 9 deletions(-) create mode 100644 src/app/datasets/empiar/empiar.component.html create mode 100644 src/app/datasets/empiar/empiar.component.scss create mode 100644 src/app/datasets/empiar/empiar.component.spec.ts create mode 100644 src/app/datasets/empiar/empiar.component.ts create mode 100644 src/app/datasets/onedep/onedep.components.spec.ts create mode 100644 src/app/shared/sdk/apis/onedep-depositor.service.spec.ts create mode 100644 src/app/shared/sdk/apis/onedep-depositor.service.ts create mode 100644 src/app/state-management/actions/onedep.actions.ts create mode 100644 src/app/state-management/effects/onedep.effects.ts diff --git a/src/app/datasets/datasets.module.ts b/src/app/datasets/datasets.module.ts index 2c007619cd..bf79aab65f 100644 --- a/src/app/datasets/datasets.module.ts +++ b/src/app/datasets/datasets.module.ts @@ -92,6 +92,9 @@ import { userReducer } from "state-management/reducers/user.reducer"; import { MatSnackBarModule } from "@angular/material/snack-bar"; import { OneDepComponent } from "./onedep/onedep.component"; import { OrcidFormatterDirective } from "./onedep/onedep.directive"; +import { EmpiarComponent } from "./empiar/empiar.component"; +import { OneDepEffects } from "./state-management/effects/onedep.effects"; + @NgModule({ imports: [ @@ -139,6 +142,7 @@ import { OrcidFormatterDirective } from "./onedep/onedep.directive"; SampleEffects, PublishedDataEffects, LogbookEffects, + OneDepEffects, ]), StoreModule.forFeature("datasets", datasetsReducer), StoreModule.forFeature("instruments", instrumentsReducer), @@ -183,6 +187,7 @@ import { OrcidFormatterDirective } from "./onedep/onedep.directive"; DatasetsFilterSettingsComponent, OneDepComponent, OrcidFormatterDirective, + EmpiarComponent, ], providers: [ ArchivingService, diff --git a/src/app/datasets/empiar/empiar.component.html b/src/app/datasets/empiar/empiar.component.html new file mode 100644 index 0000000000..4f527d88ca --- /dev/null +++ b/src/app/datasets/empiar/empiar.component.html @@ -0,0 +1 @@ +

empiar works!

diff --git a/src/app/datasets/empiar/empiar.component.scss b/src/app/datasets/empiar/empiar.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/datasets/empiar/empiar.component.spec.ts b/src/app/datasets/empiar/empiar.component.spec.ts new file mode 100644 index 0000000000..24c4b1d590 --- /dev/null +++ b/src/app/datasets/empiar/empiar.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; + +import { EmpiarComponent } from "./empiar.component"; + +describe("EmpiarComponent", () => { + let component: EmpiarComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [EmpiarComponent], + }); + fixture = TestBed.createComponent(EmpiarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/datasets/empiar/empiar.component.ts b/src/app/datasets/empiar/empiar.component.ts new file mode 100644 index 0000000000..c7e535f5dc --- /dev/null +++ b/src/app/datasets/empiar/empiar.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-empiar', + templateUrl: './empiar.component.html', + styleUrls: ['./empiar.component.scss'] +}) +export class EmpiarComponent { + +} diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index af13dc9f06..7e98091306 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -25,6 +25,7 @@ import { import { selectCurrentDataset } from "state-management/selectors/datasets.selectors"; import { selectCurrentUser } from "state-management/selectors/user.selectors"; import { methodsList, EmFile, DepositionFiles } from "./types/methods.enum"; +import { Depositor } from "shared/sdk/apis/onedep-depositor.service"; import { Subscription, fromEvent } from "rxjs"; @Component({ @@ -65,6 +66,7 @@ export class OneDepComponent implements OnInit, OnDestroy { private http: HttpClient, private router: Router, private fb: FormBuilder, + private depositor: Depositor, ) { this.config = this.appConfigService.getConfig(); } @@ -111,12 +113,14 @@ export class OneDepComponent implements OnInit, OnDestroy { }), ]), }); + console.log(this.form); } ngOnDestroy() { this.subscriptions.forEach((subscription) => { subscription.unsubscribe(); }); + this.fileTypes = []; } hasUnsavedChanges() { return this._hasUnsavedChanges; @@ -388,14 +392,10 @@ export class OneDepComponent implements OnInit, OnDestroy { interface OneDepCreate { depID: string; } - this.http - .post(this.connectedDepositionBackend + "onedep", body, { - headers: { "Content-Type": "application/json" }, - }) - .subscribe({ - next: (response: OneDepCreate) => { - depID = response.depID; - console.log("Created deposition in OneDep", depID); + this.depositor.createDep(body).subscribe({ + next: (response: OneDepCreate) => { + // depID = response.depID; + console.log("Created deposition in OneDep", response); // Call subsequent requests this.fileTypes.forEach((fT) => { diff --git a/src/app/datasets/onedep/onedep.components.spec.ts b/src/app/datasets/onedep/onedep.components.spec.ts new file mode 100644 index 0000000000..39c765d51b --- /dev/null +++ b/src/app/datasets/onedep/onedep.components.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; + +import { OneDepComponent } from "./onedep.component"; + +describe("OneDepComponent", () => { + let component: OneDepComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [OneDepComponent], + }); + fixture = TestBed.createComponent(OneDepComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/sdk/apis/JobsService.ts b/src/app/shared/sdk/apis/JobsService.ts index b39d7a0152..a762e12af3 100644 --- a/src/app/shared/sdk/apis/JobsService.ts +++ b/src/app/shared/sdk/apis/JobsService.ts @@ -1,5 +1,12 @@ import { Injectable } from "@angular/core"; -import { HttpClient, HttpHeaders, HttpResponse, HttpEvent, HttpParameterCodec, HttpContext } from "@angular/common/http"; +import { + HttpClient, + HttpHeaders, + HttpResponse, + HttpEvent, + HttpParameterCodec, + HttpContext, +} from "@angular/common/http"; import { Observable } from "rxjs"; import { Configuration } from "@scicatproject/scicat-sdk-ts"; import { Job, JobInterface } from "../models/Job"; diff --git a/src/app/shared/sdk/apis/onedep-depositor.service.spec.ts b/src/app/shared/sdk/apis/onedep-depositor.service.spec.ts new file mode 100644 index 0000000000..ff57d2901d --- /dev/null +++ b/src/app/shared/sdk/apis/onedep-depositor.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { OnedepDepositorService } from './onedep-depositor.service'; + +describe('OnedepDepositorService', () => { + let service: OnedepDepositorService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(OnedepDepositorService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/shared/sdk/apis/onedep-depositor.service.ts b/src/app/shared/sdk/apis/onedep-depositor.service.ts new file mode 100644 index 0000000000..aa10a96f92 --- /dev/null +++ b/src/app/shared/sdk/apis/onedep-depositor.service.ts @@ -0,0 +1,43 @@ +import { Injectable } from "@angular/core"; +import { HttpClient, HttpHeaders, HttpResponse } from "@angular/common/http"; +import { Observable } from "rxjs"; +import { AppConfigService, AppConfig } from "app-config.service"; + +interface OneDepCreate { + depID: string; +} + +@Injectable({ + providedIn: "root", +}) +export class Depositor { + config: AppConfig; + constructor( + private http: HttpClient, + public appConfigService: AppConfigService, + ) { + this.config = this.appConfigService.getConfig(); + } + + // create deposition + createDep(body: string): Observable { + return this.http.post( + `${this.config.depositorURL}/onedep`, + body, + { + headers: { "Content-Type": "application/json" }, + }, + ); + } + + // // Example POST request + // createItem(item: any): Observable { + // const headers = new HttpHeaders({ "Content-Type": "application/json" }); + // return this.http.post(`${this.apiUrl}/items`, item, { headers }); + // } + + // // Example DELETE request + // deleteItem(id: string): Observable { + // return this.http.delete(`${this.apiUrl}/items/${id}`); + // } +} diff --git a/src/app/state-management/actions/onedep.actions.ts b/src/app/state-management/actions/onedep.actions.ts new file mode 100644 index 0000000000..2d68b5d7ab --- /dev/null +++ b/src/app/state-management/actions/onedep.actions.ts @@ -0,0 +1,62 @@ +import { createAction, props } from "@ngrx/store"; + +export const createDeposition = createAction("[OneDep] Create Deposition"); + +export const createDepositionSuccess = createAction( + "[OneDep] Create Deposition Complete", + props<{ depID: string }>(), +); + +export const createDepositionFailure = createAction( + "[OneDep] Create Deposition Failure", +); + +// export const sendFile = createAction( +// "[OneDep] Send File", +// props<{ depID: string; form: FormData; fileType: string }>() +// ); + +// export const sendFile = createAction( +// "[OneDep] Send File", +// props<{ depID: string; form: FormData; fileType: string }>() +// ); + +// export const sendFileSuccess = createAction( +// "[OneDep] Send File Success", +// props<{ fileType: string; res: any }>() +// ); + +// export const sendFileFailure = createAction( +// "[OneDep] Send File Failure", +// props<{ error: any }>() +// ); + +// export const sendCoordFile = createAction( +// "[OneDep] Send Coord File", +// props<{ depID: string; form: FormData }>() +// ); + +// export const sendCoordFileSuccess = createAction( +// "[OneDep] Send Coord File Success", +// props<{ res: any }>() +// ); + +// export const sendCoordFileFailure = createAction( +// "[OneDep] Send Coord File Failure", +// props<{ error: any }>() +// ); + +// export const sendMetadata = createAction( +// "[OneDep] Send Metadata", +// props<{ depID: string; form: FormData }>() +// ); + +// export const sendMetadataSuccess = createAction( +// "[OneDep] Send Metadata Success", +// props<{ res: any }>() +// ); + +// export const sendMetadataFailure = createAction( +// "[OneDep] Send Metadata Failure", +// props<{ error: any }>() +// ); diff --git a/src/app/state-management/effects/onedep.effects.ts b/src/app/state-management/effects/onedep.effects.ts new file mode 100644 index 0000000000..8ed164759d --- /dev/null +++ b/src/app/state-management/effects/onedep.effects.ts @@ -0,0 +1,121 @@ +import { Injectable } from "@angular/core"; +import { Actions, createEffect, ofType } from "@ngrx/effects"; +import { of } from "rxjs"; +import { catchError, map, mergeMap } from "rxjs/operators"; +import { HttpClient } from "@angular/common/http"; +import * as fromActions from "state-management/actions/onedep.actions"; +import { Depositor } from "shared/sdk/apis/onedep-depositor.service"; +import { Store } from "@ngrx/store"; + +@Injectable() +export class OneDepEffects { + + submitJob$ = createEffect(() => { + return this.actions$.pipe( + ofType(fromActions.submitJobAction), + switchMap(({ job }) => + this.jobsServiceV4.jobsControllerCreateV4(job as Job).pipe( + map((res) => fromActions.submitJobCompleteAction({ job: res as JobInterface })), + catchError((err) => of(fromActions.submitJobFailedAction({ err }))), + ), + ), + ); + }); + + submitJobCompleteMessage$ = createEffect(() => { + return this.actions$.pipe( + ofType(fromActions.submitJobCompleteAction), + switchMap(() => { + const message = { + type: MessageType.Success, + content: "Job Created Successfully", + duration: 5000, + }; + return of(showMessageAction({ message })); + }), + ); + }); + + submitJobFailedMessage$ = createEffect(() => { + return this.actions$.pipe( + ofType(fromActions.submitJobFailedAction), + switchMap(({ err }) => { + const message = { + type: MessageType.Error, + content: "Job Not Submitted: " + err.message, + duration: 5000, + }; + return of(showMessageAction({ message })); + }), + ); + }); + + + // sendFile$ = createEffect(() => + // this.actions$.pipe( + // ofType(OneDepActions.sendFile), + // mergeMap((action) => + // this.http + // .post( + // `${this.connectedDepositionBackend}onedep/${action.depID}/file`, + // action.form + // ) + // .pipe( + // map((res) => + // OneDepActions.sendFileSuccess({ fileType: action.fileType, res }) + // ), + // catchError((error) => + // of(OneDepActions.sendFileFailure({ error })) + // ) + // ) + // ) + // ) + // ); + + // sendCoordFile$ = createEffect(() => + // this.actions$.pipe( + // ofType(OneDepActions.sendCoordFile), + // mergeMap((action) => + // this.http + // .post( + // `${this.connectedDepositionBackend}onedep/${action.depID}/pdb`, + // action.form + // ) + // .pipe( + // map((res) => + // OneDepActions.sendCoordFileSuccess({ res }) + // ), + // catchError((error) => + // of(OneDepActions.sendCoordFileFailure({ error })) + // ) + // ) + // ) + // ) + // ); + + // sendMetadata$ = createEffect(() => + // this.actions$.pipe( + // ofType(OneDepActions.sendMetadata), + // mergeMap((action) => + // this.http + // .post( + // `${this.connectedDepositionBackend}onedep/${action.depID}/metadata`, + // action.form + // ) + // .pipe( + // map((res) => + // OneDepActions.sendMetadataSuccess({ res }) + // ), + // catchError((error) => + // of(OneDepActions.sendMetadataFailure({ error })) + // ) + // ) + // ) + // ) + // ); + constructor( + private actions$: Actions, + private onedepDepositor: Depositor, + private store: Store, + ) {} +} From 665fb1e3683867bce5f64ba3ce10f171891fb9e3 Mon Sep 17 00:00:00 2001 From: David Wiessner Date: Thu, 19 Dec 2024 13:36:51 +0000 Subject: [PATCH 067/245] add @jsonforms/angular and @jsonforms/angular-material dependencies --- package-lock.json | 75 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index ab451f69cf..b4978fa094 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,8 @@ "@angular/platform-server": "^16", "@angular/router": "^16", "@angular/service-worker": "^16", + "@jsonforms/angular": "3.2.*", + "@jsonforms/angular-material": "3.2.*", "@ngbracket/ngx-layout": "^16.0.0", "@ngrx/effects": "^16", "@ngrx/router-store": "^16", @@ -3619,6 +3621,54 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsonforms/angular": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@jsonforms/angular/-/angular-3.2.1.tgz", + "integrity": "sha512-Kv9dxSttXZ2zXeHM+VEAtpkNoJbEqk6a1FrPv4Ol/p53EDNu7lyyuaWQ5B+YuqaRhpdq8yLce5zpkSk5P2Z2nw==", + "dependencies": { + "lodash": "^4.17.21" + }, + "peerDependencies": { + "@angular/core": "^16.0.0 || ^17.0.0", + "@angular/forms": "^16.0.0 || ^17.0.0", + "@jsonforms/core": "3.2.1", + "rxjs": "^6.6.0 || ^7.4.0" + } + }, + "node_modules/@jsonforms/angular-material": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@jsonforms/angular-material/-/angular-material-3.2.1.tgz", + "integrity": "sha512-qkhOL3oZaaNfcwxkO6HUzQerd7SWK93dTGIT29SO8frY1vjcnnw1qmRMIplDtaX+zR+/GJuCJfKP690bVCp3EA==", + "dependencies": { + "hammerjs": "2.0.8", + "lodash": "^4.17.21" + }, + "peerDependencies": { + "@angular/animations": "^16.0.0 || ^17.0.0", + "@angular/cdk": "^16.0.0 || ^17.0.0", + "@angular/common": "^16.0.0 || ^17.0.0", + "@angular/core": "^16.0.0 || ^17.0.0", + "@angular/forms": "^16.0.0 || ^17.0.0", + "@angular/material": "^16.0.0 || ^17.0.0", + "@angular/platform-browser": "^16.0.0 || ^17.0.0", + "@angular/router": "^16.0.0 || ^17.0.0", + "@jsonforms/angular": "3.2.1", + "@jsonforms/core": "3.2.1", + "rxjs": "^6.6.0 || ^7.4.0" + } + }, + "node_modules/@jsonforms/core": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@jsonforms/core/-/core-3.2.1.tgz", + "integrity": "sha512-G5ldiWM2e5Vh5WrbjDKb0QJ1SG/39kxC48x2ufFKhfpDHibqhPaXegg5jmSBTNcB0ldE9jMmpKwxF6fi5VBCEA==", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.3", + "ajv": "^8.6.1", + "ajv-formats": "^2.1.0", + "lodash": "^4.17.21" + } + }, "node_modules/@kwsites/file-exists": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", @@ -5350,8 +5400,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.13", @@ -6454,7 +6503,6 @@ "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -6470,7 +6518,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, "dependencies": { "ajv": "^8.0.0" }, @@ -10152,8 +10199,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", @@ -10850,6 +10896,14 @@ "typescript": ">=3.7.5" } }, + "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", @@ -12593,8 +12647,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", @@ -13246,8 +13299,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", @@ -15979,7 +16031,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "engines": { "node": ">=6" } @@ -16405,7 +16456,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" } @@ -18691,7 +18741,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "dependencies": { "punycode": "^2.1.0" } From 753ccd6209bb373230b687ff380022e44cdfed8c Mon Sep 17 00:00:00 2001 From: David Wiessner Date: Thu, 19 Dec 2024 13:37:51 +0000 Subject: [PATCH 068/245] Added @jsonforms/angular and @jsonforms/angular-material in package-lock.json --- package-lock.json | 75 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index ab451f69cf..b4978fa094 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,8 @@ "@angular/platform-server": "^16", "@angular/router": "^16", "@angular/service-worker": "^16", + "@jsonforms/angular": "3.2.*", + "@jsonforms/angular-material": "3.2.*", "@ngbracket/ngx-layout": "^16.0.0", "@ngrx/effects": "^16", "@ngrx/router-store": "^16", @@ -3619,6 +3621,54 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsonforms/angular": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@jsonforms/angular/-/angular-3.2.1.tgz", + "integrity": "sha512-Kv9dxSttXZ2zXeHM+VEAtpkNoJbEqk6a1FrPv4Ol/p53EDNu7lyyuaWQ5B+YuqaRhpdq8yLce5zpkSk5P2Z2nw==", + "dependencies": { + "lodash": "^4.17.21" + }, + "peerDependencies": { + "@angular/core": "^16.0.0 || ^17.0.0", + "@angular/forms": "^16.0.0 || ^17.0.0", + "@jsonforms/core": "3.2.1", + "rxjs": "^6.6.0 || ^7.4.0" + } + }, + "node_modules/@jsonforms/angular-material": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@jsonforms/angular-material/-/angular-material-3.2.1.tgz", + "integrity": "sha512-qkhOL3oZaaNfcwxkO6HUzQerd7SWK93dTGIT29SO8frY1vjcnnw1qmRMIplDtaX+zR+/GJuCJfKP690bVCp3EA==", + "dependencies": { + "hammerjs": "2.0.8", + "lodash": "^4.17.21" + }, + "peerDependencies": { + "@angular/animations": "^16.0.0 || ^17.0.0", + "@angular/cdk": "^16.0.0 || ^17.0.0", + "@angular/common": "^16.0.0 || ^17.0.0", + "@angular/core": "^16.0.0 || ^17.0.0", + "@angular/forms": "^16.0.0 || ^17.0.0", + "@angular/material": "^16.0.0 || ^17.0.0", + "@angular/platform-browser": "^16.0.0 || ^17.0.0", + "@angular/router": "^16.0.0 || ^17.0.0", + "@jsonforms/angular": "3.2.1", + "@jsonforms/core": "3.2.1", + "rxjs": "^6.6.0 || ^7.4.0" + } + }, + "node_modules/@jsonforms/core": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@jsonforms/core/-/core-3.2.1.tgz", + "integrity": "sha512-G5ldiWM2e5Vh5WrbjDKb0QJ1SG/39kxC48x2ufFKhfpDHibqhPaXegg5jmSBTNcB0ldE9jMmpKwxF6fi5VBCEA==", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.3", + "ajv": "^8.6.1", + "ajv-formats": "^2.1.0", + "lodash": "^4.17.21" + } + }, "node_modules/@kwsites/file-exists": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", @@ -5350,8 +5400,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.13", @@ -6454,7 +6503,6 @@ "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -6470,7 +6518,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, "dependencies": { "ajv": "^8.0.0" }, @@ -10152,8 +10199,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", @@ -10850,6 +10896,14 @@ "typescript": ">=3.7.5" } }, + "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", @@ -12593,8 +12647,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", @@ -13246,8 +13299,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", @@ -15979,7 +16031,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "engines": { "node": ">=6" } @@ -16405,7 +16456,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" } @@ -18691,7 +18741,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "dependencies": { "punycode": "^2.1.0" } From ef50ac9e40457a28a67269e1168fc645b81181a7 Mon Sep 17 00:00:00 2001 From: David Wiessner Date: Thu, 19 Dec 2024 14:28:51 +0000 Subject: [PATCH 069/245] fix eslint complains --- .../customRenderer/all-of-renderer.ts | 10 +- .../customRenderer/any-of-renderer.ts | 115 +++-- .../customRenderer/custom-renderers.ts | 25 +- .../customRenderer/one-of-renderer.ts | 118 +++-- .../ingestor-metadata-editor-helper.ts | 15 +- .../ingestor-metadata-editor-schema_demo.ts | 483 ------------------ .../ingestor-metadata-editor.component.ts | 23 +- src/app/ingestor/ingestor.module.ts | 42 +- ...estor.confirm-transfer-dialog.component.ts | 73 ++- ...stor.dialog-stepper.component.component.ts | 12 +- ...tor.extractor-metadata-dialog.component.ts | 71 +-- .../ingestor.new-transfer-dialog.component.ts | 143 +++--- ...ingestor.user-metadata-dialog.component.ts | 59 ++- .../ingestor/ingestor-api-endpoints.ts | 16 +- .../ingestor/ingestor.component-helper.ts | 84 +-- .../ingestor/ingestor.component.spec.ts | 15 +- .../ingestor/ingestor/ingestor.component.ts | 297 ++++++----- 17 files changed, 651 insertions(+), 950 deletions(-) delete mode 100644 src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schema_demo.ts diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer.ts index 91085fbaf7..0eeefb6796 100644 --- a/src/app/ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer.ts +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer.ts @@ -1,11 +1,11 @@ -import { Component } from '@angular/core'; -import { JsonFormsControl } from '@jsonforms/angular'; +import { Component, OnInit } from "@angular/core"; +import { JsonFormsControl } from "@jsonforms/angular"; @Component({ - selector: 'AllOfRenderer', - template: `
AllOf Renderer
` + selector: "AllOfRenderer", + template: `
AllOf Renderer
`, }) -export class AllOfRenderer extends JsonFormsControl { +export class AllOfRendererComponent extends JsonFormsControl implements OnInit { data: any[] = []; ngOnInit() { diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts index 530d762196..1df486fff0 100644 --- a/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts @@ -1,70 +1,79 @@ -import { Component } from '@angular/core'; -import { JsonFormsAngularService, JsonFormsControl } from '@jsonforms/angular'; -import { ControlProps, JsonSchema } from '@jsonforms/core'; -import { configuredRenderer } from '../ingestor-metadata-editor-helper'; +import { Component } from "@angular/core"; +import { JsonFormsAngularService, JsonFormsControl } from "@jsonforms/angular"; +import { ControlProps, JsonSchema } from "@jsonforms/core"; +import { configuredRenderer } from "../ingestor-metadata-editor-helper"; @Component({ - selector: 'app-anyof-renderer', - template: ` + selector: "app-anyof-renderer", + template: `
- {{anyOfTitle}} - - - -
- -
+ {{ anyOfTitle }} + + +
+ +
-
+
- ` + `, }) -export class AnyOfRenderer extends JsonFormsControl { +export class AnyOfRendererComponent extends JsonFormsControl { + dataAsString: string; + options: string[] = []; + anyOfTitle: string; + selectedTabIndex = 0; // default value - dataAsString: string; - options: string[] = []; - anyOfTitle: string; - selectedTabIndex: number = 0; // default value + rendererService: JsonFormsAngularService; - rendererService: JsonFormsAngularService; + defaultRenderer = configuredRenderer; + passedProps: ControlProps; - defaultRenderer = configuredRenderer; - passedProps: ControlProps; + constructor(service: JsonFormsAngularService) { + super(service); + this.rendererService = service; + } - constructor(service: JsonFormsAngularService) { - super(service); - this.rendererService = service; - } + public mapAdditionalProps(props: ControlProps) { + this.passedProps = props; + this.anyOfTitle = props.label || "AnyOf"; + this.options = props.schema.anyOf.map( + (option: any) => option.title || option.type || JSON.stringify(option), + ); - public mapAdditionalProps(props: ControlProps) { - this.passedProps = props; - this.anyOfTitle = props.label || 'AnyOf'; - this.options = props.schema.anyOf.map((option: any) => option.title || option.type || JSON.stringify(option)); - - if (this.options.includes("null") && !props.data) { - this.selectedTabIndex = this.options.indexOf("null"); - } + if (this.options.includes("null") && !props.data) { + this.selectedTabIndex = this.options.indexOf("null"); } + } - public getTabSchema(tabOption: string): JsonSchema { - const selectedSchema = (this.passedProps.schema.anyOf as any).find((option: any) => option.title === tabOption || option.type === tabOption || JSON.stringify(option) === tabOption); - return selectedSchema; - } + public getTabSchema(tabOption: string): JsonSchema { + const selectedSchema = (this.passedProps.schema.anyOf as any).find( + (option: any) => + option.title === tabOption || + option.type === tabOption || + JSON.stringify(option) === tabOption, + ); + return selectedSchema; + } - public onInnerJsonFormsChange(event: any) { - // Check if data is equal to the passedProps.data - if (event !== this.passedProps.data) { - const updatedData = this.rendererService.getState().jsonforms.core.data; + public onInnerJsonFormsChange(event: any) { + if (event !== this.passedProps.data) { + const updatedData = this.rendererService.getState().jsonforms.core.data; - // aktualisiere das aktuelle Datenobjekt - const pathSegments = this.passedProps.path.split('.'); - let current = updatedData; - for (let i = 0; i < pathSegments.length - 1; i++) { - current = current[pathSegments[i]]; - } - current[pathSegments[pathSegments.length - 1]] = event; + // Update the data in the correct path + const pathSegments = this.passedProps.path.split("."); + let current = updatedData; + for (let i = 0; i < pathSegments.length - 1; i++) { + current = current[pathSegments[i]]; + } + current[pathSegments[pathSegments.length - 1]] = event; - this.rendererService.setData(updatedData); - } + this.rendererService.setData(updatedData); } -} \ No newline at end of file + } +} diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/custom-renderers.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/custom-renderers.ts index 8b807f113b..67f76f65d9 100644 --- a/src/app/ingestor/ingestor-metadata-editor/customRenderer/custom-renderers.ts +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/custom-renderers.ts @@ -1,20 +1,25 @@ -import { isAllOfControl, isAnyOfControl, isOneOfControl, JsonFormsRendererRegistryEntry } from '@jsonforms/core'; -import { OneOfRenderer } from 'ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer'; -import { AllOfRenderer } from 'ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer'; -import { AnyOfRenderer } from 'ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer'; -import { isRangeControl, RankedTester, rankWith } from '@jsonforms/core'; +import { + isAllOfControl, + isAnyOfControl, + isOneOfControl, + JsonFormsRendererRegistryEntry, +} from "@jsonforms/core"; +import { OneOfRendererComponent } from "ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer"; +import { AllOfRendererComponent } from "ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer"; +import { AnyOfRendererComponent } from "ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer"; +import { rankWith } from "@jsonforms/core"; export const customRenderers: JsonFormsRendererRegistryEntry[] = [ { tester: rankWith(4, isOneOfControl), - renderer: OneOfRenderer + renderer: OneOfRendererComponent, }, { tester: rankWith(4, isAllOfControl), - renderer: AllOfRenderer + renderer: AllOfRendererComponent, }, { tester: rankWith(4, isAnyOfControl), - renderer: AnyOfRenderer - } -]; \ No newline at end of file + renderer: AnyOfRendererComponent, + }, +]; diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts index 42665d5c62..3f863aa3b1 100644 --- a/src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts @@ -1,69 +1,87 @@ -import { Component } from '@angular/core'; -import { JsonFormsAngularService, JsonFormsControl } from '@jsonforms/angular'; -import { ControlProps, JsonSchema } from '@jsonforms/core'; -import { configuredRenderer } from '../ingestor-metadata-editor-helper'; +import { Component } from "@angular/core"; +import { JsonFormsAngularService, JsonFormsControl } from "@jsonforms/angular"; +import { ControlProps, JsonSchema } from "@jsonforms/core"; +import { configuredRenderer } from "../ingestor-metadata-editor-helper"; @Component({ - selector: 'app-oneof-component', - template: ` + selector: "app-oneof-component", + template: `
-

{{anyOfTitle}}

+

{{ anyOfTitle }}

- - {{option}} + + {{ option }} -
- +
+
- ` + `, }) -export class OneOfRenderer extends JsonFormsControl { +export class OneOfRendererComponent extends JsonFormsControl { + dataAsString: string; + options: string[] = []; + anyOfTitle: string; + selectedOption: string; + selectedAnyOption: JsonSchema; - dataAsString: string; - options: string[] = []; - anyOfTitle: string; - selectedOption: string; - selectedAnyOption: JsonSchema; + rendererService: JsonFormsAngularService; - rendererService: JsonFormsAngularService; + defaultRenderer = configuredRenderer; + passedProps: ControlProps; - defaultRenderer = configuredRenderer; - passedProps: ControlProps; + constructor(service: JsonFormsAngularService) { + super(service); + this.rendererService = service; + } - constructor(service: JsonFormsAngularService) { - super(service); - this.rendererService = service; + public mapAdditionalProps(props: ControlProps) { + this.passedProps = props; + this.anyOfTitle = props.label || "AnyOf"; + this.options = props.schema.anyOf.map( + (option: any) => option.title || option.type || JSON.stringify(option), + ); + if (!props.data) { + this.selectedOption = "null"; // Auf "null" setzen, wenn die Daten leer sind } + } - public mapAdditionalProps(props: ControlProps) { - this.passedProps = props; - this.anyOfTitle = props.label || 'AnyOf'; - this.options = props.schema.anyOf.map((option: any) => option.title || option.type || JSON.stringify(option)); - if (!props.data) { - this.selectedOption = 'null'; // Auf "null" setzen, wenn die Daten leer sind - } - } - - public onOptionChange() { - this.selectedAnyOption = (this.passedProps.schema.anyOf as any).find((option: any) => option.title === this.selectedOption || option.type === this.selectedOption || JSON.stringify(option) === this.selectedOption); - } + public onOptionChange() { + this.selectedAnyOption = (this.passedProps.schema.anyOf as any).find( + (option: any) => + option.title === this.selectedOption || + option.type === this.selectedOption || + JSON.stringify(option) === this.selectedOption, + ); + } - public onInnerJsonFormsChange(event: any) { - // Check if data is equal to the passedProps.data - if (event !== this.passedProps.data) { - const updatedData = this.rendererService.getState().jsonforms.core.data; + public onInnerJsonFormsChange(event: any) { + // Check if data is equal to the passedProps.data + if (event !== this.passedProps.data) { + const updatedData = this.rendererService.getState().jsonforms.core.data; - // aktualisiere das aktuelle Datenobjekt - const pathSegments = this.passedProps.path.split('.'); - let current = updatedData; - for (let i = 0; i < pathSegments.length - 1; i++) { - current = current[pathSegments[i]]; - } - current[pathSegments[pathSegments.length - 1]] = event; + // aktualisiere das aktuelle Datenobjekt + const pathSegments = this.passedProps.path.split("."); + let current = updatedData; + for (let i = 0; i < pathSegments.length - 1; i++) { + current = current[pathSegments[i]]; + } + current[pathSegments[pathSegments.length - 1]] = event; - this.rendererService.setData(updatedData); - } + this.rendererService.setData(updatedData); } -} \ No newline at end of file + } +} diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts index 65c4d640d3..5eb5a4964f 100644 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts @@ -14,19 +14,22 @@ export class IngestorMetadataEditorHelper { } if (schema.$ref) { - const refPath = schema.$ref.replace('#/', '').split('/'); + const refPath = schema.$ref.replace("#/", "").split("/"); let ref = rootSchema; refPath.forEach((part) => { ref = ref[part]; }); return IngestorMetadataEditorHelper.resolveRefs(ref, rootSchema); - } else if (typeof schema === 'object') { + } else if (typeof schema === "object") { for (const key in schema) { - if (schema.hasOwnProperty(key)) { - schema[key] = IngestorMetadataEditorHelper.resolveRefs(schema[key], rootSchema); + if (Object.prototype.hasOwnProperty.call(schema, key)) { + schema[key] = IngestorMetadataEditorHelper.resolveRefs( + schema[key], + rootSchema, + ); } } } return schema; - }; -}; \ No newline at end of file + } +} diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schema_demo.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schema_demo.ts deleted file mode 100644 index 5874d74d42..0000000000 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-schema_demo.ts +++ /dev/null @@ -1,483 +0,0 @@ -export const demo_organizational_schema = { - type: 'object', - properties: { - grants: { - type: 'array', - items: { - type: 'object', - properties: { - grant_name: { - type: 'string', - description: 'name of the grant', - }, - start_date: { - type: 'string', - format: 'date', - description: 'start date', - }, - end_date: { - type: 'string', - format: 'date', - description: 'end date', - }, - budget: { - type: 'number', - description: 'budget', - }, - project_id: { - type: 'string', - description: 'project id', - }, - country: { - type: 'string', - description: 'Country of the institution', - }, - }, - }, - description: 'List of grants associated with the project', - }, - authors: { - type: 'array', - items: { - type: 'object', - properties: { - first_name: { - type: 'string', - description: 'first name', - }, - work_status: { - type: 'boolean', - description: 'work status', - }, - email: { - type: 'string', - description: 'email', - pattern: '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$', - }, - work_phone: { - type: 'string', - description: 'work phone', - }, - name: { - type: 'string', - description: 'name', - }, - name_org: { - type: 'string', - description: 'Name of the organization', - }, - type_org: { - type: 'string', - description: 'Type of organization, academic, commercial, governmental, etc.', - enum: ['Academic', 'Commercial', 'Government', 'Other'], - }, - country: { - type: 'string', - description: 'Country of the institution', - }, - role: { - type: 'string', - description: 'Role of the author, for example principal investigator', - }, - orcid: { - type: 'string', - description: 'ORCID of the author, a type of unique identifier', - }, - }, - }, - description: 'List of authors associated with the project', - }, - funder: { - type: 'array', - items: { - type: 'object', - properties: { - funder_name: { - type: 'string', - description: 'funding organization/person.', - }, - type_org: { - type: 'string', - description: 'Type of organization, academic, commercial, governmental, etc.', - enum: ['Academic', 'Commercial', 'Government', 'Other'], - }, - country: { - type: 'string', - description: 'Country of the institution', - }, - }, - }, - description: 'Description of the project funding', - }, - }, - required: ['authors', 'funder'], -}; - -export const demo_acquisition_schema = { - type: 'object', - properties: { - nominal_defocus: { - type: 'object', - description: 'Target defocus set, min and max values in µm.', - }, - calibrated_defocus: { - type: 'object', - description: 'Machine estimated defocus, min and max values in µm. Has a tendency to be off.', - }, - nominal_magnification: { - type: 'integer', - description: 'Magnification level as indicated by the instrument, no unit', - }, - calibrated_magnification: { - type: 'integer', - description: 'Calculated magnification, no unit', - }, - holder: { - type: 'string', - description: 'Speciman holder model', - }, - holder_cryogen: { - type: 'string', - description: 'Type of cryogen used in the holder - if the holder is cooled seperately', - }, - temperature_range: { - type: 'object', - description: 'Temperature during data collection, in K with min and max values.', - }, - microscope_software: { - type: 'string', - description: 'Software used for instrument control', - }, - detector: { - type: 'string', - description: 'Make and model of the detector used', - }, - detector_mode: { - type: 'string', - description: 'Operating mode of the detector', - }, - dose_per_movie: { - type: 'object', - description: 'Average dose per image/movie/tilt - given in electrons per square Angstrom', - }, - energy_filter: { - type: 'object', - description: 'Whether an energy filter was used and its specifics.', - }, - image_size: { - type: 'object', - description: 'The size of the image in pixels, height and width given.', - }, - date_time: { - type: 'string', - description: 'Time and date of the data acquisition', - }, - exposure_time: { - type: 'object', - description: 'Time of data acquisition per movie/tilt - in s', - }, - cryogen: { - type: 'string', - description: 'Cryogen used in cooling the instrument and sample, usually nitrogen', - }, - frames_per_movie: { - type: 'integer', - description: 'Number of frames that on average constitute a full movie, can be a bit hard to define for some detectors', - }, - grids_imaged: { - type: 'integer', - description: 'Number of grids imaged for this project - here with qualifier during this data acquisition', - }, - images_generated: { - type: 'integer', - description: 'Number of images generated total for this data collection - might need a qualifier for tilt series to determine whether full series or individual tilts are counted', - }, - binning_camera: { - type: 'number', - description: 'Level of binning on the images applied during data collection', - }, - pixel_size: { - type: 'object', - description: 'Pixel size, in Angstrom', - }, - specialist_optics: { - type: 'object', - description: 'Any type of special optics, such as a phaseplate', - }, - beamshift: { - type: 'object', - description: 'Movement of the beam above the sample for data collection purposes that does not require movement of the stage. Given in mrad.', - }, - beamtilt: { - type: 'object', - description: 'Another way to move the beam above the sample for data collection purposes that does not require movement of the stage. Given in mrad.', - }, - imageshift: { - type: 'object', - description: 'Movement of the Beam below the image in order to shift the image on the detector. Given in µm.', - }, - beamtiltgroups: { - type: 'integer', - description: 'Number of Beamtilt groups present in this dataset - for optimized processing split dataset into groups of same tilt angle. Despite its name Beamshift is often used to achieve this result.', - }, - gainref_flip_rotate: { - type: 'string', - description: 'Whether and how you have to flip or rotate the gainref in order to align with your acquired images', - }, - }, - required: ['detector', 'dose_per_movie', 'date_time', 'binning_camera', 'pixel_size'], -}; - -export const demo_sample_schema = { - type: 'object', - properties: { - overall_molecule: { - type: 'object', - description: 'Description of the overall molecule', - properties: { - molecular_type: { - type: 'string', - description: 'Description of the overall molecular type, i.e., a complex', - }, - name_sample: { - type: 'string', - description: 'Name of the full sample', - }, - source: { - type: 'string', - description: 'Where the sample was taken from, i.e., natural host, recombinantly expressed, etc.', - }, - molecular_weight: { - type: 'object', - description: 'Molecular weight in Da', - }, - assembly: { - type: 'string', - description: 'What type of higher order structure your sample forms - if any.', - enum: ['FILAMENT', 'HELICAL ARRAY', 'PARTICLE'], - }, - }, - required: ['molecular_type', 'name_sample', 'source', 'assembly'], - }, - molecule: { - type: 'array', - items: { - type: 'object', - properties: { - name_mol: { - type: 'string', - description: 'Name of an individual molecule (often protein) in the sample', - }, - molecular_type: { - type: 'string', - description: 'Description of the overall molecular type, i.e., a complex', - }, - molecular_class: { - type: 'string', - description: 'Class of the molecule', - enum: ['Antibiotic', 'Carbohydrate', 'Chimera', 'None of these'], - }, - sequence: { - type: 'string', - description: 'Full sequence of the sample as in the data, i.e., cleaved tags should also be removed from sequence here', - }, - natural_source: { - type: 'string', - description: 'Scientific name of the natural host organism', - }, - taxonomy_id_source: { - type: 'string', - description: 'Taxonomy ID of the natural source organism', - }, - expression_system: { - type: 'string', - description: 'Scientific name of the organism used to produce the molecule of interest', - }, - taxonomy_id_expression: { - type: 'string', - description: 'Taxonomy ID of the expression system organism', - }, - gene_name: { - type: 'string', - description: 'Name of the gene of interest', - }, - }, - }, - required: ['name_mol', 'molecular_type', 'molecular_class', 'sequence', 'natural_source', 'taxonomy_id_source', 'expression_system', 'taxonomy_id_expression'], - }, - ligands: { - type: 'array', - items: { - type: 'object', - properties: { - present: { - type: 'boolean', - description: 'Whether the model contains any ligands', - }, - smiles: { - type: 'string', - description: 'Provide a valid SMILES string of your ligand', - }, - reference: { - type: 'string', - description: 'Link to a reference of your ligand, i.e., CCD, PubChem, etc.', - }, - }, - }, - description: 'List of ligands associated with the sample', - }, - specimen: { - type: 'object', - description: 'Description of the specimen', - properties: { - buffer: { - type: 'string', - description: 'Name/composition of the (chemical) sample buffer during grid preparation', - }, - concentration: { - type: 'object', - description: 'Concentration of the (supra)molecule in the sample, in mg/ml', - }, - ph: { - type: 'number', - description: 'pH of the sample buffer', - }, - vitrification: { - type: 'boolean', - description: 'Whether the sample was vitrified', - }, - vitrification_cryogen: { - type: 'string', - description: 'Which cryogen was used for vitrification', - }, - humidity: { - type: 'object', - description: 'Environmental humidity just before vitrification, in %', - }, - temperature: { - type: 'object', - description: 'Environmental temperature just before vitrification, in K', - minimum: 0.0, - }, - staining: { - type: 'boolean', - description: 'Whether the sample was stained', - }, - embedding: { - type: 'boolean', - description: 'Whether the sample was embedded', - }, - shadowing: { - type: 'boolean', - description: 'Whether the sample was shadowed', - }, - }, - required: ['ph', 'vitrification', 'vitrification_cryogen', 'staining', 'embedding', 'shadowing'], - }, - grid: { - type: 'object', - description: 'Description of the grid used', - properties: { - manufacturer: { - type: 'string', - description: 'Grid manufacturer', - }, - material: { - type: 'string', - description: 'Material out of which the grid is made', - }, - mesh: { - type: 'number', - description: 'Grid mesh in lines per inch', - }, - film_support: { - type: 'boolean', - description: 'Whether a support film was used', - }, - film_material: { - type: 'string', - description: 'Type of material the support film is made of', - }, - film_topology: { - type: 'string', - description: 'Topology of the support film', - }, - film_thickness: { - type: 'string', - description: 'Thickness of the support film', - }, - pretreatment_type: { - type: 'string', - description: 'Type of pretreatment of the grid, i.e., glow discharge', - }, - pretreatment_time: { - type: 'object', - description: 'Length of time of the pretreatment in s', - }, - pretreatment_pressure: { - type: 'object', - description: 'Pressure of the chamber during pretreatment, in Pa', - }, - pretreatment_atmosphere: { - type: 'string', - description: 'Atmospheric conditions in the chamber during pretreatment, i.e., addition of specific gases, etc.', - }, - }, - }, - }, - required: ['overall_molecule', 'molecule', 'specimen', 'grid'], -}; - -export const demo_instrument_schema = { - type: 'object', - properties: { - microscope: { - type: 'string', - description: 'Name/Type of the Microscope', - }, - illumination: { - type: 'string', - description: 'Mode of illumination used during data collection', - }, - imaging: { - type: 'string', - description: 'Mode of imaging used during data collection', - }, - electron_source: { - type: 'string', - description: 'Type of electron source used in the microscope, such as FEG', - }, - acceleration_voltage: { - type: 'object', - description: 'Voltage used for the electron acceleration, in kV', - }, - c2_aperture: { - type: 'object', - description: 'C2 aperture size used in data acquisition, in µm', - }, - cs: { - type: 'object', - description: 'Spherical aberration of the instrument, in mm', - }, - }, - required: ['microscope', 'illumination', 'imaging', 'electron_source', 'acceleration_voltage', 'cs'], -}; - -export const demo_scicatheader_schema = { - type: "object", - properties: { - datasetName: { type: "string" }, - description: { type: "string" }, - creationLocation: { type: "string" }, - dataFormat: { type: "string" }, - ownerGroup: { type: "string" }, - type: { type: "string" }, - license: { type: "string" }, - keywords: { - type: "array", - items: { type: "string" } - }, - scientificMetadata: { type: "string" } - }, - required: ["datasetName", "description", "creationLocation", "dataFormat", "ownerGroup", "type", "license", "keywords", "scientificMetadata"] -} \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts index 308a64143b..d4bf2a72e1 100644 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts @@ -1,19 +1,18 @@ -import { Component, EventEmitter, Output, Input } from '@angular/core'; -import { JsonSchema } from '@jsonforms/core'; -import { configuredRenderer } from './ingestor-metadata-editor-helper'; +import { Component, EventEmitter, Output, Input } from "@angular/core"; +import { JsonSchema } from "@jsonforms/core"; +import { configuredRenderer } from "./ingestor-metadata-editor-helper"; @Component({ - selector: 'app-metadata-editor', + selector: "app-metadata-editor", template: ``, + [data]="data" + [schema]="schema" + [renderers]="combinedRenderers" + (dataChange)="onDataChange($event)" + >`, }) - export class IngestorMetadataEditorComponent { - @Input() data: Object; + @Input() data: object; @Input() schema: JsonSchema; @Output() dataChange = new EventEmitter(); @@ -25,4 +24,4 @@ export class IngestorMetadataEditorComponent { onDataChange(event: any) { this.dataChange.emit(event); } -} \ No newline at end of file +} diff --git a/src/app/ingestor/ingestor.module.ts b/src/app/ingestor/ingestor.module.ts index eb79e0e644..1dfd044bf2 100644 --- a/src/app/ingestor/ingestor.module.ts +++ b/src/app/ingestor/ingestor.module.ts @@ -9,8 +9,8 @@ import { MatFormFieldModule } from "@angular/material/form-field"; import { MatInputModule } from "@angular/material/input"; import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; import { FormsModule } from "@angular/forms"; -import { MatListModule } from '@angular/material/list'; -import { MatIconModule } from '@angular/material/icon'; +import { MatListModule } from "@angular/material/list"; +import { MatIconModule } from "@angular/material/icon"; import { MatTabsModule } from "@angular/material/tabs"; import { MatTableModule } from "@angular/material/table"; import { MatDialogModule } from "@angular/material/dialog"; @@ -18,15 +18,15 @@ import { MatSelectModule } from "@angular/material/select"; import { MatOptionModule } from "@angular/material/core"; import { MatAutocompleteModule } from "@angular/material/autocomplete"; import { IngestorNewTransferDialogComponent } from "./ingestor/dialog/ingestor.new-transfer-dialog.component"; -import { IngestorUserMetadataDialog } from "./ingestor/dialog/ingestor.user-metadata-dialog.component"; -import { JsonFormsModule } from '@jsonforms/angular'; +import { IngestorUserMetadataDialogComponent } from "./ingestor/dialog/ingestor.user-metadata-dialog.component"; +import { JsonFormsModule } from "@jsonforms/angular"; import { JsonFormsAngularMaterialModule } from "@jsonforms/angular-material"; -import { IngestorExtractorMetadataDialog } from "./ingestor/dialog/ingestor.extractor-metadata-dialog.component"; -import { IngestorConfirmTransferDialog } from "./ingestor/dialog/ingestor.confirm-transfer-dialog.component"; +import { IngestorExtractorMetadataDialogComponent } from "./ingestor/dialog/ingestor.extractor-metadata-dialog.component"; +import { IngestorConfirmTransferDialogComponent } from "./ingestor/dialog/ingestor.confirm-transfer-dialog.component"; import { MatStepperModule } from "@angular/material/stepper"; import { IngestorDialogStepperComponent } from "./ingestor/dialog/ingestor.dialog-stepper.component.component"; -import { AnyOfRenderer } from "./ingestor-metadata-editor/customRenderer/any-of-renderer"; -import { OneOfRenderer } from "./ingestor-metadata-editor/customRenderer/one-of-renderer"; +import { AnyOfRendererComponent } from "./ingestor-metadata-editor/customRenderer/any-of-renderer"; +import { OneOfRendererComponent } from "./ingestor-metadata-editor/customRenderer/one-of-renderer"; import { MatRadioModule } from "@angular/material/radio"; @NgModule({ @@ -34,21 +34,21 @@ import { MatRadioModule } from "@angular/material/radio"; IngestorComponent, IngestorMetadataEditorComponent, IngestorNewTransferDialogComponent, - IngestorUserMetadataDialog, - IngestorExtractorMetadataDialog, - IngestorConfirmTransferDialog, + IngestorUserMetadataDialogComponent, + IngestorExtractorMetadataDialogComponent, + IngestorConfirmTransferDialogComponent, IngestorDialogStepperComponent, - AnyOfRenderer, - OneOfRenderer, + AnyOfRendererComponent, + OneOfRendererComponent, ], imports: [ - CommonModule, - MatCardModule, - FormsModule, - MatFormFieldModule, - MatInputModule, - MatButtonModule, - MatProgressSpinnerModule, + CommonModule, + MatCardModule, + FormsModule, + MatFormFieldModule, + MatInputModule, + MatButtonModule, + MatProgressSpinnerModule, RouterModule, MatListModule, MatIconModule, @@ -64,4 +64,4 @@ import { MatRadioModule } from "@angular/material/radio"; JsonFormsAngularMaterialModule, ], }) -export class IngestorModule { } +export class IngestorModule {} diff --git a/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts index 52285fca6b..36b22feb0d 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts @@ -1,20 +1,33 @@ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; -import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { IDialogDataObject, IIngestionRequestInformation, IngestorHelper, ISciCatHeader } from '../ingestor.component-helper'; +import { + ChangeDetectionStrategy, + Component, + Inject, + OnInit, +} from "@angular/core"; +import { MatDialog, MAT_DIALOG_DATA } from "@angular/material/dialog"; +import { + DialogDataObject, + IngestionRequestInformation, + IngestorHelper, + SciCatHeader, +} from "../ingestor.component-helper"; @Component({ - selector: 'ingestor.confirm-transfer-dialog', - templateUrl: 'ingestor.confirm-transfer-dialog.html', + selector: "ingestor.confirm-transfer-dialog", + templateUrl: "ingestor.confirm-transfer-dialog.html", changeDetection: ChangeDetectionStrategy.OnPush, - styleUrls: ['../ingestor.component.scss'], + styleUrls: ["../ingestor.component.scss"], }) +export class IngestorConfirmTransferDialogComponent implements OnInit { + createNewTransferData: IngestionRequestInformation = + IngestorHelper.createEmptyRequestInformation(); + provideMergeMetaData = ""; + backendURL = ""; -export class IngestorConfirmTransferDialog { - createNewTransferData: IIngestionRequestInformation = IngestorHelper.createEmptyRequestInformation(); - provideMergeMetaData: string = ''; - backendURL: string = ''; - - constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: IDialogDataObject) { + constructor( + public dialog: MatDialog, + @Inject(MAT_DIALOG_DATA) public data: DialogDataObject, + ) { this.createNewTransferData = data.createNewTransferData; this.backendURL = data.backendURL; } @@ -25,21 +38,24 @@ export class IngestorConfirmTransferDialog { createMetaDataString(): string { const space = 2; - const scicatMetadata: ISciCatHeader = { - datasetName: this.createNewTransferData.scicatHeader['datasetName'], - description: this.createNewTransferData.scicatHeader['description'], - creationLocation: this.createNewTransferData.scicatHeader['creationLocation'], - dataFormat: this.createNewTransferData.scicatHeader['dataFormat'], - ownerGroup: this.createNewTransferData.scicatHeader['ownerGroup'], - type: this.createNewTransferData.scicatHeader['type'], - license: this.createNewTransferData.scicatHeader['license'], - keywords: this.createNewTransferData.scicatHeader['keywords'], - filePath: this.createNewTransferData.scicatHeader['filePath'], + const scicatMetadata: SciCatHeader = { + datasetName: this.createNewTransferData.scicatHeader["datasetName"], + description: this.createNewTransferData.scicatHeader["description"], + creationLocation: + this.createNewTransferData.scicatHeader["creationLocation"], + dataFormat: this.createNewTransferData.scicatHeader["dataFormat"], + ownerGroup: this.createNewTransferData.scicatHeader["ownerGroup"], + type: this.createNewTransferData.scicatHeader["type"], + license: this.createNewTransferData.scicatHeader["license"], + keywords: this.createNewTransferData.scicatHeader["keywords"], + filePath: this.createNewTransferData.scicatHeader["filePath"], scientificMetadata: { - organizational: this.createNewTransferData.userMetaData['organizational'], - sample: this.createNewTransferData.userMetaData['sample'], - acquisition: this.createNewTransferData.extractorMetaData['acquisition'], - instrument: this.createNewTransferData.extractorMetaData['instrument'], + organizational: + this.createNewTransferData.userMetaData["organizational"], + sample: this.createNewTransferData.userMetaData["sample"], + acquisition: + this.createNewTransferData.extractorMetaData["acquisition"], + instrument: this.createNewTransferData.extractorMetaData["instrument"], }, }; @@ -54,8 +70,9 @@ export class IngestorConfirmTransferDialog { onClickConfirm(): void { if (this.data && this.data.onClickNext) { - this.createNewTransferData.mergedMetaDataString = this.provideMergeMetaData; + this.createNewTransferData.mergedMetaDataString = + this.provideMergeMetaData; this.data.onClickNext(4); } } -} \ No newline at end of file +} diff --git a/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.component.ts index cda2927a18..1531c5404f 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.component.ts @@ -1,10 +1,10 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input } from "@angular/core"; @Component({ - selector: 'ingestor-dialog-stepper', - templateUrl: './ingestor.dialog-stepper.component.html', - styleUrls: ['./ingestor.dialog-stepper.component.css'] + selector: "ingestor-dialog-stepper", + templateUrl: "./ingestor.dialog-stepper.component.html", + styleUrls: ["./ingestor.dialog-stepper.component.css"], }) export class IngestorDialogStepperComponent { - @Input() activeStep: number = 0; -} \ No newline at end of file + @Input() activeStep = 0; +} diff --git a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts index 97ba8e811d..9fa86aa138 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts @@ -1,39 +1,52 @@ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; -import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { JsonSchema } from '@jsonforms/core'; -import { IDialogDataObject, IIngestionRequestInformation, IngestorHelper } from '../ingestor.component-helper'; +import { ChangeDetectionStrategy, Component, Inject } from "@angular/core"; +import { MatDialog, MAT_DIALOG_DATA } from "@angular/material/dialog"; +import { JsonSchema } from "@jsonforms/core"; +import { + DialogDataObject, + IngestionRequestInformation, + IngestorHelper, +} from "../ingestor.component-helper"; @Component({ - selector: 'ingestor.extractor-metadata-dialog', - templateUrl: 'ingestor.extractor-metadata-dialog.html', - styleUrls: ['../ingestor.component.scss'], + selector: "ingestor.extractor-metadata-dialog", + templateUrl: "ingestor.extractor-metadata-dialog.html", + styleUrls: ["../ingestor.component.scss"], changeDetection: ChangeDetectionStrategy.OnPush, }) - -export class IngestorExtractorMetadataDialog { +export class IngestorExtractorMetadataDialogComponent { metadataSchemaInstrument: JsonSchema; metadataSchemaAcquisition: JsonSchema; - createNewTransferData: IIngestionRequestInformation = IngestorHelper.createEmptyRequestInformation(); + createNewTransferData: IngestionRequestInformation = + IngestorHelper.createEmptyRequestInformation(); - backendURL: string = ''; - extractorMetaDataReady: boolean = false; - extractorMetaDataError: boolean = false; + backendURL = ""; + extractorMetaDataReady = false; + extractorMetaDataError = false; isCardContentVisible = { instrument: true, - acquisition: true + acquisition: true, }; - constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: IDialogDataObject) { - this.createNewTransferData = data.createNewTransferData; - this.backendURL = data.backendURL; - const instrumentSchema = this.createNewTransferData.selectedResolvedDecodedSchema.properties.instrument; - const acqusitionSchema = this.createNewTransferData.selectedResolvedDecodedSchema.properties.acquisition; - - this.metadataSchemaInstrument = instrumentSchema; - this.metadataSchemaAcquisition = acqusitionSchema; - this.extractorMetaDataReady = this.createNewTransferData.extractorMetaDataReady; - this.extractorMetaDataError = this.createNewTransferData.apiErrorInformation.metaDataExtraction; + constructor( + public dialog: MatDialog, + @Inject(MAT_DIALOG_DATA) public data: DialogDataObject, + ) { + this.createNewTransferData = data.createNewTransferData; + this.backendURL = data.backendURL; + const instrumentSchema = + this.createNewTransferData.selectedResolvedDecodedSchema.properties + .instrument; + const acqusitionSchema = + this.createNewTransferData.selectedResolvedDecodedSchema.properties + .acquisition; + + this.metadataSchemaInstrument = instrumentSchema; + this.metadataSchemaAcquisition = acqusitionSchema; + this.extractorMetaDataReady = + this.createNewTransferData.extractorMetaDataReady; + this.extractorMetaDataError = + this.createNewTransferData.apiErrorInformation.metaDataExtraction; } onClickBack(): void { @@ -48,15 +61,15 @@ export class IngestorExtractorMetadataDialog { } } - onDataChangeExtractorMetadataInstrument(event: Object) { - this.createNewTransferData.extractorMetaData['instrument'] = event; + onDataChangeExtractorMetadataInstrument(event: any) { + this.createNewTransferData.extractorMetaData["instrument"] = event; } - onDataChangeExtractorMetadataAcquisition(event: Object) { - this.createNewTransferData.extractorMetaData['acquisition'] = event; + onDataChangeExtractorMetadataAcquisition(event: any) { + this.createNewTransferData.extractorMetaData["acquisition"] = event; } toggleCardContent(card: string): void { this.isCardContentVisible[card] = !this.isCardContentVisible[card]; } -} \ No newline at end of file +} diff --git a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts index 198b11fdfd..ba4cae3bef 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts @@ -1,29 +1,43 @@ -import { ChangeDetectionStrategy, Component, Inject, OnInit } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; -import { HttpClient } from '@angular/common/http'; -import { INGESTOR_API_ENDPOINTS_V1 } from '../ingestor-api-endpoints'; -import { IDialogDataObject, IExtractionMethod, IIngestionRequestInformation, IngestorHelper } from '../ingestor.component-helper'; -import { IngestorMetadataEditorHelper } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; +import { + ChangeDetectionStrategy, + Component, + Inject, + OnInit, +} from "@angular/core"; +import { MAT_DIALOG_DATA, MatDialog } from "@angular/material/dialog"; +import { HttpClient } from "@angular/common/http"; +import { INGESTOR_API_ENDPOINTS_V1 } from "../ingestor-api-endpoints"; +import { + DialogDataObject, + ExtractionMethod, + IngestionRequestInformation, + IngestorHelper, +} from "../ingestor.component-helper"; +import { IngestorMetadataEditorHelper } from "ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper"; @Component({ - selector: 'ingestor.new-transfer-dialog', - templateUrl: 'ingestor.new-transfer-dialog.html', + selector: "ingestor.new-transfer-dialog", + templateUrl: "ingestor.new-transfer-dialog.html", changeDetection: ChangeDetectionStrategy.OnPush, - styleUrls: ['../ingestor.component.scss'] + styleUrls: ["../ingestor.component.scss"], }) - export class IngestorNewTransferDialogComponent implements OnInit { - extractionMethods: IExtractionMethod[] = []; + extractionMethods: ExtractionMethod[] = []; availableFilePaths: string[] = []; - backendURL: string = ''; - extractionMethodsError: string = ''; - availableFilePathsError: string = ''; + backendURL = ""; + extractionMethodsError = ""; + availableFilePathsError = ""; - uiNextButtonReady: boolean = false; + uiNextButtonReady = false; - createNewTransferData: IIngestionRequestInformation = IngestorHelper.createEmptyRequestInformation(); + createNewTransferData: IngestionRequestInformation = + IngestorHelper.createEmptyRequestInformation(); - constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: IDialogDataObject, private http: HttpClient) { + constructor( + public dialog: MatDialog, + @Inject(MAT_DIALOG_DATA) public data: DialogDataObject, + private http: HttpClient, + ) { this.createNewTransferData = data.createNewTransferData; this.backendURL = data.backendURL; } @@ -42,66 +56,75 @@ export class IngestorNewTransferDialogComponent implements OnInit { return this.createNewTransferData.selectedPath; } - set selectedMethod(value: IExtractionMethod) { + set selectedMethod(value: ExtractionMethod) { this.createNewTransferData.selectedMethod = value; this.validateNextButton(); } - get selectedMethod(): IExtractionMethod { + get selectedMethod(): ExtractionMethod { return this.createNewTransferData.selectedMethod; } apiGetExtractionMethods(): void { - this.http.get(this.backendURL + INGESTOR_API_ENDPOINTS_V1.EXTRACTOR).subscribe( - (response: any) => { - if (response.methods && response.methods.length > 0) { - this.extractionMethods = response.methods; - } - else { - this.extractionMethodsError = 'No extraction methods found.'; - } - }, - (error: any) => { - this.extractionMethodsError = error.message; - console.error(this.extractionMethodsError); - } - ); + this.http + .get(this.backendURL + INGESTOR_API_ENDPOINTS_V1.EXTRACTOR) + .subscribe( + (response: any) => { + if (response.methods && response.methods.length > 0) { + this.extractionMethods = response.methods; + } else { + this.extractionMethodsError = "No extraction methods found."; + } + }, + (error: any) => { + this.extractionMethodsError = error.message; + console.error(this.extractionMethodsError); + }, + ); } apiGetAvailableFilePaths(): void { - this.http.get(this.backendURL + INGESTOR_API_ENDPOINTS_V1.DATASET).subscribe( - (response: any) => { - if (response.datasets && response.datasets.length > 0) { - this.availableFilePaths = response.datasets; - } - else { - this.availableFilePathsError = 'No datasets found.'; - } - }, - (error: any) => { - this.availableFilePathsError = error.message; - console.error(this.availableFilePathsError); - } - ); + this.http + .get(this.backendURL + INGESTOR_API_ENDPOINTS_V1.DATASET) + .subscribe( + (response: any) => { + if (response.datasets && response.datasets.length > 0) { + this.availableFilePaths = response.datasets; + } else { + this.availableFilePathsError = "No datasets found."; + } + }, + (error: any) => { + this.availableFilePathsError = error.message; + console.error(this.availableFilePathsError); + }, + ); } generateExampleDataForSciCatHeader(): void { - this.data.createNewTransferData.scicatHeader['filePath'] = this.createNewTransferData.selectedPath; - this.data.createNewTransferData.scicatHeader['keywords'] = ['OpenEM']; - - const nameWithoutPath = this.createNewTransferData.selectedPath.split('/|\\')[-1] ?? this.createNewTransferData.selectedPath; - this.data.createNewTransferData.scicatHeader['datasetName'] = nameWithoutPath; - this.data.createNewTransferData.scicatHeader['license'] = 'MIT License'; - this.data.createNewTransferData.scicatHeader['type'] = 'raw'; - this.data.createNewTransferData.scicatHeader['dataFormat'] = 'root'; - this.data.createNewTransferData.scicatHeader['owner'] = 'User'; + this.data.createNewTransferData.scicatHeader["filePath"] = + this.createNewTransferData.selectedPath; + this.data.createNewTransferData.scicatHeader["keywords"] = ["OpenEM"]; + + const nameWithoutPath = + this.createNewTransferData.selectedPath.split("/|\\")[-1] ?? + this.createNewTransferData.selectedPath; + this.data.createNewTransferData.scicatHeader["datasetName"] = + nameWithoutPath; + this.data.createNewTransferData.scicatHeader["license"] = "MIT License"; + this.data.createNewTransferData.scicatHeader["type"] = "raw"; + this.data.createNewTransferData.scicatHeader["dataFormat"] = "root"; + this.data.createNewTransferData.scicatHeader["owner"] = "User"; } prepareSchemaForProcessing(): void { const encodedSchema = this.createNewTransferData.selectedMethod.schema; const decodedSchema = atob(encodedSchema); const schema = JSON.parse(decodedSchema); - const resolvedSchema = IngestorMetadataEditorHelper.resolveRefs(schema, schema); + const resolvedSchema = IngestorMetadataEditorHelper.resolveRefs( + schema, + schema, + ); this.createNewTransferData.selectedResolvedDecodedSchema = resolvedSchema; } @@ -119,6 +142,8 @@ export class IngestorNewTransferDialogComponent implements OnInit { } validateNextButton(): void { - this.uiNextButtonReady = !!this.createNewTransferData.selectedPath && !!this.createNewTransferData.selectedMethod?.name; + this.uiNextButtonReady = + !!this.createNewTransferData.selectedPath && + !!this.createNewTransferData.selectedMethod?.name; } -} \ No newline at end of file +} diff --git a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts index 427bacd801..896033c8a0 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts @@ -1,37 +1,48 @@ -import {ChangeDetectionStrategy, Component, Inject} from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; -import { IngestorMetadataEditorHelper } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; -import { JsonSchema } from '@jsonforms/core'; -import { IDialogDataObject, IIngestionRequestInformation, IngestorHelper, SciCatHeader_Schema } from '../ingestor.component-helper'; +import { ChangeDetectionStrategy, Component, Inject } from "@angular/core"; +import { MAT_DIALOG_DATA, MatDialog } from "@angular/material/dialog"; +import { JsonSchema } from "@jsonforms/core"; +import { + DialogDataObject, + IngestionRequestInformation, + IngestorHelper, + SciCatHeader_Schema, +} from "../ingestor.component-helper"; @Component({ - selector: 'ingestor.user-metadata-dialog', - templateUrl: 'ingestor.user-metadata-dialog.html', + selector: "ingestor.user-metadata-dialog", + templateUrl: "ingestor.user-metadata-dialog.html", changeDetection: ChangeDetectionStrategy.OnPush, - styleUrls: ['../ingestor.component.scss'], + styleUrls: ["../ingestor.component.scss"], }) - -export class IngestorUserMetadataDialog { +export class IngestorUserMetadataDialogComponent { metadataSchemaOrganizational: JsonSchema; metadataSchemaSample: JsonSchema; scicatHeaderSchema: JsonSchema; - createNewTransferData: IIngestionRequestInformation = IngestorHelper.createEmptyRequestInformation(); - backendURL: string = ''; + createNewTransferData: IngestionRequestInformation = + IngestorHelper.createEmptyRequestInformation(); + backendURL = ""; - uiNextButtonReady: boolean = true; // Change to false when dev is ready + uiNextButtonReady = true; // Change to false when dev is ready isCardContentVisible = { scicat: true, organizational: true, - sample: true + sample: true, }; - constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: IDialogDataObject) { + constructor( + public dialog: MatDialog, + @Inject(MAT_DIALOG_DATA) public data: DialogDataObject, + ) { this.createNewTransferData = data.createNewTransferData; this.backendURL = data.backendURL; - const organizationalSchema = this.createNewTransferData.selectedResolvedDecodedSchema.properties.organizational; - const sampleSchema = this.createNewTransferData.selectedResolvedDecodedSchema.properties.sample; - + const organizationalSchema = + this.createNewTransferData.selectedResolvedDecodedSchema.properties + .organizational; + const sampleSchema = + this.createNewTransferData.selectedResolvedDecodedSchema.properties + .sample; + this.metadataSchemaOrganizational = organizationalSchema; this.metadataSchemaSample = sampleSchema; this.scicatHeaderSchema = SciCatHeader_Schema; @@ -49,19 +60,19 @@ export class IngestorUserMetadataDialog { } } - onDataChangeUserMetadataOrganization(event: Object) { - this.createNewTransferData.userMetaData['organizational'] = event; + onDataChangeUserMetadataOrganization(event: any) { + this.createNewTransferData.userMetaData["organizational"] = event; } - onDataChangeUserMetadataSample(event: Object) { - this.createNewTransferData.userMetaData['sample'] = event; + onDataChangeUserMetadataSample(event: any) { + this.createNewTransferData.userMetaData["sample"] = event; } - onDataChangeUserScicatHeader(event: Object) { + onDataChangeUserScicatHeader(event: any) { this.createNewTransferData.scicatHeader = event; } toggleCardContent(card: string): void { this.isCardContentVisible[card] = !this.isCardContentVisible[card]; } -} \ No newline at end of file +} diff --git a/src/app/ingestor/ingestor/ingestor-api-endpoints.ts b/src/app/ingestor/ingestor/ingestor-api-endpoints.ts index df2d088330..acd08c0177 100644 --- a/src/app/ingestor/ingestor/ingestor-api-endpoints.ts +++ b/src/app/ingestor/ingestor/ingestor-api-endpoints.ts @@ -2,16 +2,16 @@ export const INGESTOR_API_ENDPOINTS_V1 = { DATASET: "dataset", TRANSFER: "transfer", OTHER: { - VERSION: 'version', + VERSION: "version", }, - EXTRACTOR: 'extractor', + EXTRACTOR: "extractor", }; -export interface IPostExtractorEndpoint { - filePath: string, - methodName: string, +export interface PostExtractorEndpoint { + filePath: string; + methodName: string; } -export interface IPostDatasetEndpoint { - metaData: string -} \ No newline at end of file +export interface PostDatasetEndpoint { + metaData: string; +} diff --git a/src/app/ingestor/ingestor/ingestor.component-helper.ts b/src/app/ingestor/ingestor/ingestor.component-helper.ts index 1d31525d57..8191b9069b 100644 --- a/src/app/ingestor/ingestor/ingestor.component-helper.ts +++ b/src/app/ingestor/ingestor/ingestor.component-helper.ts @@ -1,22 +1,22 @@ -import { JsonSchema } from '@jsonforms/core'; +import { JsonSchema } from "@jsonforms/core"; -export interface IExtractionMethod { +export interface ExtractionMethod { name: string; schema: string; // Base64 encoded JSON schema -}; +} -export interface IIngestionRequestInformation { +export interface IngestionRequestInformation { selectedPath: string; - selectedMethod: IExtractionMethod; + selectedMethod: ExtractionMethod; selectedResolvedDecodedSchema: JsonSchema; - scicatHeader: Object; + scicatHeader: object; userMetaData: { - organizational: Object, - sample: Object, + organizational: object; + sample: object; }; extractorMetaData: { - instrument: Object, - acquisition: Object, + instrument: object; + acquisition: object; }; extractorMetaDataReady: boolean; extractMetaDataRequested: boolean; @@ -24,11 +24,16 @@ export interface IIngestionRequestInformation { apiErrorInformation: { metaDataExtraction: boolean; - } + }; +} + +export interface TransferDataListEntry { + transferId: string; + status: string; } // There are many more... see DerivedDataset.ts -export interface ISciCatHeader { +export interface SciCatHeader { datasetName: string; description: string; creationLocation: string; @@ -38,27 +43,27 @@ export interface ISciCatHeader { license: string; keywords: string[]; filePath: string; - scientificMetadata: IScientificMetadata; + scientificMetadata: ScientificMetadata; } -export interface IScientificMetadata { - organizational: Object; - sample: Object; - acquisition: Object; - instrument: Object; +export interface ScientificMetadata { + organizational: object; + sample: object; + acquisition: object; + instrument: object; } -export interface IDialogDataObject { - createNewTransferData: IIngestionRequestInformation; +export interface DialogDataObject { + createNewTransferData: IngestionRequestInformation; backendURL: string; onClickNext: (step: number) => void; } export class IngestorHelper { - static createEmptyRequestInformation = (): IIngestionRequestInformation => { + static createEmptyRequestInformation = (): IngestionRequestInformation => { return { - selectedPath: '', - selectedMethod: { name: '', schema: '' }, + selectedPath: "", + selectedMethod: { name: "", schema: "" }, selectedResolvedDecodedSchema: {}, scicatHeader: {}, userMetaData: { @@ -71,18 +76,27 @@ export class IngestorHelper { }, extractorMetaDataReady: false, extractMetaDataRequested: false, - mergedMetaDataString: '', + mergedMetaDataString: "", apiErrorInformation: { metaDataExtraction: false, }, }; }; - static mergeUserAndExtractorMetadata(userMetadata: Object, extractorMetadata: Object, space: number): string { - return JSON.stringify({ ...userMetadata, ...extractorMetadata }, null, space); - }; + static mergeUserAndExtractorMetadata( + userMetadata: object, + extractorMetadata: object, + space: number, + ): string { + return JSON.stringify( + { ...userMetadata, ...extractorMetadata }, + null, + space, + ); + } } +// eslint-disable-next-line @typescript-eslint/naming-convention export const SciCatHeader_Schema: JsonSchema = { type: "object", properties: { @@ -96,9 +110,19 @@ export const SciCatHeader_Schema: JsonSchema = { license: { type: "string" }, keywords: { type: "array", - items: { type: "string" } + items: { type: "string" }, }, // scientificMetadata: { type: "string" } ; is created during the ingestor process }, - required: ["datasetName", "creationLocation", "dataFormat", "ownerGroup", "type", "license", "keywords", "scientificMetadata", "filePath"] -} \ No newline at end of file + required: [ + "datasetName", + "creationLocation", + "dataFormat", + "ownerGroup", + "type", + "license", + "keywords", + "scientificMetadata", + "filePath", + ], +}; diff --git a/src/app/ingestor/ingestor/ingestor.component.spec.ts b/src/app/ingestor/ingestor/ingestor.component.spec.ts index 52186b1077..dd70900334 100644 --- a/src/app/ingestor/ingestor/ingestor.component.spec.ts +++ b/src/app/ingestor/ingestor/ingestor.component.spec.ts @@ -1,15 +1,14 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { IngestorComponent } from './ingestor.component'; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { IngestorComponent } from "./ingestor.component"; -describe('IngestorComponent', () => { +describe("IngestorComponent", () => { let component: IngestorComponent; let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ IngestorComponent ] - }) - .compileComponents(); + declarations: [IngestorComponent], + }).compileComponents(); }); beforeEach(() => { @@ -18,7 +17,7 @@ describe('IngestorComponent', () => { fixture.detectChanges(); }); - it('should create', () => { + it("should create", () => { expect(component).toBeTruthy(); }); -}); \ No newline at end of file +}); diff --git a/src/app/ingestor/ingestor/ingestor.component.ts b/src/app/ingestor/ingestor/ingestor.component.ts index 2b49f2f90a..33210529ec 100644 --- a/src/app/ingestor/ingestor/ingestor.component.ts +++ b/src/app/ingestor/ingestor/ingestor.component.ts @@ -1,19 +1,22 @@ import { Component, inject, OnInit } from "@angular/core"; import { AppConfigService } from "app-config.service"; -import { HttpClient } from '@angular/common/http'; -import { ActivatedRoute, Router } from '@angular/router'; -import { INGESTOR_API_ENDPOINTS_V1, IPostDatasetEndpoint, IPostExtractorEndpoint } from "./ingestor-api-endpoints"; +import { HttpClient } from "@angular/common/http"; +import { ActivatedRoute, Router } from "@angular/router"; +import { + INGESTOR_API_ENDPOINTS_V1, + PostDatasetEndpoint, + PostExtractorEndpoint, +} from "./ingestor-api-endpoints"; import { IngestorNewTransferDialogComponent } from "./dialog/ingestor.new-transfer-dialog.component"; import { MatDialog } from "@angular/material/dialog"; -import { IngestorUserMetadataDialog } from "./dialog/ingestor.user-metadata-dialog.component"; -import { IngestorExtractorMetadataDialog } from "./dialog/ingestor.extractor-metadata-dialog.component"; -import { IngestorConfirmTransferDialog } from "./dialog/ingestor.confirm-transfer-dialog.component"; -import { IIngestionRequestInformation, IngestorHelper } from "./ingestor.component-helper"; - -interface ITransferDataListEntry { - transferId: string; - status: string; -} +import { IngestorUserMetadataDialogComponent } from "./dialog/ingestor.user-metadata-dialog.component"; +import { IngestorExtractorMetadataDialogComponent } from "./dialog/ingestor.extractor-metadata-dialog.component"; +import { IngestorConfirmTransferDialogComponent } from "./dialog/ingestor.confirm-transfer-dialog.component"; +import { + IngestionRequestInformation, + IngestorHelper, + TransferDataListEntry, +} from "./ingestor.component-helper"; @Component({ selector: "ingestor", @@ -23,37 +26,42 @@ interface ITransferDataListEntry { export class IngestorComponent implements OnInit { readonly dialog = inject(MatDialog); - filePath: string = ''; - loading: boolean = false; - forwardFacilityBackend: string = ''; + filePath = ""; + loading = false; + forwardFacilityBackend = ""; - connectedFacilityBackend: string = ''; - connectedFacilityBackendVersion: string = ''; - connectingToFacilityBackend: boolean = false; + connectedFacilityBackend = ""; + connectedFacilityBackendVersion = ""; + connectingToFacilityBackend = false; lastUsedFacilityBackends: string[] = []; - transferDataSource: ITransferDataListEntry[] = []; // List of files to be transferred - displayedColumns: string[] = ['transferId', 'status', 'actions']; + transferDataSource: TransferDataListEntry[] = []; // List of files to be transferred + displayedColumns: string[] = ["transferId", "status", "actions"]; - errorMessage: string = ''; - returnValue: string = ''; + errorMessage = ""; + returnValue = ""; - createNewTransferData: IIngestionRequestInformation = IngestorHelper.createEmptyRequestInformation(); + createNewTransferData: IngestionRequestInformation = + IngestorHelper.createEmptyRequestInformation(); - constructor(public appConfigService: AppConfigService, private http: HttpClient, private route: ActivatedRoute, private router: Router) { } + constructor( + public appConfigService: AppConfigService, + private http: HttpClient, + private route: ActivatedRoute, + private router: Router, + ) {} ngOnInit() { this.connectingToFacilityBackend = true; this.lastUsedFacilityBackends = this.loadLastUsedFacilityBackends(); this.transferDataSource = []; // Get the GET parameter 'backendUrl' from the URL - this.route.queryParams.subscribe(params => { - const backendUrl = params['backendUrl']; + this.route.queryParams.subscribe((params) => { + const backendUrl = params["backendUrl"]; if (backendUrl) { this.apiConnectToFacilityBackend(backendUrl); - } - else { + } else { this.connectingToFacilityBackend = false; } }); @@ -62,35 +70,40 @@ export class IngestorComponent implements OnInit { apiConnectToFacilityBackend(facilityBackendUrl: string): boolean { let facilityBackendUrlCleaned = facilityBackendUrl.slice(); // Check if last symbol is a slash and add version endpoint - if (!facilityBackendUrlCleaned.endsWith('/')) { - facilityBackendUrlCleaned += '/'; + if (!facilityBackendUrlCleaned.endsWith("/")) { + facilityBackendUrlCleaned += "/"; } - let facilityBackendUrlVersion = facilityBackendUrlCleaned + INGESTOR_API_ENDPOINTS_V1.OTHER.VERSION; + const facilityBackendUrlVersion = + facilityBackendUrlCleaned + INGESTOR_API_ENDPOINTS_V1.OTHER.VERSION; // Try to connect to the facility backend/version to check if it is available - console.log('Connecting to facility backend: ' + facilityBackendUrlVersion); + console.log("Connecting to facility backend: " + facilityBackendUrlVersion); this.http.get(facilityBackendUrlVersion).subscribe( - response => { - console.log('Connected to facility backend', response); + (response) => { + console.log("Connected to facility backend", response); // If the connection is successful, store the connected facility backend URL this.connectedFacilityBackend = facilityBackendUrlCleaned; this.connectingToFacilityBackend = false; - this.connectedFacilityBackendVersion = response['version']; + this.connectedFacilityBackendVersion = response["version"]; }, - error => { + (error) => { this.errorMessage += `${new Date().toLocaleString()}: ${error.message}
`; - console.error('Request failed', error); - this.connectedFacilityBackend = ''; + console.error("Request failed", error); + this.connectedFacilityBackend = ""; this.connectingToFacilityBackend = false; this.lastUsedFacilityBackends = this.loadLastUsedFacilityBackends(); - } + }, ); return true; } - apiGetTransferList(page: number, pageSize: number, transferId?: string): void { + apiGetTransferList( + page: number, + pageSize: number, + transferId?: string, + ): void { const params: any = { page: page.toString(), pageSize: pageSize.toString(), @@ -98,72 +111,92 @@ export class IngestorComponent implements OnInit { if (transferId) { params.transferId = transferId; } - this.http.get(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.TRANSFER, { params }).subscribe( - response => { - console.log('Transfer list received', response); - this.transferDataSource = response['transfers']; - }, - error => { - this.errorMessage += `${new Date().toLocaleString()}: ${error.message}]
`; - console.error('Request failed', error); - } - ); + this.http + .get(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.TRANSFER, { + params, + }) + .subscribe( + (response) => { + console.log("Transfer list received", response); + this.transferDataSource = response["transfers"]; + }, + (error) => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}]
`; + console.error("Request failed", error); + }, + ); } apiUpload() { this.loading = true; - const payload: IPostDatasetEndpoint = { + const payload: PostDatasetEndpoint = { metaData: this.createNewTransferData.mergedMetaDataString, }; - this.http.post(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.DATASET, payload).subscribe( - response => { - console.log('Upload successfully started', response); - this.returnValue = JSON.stringify(response); - this.loading = false; - }, - error => { - this.errorMessage += `${new Date().toLocaleString()}: ${error.message}]
`; - console.error('Upload failed', error); - this.loading = false; - } - ); + this.http + .post( + this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.DATASET, + payload, + ) + .subscribe( + (response) => { + console.log("Upload successfully started", response); + this.returnValue = JSON.stringify(response); + this.loading = false; + }, + (error) => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}]
`; + console.error("Upload failed", error); + this.loading = false; + }, + ); } async apiStartMetadataExtraction(): Promise { this.createNewTransferData.apiErrorInformation.metaDataExtraction = false; if (this.createNewTransferData.extractMetaDataRequested) { - console.log(this.createNewTransferData.extractMetaDataRequested, ' already requested'); // Debugging + console.log( + this.createNewTransferData.extractMetaDataRequested, + " already requested", + ); // Debugging return false; } this.createNewTransferData.extractorMetaDataReady = false; this.createNewTransferData.extractMetaDataRequested = true; - const payload: IPostExtractorEndpoint = { + const payload: PostExtractorEndpoint = { filePath: this.createNewTransferData.selectedPath, methodName: this.createNewTransferData.selectedMethod.name, }; return new Promise((resolve) => { - this.http.post(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.EXTRACTOR, payload).subscribe( - response => { - console.log('Metadata extraction result', response); - this.createNewTransferData.extractorMetaData.instrument = (response as any).instrument ?? {}; - this.createNewTransferData.extractorMetaData.acquisition = (response as any).acquisition ?? {}; - this.createNewTransferData.extractorMetaDataReady = true; - resolve(true); - }, - error => { - this.errorMessage += `${new Date().toLocaleString()}: ${error.message}]
`; - console.error('Metadata extraction failed', error); - this.createNewTransferData.extractorMetaDataReady = true; - this.createNewTransferData.apiErrorInformation.metaDataExtraction = true; - resolve(false); - } - ); + this.http + .post( + this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.EXTRACTOR, + payload, + ) + .subscribe( + (response) => { + console.log("Metadata extraction result", response); + this.createNewTransferData.extractorMetaData.instrument = + (response as any).instrument ?? {}; + this.createNewTransferData.extractorMetaData.acquisition = + (response as any).acquisition ?? {}; + this.createNewTransferData.extractorMetaDataReady = true; + resolve(true); + }, + (error) => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}]
`; + console.error("Metadata extraction failed", error); + this.createNewTransferData.extractorMetaDataReady = true; + this.createNewTransferData.apiErrorInformation.metaDataExtraction = + true; + resolve(false); + }, + ); }); } @@ -177,15 +210,17 @@ export class IngestorComponent implements OnInit { return; } - this.router.navigate(['/ingestor'], { queryParams: { backendUrl: this.forwardFacilityBackend } }); + this.router.navigate(["/ingestor"], { + queryParams: { backendUrl: this.forwardFacilityBackend }, + }); } } onClickDisconnectIngestor() { - this.returnValue = ''; - this.connectedFacilityBackend = ''; + this.returnValue = ""; + this.connectedFacilityBackend = ""; // Remove the GET parameter 'backendUrl' from the URL - this.router.navigate(['/ingestor']); + this.router.navigate(["/ingestor"]); } // Helper functions @@ -195,7 +230,8 @@ export class IngestorComponent implements OnInit { loadLastUsedFacilityBackends(): string[] { // Load the list from the local Storage - const lastUsedFacilityBackends = '["http://localhost:8000", "http://localhost:8888"]'; + const lastUsedFacilityBackends = + '["http://localhost:8000", "http://localhost:8888"]'; if (lastUsedFacilityBackends) { return JSON.parse(lastUsedFacilityBackends); } @@ -203,7 +239,7 @@ export class IngestorComponent implements OnInit { } clearErrorMessage(): void { - this.errorMessage = ''; + this.errorMessage = ""; } onClickAddIngestion(): void { @@ -221,41 +257,59 @@ export class IngestorComponent implements OnInit { this.createNewTransferData.extractMetaDataRequested = false; this.createNewTransferData.extractorMetaDataReady = false; dialogRef = this.dialog.open(IngestorNewTransferDialogComponent, { - data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData, backendURL: this.connectedFacilityBackend }, - disableClose: true + data: { + onClickNext: this.onClickNext.bind(this), + createNewTransferData: this.createNewTransferData, + backendURL: this.connectedFacilityBackend, + }, + disableClose: true, }); break; case 1: - this.apiStartMetadataExtraction().then((response: boolean) => { - if (response) console.log('Metadata extraction finished'); - else console.error('Metadata extraction failed'); - }).catch(error => { - console.error('Metadata extraction error', error); - }); - - dialogRef = this.dialog.open(IngestorUserMetadataDialog, { - data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData, backendURL: this.connectedFacilityBackend }, - disableClose: true + this.apiStartMetadataExtraction() + .then((response: boolean) => { + if (response) console.log("Metadata extraction finished"); + else console.error("Metadata extraction failed"); + }) + .catch((error) => { + console.error("Metadata extraction error", error); + }); + + dialogRef = this.dialog.open(IngestorUserMetadataDialogComponent, { + data: { + onClickNext: this.onClickNext.bind(this), + createNewTransferData: this.createNewTransferData, + backendURL: this.connectedFacilityBackend, + }, + disableClose: true, }); break; case 2: - dialogRef = this.dialog.open(IngestorExtractorMetadataDialog, { - data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData, backendURL: this.connectedFacilityBackend }, - disableClose: true + dialogRef = this.dialog.open(IngestorExtractorMetadataDialogComponent, { + data: { + onClickNext: this.onClickNext.bind(this), + createNewTransferData: this.createNewTransferData, + backendURL: this.connectedFacilityBackend, + }, + disableClose: true, }); break; case 3: - dialogRef = this.dialog.open(IngestorConfirmTransferDialog, { - data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData, backendURL: this.connectedFacilityBackend }, - disableClose: true + dialogRef = this.dialog.open(IngestorConfirmTransferDialogComponent, { + data: { + onClickNext: this.onClickNext.bind(this), + createNewTransferData: this.createNewTransferData, + backendURL: this.connectedFacilityBackend, + }, + disableClose: true, }); break; case 4: this.apiUpload(); break; default: - console.error('Unknown step', step); + console.error("Unknown step", step); } // Error if the dialog reference is not set @@ -267,16 +321,23 @@ export class IngestorComponent implements OnInit { } onCancelTransfer(transferId: string) { - console.log('Cancel transfer', transferId); - this.http.delete(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.TRANSFER + '/' + transferId).subscribe( - response => { - console.log('Transfer cancelled', response); - this.apiGetTransferList(1, 100); - }, - error => { - this.errorMessage += `${new Date().toLocaleString()}: ${error.message}
`; - console.error('Cancel transfer failed', error); - } - ); + console.log("Cancel transfer", transferId); + this.http + .delete( + this.connectedFacilityBackend + + INGESTOR_API_ENDPOINTS_V1.TRANSFER + + "/" + + transferId, + ) + .subscribe( + (response) => { + console.log("Transfer cancelled", response); + this.apiGetTransferList(1, 100); + }, + (error) => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}
`; + console.error("Cancel transfer failed", error); + }, + ); } -} \ No newline at end of file +} From 994851f4466917a527b213194a74ec9372bc921d Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Mon, 6 Jan 2025 16:45:55 +0000 Subject: [PATCH 070/245] messages added on onedep deposition creation --- src/app/datasets/datasets.module.ts | 2 +- src/app/datasets/onedep/onedep.component.ts | 101 +++++++------- src/app/datasets/onedep/types/methods.enum.ts | 12 ++ .../sdk/apis/onedep-depositor.service.spec.ts | 12 +- .../sdk/apis/onedep-depositor.service.ts | 30 ++--- src/app/shared/sdk/models/OneDep.ts | 17 +++ .../actions/onedep.actions.ts | 42 +++--- .../effects/onedep.effects.ts | 123 ++++++++++++------ 8 files changed, 210 insertions(+), 129 deletions(-) create mode 100644 src/app/shared/sdk/models/OneDep.ts diff --git a/src/app/datasets/datasets.module.ts b/src/app/datasets/datasets.module.ts index bf79aab65f..34a955f4cd 100644 --- a/src/app/datasets/datasets.module.ts +++ b/src/app/datasets/datasets.module.ts @@ -93,7 +93,7 @@ import { MatSnackBarModule } from "@angular/material/snack-bar"; import { OneDepComponent } from "./onedep/onedep.component"; import { OrcidFormatterDirective } from "./onedep/onedep.directive"; import { EmpiarComponent } from "./empiar/empiar.component"; -import { OneDepEffects } from "./state-management/effects/onedep.effects"; +import { OneDepEffects } from "state-management/effects/onedep.effects"; @NgModule({ diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index 7e98091306..3c0d393bae 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -24,7 +24,14 @@ import { } from "@scicatproject/scicat-sdk-ts"; import { selectCurrentDataset } from "state-management/selectors/datasets.selectors"; import { selectCurrentUser } from "state-management/selectors/user.selectors"; -import { methodsList, EmFile, DepositionFiles } from "./types/methods.enum"; +import * as fromActions from "state-management/actions/onedep.actions"; +import { + methodsList, + EmFile, + DepositionFiles, + OneDepUserInfo, + OneDepCreate, +} from "./types/methods.enum"; import { Depositor } from "shared/sdk/apis/onedep-depositor.service"; import { Subscription, fromEvent } from "rxjs"; @@ -113,7 +120,6 @@ export class OneDepComponent implements OnInit, OnDestroy { }), ]), }); - console.log(this.form); } ngOnDestroy() { @@ -367,73 +373,76 @@ export class OneDepComponent implements OnInit, OnDestroy { } onDepositClick() { // Create a deposition - let body: string; + let body: OneDepUserInfo; if (this.form.value.password) { - body = JSON.stringify({ + body = { email: this.form.value.email, orcidIds: this.orcidArray().value.map((item) => item.orcidId), country: "United States", method: this.form.value.emMethod, jwtToken: this.form.value.jwtToken, password: this.form.value.password, - }); + }; } else { - body = JSON.stringify({ + body = { email: this.form.value.email, orcidIds: this.orcidArray().value.map((item) => item.orcidId), country: "United States", method: this.form.value.emMethod, jwtToken: this.form.value.jwtToken, - }); + }; } let depID: string; let metadataAdded = false; - interface OneDepCreate { - depID: string; - } + this.store.dispatch( + fromActions.createDepositionAction({ + deposition: body as OneDepUserInfo, + }), + ); + this.depositor.createDep(body).subscribe({ next: (response: OneDepCreate) => { - // depID = response.depID; - console.log("Created deposition in OneDep", response); + depID = response.depID; - // Call subsequent requests - this.fileTypes.forEach((fT) => { - if (fT.file) { - const formDataFile = new FormData(); - formDataFile.append("jwtToken", this.form.value.jwtToken); - formDataFile.append("file", fT.file); - if (fT.emName === this.emFile.Coordinates) { - formDataFile.append( - "scientificMetadata", - JSON.stringify(this.form.value.metadata), - ); - this.sendCoordFile(depID, formDataFile); - metadataAdded = true; - } else { - formDataFile.append( - "fileMetadata", - JSON.stringify({ - name: fT.fileName, - type: fT.type, - contour: fT.contour, - details: fT.details, - }), - ); - this.sendFile(depID, formDataFile, fT.type); - } - } - }); - if (!metadataAdded) { + // Call subsequent requests + this.fileTypes.forEach((fT) => { + if (fT.file) { const formDataFile = new FormData(); - formDataFile.append("jwtToken", this.form.value.jwtToken); - formDataFile.append( - "scientificMetadata", - JSON.stringify(this.form.value.metadata), - ); - this.sendMetadata(depID, formDataFile); + formDataFile.append("file", fT.file); + if (fT.emName === this.emFile.Coordinates) { + formDataFile.append( + "scientificMetadata", + JSON.stringify(this.form.value.metadata), + ); + this.sendCoordFile(depID, formDataFile); + metadataAdded = true; + } else { + formDataFile.append( + "fileMetadata", + JSON.stringify({ + name: fT.fileName, + type: fT.type, + contour: fT.contour, + details: fT.details, + }), + ); + // log that into message fT.type + this.depositor.sendFile(depID, formDataFile); + } } + }); + // if (!metadataAdded) { + // const formDataFile = new FormData(); + + // formDataFile.append("jwtToken", this.form.value.jwtToken); + // formDataFile.append( + // "scientificMetadata", + // JSON.stringify(this.form.value.metadata), + // ); + // this.sendMetadata(depID, formDataFile); + // } }, error: (error) => console.error("Request failed", error.error), }); diff --git a/src/app/datasets/onedep/types/methods.enum.ts b/src/app/datasets/onedep/types/methods.enum.ts index f2c8b8bdfa..fce5bc7ee6 100644 --- a/src/app/datasets/onedep/types/methods.enum.ts +++ b/src/app/datasets/onedep/types/methods.enum.ts @@ -20,6 +20,18 @@ export enum EmFile { MTZ = "xs-mtz", } +export interface OneDepUserInfo { + email: string; + orcidIds: string[]; + country: string; + method: string; + jwtToken: string; + password?: string; +} + +export interface OneDepCreate { + depID: string; +} export interface DepositionFiles { emName: EmFile; id?: number; diff --git a/src/app/shared/sdk/apis/onedep-depositor.service.spec.ts b/src/app/shared/sdk/apis/onedep-depositor.service.spec.ts index ff57d2901d..bbf5cd5690 100644 --- a/src/app/shared/sdk/apis/onedep-depositor.service.spec.ts +++ b/src/app/shared/sdk/apis/onedep-depositor.service.spec.ts @@ -1,16 +1,16 @@ -import { TestBed } from '@angular/core/testing'; +import { TestBed } from "@angular/core/testing"; -import { OnedepDepositorService } from './onedep-depositor.service'; +import { Depositor } from "./onedep-depositor.service"; -describe('OnedepDepositorService', () => { - let service: OnedepDepositorService; +describe("OnedepDepositorService", () => { + let service: Depositor; beforeEach(() => { TestBed.configureTestingModule({}); - service = TestBed.inject(OnedepDepositorService); + service = TestBed.inject(Depositor); }); - it('should be created', () => { + it("should be created", () => { expect(service).toBeTruthy(); }); }); diff --git a/src/app/shared/sdk/apis/onedep-depositor.service.ts b/src/app/shared/sdk/apis/onedep-depositor.service.ts index aa10a96f92..2382847092 100644 --- a/src/app/shared/sdk/apis/onedep-depositor.service.ts +++ b/src/app/shared/sdk/apis/onedep-depositor.service.ts @@ -1,11 +1,11 @@ import { Injectable } from "@angular/core"; +import { Store } from "@ngrx/store"; +import { take } from "rxjs/operators"; import { HttpClient, HttpHeaders, HttpResponse } from "@angular/common/http"; import { Observable } from "rxjs"; import { AppConfigService, AppConfig } from "app-config.service"; - -interface OneDepCreate { - depID: string; -} +import { OneDepUserInfo, OneDepCreated, UploadedFile } from "../models/OneDep"; +import * as fromActions from "state-management/actions/onedep.actions"; @Injectable({ providedIn: "root", @@ -14,14 +14,15 @@ export class Depositor { config: AppConfig; constructor( private http: HttpClient, + private store: Store, public appConfigService: AppConfigService, ) { this.config = this.appConfigService.getConfig(); } // create deposition - createDep(body: string): Observable { - return this.http.post( + createDep(body: OneDepUserInfo): Observable { + return this.http.post( `${this.config.depositorURL}/onedep`, body, { @@ -29,15 +30,10 @@ export class Depositor { }, ); } - - // // Example POST request - // createItem(item: any): Observable { - // const headers = new HttpHeaders({ "Content-Type": "application/json" }); - // return this.http.post(`${this.apiUrl}/items`, item, { headers }); - // } - - // // Example DELETE request - // deleteItem(id: string): Observable { - // return this.http.delete(`${this.apiUrl}/items/${id}`); - // } + sendFile(depID: string, form: FormData): Observable { + return this.http.post( + `${this.config.depositorURL}/onedep/${depID}/file`, + form, + ); + } } diff --git a/src/app/shared/sdk/models/OneDep.ts b/src/app/shared/sdk/models/OneDep.ts new file mode 100644 index 0000000000..1721eedc4e --- /dev/null +++ b/src/app/shared/sdk/models/OneDep.ts @@ -0,0 +1,17 @@ +export interface OneDepCreated { + depID: string; +} + +export interface OneDepUserInfo { + email: string; + orcidIds: string[]; + country: string; + method: string; + jwtToken: string; + password?: string; +} + +export interface UploadedFile { + depID: string; + FileID: string; +} diff --git a/src/app/state-management/actions/onedep.actions.ts b/src/app/state-management/actions/onedep.actions.ts index 2d68b5d7ab..0f8910e0d2 100644 --- a/src/app/state-management/actions/onedep.actions.ts +++ b/src/app/state-management/actions/onedep.actions.ts @@ -1,35 +1,39 @@ import { createAction, props } from "@ngrx/store"; +import { + OneDepUserInfo, + OneDepCreated, + UploadedFile, +} from "shared/sdk/models/OneDep"; -export const createDeposition = createAction("[OneDep] Create Deposition"); +export const createDepositionAction = createAction( + "[OneDep] Create Deposition", + props<{ deposition: OneDepUserInfo }>(), +); export const createDepositionSuccess = createAction( "[OneDep] Create Deposition Complete", - props<{ depID: string }>(), + props<{ deposition: OneDepCreated }>(), ); export const createDepositionFailure = createAction( "[OneDep] Create Deposition Failure", + props<{ err: Error }>(), ); -// export const sendFile = createAction( -// "[OneDep] Send File", -// props<{ depID: string; form: FormData; fileType: string }>() -// ); - -// export const sendFile = createAction( -// "[OneDep] Send File", -// props<{ depID: string; form: FormData; fileType: string }>() -// ); +export const sendFile = createAction( + "[OneDep] Send File", + props<{ depID: string; form: FormData }>(), +); -// export const sendFileSuccess = createAction( -// "[OneDep] Send File Success", -// props<{ fileType: string; res: any }>() -// ); +export const sendFileSuccess = createAction( + "[OneDep] Send File Success", + props<{ uploadedFile: UploadedFile }>(), +); -// export const sendFileFailure = createAction( -// "[OneDep] Send File Failure", -// props<{ error: any }>() -// ); +export const sendFileFailure = createAction( + "[OneDep] Send File Failure", + props<{ err: Error }>(), +); // export const sendCoordFile = createAction( // "[OneDep] Send Coord File", diff --git a/src/app/state-management/effects/onedep.effects.ts b/src/app/state-management/effects/onedep.effects.ts index 8ed164759d..169b9c796c 100644 --- a/src/app/state-management/effects/onedep.effects.ts +++ b/src/app/state-management/effects/onedep.effects.ts @@ -1,34 +1,45 @@ import { Injectable } from "@angular/core"; import { Actions, createEffect, ofType } from "@ngrx/effects"; import { of } from "rxjs"; -import { catchError, map, mergeMap } from "rxjs/operators"; -import { HttpClient } from "@angular/common/http"; +import { catchError, map, switchMap } from "rxjs/operators"; +import { HttpErrorResponse } from "@angular/common/http"; +import { MessageType } from "state-management/models"; +import { showMessageAction } from "state-management/actions/user.actions"; import * as fromActions from "state-management/actions/onedep.actions"; import { Depositor } from "shared/sdk/apis/onedep-depositor.service"; -import { Store } from "@ngrx/store"; +import { + OneDepUserInfo, + OneDepCreated, + UploadedFile, +} from "shared/sdk/models/OneDep"; @Injectable() export class OneDepEffects { - - submitJob$ = createEffect(() => { + createDeposition$ = createEffect(() => { return this.actions$.pipe( - ofType(fromActions.submitJobAction), - switchMap(({ job }) => - this.jobsServiceV4.jobsControllerCreateV4(job as Job).pipe( - map((res) => fromActions.submitJobCompleteAction({ job: res as JobInterface })), - catchError((err) => of(fromActions.submitJobFailedAction({ err }))), + ofType(fromActions.createDepositionAction), + switchMap(({ deposition }) => + this.onedepDepositor.createDep(deposition as OneDepUserInfo).pipe( + map((res) => + fromActions.createDepositionSuccess({ + deposition: res as OneDepCreated, + }), + ), + catchError((err) => of(fromActions.createDepositionFailure({ err }))), ), ), ); }); - submitJobCompleteMessage$ = createEffect(() => { + createDepositionSuccessMessage$ = createEffect(() => { return this.actions$.pipe( - ofType(fromActions.submitJobCompleteAction), - switchMap(() => { + ofType(fromActions.createDepositionSuccess), + switchMap(({ deposition }) => { const message = { type: MessageType.Success, - content: "Job Created Successfully", + content: + "Deposition Created Successfully. Deposition ID: " + + deposition.depID, duration: 5000, }; return of(showMessageAction({ message })); @@ -36,42 +47,75 @@ export class OneDepEffects { ); }); - submitJobFailedMessage$ = createEffect(() => { + createDepositionFailureMessage$ = createEffect(() => { return this.actions$.pipe( - ofType(fromActions.submitJobFailedAction), + ofType(fromActions.createDepositionFailure), switchMap(({ err }) => { + const errorMessage = + err instanceof HttpErrorResponse + ? (err.error?.message ?? err.message ?? "Unknown error") + : err.message || "Unknown error"; const message = { type: MessageType.Error, - content: "Job Not Submitted: " + err.message, - duration: 5000, + content: "Deposition to OneDep failed: " + errorMessage, + duration: 10000, }; return of(showMessageAction({ message })); }), ); }); - - // sendFile$ = createEffect(() => - // this.actions$.pipe( - // ofType(OneDepActions.sendFile), - // mergeMap((action) => - // this.http - // .post( - // `${this.connectedDepositionBackend}onedep/${action.depID}/file`, - // action.form - // ) - // .pipe( - // map((res) => - // OneDepActions.sendFileSuccess({ fileType: action.fileType, res }) - // ), - // catchError((error) => - // of(OneDepActions.sendFileFailure({ error })) - // ) - // ) - // ) - // ) - // ); + sendFile$ = createEffect(() => { + return this.actions$.pipe( + ofType(fromActions.sendFile), + switchMap(({ depID, form }) => + this.onedepDepositor.sendFile(depID, form).pipe( + map((res) => + fromActions.sendFileSuccess({ + uploadedFile: res as UploadedFile, + }), + ), + catchError((err) => of(fromActions.sendFileFailure({ err }))), + ), + ), + ); + }); + + sendFileSuccessMessage$ = createEffect(() => { + return this.actions$.pipe( + ofType(fromActions.sendFileSuccess), + switchMap(({ uploadedFile }) => { + const message = { + type: MessageType.Success, + content: + "File Upladed to Deposition ID: " + + uploadedFile.depID + + " with File ID: " + + uploadedFile.FileID, + duration: 5000, + }; + return of(showMessageAction({ message })); + }), + ); + }); + sendFileFailureMessage$ = createEffect(() => { + return this.actions$.pipe( + ofType(fromActions.sendFileFailure), + switchMap(({ err }) => { + const errorMessage = + err instanceof HttpErrorResponse + ? (err.error?.message ?? err.message ?? "Unknown error") + : err.message || "Unknown error"; + const message = { + type: MessageType.Error, + content: "Deposition to OneDep failed: " + errorMessage, + duration: 10000, + }; + return of(showMessageAction({ message })); + }), + ); + }); // sendCoordFile$ = createEffect(() => // this.actions$.pipe( // ofType(OneDepActions.sendCoordFile), @@ -116,6 +160,5 @@ export class OneDepEffects { constructor( private actions$: Actions, private onedepDepositor: Depositor, - private store: Store, ) {} } From a86748e9f44b85e435f47a2c7e2a5b357fcfa2e6 Mon Sep 17 00:00:00 2001 From: David Wiessner Date: Tue, 7 Jan 2025 13:31:16 +0000 Subject: [PATCH 071/245] Further development of metadata editor --- .../customRenderer/all-of-renderer.ts | 1 + .../customRenderer/any-of-renderer.ts | 60 ++++-- .../customRenderer/array-renderer.ts | 194 ++++++++++++++++++ .../customRenderer/custom-renderers.ts | 9 + .../customRenderer/one-of-renderer.ts | 1 + .../ingestor-metadata-editor-helper.ts | 2 +- .../ingestor-metadata-editor.component.scss | 36 ++++ src/app/ingestor/ingestor.module.ts | 6 + ...ingestor.user-metadata-dialog.component.ts | 4 + .../ingestor/ingestor.component-helper.ts | 6 + .../ingestor/ingestor/ingestor.component.ts | 11 +- 11 files changed, 312 insertions(+), 18 deletions(-) create mode 100644 src/app/ingestor/ingestor-metadata-editor/customRenderer/array-renderer.ts diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer.ts index 0eeefb6796..bc0df73f5e 100644 --- a/src/app/ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer.ts +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer.ts @@ -3,6 +3,7 @@ import { JsonFormsControl } from "@jsonforms/angular"; @Component({ selector: "AllOfRenderer", + styleUrls: ["../ingestor-metadata-editor.component.scss"], template: `
AllOf Renderer
`, }) export class AllOfRendererComponent extends JsonFormsControl implements OnInit { diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts index 1df486fff0..663d693217 100644 --- a/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts @@ -5,29 +5,55 @@ import { configuredRenderer } from "../ingestor-metadata-editor-helper"; @Component({ selector: "app-anyof-renderer", + styleUrls: ["../ingestor-metadata-editor.component.scss"], template: ` -
- {{ anyOfTitle }} - - -
- -
-
-
-
+ + {{ anyOfTitle }} + + + Enabled + + + + animationDuration="0ms" [selectedIndex]="selectedTabIndex" > + +
+ +
+
+
+ +
+ +
+
+
`, }) export class AnyOfRendererComponent extends JsonFormsControl { dataAsString: string; options: string[] = []; + filteredOptions: string[] = []; anyOfTitle: string; + nullOptionSelected = false; selectedTabIndex = 0; // default value + tabAmount = 0; // max tabs rendererService: JsonFormsAngularService; @@ -48,7 +74,11 @@ export class AnyOfRendererComponent extends JsonFormsControl { if (this.options.includes("null") && !props.data) { this.selectedTabIndex = this.options.indexOf("null"); + this.nullOptionSelected = true; } + + this.filteredOptions = this.options.filter((option) => option !== "null"); + this.tabAmount = this.filteredOptions.length; } public getTabSchema(tabOption: string): JsonSchema { diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/array-renderer.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/array-renderer.ts new file mode 100644 index 0000000000..49dedb6987 --- /dev/null +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/array-renderer.ts @@ -0,0 +1,194 @@ +import { + ChangeDetectionStrategy, + Component, + OnDestroy, + OnInit, +} from "@angular/core"; +import { + JsonFormsAngularService, + JsonFormsAbstractControl, +} from "@jsonforms/angular"; +import { + ArrayLayoutProps, + ArrayTranslations, + createDefaultValue, + findUISchema, + JsonFormsState, + mapDispatchToArrayControlProps, + mapStateToArrayLayoutProps, + OwnPropsOfRenderer, + Paths, + setReadonly, + StatePropsOfArrayLayout, + UISchemaElement, + UISchemaTester, + unsetReadonly, +} from "@jsonforms/core"; + +@Component({ + selector: "app-array-layout-renderer-custom", + styleUrls: ["../ingestor-metadata-editor.component.scss"], + template: ` + + +

{{ label }}

+ + + error_outline + + + +
+ +

{{ translations.noDataMessage }}

+
+ + + + + + + + + + + + +
+ `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +export class ArrayLayoutRendererCustom + extends JsonFormsAbstractControl + implements OnInit, OnDestroy +{ + noData: boolean; + translations: ArrayTranslations; + addItem: (path: string, value: any) => () => void; + moveItemUp: (path: string, index: number) => () => void; + moveItemDown: (path: string, index: number) => () => void; + removeItems: (path: string, toDelete: number[]) => () => void; + uischemas: { + tester: UISchemaTester; + uischema: UISchemaElement; + }[]; + constructor(jsonFormsService: JsonFormsAngularService) { + super(jsonFormsService); + } + mapToProps(state: JsonFormsState): StatePropsOfArrayLayout { + const props = mapStateToArrayLayoutProps(state, this.getOwnProps()); + return { ...props }; + } + remove(index: number): void { + this.removeItems(this.propsPath, [index])(); + } + add(): void { + this.addItem( + this.propsPath, + createDefaultValue(this.scopedSchema, this.rootSchema) + )(); + } + up(index: number): void { + this.moveItemUp(this.propsPath, index)(); + } + down(index: number): void { + this.moveItemDown(this.propsPath, index)(); + } + ngOnInit() { + super.ngOnInit(); + const { addItem, removeItems, moveUp, moveDown } = + mapDispatchToArrayControlProps( + this.jsonFormsService.updateCore.bind(this.jsonFormsService), + ); + this.addItem = addItem; + this.moveItemUp = moveUp; + this.moveItemDown = moveDown; + this.removeItems = removeItems; + } + mapAdditionalProps(props: ArrayLayoutProps) { + this.translations = props.translations; + this.noData = !props.data || props.data === 0; + this.uischemas = props.uischemas; + } + getProps(index: number): OwnPropsOfRenderer { + const uischema = findUISchema( + this.uischemas, + this.scopedSchema, + this.uischema.scope, + this.propsPath, + undefined, + this.uischema, + this.rootSchema, + ); + if (this.isEnabled()) { + unsetReadonly(uischema); + } else { + setReadonly(uischema); + } + return { + schema: this.scopedSchema, + path: Paths.compose(this.propsPath, `${index}`), + uischema, + }; + } + trackByFn(index: number) { + return index; + } +} diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/custom-renderers.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/custom-renderers.ts index 67f76f65d9..04b118182c 100644 --- a/src/app/ingestor/ingestor-metadata-editor/customRenderer/custom-renderers.ts +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/custom-renderers.ts @@ -1,6 +1,7 @@ import { isAllOfControl, isAnyOfControl, + isObjectArrayWithNesting, isOneOfControl, JsonFormsRendererRegistryEntry, } from "@jsonforms/core"; @@ -8,6 +9,8 @@ import { OneOfRendererComponent } from "ingestor/ingestor-metadata-editor/custom import { AllOfRendererComponent } from "ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer"; import { AnyOfRendererComponent } from "ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer"; import { rankWith } from "@jsonforms/core"; +import { TableRendererTester } from "@jsonforms/angular-material"; +import { ArrayLayoutRendererCustom } from "./array-renderer"; export const customRenderers: JsonFormsRendererRegistryEntry[] = [ { @@ -22,4 +25,10 @@ export const customRenderers: JsonFormsRendererRegistryEntry[] = [ tester: rankWith(4, isAnyOfControl), renderer: AnyOfRendererComponent, }, + // other + { + tester: rankWith(4, isObjectArrayWithNesting), + renderer: ArrayLayoutRendererCustom, + }, + { tester: TableRendererTester, renderer: ArrayLayoutRendererCustom }, ]; diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts index 3f863aa3b1..76324cab20 100644 --- a/src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts @@ -5,6 +5,7 @@ import { configuredRenderer } from "../ingestor-metadata-editor-helper"; @Component({ selector: "app-oneof-component", + styleUrls: ["../ingestor-metadata-editor.component.scss"], template: `

{{ anyOfTitle }}

diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts index 5eb5a4964f..404a614e90 100644 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts @@ -2,8 +2,8 @@ import { angularMaterialRenderers } from "@jsonforms/angular-material"; import { customRenderers } from "./customRenderer/custom-renderers"; export const configuredRenderer = [ - ...angularMaterialRenderers, ...customRenderers, + ...angularMaterialRenderers, ]; export class IngestorMetadataEditorHelper { diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.scss b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.scss index 89fe8050d3..b41bf4ab35 100644 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.scss +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.scss @@ -1,3 +1,39 @@ .ingestor-metadata-editor { width: 100%; +} + +.spacer { + flex: 1 1 auto; +} + +mat-card { + .mat-mdc-card-title { + display: flex; + padding: 16px; + } +} + +.array-layout { + display: flex; + flex-direction: column; + gap: 16px; +} +.array-layout > * { + flex: 1 1 auto; +} +.array-layout-toolbar { + display: flex; + align-items: center; +} +.array-layout-title { + margin: 0; +} +.array-layout-toolbar > span { + flex: 1 1 auto; +} +.array-item { + padding: 16px; +} +::ng-deep .error-message-tooltip { + white-space: pre-line; } \ No newline at end of file diff --git a/src/app/ingestor/ingestor.module.ts b/src/app/ingestor/ingestor.module.ts index 1dfd044bf2..9e9b54d6e1 100644 --- a/src/app/ingestor/ingestor.module.ts +++ b/src/app/ingestor/ingestor.module.ts @@ -28,6 +28,9 @@ import { IngestorDialogStepperComponent } from "./ingestor/dialog/ingestor.dialo import { AnyOfRendererComponent } from "./ingestor-metadata-editor/customRenderer/any-of-renderer"; import { OneOfRendererComponent } from "./ingestor-metadata-editor/customRenderer/one-of-renderer"; import { MatRadioModule } from "@angular/material/radio"; +import { ArrayLayoutRendererCustom } from "./ingestor-metadata-editor/customRenderer/array-renderer"; +import { MatBadgeModule } from "@angular/material/badge"; +import { MatTooltipModule } from "@angular/material/tooltip"; @NgModule({ declarations: [ @@ -40,6 +43,7 @@ import { MatRadioModule } from "@angular/material/radio"; IngestorDialogStepperComponent, AnyOfRendererComponent, OneOfRendererComponent, + ArrayLayoutRendererCustom, ], imports: [ CommonModule, @@ -55,11 +59,13 @@ import { MatRadioModule } from "@angular/material/radio"; MatTabsModule, MatTableModule, MatDialogModule, + MatTooltipModule, MatSelectModule, MatOptionModule, MatStepperModule, MatRadioModule, MatAutocompleteModule, + MatBadgeModule, JsonFormsModule, JsonFormsAngularMaterialModule, ], diff --git a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts index 896033c8a0..39f6298523 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts @@ -44,6 +44,10 @@ export class IngestorUserMetadataDialogComponent { .sample; this.metadataSchemaOrganizational = organizationalSchema; + + // TODO Remove after debug + console.log("organizationalSchema", organizationalSchema); + this.metadataSchemaSample = sampleSchema; this.scicatHeaderSchema = SciCatHeader_Schema; } diff --git a/src/app/ingestor/ingestor/ingestor.component-helper.ts b/src/app/ingestor/ingestor/ingestor.component-helper.ts index 8191b9069b..c022782eae 100644 --- a/src/app/ingestor/ingestor/ingestor.component-helper.ts +++ b/src/app/ingestor/ingestor/ingestor.component-helper.ts @@ -53,6 +53,12 @@ export interface ScientificMetadata { instrument: object; } +export interface MetadataExtractorResult { + cmdStdErr: string; + cmdStdOut: string; + result: string; +} + export interface DialogDataObject { createNewTransferData: IngestionRequestInformation; backendURL: string; diff --git a/src/app/ingestor/ingestor/ingestor.component.ts b/src/app/ingestor/ingestor/ingestor.component.ts index 33210529ec..637cf7f949 100644 --- a/src/app/ingestor/ingestor/ingestor.component.ts +++ b/src/app/ingestor/ingestor/ingestor.component.ts @@ -15,6 +15,8 @@ import { IngestorConfirmTransferDialogComponent } from "./dialog/ingestor.confir import { IngestionRequestInformation, IngestorHelper, + MetadataExtractorResult, + ScientificMetadata, TransferDataListEntry, } from "./ingestor.component-helper"; @@ -181,10 +183,15 @@ export class IngestorComponent implements OnInit { .subscribe( (response) => { console.log("Metadata extraction result", response); + const extractedMetadata = response as MetadataExtractorResult; + const extractedScientificMetadata = JSON.parse( + extractedMetadata.result, + ) as ScientificMetadata; + this.createNewTransferData.extractorMetaData.instrument = - (response as any).instrument ?? {}; + extractedScientificMetadata.instrument ?? {}; this.createNewTransferData.extractorMetaData.acquisition = - (response as any).acquisition ?? {}; + extractedScientificMetadata.acquisition ?? {}; this.createNewTransferData.extractorMetaDataReady = true; resolve(true); }, From 7189d05ef93d444486be9b1e6ee4e7739ad43ab5 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Tue, 7 Jan 2025 15:46:41 +0000 Subject: [PATCH 072/245] deposition submission effects, improve to include more info --- src/app/datasets/onedep/onedep.component.ts | 146 ++++++------ src/app/datasets/onedep/types/methods.enum.ts | 1 - .../sdk/apis/onedep-depositor.service.ts | 15 +- src/app/shared/sdk/models/OneDep.ts | 9 +- .../actions/onedep.actions.ts | 68 ++++-- .../effects/onedep.effects.spec.ts | 29 +++ .../effects/onedep.effects.ts | 210 ++++++++++++------ .../reducers/onedep.reducer.spec.ts | 0 .../reducers/onedep.reducer.ts | 35 +++ .../selectors/onedep.selectors.ts | 19 ++ .../state-management/state/onedep.store.ts | 21 ++ 11 files changed, 379 insertions(+), 174 deletions(-) create mode 100644 src/app/state-management/effects/onedep.effects.spec.ts create mode 100644 src/app/state-management/reducers/onedep.reducer.spec.ts create mode 100644 src/app/state-management/reducers/onedep.reducer.ts create mode 100644 src/app/state-management/selectors/onedep.selectors.ts create mode 100644 src/app/state-management/state/onedep.store.ts diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index 3c0d393bae..f2280d8b46 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -24,6 +24,7 @@ import { } from "@scicatproject/scicat-sdk-ts"; import { selectCurrentDataset } from "state-management/selectors/datasets.selectors"; import { selectCurrentUser } from "state-management/selectors/user.selectors"; +import { selectDepID } from "state-management/selectors/onedep.selectors"; import * as fromActions from "state-management/actions/onedep.actions"; import { methodsList, @@ -33,7 +34,8 @@ import { OneDepCreate, } from "./types/methods.enum"; import { Depositor } from "shared/sdk/apis/onedep-depositor.service"; -import { Subscription, fromEvent } from "rxjs"; +import { Observable, Subscription, fromEvent } from "rxjs"; +import { filter, map, take } from "rxjs/operators"; @Component({ selector: "onedep", @@ -64,7 +66,7 @@ export class OneDepComponent implements OnInit, OnDestroy { lastUsedDepositionBackends: string[] = []; forwardDepositionBackend = ""; errorMessage = ""; - + depID$: Observable; @ViewChild("fileInput") fileInput: ElementRef | undefined; constructor( @@ -341,38 +343,29 @@ export class OneDepComponent implements OnInit, OnDestroy { this.fileTypes.push(newMap); } - sendFile(depID: string, form: FormData, fileType: string) { - this.http - .post(this.connectedDepositionBackend + "onedep/" + depID + "/file", form) - .subscribe({ - next: (res) => console.log("Uploaded", fileType, res), - error: (error) => - console.error("Could not upload File and Metadata", error), - }); - } - sendCoordFile(depID: string, form: FormData) { - this.http - .post(this.connectedDepositionBackend + "onedep/" + depID + "/pdb", form) - .subscribe({ - next: (res) => console.log("Uploaded Coordinates and Metadata", res), - error: (error) => - console.error("Could not upload Coordinates and Metadata", error), - }); - } - sendMetadata(depID: string, form: FormData) { - // missing token! - this.http - .post( - this.connectedDepositionBackend + "onedep/" + depID + "/metadata", - form, - ) - .subscribe({ - next: (res) => console.log("Uploaded Metadata", res), - error: (error) => console.error("Could not upload Metadata", error), - }); - } + // sendFile(depID: string, form: FormData, fileType: string) { + // this.http + // .post(this.connectedDepositionBackend + "onedep/" + depID + "/file", form) + // .subscribe({ + // next: (res) => console.log("Uploaded", fileType, res), + // error: (error) => + // console.error("Could not upload File and Metadata", error), + // }); + // } + + // sendMetadata(depID: string, form: FormData) { + // // missing token! + // this.http + // .post( + // this.connectedDepositionBackend + "onedep/" + depID + "/metadata", + // form, + // ) + // .subscribe({ + // next: (res) => console.log("Uploaded Metadata", res), + // error: (error) => console.error("Could not upload Metadata", error), + // }); + // } onDepositClick() { - // Create a deposition let body: OneDepUserInfo; if (this.form.value.password) { body = { @@ -392,60 +385,51 @@ export class OneDepComponent implements OnInit, OnDestroy { jwtToken: this.form.value.jwtToken, }; } - let depID: string; let metadataAdded = false; + const filesToUpload = this.fileTypes + .filter((fT) => fT.file) + .map((fT) => { + const formDataFile = new FormData(); + formDataFile.append("jwtToken", this.form.value.jwtToken); + formDataFile.append("file", fT.file); + if (fT.emName === this.emFile.Coordinates) { + formDataFile.append( + "scientificMetadata", + JSON.stringify(this.form.value.metadata), + ); + metadataAdded = true; + } else { + formDataFile.append( + "fileMetadata", + JSON.stringify({ + name: fT.fileName, + type: fT.type, + contour: fT.contour, + details: fT.details, + }), + ); + } + return { form: formDataFile, fileType: fT.emName }; + }); + // if (!metadataAdded) { + // const formDataFile = new FormData(); + + // formDataFile.append("jwtToken", this.form.value.jwtToken); + // formDataFile.append( + // "scientificMetadata", + // JSON.stringify(this.form.value.metadata), + // ); + // // FIXME: This is a temporary fix, the metadata fileType should be specified as such, once supported by OneDep API + // filesToUpload.push({ form: formDataFile, fileType: EmFile.Coordinates }); + // } + this.store.dispatch( - fromActions.createDepositionAction({ + fromActions.submitDeposition({ deposition: body as OneDepUserInfo, + files: filesToUpload, }), ); - - this.depositor.createDep(body).subscribe({ - next: (response: OneDepCreate) => { - depID = response.depID; - - // Call subsequent requests - this.fileTypes.forEach((fT) => { - if (fT.file) { - const formDataFile = new FormData(); - formDataFile.append("jwtToken", this.form.value.jwtToken); - formDataFile.append("file", fT.file); - if (fT.emName === this.emFile.Coordinates) { - formDataFile.append( - "scientificMetadata", - JSON.stringify(this.form.value.metadata), - ); - this.sendCoordFile(depID, formDataFile); - metadataAdded = true; - } else { - formDataFile.append( - "fileMetadata", - JSON.stringify({ - name: fT.fileName, - type: fT.type, - contour: fT.contour, - details: fT.details, - }), - ); - // log that into message fT.type - this.depositor.sendFile(depID, formDataFile); - } - } - }); - // if (!metadataAdded) { - // const formDataFile = new FormData(); - - // formDataFile.append("jwtToken", this.form.value.jwtToken); - // formDataFile.append( - // "scientificMetadata", - // JSON.stringify(this.form.value.metadata), - // ); - // this.sendMetadata(depID, formDataFile); - // } - }, - error: (error) => console.error("Request failed", error.error), - }); } onDownloadClick() { if (this.form.value.deposingCoordinates === "true") { diff --git a/src/app/datasets/onedep/types/methods.enum.ts b/src/app/datasets/onedep/types/methods.enum.ts index fce5bc7ee6..1895a716ce 100644 --- a/src/app/datasets/onedep/types/methods.enum.ts +++ b/src/app/datasets/onedep/types/methods.enum.ts @@ -246,4 +246,3 @@ export const methodsList: EmMethod[] = [ ], }, ]; - diff --git a/src/app/shared/sdk/apis/onedep-depositor.service.ts b/src/app/shared/sdk/apis/onedep-depositor.service.ts index 2382847092..8391e4a3a7 100644 --- a/src/app/shared/sdk/apis/onedep-depositor.service.ts +++ b/src/app/shared/sdk/apis/onedep-depositor.service.ts @@ -1,6 +1,6 @@ import { Injectable } from "@angular/core"; import { Store } from "@ngrx/store"; -import { take } from "rxjs/operators"; +import { tap } from "rxjs/operators"; import { HttpClient, HttpHeaders, HttpResponse } from "@angular/common/http"; import { Observable } from "rxjs"; import { AppConfigService, AppConfig } from "app-config.service"; @@ -20,7 +20,6 @@ export class Depositor { this.config = this.appConfigService.getConfig(); } - // create deposition createDep(body: OneDepUserInfo): Observable { return this.http.post( `${this.config.depositorURL}/onedep`, @@ -36,4 +35,16 @@ export class Depositor { form, ); } + sendCoordFile(depID: string, form: FormData): Observable { + return this.http.post( + `${this.config.depositorURL}/onedep/${depID}/pdb`, + form, + ); + } + sendMetadata(depID: string, form: FormData): Observable { + return this.http.post( + `${this.config.depositorURL}/onedep/${depID}/metadata`, + form, + ); + } } diff --git a/src/app/shared/sdk/models/OneDep.ts b/src/app/shared/sdk/models/OneDep.ts index 1721eedc4e..cd7ddbb337 100644 --- a/src/app/shared/sdk/models/OneDep.ts +++ b/src/app/shared/sdk/models/OneDep.ts @@ -1,3 +1,5 @@ +import { EmFile } from "../../../datasets/onedep/types/methods.enum"; + export interface OneDepCreated { depID: string; } @@ -13,5 +15,10 @@ export interface OneDepUserInfo { export interface UploadedFile { depID: string; - FileID: string; + fileID: string; +} + +export interface FileUpload { + form: FormData; + fileType: EmFile; } diff --git a/src/app/state-management/actions/onedep.actions.ts b/src/app/state-management/actions/onedep.actions.ts index 0f8910e0d2..acffdeb40a 100644 --- a/src/app/state-management/actions/onedep.actions.ts +++ b/src/app/state-management/actions/onedep.actions.ts @@ -3,64 +3,84 @@ import { OneDepUserInfo, OneDepCreated, UploadedFile, + FileUpload, } from "shared/sdk/models/OneDep"; -export const createDepositionAction = createAction( - "[OneDep] Create Deposition", - props<{ deposition: OneDepUserInfo }>(), +export const submitDeposition = createAction( + "[OneDep] Submit Deposition", + props<{ deposition: OneDepUserInfo; files: FileUpload[] }>(), ); - -export const createDepositionSuccess = createAction( +export const submitDepositionSuccess = createAction( "[OneDep] Create Deposition Complete", props<{ deposition: OneDepCreated }>(), ); -export const createDepositionFailure = createAction( +export const submitDepositionFailure = createAction( "[OneDep] Create Deposition Failure", props<{ err: Error }>(), ); -export const sendFile = createAction( - "[OneDep] Send File", - props<{ depID: string; form: FormData }>(), -); +// export const createDepositionAction = createAction( +// "[OneDep] Create Deposition", +// props<{ deposition: OneDepUserInfo }>(), +// ); -export const sendFileSuccess = createAction( - "[OneDep] Send File Success", - props<{ uploadedFile: UploadedFile }>(), -); +// export const createDepositionSuccess = createAction( +// "[OneDep] Create Deposition Complete", +// props<{ deposition: OneDepCreated }>(), +// ); -export const sendFileFailure = createAction( - "[OneDep] Send File Failure", - props<{ err: Error }>(), -); +// export const createDepositionFailure = createAction( +// "[OneDep] Create Deposition Failure", +// props<{ err: Error }>(), +// ); + +// export const sendFile = createAction( +// "[OneDep] Send File", +// props<{ depID: string; form: FormData }>(), +// ); + +// export const sendFileSuccess = createAction( +// "[OneDep] Send File Success", +// props<{ uploadedFile: UploadedFile }>(), +// ); + +// export const sendFileFailure = createAction( +// "[OneDep] Send File Failure", +// props<{ err: Error }>(), +// ); // export const sendCoordFile = createAction( // "[OneDep] Send Coord File", -// props<{ depID: string; form: FormData }>() +// props<{ depID: string; form: FormData }>(), +// ); + +// export const uploadFilesAction = createAction( +// "[OneDep] Upload Files", +// props<{ depID: string; files: FileUpload[] }>(), // ); // export const sendCoordFileSuccess = createAction( // "[OneDep] Send Coord File Success", -// props<{ res: any }>() +// props<{ uploadedFile: UploadedFile }>(), // ); // export const sendCoordFileFailure = createAction( // "[OneDep] Send Coord File Failure", -// props<{ error: any }>() +// props<{ error: Error }>(), // ); // export const sendMetadata = createAction( // "[OneDep] Send Metadata", -// props<{ depID: string; form: FormData }>() +// props<{ depID: string; form: FormData }>(), // ); // export const sendMetadataSuccess = createAction( // "[OneDep] Send Metadata Success", -// props<{ res: any }>() +// props<{ uploadedFile: UploadedFile }>(), // ); // export const sendMetadataFailure = createAction( // "[OneDep] Send Metadata Failure", -// props<{ error: any }>() +// props<{ error: Error }>(), // ); diff --git a/src/app/state-management/effects/onedep.effects.spec.ts b/src/app/state-management/effects/onedep.effects.spec.ts new file mode 100644 index 0000000000..5264d83aa2 --- /dev/null +++ b/src/app/state-management/effects/onedep.effects.spec.ts @@ -0,0 +1,29 @@ +import { TestBed } from "@angular/core/testing"; +import { provideMockActions } from "@ngrx/effects/testing"; +import { provideMockStore } from "@ngrx/store/testing"; +import { HttpClientTestingModule } from "@angular/common/http/testing"; +import { Observable } from "rxjs"; +import { OneDepEffects } from "./onedep.effects"; +import { Actions } from "@ngrx/effects"; + +describe("OneDepEffects", () => { + let actions$: Observable; + let effects: OneDepEffects; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ + OneDepEffects, + provideMockActions(() => actions$), + provideMockStore(), + ], + }); + + effects = TestBed.inject(OneDepEffects); + }); + + it("should be created", () => { + expect(effects).toBeTruthy(); + }); +}); \ No newline at end of file diff --git a/src/app/state-management/effects/onedep.effects.ts b/src/app/state-management/effects/onedep.effects.ts index 169b9c796c..d1f654d2c5 100644 --- a/src/app/state-management/effects/onedep.effects.ts +++ b/src/app/state-management/effects/onedep.effects.ts @@ -1,7 +1,7 @@ import { Injectable } from "@angular/core"; import { Actions, createEffect, ofType } from "@ngrx/effects"; -import { of } from "rxjs"; -import { catchError, map, switchMap } from "rxjs/operators"; +import { of, from } from "rxjs"; +import { catchError, map, switchMap, concatMap, last } from "rxjs/operators"; import { HttpErrorResponse } from "@angular/common/http"; import { MessageType } from "state-management/models"; import { showMessageAction } from "state-management/actions/user.actions"; @@ -12,28 +12,39 @@ import { OneDepCreated, UploadedFile, } from "shared/sdk/models/OneDep"; - +import { EmFile } from "../../datasets/onedep/types/methods.enum"; @Injectable() export class OneDepEffects { - createDeposition$ = createEffect(() => { + submitDeposition$ = createEffect(() => { return this.actions$.pipe( - ofType(fromActions.createDepositionAction), - switchMap(({ deposition }) => - this.onedepDepositor.createDep(deposition as OneDepUserInfo).pipe( - map((res) => - fromActions.createDepositionSuccess({ - deposition: res as OneDepCreated, - }), + ofType(fromActions.submitDeposition), + switchMap(({ deposition, files }) => + this.onedepDepositor.createDep(deposition).pipe( + switchMap((dep) => + from(files).pipe( + concatMap((file) => + file.fileType === EmFile.Coordinates + ? this.onedepDepositor.sendCoordFile(dep.depID, file.form) + : this.onedepDepositor.sendFile(dep.depID, file.form), + ), + + last(), // Ensures the final emission happens only after all uploads are complete + map(() => + fromActions.submitDepositionSuccess({ + deposition: dep as OneDepCreated, + }), + ), + ), ), - catchError((err) => of(fromActions.createDepositionFailure({ err }))), + catchError((err) => of(fromActions.submitDepositionFailure({ err }))), ), ), ); }); - createDepositionSuccessMessage$ = createEffect(() => { + submitDepositionSuccess$ = createEffect(() => { return this.actions$.pipe( - ofType(fromActions.createDepositionSuccess), + ofType(fromActions.submitDepositionSuccess), switchMap(({ deposition }) => { const message = { type: MessageType.Success, @@ -47,9 +58,9 @@ export class OneDepEffects { ); }); - createDepositionFailureMessage$ = createEffect(() => { + submitDepositionFailure$ = createEffect(() => { return this.actions$.pipe( - ofType(fromActions.createDepositionFailure), + ofType(fromActions.submitDepositionFailure), switchMap(({ err }) => { const errorMessage = err instanceof HttpErrorResponse @@ -65,57 +76,126 @@ export class OneDepEffects { ); }); - sendFile$ = createEffect(() => { - return this.actions$.pipe( - ofType(fromActions.sendFile), - switchMap(({ depID, form }) => - this.onedepDepositor.sendFile(depID, form).pipe( - map((res) => - fromActions.sendFileSuccess({ - uploadedFile: res as UploadedFile, - }), - ), - catchError((err) => of(fromActions.sendFileFailure({ err }))), - ), - ), - ); - }); + // createDeposition$ = createEffect(() => { + // return this.actions$.pipe( + // ofType(fromActions.createDepositionAction), + // switchMap(({ deposition }) => + // this.onedepDepositor.createDep(deposition as OneDepUserInfo).pipe( + // map((res) => + // fromActions.createDepositionSuccess({ + // deposition: res as OneDepCreated, + // }), + // ), + // catchError((err) => of(fromActions.createDepositionFailure({ err }))), + // ), + // ), + // ); + // }); - sendFileSuccessMessage$ = createEffect(() => { - return this.actions$.pipe( - ofType(fromActions.sendFileSuccess), - switchMap(({ uploadedFile }) => { - const message = { - type: MessageType.Success, - content: - "File Upladed to Deposition ID: " + - uploadedFile.depID + - " with File ID: " + - uploadedFile.FileID, - duration: 5000, - }; - return of(showMessageAction({ message })); - }), - ); - }); + // createDepositionSuccessMessage$ = createEffect(() => { + // return this.actions$.pipe( + // ofType(fromActions.createDepositionSuccess), + // switchMap(({ deposition }) => { + // const message = { + // type: MessageType.Success, + // content: + // "Deposition Created Successfully. Deposition ID: " + + // deposition.depID, + // duration: 5000, + // }; + // return of(showMessageAction({ message })); + // }), + // ); + // }); + + // createDepositionFailureMessage$ = createEffect(() => { + // return this.actions$.pipe( + // ofType(fromActions.createDepositionFailure), + // switchMap(({ err }) => { + // const errorMessage = + // err instanceof HttpErrorResponse + // ? (err.error?.message ?? err.message ?? "Unknown error") + // : err.message || "Unknown error"; + // const message = { + // type: MessageType.Error, + // content: "Deposition to OneDep failed: " + errorMessage, + // duration: 10000, + // }; + // return of(showMessageAction({ message })); + // }), + // ); + // }); + // uploadFiles$ = createEffect(() => { + // return this.actions$.pipe( + // ofType(fromActions.uploadFilesAction), + // mergeMap(({ depID, files }) => + // from(files).pipe( + // mergeMap((file) => + // this.onedepDepositor.sendFile(depID, file.form).pipe( + // map((res) => + // fromActions.sendFileSuccess({ + // uploadedFile: res as UploadedFile, + // }), + // ), + // catchError((err) => of(fromActions.sendFileFailure({ err }))), + // ), + // ), + // ), + // ), + // ); + // }); + // sendFile$ = createEffect(() => { + // return this.actions$.pipe( + // ofType(fromActions.sendFile), + // switchMap(({ depID, form }) => + // this.onedepDepositor.sendFile(depID, form).pipe( + // map((res) => + // fromActions.sendFileSuccess({ + // uploadedFile: res as UploadedFile, + // }), + // ), + // catchError((err) => of(fromActions.sendFileFailure({ err }))), + // ), + // ), + // ); + // }); + + // sendFileSuccessMessage$ = createEffect(() => { + // return this.actions$.pipe( + // ofType(fromActions.sendFileSuccess), + // switchMap(({ uploadedFile }) => { + // const message = { + // type: MessageType.Success, + // content: + // "File Upladed to Deposition ID: " + + // uploadedFile.depID + + // " with File ID: " + + // uploadedFile.fileID, + // duration: 5000, + // }; + // return of(showMessageAction({ message })); + // }), + // ); + // }); + + // sendFileFailureMessage$ = createEffect(() => { + // return this.actions$.pipe( + // ofType(fromActions.sendFileFailure), + // switchMap(({ err }) => { + // const errorMessage = + // err instanceof HttpErrorResponse + // ? (err.error?.message ?? err.message ?? "Unknown error") + // : err.message || "Unknown error"; + // const message = { + // type: MessageType.Error, + // content: "Deposition to OneDep failed: " + errorMessage, + // duration: 10000, + // }; + // return of(showMessageAction({ message })); + // }), + // ); + // }); - sendFileFailureMessage$ = createEffect(() => { - return this.actions$.pipe( - ofType(fromActions.sendFileFailure), - switchMap(({ err }) => { - const errorMessage = - err instanceof HttpErrorResponse - ? (err.error?.message ?? err.message ?? "Unknown error") - : err.message || "Unknown error"; - const message = { - type: MessageType.Error, - content: "Deposition to OneDep failed: " + errorMessage, - duration: 10000, - }; - return of(showMessageAction({ message })); - }), - ); - }); // sendCoordFile$ = createEffect(() => // this.actions$.pipe( // ofType(OneDepActions.sendCoordFile), diff --git a/src/app/state-management/reducers/onedep.reducer.spec.ts b/src/app/state-management/reducers/onedep.reducer.spec.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/state-management/reducers/onedep.reducer.ts b/src/app/state-management/reducers/onedep.reducer.ts new file mode 100644 index 0000000000..516e8bf8be --- /dev/null +++ b/src/app/state-management/reducers/onedep.reducer.ts @@ -0,0 +1,35 @@ +import { createReducer, Action, on } from "@ngrx/store"; +import * as fromActions from "state-management/actions/onedep.actions"; +import { + OneDepState, + initialOneDepState, +} from "state-management/state/onedep.store"; + +const reducer = createReducer( + initialOneDepState, + on( + fromActions.submitDepositionSuccess, + (state, { deposition }): OneDepState => ({ + ...state, + depositionCreated: deposition, + oneDepInteractionError: undefined, + }), + ), + on( + fromActions.submitDepositionFailure, + (state, { err }): OneDepState => ({ + ...state, + oneDepInteractionError: err, + }), + ), +); + +export const onedepReducer = ( + state: OneDepState | undefined, + action: Action, +) => { + if (action.type.indexOf("[OneDep]") !== -1) { + console.log("Action came in! " + action.type); + } + return reducer(state, action); +}; diff --git a/src/app/state-management/selectors/onedep.selectors.ts b/src/app/state-management/selectors/onedep.selectors.ts new file mode 100644 index 0000000000..0b58a25878 --- /dev/null +++ b/src/app/state-management/selectors/onedep.selectors.ts @@ -0,0 +1,19 @@ +import { createFeatureSelector, createSelector } from "@ngrx/store"; +import { OneDepState } from "state-management/state/onedep.store"; + +export const selectOneDepState = createFeatureSelector("onedep"); + +export const selectDeposition = createSelector( + selectOneDepState, + (state) => state.depositionCreated, +); + +export const selectDepID = createSelector( + selectDeposition, + (deposition) => deposition?.depID, +); + +export const selectCurrentFileID = createSelector( + selectOneDepState, + (state) => state.currentFileID, +); diff --git a/src/app/state-management/state/onedep.store.ts b/src/app/state-management/state/onedep.store.ts new file mode 100644 index 0000000000..32aa33be30 --- /dev/null +++ b/src/app/state-management/state/onedep.store.ts @@ -0,0 +1,21 @@ +import { OneDepCreated, OneDepUserInfo } from "shared/sdk/models/OneDep"; + +export interface OneDepState { + // depositionInfo: OneDepUserInfo | undefined; + + depositionCreated: OneDepCreated | undefined; + + oneDepInteractionError: Error | undefined; + + fileIDs: string[] | undefined; + + currentFileID: string | undefined; +} + +export const initialOneDepState: OneDepState = { + // depositionInfo: undefined, + depositionCreated: undefined, + oneDepInteractionError: undefined, + fileIDs: [], + currentFileID: undefined, +}; From f4815a1875aa225328bbc892a2b98e1ce73e62b6 Mon Sep 17 00:00:00 2001 From: David Wiessner Date: Thu, 9 Jan 2025 07:17:07 +0000 Subject: [PATCH 073/245] Update Changes and Bugfixes in psi-deployment-openem --- .../customRenderer/all-of-renderer.ts | 11 +- .../customRenderer/any-of-renderer.ts | 149 +++++---- .../customRenderer/array-renderer.ts | 194 +++++++++++ .../customRenderer/custom-renderers.ts | 34 +- .../customRenderer/one-of-renderer.ts | 119 ++++--- .../ingestor-metadata-editor-helper.ts | 17 +- .../ingestor-metadata-editor.component.scss | 36 +++ .../ingestor-metadata-editor.component.ts | 23 +- src/app/ingestor/ingestor.module.ts | 48 +-- ...estor.confirm-transfer-dialog.component.ts | 73 +++-- ...stor.dialog-stepper.component.component.ts | 12 +- ...tor.extractor-metadata-dialog.component.ts | 71 ++-- .../ingestor.new-transfer-dialog.component.ts | 143 ++++---- ...ingestor.user-metadata-dialog.component.ts | 63 ++-- .../ingestor/ingestor-api-endpoints.ts | 16 +- .../ingestor/ingestor.component-helper.ts | 90 ++++-- .../ingestor/ingestor.component.spec.ts | 15 +- .../ingestor/ingestor/ingestor.component.ts | 304 +++++++++++------- 18 files changed, 948 insertions(+), 470 deletions(-) create mode 100644 src/app/ingestor/ingestor-metadata-editor/customRenderer/array-renderer.ts diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer.ts index 91085fbaf7..bc0df73f5e 100644 --- a/src/app/ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer.ts +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer.ts @@ -1,11 +1,12 @@ -import { Component } from '@angular/core'; -import { JsonFormsControl } from '@jsonforms/angular'; +import { Component, OnInit } from "@angular/core"; +import { JsonFormsControl } from "@jsonforms/angular"; @Component({ - selector: 'AllOfRenderer', - template: `
AllOf Renderer
` + selector: "AllOfRenderer", + styleUrls: ["../ingestor-metadata-editor.component.scss"], + template: `
AllOf Renderer
`, }) -export class AllOfRenderer extends JsonFormsControl { +export class AllOfRendererComponent extends JsonFormsControl implements OnInit { data: any[] = []; ngOnInit() { diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts index 530d762196..663d693217 100644 --- a/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer.ts @@ -1,70 +1,109 @@ -import { Component } from '@angular/core'; -import { JsonFormsAngularService, JsonFormsControl } from '@jsonforms/angular'; -import { ControlProps, JsonSchema } from '@jsonforms/core'; -import { configuredRenderer } from '../ingestor-metadata-editor-helper'; +import { Component } from "@angular/core"; +import { JsonFormsAngularService, JsonFormsControl } from "@jsonforms/angular"; +import { ControlProps, JsonSchema } from "@jsonforms/core"; +import { configuredRenderer } from "../ingestor-metadata-editor-helper"; @Component({ - selector: 'app-anyof-renderer', - template: ` -
- {{anyOfTitle}} - - - -
- + selector: "app-anyof-renderer", + styleUrls: ["../ingestor-metadata-editor.component.scss"], + template: ` + + {{ anyOfTitle }} + + + Enabled + + + + animationDuration="0ms" [selectedIndex]="selectedTabIndex" > + +
+
-
-
-
- ` +
+
+ +
+ +
+ + + `, }) -export class AnyOfRenderer extends JsonFormsControl { +export class AnyOfRendererComponent extends JsonFormsControl { + dataAsString: string; + options: string[] = []; + filteredOptions: string[] = []; + anyOfTitle: string; + nullOptionSelected = false; + selectedTabIndex = 0; // default value + tabAmount = 0; // max tabs - dataAsString: string; - options: string[] = []; - anyOfTitle: string; - selectedTabIndex: number = 0; // default value + rendererService: JsonFormsAngularService; - rendererService: JsonFormsAngularService; + defaultRenderer = configuredRenderer; + passedProps: ControlProps; - defaultRenderer = configuredRenderer; - passedProps: ControlProps; + constructor(service: JsonFormsAngularService) { + super(service); + this.rendererService = service; + } - constructor(service: JsonFormsAngularService) { - super(service); - this.rendererService = service; - } + public mapAdditionalProps(props: ControlProps) { + this.passedProps = props; + this.anyOfTitle = props.label || "AnyOf"; + this.options = props.schema.anyOf.map( + (option: any) => option.title || option.type || JSON.stringify(option), + ); - public mapAdditionalProps(props: ControlProps) { - this.passedProps = props; - this.anyOfTitle = props.label || 'AnyOf'; - this.options = props.schema.anyOf.map((option: any) => option.title || option.type || JSON.stringify(option)); - - if (this.options.includes("null") && !props.data) { - this.selectedTabIndex = this.options.indexOf("null"); - } + if (this.options.includes("null") && !props.data) { + this.selectedTabIndex = this.options.indexOf("null"); + this.nullOptionSelected = true; } - public getTabSchema(tabOption: string): JsonSchema { - const selectedSchema = (this.passedProps.schema.anyOf as any).find((option: any) => option.title === tabOption || option.type === tabOption || JSON.stringify(option) === tabOption); - return selectedSchema; - } + this.filteredOptions = this.options.filter((option) => option !== "null"); + this.tabAmount = this.filteredOptions.length; + } + + public getTabSchema(tabOption: string): JsonSchema { + const selectedSchema = (this.passedProps.schema.anyOf as any).find( + (option: any) => + option.title === tabOption || + option.type === tabOption || + JSON.stringify(option) === tabOption, + ); + return selectedSchema; + } - public onInnerJsonFormsChange(event: any) { - // Check if data is equal to the passedProps.data - if (event !== this.passedProps.data) { - const updatedData = this.rendererService.getState().jsonforms.core.data; + public onInnerJsonFormsChange(event: any) { + if (event !== this.passedProps.data) { + const updatedData = this.rendererService.getState().jsonforms.core.data; - // aktualisiere das aktuelle Datenobjekt - const pathSegments = this.passedProps.path.split('.'); - let current = updatedData; - for (let i = 0; i < pathSegments.length - 1; i++) { - current = current[pathSegments[i]]; - } - current[pathSegments[pathSegments.length - 1]] = event; + // Update the data in the correct path + const pathSegments = this.passedProps.path.split("."); + let current = updatedData; + for (let i = 0; i < pathSegments.length - 1; i++) { + current = current[pathSegments[i]]; + } + current[pathSegments[pathSegments.length - 1]] = event; - this.rendererService.setData(updatedData); - } + this.rendererService.setData(updatedData); } -} \ No newline at end of file + } +} diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/array-renderer.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/array-renderer.ts new file mode 100644 index 0000000000..49dedb6987 --- /dev/null +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/array-renderer.ts @@ -0,0 +1,194 @@ +import { + ChangeDetectionStrategy, + Component, + OnDestroy, + OnInit, +} from "@angular/core"; +import { + JsonFormsAngularService, + JsonFormsAbstractControl, +} from "@jsonforms/angular"; +import { + ArrayLayoutProps, + ArrayTranslations, + createDefaultValue, + findUISchema, + JsonFormsState, + mapDispatchToArrayControlProps, + mapStateToArrayLayoutProps, + OwnPropsOfRenderer, + Paths, + setReadonly, + StatePropsOfArrayLayout, + UISchemaElement, + UISchemaTester, + unsetReadonly, +} from "@jsonforms/core"; + +@Component({ + selector: "app-array-layout-renderer-custom", + styleUrls: ["../ingestor-metadata-editor.component.scss"], + template: ` + + +

{{ label }}

+ + + error_outline + + + +
+ +

{{ translations.noDataMessage }}

+
+ + + + + + + + + + + + +
+ `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +export class ArrayLayoutRendererCustom + extends JsonFormsAbstractControl + implements OnInit, OnDestroy +{ + noData: boolean; + translations: ArrayTranslations; + addItem: (path: string, value: any) => () => void; + moveItemUp: (path: string, index: number) => () => void; + moveItemDown: (path: string, index: number) => () => void; + removeItems: (path: string, toDelete: number[]) => () => void; + uischemas: { + tester: UISchemaTester; + uischema: UISchemaElement; + }[]; + constructor(jsonFormsService: JsonFormsAngularService) { + super(jsonFormsService); + } + mapToProps(state: JsonFormsState): StatePropsOfArrayLayout { + const props = mapStateToArrayLayoutProps(state, this.getOwnProps()); + return { ...props }; + } + remove(index: number): void { + this.removeItems(this.propsPath, [index])(); + } + add(): void { + this.addItem( + this.propsPath, + createDefaultValue(this.scopedSchema, this.rootSchema) + )(); + } + up(index: number): void { + this.moveItemUp(this.propsPath, index)(); + } + down(index: number): void { + this.moveItemDown(this.propsPath, index)(); + } + ngOnInit() { + super.ngOnInit(); + const { addItem, removeItems, moveUp, moveDown } = + mapDispatchToArrayControlProps( + this.jsonFormsService.updateCore.bind(this.jsonFormsService), + ); + this.addItem = addItem; + this.moveItemUp = moveUp; + this.moveItemDown = moveDown; + this.removeItems = removeItems; + } + mapAdditionalProps(props: ArrayLayoutProps) { + this.translations = props.translations; + this.noData = !props.data || props.data === 0; + this.uischemas = props.uischemas; + } + getProps(index: number): OwnPropsOfRenderer { + const uischema = findUISchema( + this.uischemas, + this.scopedSchema, + this.uischema.scope, + this.propsPath, + undefined, + this.uischema, + this.rootSchema, + ); + if (this.isEnabled()) { + unsetReadonly(uischema); + } else { + setReadonly(uischema); + } + return { + schema: this.scopedSchema, + path: Paths.compose(this.propsPath, `${index}`), + uischema, + }; + } + trackByFn(index: number) { + return index; + } +} diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/custom-renderers.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/custom-renderers.ts index 8b807f113b..04b118182c 100644 --- a/src/app/ingestor/ingestor-metadata-editor/customRenderer/custom-renderers.ts +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/custom-renderers.ts @@ -1,20 +1,34 @@ -import { isAllOfControl, isAnyOfControl, isOneOfControl, JsonFormsRendererRegistryEntry } from '@jsonforms/core'; -import { OneOfRenderer } from 'ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer'; -import { AllOfRenderer } from 'ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer'; -import { AnyOfRenderer } from 'ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer'; -import { isRangeControl, RankedTester, rankWith } from '@jsonforms/core'; +import { + isAllOfControl, + isAnyOfControl, + isObjectArrayWithNesting, + isOneOfControl, + JsonFormsRendererRegistryEntry, +} from "@jsonforms/core"; +import { OneOfRendererComponent } from "ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer"; +import { AllOfRendererComponent } from "ingestor/ingestor-metadata-editor/customRenderer/all-of-renderer"; +import { AnyOfRendererComponent } from "ingestor/ingestor-metadata-editor/customRenderer/any-of-renderer"; +import { rankWith } from "@jsonforms/core"; +import { TableRendererTester } from "@jsonforms/angular-material"; +import { ArrayLayoutRendererCustom } from "./array-renderer"; export const customRenderers: JsonFormsRendererRegistryEntry[] = [ { tester: rankWith(4, isOneOfControl), - renderer: OneOfRenderer + renderer: OneOfRendererComponent, }, { tester: rankWith(4, isAllOfControl), - renderer: AllOfRenderer + renderer: AllOfRendererComponent, }, { tester: rankWith(4, isAnyOfControl), - renderer: AnyOfRenderer - } -]; \ No newline at end of file + renderer: AnyOfRendererComponent, + }, + // other + { + tester: rankWith(4, isObjectArrayWithNesting), + renderer: ArrayLayoutRendererCustom, + }, + { tester: TableRendererTester, renderer: ArrayLayoutRendererCustom }, +]; diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts index 42665d5c62..76324cab20 100644 --- a/src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/one-of-renderer.ts @@ -1,69 +1,88 @@ -import { Component } from '@angular/core'; -import { JsonFormsAngularService, JsonFormsControl } from '@jsonforms/angular'; -import { ControlProps, JsonSchema } from '@jsonforms/core'; -import { configuredRenderer } from '../ingestor-metadata-editor-helper'; +import { Component } from "@angular/core"; +import { JsonFormsAngularService, JsonFormsControl } from "@jsonforms/angular"; +import { ControlProps, JsonSchema } from "@jsonforms/core"; +import { configuredRenderer } from "../ingestor-metadata-editor-helper"; @Component({ - selector: 'app-oneof-component', - template: ` + selector: "app-oneof-component", + styleUrls: ["../ingestor-metadata-editor.component.scss"], + template: `
-

{{anyOfTitle}}

+

{{ anyOfTitle }}

- - {{option}} + + {{ option }} -
- +
+
- ` + `, }) -export class OneOfRenderer extends JsonFormsControl { +export class OneOfRendererComponent extends JsonFormsControl { + dataAsString: string; + options: string[] = []; + anyOfTitle: string; + selectedOption: string; + selectedAnyOption: JsonSchema; - dataAsString: string; - options: string[] = []; - anyOfTitle: string; - selectedOption: string; - selectedAnyOption: JsonSchema; + rendererService: JsonFormsAngularService; - rendererService: JsonFormsAngularService; + defaultRenderer = configuredRenderer; + passedProps: ControlProps; - defaultRenderer = configuredRenderer; - passedProps: ControlProps; + constructor(service: JsonFormsAngularService) { + super(service); + this.rendererService = service; + } - constructor(service: JsonFormsAngularService) { - super(service); - this.rendererService = service; + public mapAdditionalProps(props: ControlProps) { + this.passedProps = props; + this.anyOfTitle = props.label || "AnyOf"; + this.options = props.schema.anyOf.map( + (option: any) => option.title || option.type || JSON.stringify(option), + ); + if (!props.data) { + this.selectedOption = "null"; // Auf "null" setzen, wenn die Daten leer sind } + } - public mapAdditionalProps(props: ControlProps) { - this.passedProps = props; - this.anyOfTitle = props.label || 'AnyOf'; - this.options = props.schema.anyOf.map((option: any) => option.title || option.type || JSON.stringify(option)); - if (!props.data) { - this.selectedOption = 'null'; // Auf "null" setzen, wenn die Daten leer sind - } - } - - public onOptionChange() { - this.selectedAnyOption = (this.passedProps.schema.anyOf as any).find((option: any) => option.title === this.selectedOption || option.type === this.selectedOption || JSON.stringify(option) === this.selectedOption); - } + public onOptionChange() { + this.selectedAnyOption = (this.passedProps.schema.anyOf as any).find( + (option: any) => + option.title === this.selectedOption || + option.type === this.selectedOption || + JSON.stringify(option) === this.selectedOption, + ); + } - public onInnerJsonFormsChange(event: any) { - // Check if data is equal to the passedProps.data - if (event !== this.passedProps.data) { - const updatedData = this.rendererService.getState().jsonforms.core.data; + public onInnerJsonFormsChange(event: any) { + // Check if data is equal to the passedProps.data + if (event !== this.passedProps.data) { + const updatedData = this.rendererService.getState().jsonforms.core.data; - // aktualisiere das aktuelle Datenobjekt - const pathSegments = this.passedProps.path.split('.'); - let current = updatedData; - for (let i = 0; i < pathSegments.length - 1; i++) { - current = current[pathSegments[i]]; - } - current[pathSegments[pathSegments.length - 1]] = event; + // aktualisiere das aktuelle Datenobjekt + const pathSegments = this.passedProps.path.split("."); + let current = updatedData; + for (let i = 0; i < pathSegments.length - 1; i++) { + current = current[pathSegments[i]]; + } + current[pathSegments[pathSegments.length - 1]] = event; - this.rendererService.setData(updatedData); - } + this.rendererService.setData(updatedData); } -} \ No newline at end of file + } +} diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts index 65c4d640d3..404a614e90 100644 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper.ts @@ -2,8 +2,8 @@ import { angularMaterialRenderers } from "@jsonforms/angular-material"; import { customRenderers } from "./customRenderer/custom-renderers"; export const configuredRenderer = [ - ...angularMaterialRenderers, ...customRenderers, + ...angularMaterialRenderers, ]; export class IngestorMetadataEditorHelper { @@ -14,19 +14,22 @@ export class IngestorMetadataEditorHelper { } if (schema.$ref) { - const refPath = schema.$ref.replace('#/', '').split('/'); + const refPath = schema.$ref.replace("#/", "").split("/"); let ref = rootSchema; refPath.forEach((part) => { ref = ref[part]; }); return IngestorMetadataEditorHelper.resolveRefs(ref, rootSchema); - } else if (typeof schema === 'object') { + } else if (typeof schema === "object") { for (const key in schema) { - if (schema.hasOwnProperty(key)) { - schema[key] = IngestorMetadataEditorHelper.resolveRefs(schema[key], rootSchema); + if (Object.prototype.hasOwnProperty.call(schema, key)) { + schema[key] = IngestorMetadataEditorHelper.resolveRefs( + schema[key], + rootSchema, + ); } } } return schema; - }; -}; \ No newline at end of file + } +} diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.scss b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.scss index 89fe8050d3..b41bf4ab35 100644 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.scss +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.scss @@ -1,3 +1,39 @@ .ingestor-metadata-editor { width: 100%; +} + +.spacer { + flex: 1 1 auto; +} + +mat-card { + .mat-mdc-card-title { + display: flex; + padding: 16px; + } +} + +.array-layout { + display: flex; + flex-direction: column; + gap: 16px; +} +.array-layout > * { + flex: 1 1 auto; +} +.array-layout-toolbar { + display: flex; + align-items: center; +} +.array-layout-title { + margin: 0; +} +.array-layout-toolbar > span { + flex: 1 1 auto; +} +.array-item { + padding: 16px; +} +::ng-deep .error-message-tooltip { + white-space: pre-line; } \ No newline at end of file diff --git a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts index 308a64143b..d4bf2a72e1 100644 --- a/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts +++ b/src/app/ingestor/ingestor-metadata-editor/ingestor-metadata-editor.component.ts @@ -1,19 +1,18 @@ -import { Component, EventEmitter, Output, Input } from '@angular/core'; -import { JsonSchema } from '@jsonforms/core'; -import { configuredRenderer } from './ingestor-metadata-editor-helper'; +import { Component, EventEmitter, Output, Input } from "@angular/core"; +import { JsonSchema } from "@jsonforms/core"; +import { configuredRenderer } from "./ingestor-metadata-editor-helper"; @Component({ - selector: 'app-metadata-editor', + selector: "app-metadata-editor", template: ``, + [data]="data" + [schema]="schema" + [renderers]="combinedRenderers" + (dataChange)="onDataChange($event)" + >`, }) - export class IngestorMetadataEditorComponent { - @Input() data: Object; + @Input() data: object; @Input() schema: JsonSchema; @Output() dataChange = new EventEmitter(); @@ -25,4 +24,4 @@ export class IngestorMetadataEditorComponent { onDataChange(event: any) { this.dataChange.emit(event); } -} \ No newline at end of file +} diff --git a/src/app/ingestor/ingestor.module.ts b/src/app/ingestor/ingestor.module.ts index eb79e0e644..9e9b54d6e1 100644 --- a/src/app/ingestor/ingestor.module.ts +++ b/src/app/ingestor/ingestor.module.ts @@ -9,8 +9,8 @@ import { MatFormFieldModule } from "@angular/material/form-field"; import { MatInputModule } from "@angular/material/input"; import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; import { FormsModule } from "@angular/forms"; -import { MatListModule } from '@angular/material/list'; -import { MatIconModule } from '@angular/material/icon'; +import { MatListModule } from "@angular/material/list"; +import { MatIconModule } from "@angular/material/icon"; import { MatTabsModule } from "@angular/material/tabs"; import { MatTableModule } from "@angular/material/table"; import { MatDialogModule } from "@angular/material/dialog"; @@ -18,50 +18,56 @@ import { MatSelectModule } from "@angular/material/select"; import { MatOptionModule } from "@angular/material/core"; import { MatAutocompleteModule } from "@angular/material/autocomplete"; import { IngestorNewTransferDialogComponent } from "./ingestor/dialog/ingestor.new-transfer-dialog.component"; -import { IngestorUserMetadataDialog } from "./ingestor/dialog/ingestor.user-metadata-dialog.component"; -import { JsonFormsModule } from '@jsonforms/angular'; +import { IngestorUserMetadataDialogComponent } from "./ingestor/dialog/ingestor.user-metadata-dialog.component"; +import { JsonFormsModule } from "@jsonforms/angular"; import { JsonFormsAngularMaterialModule } from "@jsonforms/angular-material"; -import { IngestorExtractorMetadataDialog } from "./ingestor/dialog/ingestor.extractor-metadata-dialog.component"; -import { IngestorConfirmTransferDialog } from "./ingestor/dialog/ingestor.confirm-transfer-dialog.component"; +import { IngestorExtractorMetadataDialogComponent } from "./ingestor/dialog/ingestor.extractor-metadata-dialog.component"; +import { IngestorConfirmTransferDialogComponent } from "./ingestor/dialog/ingestor.confirm-transfer-dialog.component"; import { MatStepperModule } from "@angular/material/stepper"; import { IngestorDialogStepperComponent } from "./ingestor/dialog/ingestor.dialog-stepper.component.component"; -import { AnyOfRenderer } from "./ingestor-metadata-editor/customRenderer/any-of-renderer"; -import { OneOfRenderer } from "./ingestor-metadata-editor/customRenderer/one-of-renderer"; +import { AnyOfRendererComponent } from "./ingestor-metadata-editor/customRenderer/any-of-renderer"; +import { OneOfRendererComponent } from "./ingestor-metadata-editor/customRenderer/one-of-renderer"; import { MatRadioModule } from "@angular/material/radio"; +import { ArrayLayoutRendererCustom } from "./ingestor-metadata-editor/customRenderer/array-renderer"; +import { MatBadgeModule } from "@angular/material/badge"; +import { MatTooltipModule } from "@angular/material/tooltip"; @NgModule({ declarations: [ IngestorComponent, IngestorMetadataEditorComponent, IngestorNewTransferDialogComponent, - IngestorUserMetadataDialog, - IngestorExtractorMetadataDialog, - IngestorConfirmTransferDialog, + IngestorUserMetadataDialogComponent, + IngestorExtractorMetadataDialogComponent, + IngestorConfirmTransferDialogComponent, IngestorDialogStepperComponent, - AnyOfRenderer, - OneOfRenderer, + AnyOfRendererComponent, + OneOfRendererComponent, + ArrayLayoutRendererCustom, ], imports: [ - CommonModule, - MatCardModule, - FormsModule, - MatFormFieldModule, - MatInputModule, - MatButtonModule, - MatProgressSpinnerModule, + CommonModule, + MatCardModule, + FormsModule, + MatFormFieldModule, + MatInputModule, + MatButtonModule, + MatProgressSpinnerModule, RouterModule, MatListModule, MatIconModule, MatTabsModule, MatTableModule, MatDialogModule, + MatTooltipModule, MatSelectModule, MatOptionModule, MatStepperModule, MatRadioModule, MatAutocompleteModule, + MatBadgeModule, JsonFormsModule, JsonFormsAngularMaterialModule, ], }) -export class IngestorModule { } +export class IngestorModule {} diff --git a/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts index 52285fca6b..36b22feb0d 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts @@ -1,20 +1,33 @@ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; -import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { IDialogDataObject, IIngestionRequestInformation, IngestorHelper, ISciCatHeader } from '../ingestor.component-helper'; +import { + ChangeDetectionStrategy, + Component, + Inject, + OnInit, +} from "@angular/core"; +import { MatDialog, MAT_DIALOG_DATA } from "@angular/material/dialog"; +import { + DialogDataObject, + IngestionRequestInformation, + IngestorHelper, + SciCatHeader, +} from "../ingestor.component-helper"; @Component({ - selector: 'ingestor.confirm-transfer-dialog', - templateUrl: 'ingestor.confirm-transfer-dialog.html', + selector: "ingestor.confirm-transfer-dialog", + templateUrl: "ingestor.confirm-transfer-dialog.html", changeDetection: ChangeDetectionStrategy.OnPush, - styleUrls: ['../ingestor.component.scss'], + styleUrls: ["../ingestor.component.scss"], }) +export class IngestorConfirmTransferDialogComponent implements OnInit { + createNewTransferData: IngestionRequestInformation = + IngestorHelper.createEmptyRequestInformation(); + provideMergeMetaData = ""; + backendURL = ""; -export class IngestorConfirmTransferDialog { - createNewTransferData: IIngestionRequestInformation = IngestorHelper.createEmptyRequestInformation(); - provideMergeMetaData: string = ''; - backendURL: string = ''; - - constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: IDialogDataObject) { + constructor( + public dialog: MatDialog, + @Inject(MAT_DIALOG_DATA) public data: DialogDataObject, + ) { this.createNewTransferData = data.createNewTransferData; this.backendURL = data.backendURL; } @@ -25,21 +38,24 @@ export class IngestorConfirmTransferDialog { createMetaDataString(): string { const space = 2; - const scicatMetadata: ISciCatHeader = { - datasetName: this.createNewTransferData.scicatHeader['datasetName'], - description: this.createNewTransferData.scicatHeader['description'], - creationLocation: this.createNewTransferData.scicatHeader['creationLocation'], - dataFormat: this.createNewTransferData.scicatHeader['dataFormat'], - ownerGroup: this.createNewTransferData.scicatHeader['ownerGroup'], - type: this.createNewTransferData.scicatHeader['type'], - license: this.createNewTransferData.scicatHeader['license'], - keywords: this.createNewTransferData.scicatHeader['keywords'], - filePath: this.createNewTransferData.scicatHeader['filePath'], + const scicatMetadata: SciCatHeader = { + datasetName: this.createNewTransferData.scicatHeader["datasetName"], + description: this.createNewTransferData.scicatHeader["description"], + creationLocation: + this.createNewTransferData.scicatHeader["creationLocation"], + dataFormat: this.createNewTransferData.scicatHeader["dataFormat"], + ownerGroup: this.createNewTransferData.scicatHeader["ownerGroup"], + type: this.createNewTransferData.scicatHeader["type"], + license: this.createNewTransferData.scicatHeader["license"], + keywords: this.createNewTransferData.scicatHeader["keywords"], + filePath: this.createNewTransferData.scicatHeader["filePath"], scientificMetadata: { - organizational: this.createNewTransferData.userMetaData['organizational'], - sample: this.createNewTransferData.userMetaData['sample'], - acquisition: this.createNewTransferData.extractorMetaData['acquisition'], - instrument: this.createNewTransferData.extractorMetaData['instrument'], + organizational: + this.createNewTransferData.userMetaData["organizational"], + sample: this.createNewTransferData.userMetaData["sample"], + acquisition: + this.createNewTransferData.extractorMetaData["acquisition"], + instrument: this.createNewTransferData.extractorMetaData["instrument"], }, }; @@ -54,8 +70,9 @@ export class IngestorConfirmTransferDialog { onClickConfirm(): void { if (this.data && this.data.onClickNext) { - this.createNewTransferData.mergedMetaDataString = this.provideMergeMetaData; + this.createNewTransferData.mergedMetaDataString = + this.provideMergeMetaData; this.data.onClickNext(4); } } -} \ No newline at end of file +} diff --git a/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.component.ts index cda2927a18..1531c5404f 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.component.ts @@ -1,10 +1,10 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input } from "@angular/core"; @Component({ - selector: 'ingestor-dialog-stepper', - templateUrl: './ingestor.dialog-stepper.component.html', - styleUrls: ['./ingestor.dialog-stepper.component.css'] + selector: "ingestor-dialog-stepper", + templateUrl: "./ingestor.dialog-stepper.component.html", + styleUrls: ["./ingestor.dialog-stepper.component.css"], }) export class IngestorDialogStepperComponent { - @Input() activeStep: number = 0; -} \ No newline at end of file + @Input() activeStep = 0; +} diff --git a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts index 97ba8e811d..9fa86aa138 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts @@ -1,39 +1,52 @@ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; -import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { JsonSchema } from '@jsonforms/core'; -import { IDialogDataObject, IIngestionRequestInformation, IngestorHelper } from '../ingestor.component-helper'; +import { ChangeDetectionStrategy, Component, Inject } from "@angular/core"; +import { MatDialog, MAT_DIALOG_DATA } from "@angular/material/dialog"; +import { JsonSchema } from "@jsonforms/core"; +import { + DialogDataObject, + IngestionRequestInformation, + IngestorHelper, +} from "../ingestor.component-helper"; @Component({ - selector: 'ingestor.extractor-metadata-dialog', - templateUrl: 'ingestor.extractor-metadata-dialog.html', - styleUrls: ['../ingestor.component.scss'], + selector: "ingestor.extractor-metadata-dialog", + templateUrl: "ingestor.extractor-metadata-dialog.html", + styleUrls: ["../ingestor.component.scss"], changeDetection: ChangeDetectionStrategy.OnPush, }) - -export class IngestorExtractorMetadataDialog { +export class IngestorExtractorMetadataDialogComponent { metadataSchemaInstrument: JsonSchema; metadataSchemaAcquisition: JsonSchema; - createNewTransferData: IIngestionRequestInformation = IngestorHelper.createEmptyRequestInformation(); + createNewTransferData: IngestionRequestInformation = + IngestorHelper.createEmptyRequestInformation(); - backendURL: string = ''; - extractorMetaDataReady: boolean = false; - extractorMetaDataError: boolean = false; + backendURL = ""; + extractorMetaDataReady = false; + extractorMetaDataError = false; isCardContentVisible = { instrument: true, - acquisition: true + acquisition: true, }; - constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: IDialogDataObject) { - this.createNewTransferData = data.createNewTransferData; - this.backendURL = data.backendURL; - const instrumentSchema = this.createNewTransferData.selectedResolvedDecodedSchema.properties.instrument; - const acqusitionSchema = this.createNewTransferData.selectedResolvedDecodedSchema.properties.acquisition; - - this.metadataSchemaInstrument = instrumentSchema; - this.metadataSchemaAcquisition = acqusitionSchema; - this.extractorMetaDataReady = this.createNewTransferData.extractorMetaDataReady; - this.extractorMetaDataError = this.createNewTransferData.apiErrorInformation.metaDataExtraction; + constructor( + public dialog: MatDialog, + @Inject(MAT_DIALOG_DATA) public data: DialogDataObject, + ) { + this.createNewTransferData = data.createNewTransferData; + this.backendURL = data.backendURL; + const instrumentSchema = + this.createNewTransferData.selectedResolvedDecodedSchema.properties + .instrument; + const acqusitionSchema = + this.createNewTransferData.selectedResolvedDecodedSchema.properties + .acquisition; + + this.metadataSchemaInstrument = instrumentSchema; + this.metadataSchemaAcquisition = acqusitionSchema; + this.extractorMetaDataReady = + this.createNewTransferData.extractorMetaDataReady; + this.extractorMetaDataError = + this.createNewTransferData.apiErrorInformation.metaDataExtraction; } onClickBack(): void { @@ -48,15 +61,15 @@ export class IngestorExtractorMetadataDialog { } } - onDataChangeExtractorMetadataInstrument(event: Object) { - this.createNewTransferData.extractorMetaData['instrument'] = event; + onDataChangeExtractorMetadataInstrument(event: any) { + this.createNewTransferData.extractorMetaData["instrument"] = event; } - onDataChangeExtractorMetadataAcquisition(event: Object) { - this.createNewTransferData.extractorMetaData['acquisition'] = event; + onDataChangeExtractorMetadataAcquisition(event: any) { + this.createNewTransferData.extractorMetaData["acquisition"] = event; } toggleCardContent(card: string): void { this.isCardContentVisible[card] = !this.isCardContentVisible[card]; } -} \ No newline at end of file +} diff --git a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts index 198b11fdfd..ba4cae3bef 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts @@ -1,29 +1,43 @@ -import { ChangeDetectionStrategy, Component, Inject, OnInit } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; -import { HttpClient } from '@angular/common/http'; -import { INGESTOR_API_ENDPOINTS_V1 } from '../ingestor-api-endpoints'; -import { IDialogDataObject, IExtractionMethod, IIngestionRequestInformation, IngestorHelper } from '../ingestor.component-helper'; -import { IngestorMetadataEditorHelper } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; +import { + ChangeDetectionStrategy, + Component, + Inject, + OnInit, +} from "@angular/core"; +import { MAT_DIALOG_DATA, MatDialog } from "@angular/material/dialog"; +import { HttpClient } from "@angular/common/http"; +import { INGESTOR_API_ENDPOINTS_V1 } from "../ingestor-api-endpoints"; +import { + DialogDataObject, + ExtractionMethod, + IngestionRequestInformation, + IngestorHelper, +} from "../ingestor.component-helper"; +import { IngestorMetadataEditorHelper } from "ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper"; @Component({ - selector: 'ingestor.new-transfer-dialog', - templateUrl: 'ingestor.new-transfer-dialog.html', + selector: "ingestor.new-transfer-dialog", + templateUrl: "ingestor.new-transfer-dialog.html", changeDetection: ChangeDetectionStrategy.OnPush, - styleUrls: ['../ingestor.component.scss'] + styleUrls: ["../ingestor.component.scss"], }) - export class IngestorNewTransferDialogComponent implements OnInit { - extractionMethods: IExtractionMethod[] = []; + extractionMethods: ExtractionMethod[] = []; availableFilePaths: string[] = []; - backendURL: string = ''; - extractionMethodsError: string = ''; - availableFilePathsError: string = ''; + backendURL = ""; + extractionMethodsError = ""; + availableFilePathsError = ""; - uiNextButtonReady: boolean = false; + uiNextButtonReady = false; - createNewTransferData: IIngestionRequestInformation = IngestorHelper.createEmptyRequestInformation(); + createNewTransferData: IngestionRequestInformation = + IngestorHelper.createEmptyRequestInformation(); - constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: IDialogDataObject, private http: HttpClient) { + constructor( + public dialog: MatDialog, + @Inject(MAT_DIALOG_DATA) public data: DialogDataObject, + private http: HttpClient, + ) { this.createNewTransferData = data.createNewTransferData; this.backendURL = data.backendURL; } @@ -42,66 +56,75 @@ export class IngestorNewTransferDialogComponent implements OnInit { return this.createNewTransferData.selectedPath; } - set selectedMethod(value: IExtractionMethod) { + set selectedMethod(value: ExtractionMethod) { this.createNewTransferData.selectedMethod = value; this.validateNextButton(); } - get selectedMethod(): IExtractionMethod { + get selectedMethod(): ExtractionMethod { return this.createNewTransferData.selectedMethod; } apiGetExtractionMethods(): void { - this.http.get(this.backendURL + INGESTOR_API_ENDPOINTS_V1.EXTRACTOR).subscribe( - (response: any) => { - if (response.methods && response.methods.length > 0) { - this.extractionMethods = response.methods; - } - else { - this.extractionMethodsError = 'No extraction methods found.'; - } - }, - (error: any) => { - this.extractionMethodsError = error.message; - console.error(this.extractionMethodsError); - } - ); + this.http + .get(this.backendURL + INGESTOR_API_ENDPOINTS_V1.EXTRACTOR) + .subscribe( + (response: any) => { + if (response.methods && response.methods.length > 0) { + this.extractionMethods = response.methods; + } else { + this.extractionMethodsError = "No extraction methods found."; + } + }, + (error: any) => { + this.extractionMethodsError = error.message; + console.error(this.extractionMethodsError); + }, + ); } apiGetAvailableFilePaths(): void { - this.http.get(this.backendURL + INGESTOR_API_ENDPOINTS_V1.DATASET).subscribe( - (response: any) => { - if (response.datasets && response.datasets.length > 0) { - this.availableFilePaths = response.datasets; - } - else { - this.availableFilePathsError = 'No datasets found.'; - } - }, - (error: any) => { - this.availableFilePathsError = error.message; - console.error(this.availableFilePathsError); - } - ); + this.http + .get(this.backendURL + INGESTOR_API_ENDPOINTS_V1.DATASET) + .subscribe( + (response: any) => { + if (response.datasets && response.datasets.length > 0) { + this.availableFilePaths = response.datasets; + } else { + this.availableFilePathsError = "No datasets found."; + } + }, + (error: any) => { + this.availableFilePathsError = error.message; + console.error(this.availableFilePathsError); + }, + ); } generateExampleDataForSciCatHeader(): void { - this.data.createNewTransferData.scicatHeader['filePath'] = this.createNewTransferData.selectedPath; - this.data.createNewTransferData.scicatHeader['keywords'] = ['OpenEM']; - - const nameWithoutPath = this.createNewTransferData.selectedPath.split('/|\\')[-1] ?? this.createNewTransferData.selectedPath; - this.data.createNewTransferData.scicatHeader['datasetName'] = nameWithoutPath; - this.data.createNewTransferData.scicatHeader['license'] = 'MIT License'; - this.data.createNewTransferData.scicatHeader['type'] = 'raw'; - this.data.createNewTransferData.scicatHeader['dataFormat'] = 'root'; - this.data.createNewTransferData.scicatHeader['owner'] = 'User'; + this.data.createNewTransferData.scicatHeader["filePath"] = + this.createNewTransferData.selectedPath; + this.data.createNewTransferData.scicatHeader["keywords"] = ["OpenEM"]; + + const nameWithoutPath = + this.createNewTransferData.selectedPath.split("/|\\")[-1] ?? + this.createNewTransferData.selectedPath; + this.data.createNewTransferData.scicatHeader["datasetName"] = + nameWithoutPath; + this.data.createNewTransferData.scicatHeader["license"] = "MIT License"; + this.data.createNewTransferData.scicatHeader["type"] = "raw"; + this.data.createNewTransferData.scicatHeader["dataFormat"] = "root"; + this.data.createNewTransferData.scicatHeader["owner"] = "User"; } prepareSchemaForProcessing(): void { const encodedSchema = this.createNewTransferData.selectedMethod.schema; const decodedSchema = atob(encodedSchema); const schema = JSON.parse(decodedSchema); - const resolvedSchema = IngestorMetadataEditorHelper.resolveRefs(schema, schema); + const resolvedSchema = IngestorMetadataEditorHelper.resolveRefs( + schema, + schema, + ); this.createNewTransferData.selectedResolvedDecodedSchema = resolvedSchema; } @@ -119,6 +142,8 @@ export class IngestorNewTransferDialogComponent implements OnInit { } validateNextButton(): void { - this.uiNextButtonReady = !!this.createNewTransferData.selectedPath && !!this.createNewTransferData.selectedMethod?.name; + this.uiNextButtonReady = + !!this.createNewTransferData.selectedPath && + !!this.createNewTransferData.selectedMethod?.name; } -} \ No newline at end of file +} diff --git a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts index 427bacd801..39f6298523 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts @@ -1,38 +1,53 @@ -import {ChangeDetectionStrategy, Component, Inject} from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; -import { IngestorMetadataEditorHelper } from 'ingestor/ingestor-metadata-editor/ingestor-metadata-editor-helper'; -import { JsonSchema } from '@jsonforms/core'; -import { IDialogDataObject, IIngestionRequestInformation, IngestorHelper, SciCatHeader_Schema } from '../ingestor.component-helper'; +import { ChangeDetectionStrategy, Component, Inject } from "@angular/core"; +import { MAT_DIALOG_DATA, MatDialog } from "@angular/material/dialog"; +import { JsonSchema } from "@jsonforms/core"; +import { + DialogDataObject, + IngestionRequestInformation, + IngestorHelper, + SciCatHeader_Schema, +} from "../ingestor.component-helper"; @Component({ - selector: 'ingestor.user-metadata-dialog', - templateUrl: 'ingestor.user-metadata-dialog.html', + selector: "ingestor.user-metadata-dialog", + templateUrl: "ingestor.user-metadata-dialog.html", changeDetection: ChangeDetectionStrategy.OnPush, - styleUrls: ['../ingestor.component.scss'], + styleUrls: ["../ingestor.component.scss"], }) - -export class IngestorUserMetadataDialog { +export class IngestorUserMetadataDialogComponent { metadataSchemaOrganizational: JsonSchema; metadataSchemaSample: JsonSchema; scicatHeaderSchema: JsonSchema; - createNewTransferData: IIngestionRequestInformation = IngestorHelper.createEmptyRequestInformation(); - backendURL: string = ''; + createNewTransferData: IngestionRequestInformation = + IngestorHelper.createEmptyRequestInformation(); + backendURL = ""; - uiNextButtonReady: boolean = true; // Change to false when dev is ready + uiNextButtonReady = true; // Change to false when dev is ready isCardContentVisible = { scicat: true, organizational: true, - sample: true + sample: true, }; - constructor(public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: IDialogDataObject) { + constructor( + public dialog: MatDialog, + @Inject(MAT_DIALOG_DATA) public data: DialogDataObject, + ) { this.createNewTransferData = data.createNewTransferData; this.backendURL = data.backendURL; - const organizationalSchema = this.createNewTransferData.selectedResolvedDecodedSchema.properties.organizational; - const sampleSchema = this.createNewTransferData.selectedResolvedDecodedSchema.properties.sample; - + const organizationalSchema = + this.createNewTransferData.selectedResolvedDecodedSchema.properties + .organizational; + const sampleSchema = + this.createNewTransferData.selectedResolvedDecodedSchema.properties + .sample; + this.metadataSchemaOrganizational = organizationalSchema; + + // TODO Remove after debug + console.log("organizationalSchema", organizationalSchema); + this.metadataSchemaSample = sampleSchema; this.scicatHeaderSchema = SciCatHeader_Schema; } @@ -49,19 +64,19 @@ export class IngestorUserMetadataDialog { } } - onDataChangeUserMetadataOrganization(event: Object) { - this.createNewTransferData.userMetaData['organizational'] = event; + onDataChangeUserMetadataOrganization(event: any) { + this.createNewTransferData.userMetaData["organizational"] = event; } - onDataChangeUserMetadataSample(event: Object) { - this.createNewTransferData.userMetaData['sample'] = event; + onDataChangeUserMetadataSample(event: any) { + this.createNewTransferData.userMetaData["sample"] = event; } - onDataChangeUserScicatHeader(event: Object) { + onDataChangeUserScicatHeader(event: any) { this.createNewTransferData.scicatHeader = event; } toggleCardContent(card: string): void { this.isCardContentVisible[card] = !this.isCardContentVisible[card]; } -} \ No newline at end of file +} diff --git a/src/app/ingestor/ingestor/ingestor-api-endpoints.ts b/src/app/ingestor/ingestor/ingestor-api-endpoints.ts index df2d088330..acd08c0177 100644 --- a/src/app/ingestor/ingestor/ingestor-api-endpoints.ts +++ b/src/app/ingestor/ingestor/ingestor-api-endpoints.ts @@ -2,16 +2,16 @@ export const INGESTOR_API_ENDPOINTS_V1 = { DATASET: "dataset", TRANSFER: "transfer", OTHER: { - VERSION: 'version', + VERSION: "version", }, - EXTRACTOR: 'extractor', + EXTRACTOR: "extractor", }; -export interface IPostExtractorEndpoint { - filePath: string, - methodName: string, +export interface PostExtractorEndpoint { + filePath: string; + methodName: string; } -export interface IPostDatasetEndpoint { - metaData: string -} \ No newline at end of file +export interface PostDatasetEndpoint { + metaData: string; +} diff --git a/src/app/ingestor/ingestor/ingestor.component-helper.ts b/src/app/ingestor/ingestor/ingestor.component-helper.ts index 1d31525d57..c022782eae 100644 --- a/src/app/ingestor/ingestor/ingestor.component-helper.ts +++ b/src/app/ingestor/ingestor/ingestor.component-helper.ts @@ -1,22 +1,22 @@ -import { JsonSchema } from '@jsonforms/core'; +import { JsonSchema } from "@jsonforms/core"; -export interface IExtractionMethod { +export interface ExtractionMethod { name: string; schema: string; // Base64 encoded JSON schema -}; +} -export interface IIngestionRequestInformation { +export interface IngestionRequestInformation { selectedPath: string; - selectedMethod: IExtractionMethod; + selectedMethod: ExtractionMethod; selectedResolvedDecodedSchema: JsonSchema; - scicatHeader: Object; + scicatHeader: object; userMetaData: { - organizational: Object, - sample: Object, + organizational: object; + sample: object; }; extractorMetaData: { - instrument: Object, - acquisition: Object, + instrument: object; + acquisition: object; }; extractorMetaDataReady: boolean; extractMetaDataRequested: boolean; @@ -24,11 +24,16 @@ export interface IIngestionRequestInformation { apiErrorInformation: { metaDataExtraction: boolean; - } + }; +} + +export interface TransferDataListEntry { + transferId: string; + status: string; } // There are many more... see DerivedDataset.ts -export interface ISciCatHeader { +export interface SciCatHeader { datasetName: string; description: string; creationLocation: string; @@ -38,27 +43,33 @@ export interface ISciCatHeader { license: string; keywords: string[]; filePath: string; - scientificMetadata: IScientificMetadata; + scientificMetadata: ScientificMetadata; } -export interface IScientificMetadata { - organizational: Object; - sample: Object; - acquisition: Object; - instrument: Object; +export interface ScientificMetadata { + organizational: object; + sample: object; + acquisition: object; + instrument: object; } -export interface IDialogDataObject { - createNewTransferData: IIngestionRequestInformation; +export interface MetadataExtractorResult { + cmdStdErr: string; + cmdStdOut: string; + result: string; +} + +export interface DialogDataObject { + createNewTransferData: IngestionRequestInformation; backendURL: string; onClickNext: (step: number) => void; } export class IngestorHelper { - static createEmptyRequestInformation = (): IIngestionRequestInformation => { + static createEmptyRequestInformation = (): IngestionRequestInformation => { return { - selectedPath: '', - selectedMethod: { name: '', schema: '' }, + selectedPath: "", + selectedMethod: { name: "", schema: "" }, selectedResolvedDecodedSchema: {}, scicatHeader: {}, userMetaData: { @@ -71,18 +82,27 @@ export class IngestorHelper { }, extractorMetaDataReady: false, extractMetaDataRequested: false, - mergedMetaDataString: '', + mergedMetaDataString: "", apiErrorInformation: { metaDataExtraction: false, }, }; }; - static mergeUserAndExtractorMetadata(userMetadata: Object, extractorMetadata: Object, space: number): string { - return JSON.stringify({ ...userMetadata, ...extractorMetadata }, null, space); - }; + static mergeUserAndExtractorMetadata( + userMetadata: object, + extractorMetadata: object, + space: number, + ): string { + return JSON.stringify( + { ...userMetadata, ...extractorMetadata }, + null, + space, + ); + } } +// eslint-disable-next-line @typescript-eslint/naming-convention export const SciCatHeader_Schema: JsonSchema = { type: "object", properties: { @@ -96,9 +116,19 @@ export const SciCatHeader_Schema: JsonSchema = { license: { type: "string" }, keywords: { type: "array", - items: { type: "string" } + items: { type: "string" }, }, // scientificMetadata: { type: "string" } ; is created during the ingestor process }, - required: ["datasetName", "creationLocation", "dataFormat", "ownerGroup", "type", "license", "keywords", "scientificMetadata", "filePath"] -} \ No newline at end of file + required: [ + "datasetName", + "creationLocation", + "dataFormat", + "ownerGroup", + "type", + "license", + "keywords", + "scientificMetadata", + "filePath", + ], +}; diff --git a/src/app/ingestor/ingestor/ingestor.component.spec.ts b/src/app/ingestor/ingestor/ingestor.component.spec.ts index 52186b1077..dd70900334 100644 --- a/src/app/ingestor/ingestor/ingestor.component.spec.ts +++ b/src/app/ingestor/ingestor/ingestor.component.spec.ts @@ -1,15 +1,14 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { IngestorComponent } from './ingestor.component'; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { IngestorComponent } from "./ingestor.component"; -describe('IngestorComponent', () => { +describe("IngestorComponent", () => { let component: IngestorComponent; let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ IngestorComponent ] - }) - .compileComponents(); + declarations: [IngestorComponent], + }).compileComponents(); }); beforeEach(() => { @@ -18,7 +17,7 @@ describe('IngestorComponent', () => { fixture.detectChanges(); }); - it('should create', () => { + it("should create", () => { expect(component).toBeTruthy(); }); -}); \ No newline at end of file +}); diff --git a/src/app/ingestor/ingestor/ingestor.component.ts b/src/app/ingestor/ingestor/ingestor.component.ts index 2b49f2f90a..637cf7f949 100644 --- a/src/app/ingestor/ingestor/ingestor.component.ts +++ b/src/app/ingestor/ingestor/ingestor.component.ts @@ -1,19 +1,24 @@ import { Component, inject, OnInit } from "@angular/core"; import { AppConfigService } from "app-config.service"; -import { HttpClient } from '@angular/common/http'; -import { ActivatedRoute, Router } from '@angular/router'; -import { INGESTOR_API_ENDPOINTS_V1, IPostDatasetEndpoint, IPostExtractorEndpoint } from "./ingestor-api-endpoints"; +import { HttpClient } from "@angular/common/http"; +import { ActivatedRoute, Router } from "@angular/router"; +import { + INGESTOR_API_ENDPOINTS_V1, + PostDatasetEndpoint, + PostExtractorEndpoint, +} from "./ingestor-api-endpoints"; import { IngestorNewTransferDialogComponent } from "./dialog/ingestor.new-transfer-dialog.component"; import { MatDialog } from "@angular/material/dialog"; -import { IngestorUserMetadataDialog } from "./dialog/ingestor.user-metadata-dialog.component"; -import { IngestorExtractorMetadataDialog } from "./dialog/ingestor.extractor-metadata-dialog.component"; -import { IngestorConfirmTransferDialog } from "./dialog/ingestor.confirm-transfer-dialog.component"; -import { IIngestionRequestInformation, IngestorHelper } from "./ingestor.component-helper"; - -interface ITransferDataListEntry { - transferId: string; - status: string; -} +import { IngestorUserMetadataDialogComponent } from "./dialog/ingestor.user-metadata-dialog.component"; +import { IngestorExtractorMetadataDialogComponent } from "./dialog/ingestor.extractor-metadata-dialog.component"; +import { IngestorConfirmTransferDialogComponent } from "./dialog/ingestor.confirm-transfer-dialog.component"; +import { + IngestionRequestInformation, + IngestorHelper, + MetadataExtractorResult, + ScientificMetadata, + TransferDataListEntry, +} from "./ingestor.component-helper"; @Component({ selector: "ingestor", @@ -23,37 +28,42 @@ interface ITransferDataListEntry { export class IngestorComponent implements OnInit { readonly dialog = inject(MatDialog); - filePath: string = ''; - loading: boolean = false; - forwardFacilityBackend: string = ''; + filePath = ""; + loading = false; + forwardFacilityBackend = ""; - connectedFacilityBackend: string = ''; - connectedFacilityBackendVersion: string = ''; - connectingToFacilityBackend: boolean = false; + connectedFacilityBackend = ""; + connectedFacilityBackendVersion = ""; + connectingToFacilityBackend = false; lastUsedFacilityBackends: string[] = []; - transferDataSource: ITransferDataListEntry[] = []; // List of files to be transferred - displayedColumns: string[] = ['transferId', 'status', 'actions']; + transferDataSource: TransferDataListEntry[] = []; // List of files to be transferred + displayedColumns: string[] = ["transferId", "status", "actions"]; - errorMessage: string = ''; - returnValue: string = ''; + errorMessage = ""; + returnValue = ""; - createNewTransferData: IIngestionRequestInformation = IngestorHelper.createEmptyRequestInformation(); + createNewTransferData: IngestionRequestInformation = + IngestorHelper.createEmptyRequestInformation(); - constructor(public appConfigService: AppConfigService, private http: HttpClient, private route: ActivatedRoute, private router: Router) { } + constructor( + public appConfigService: AppConfigService, + private http: HttpClient, + private route: ActivatedRoute, + private router: Router, + ) {} ngOnInit() { this.connectingToFacilityBackend = true; this.lastUsedFacilityBackends = this.loadLastUsedFacilityBackends(); this.transferDataSource = []; // Get the GET parameter 'backendUrl' from the URL - this.route.queryParams.subscribe(params => { - const backendUrl = params['backendUrl']; + this.route.queryParams.subscribe((params) => { + const backendUrl = params["backendUrl"]; if (backendUrl) { this.apiConnectToFacilityBackend(backendUrl); - } - else { + } else { this.connectingToFacilityBackend = false; } }); @@ -62,35 +72,40 @@ export class IngestorComponent implements OnInit { apiConnectToFacilityBackend(facilityBackendUrl: string): boolean { let facilityBackendUrlCleaned = facilityBackendUrl.slice(); // Check if last symbol is a slash and add version endpoint - if (!facilityBackendUrlCleaned.endsWith('/')) { - facilityBackendUrlCleaned += '/'; + if (!facilityBackendUrlCleaned.endsWith("/")) { + facilityBackendUrlCleaned += "/"; } - let facilityBackendUrlVersion = facilityBackendUrlCleaned + INGESTOR_API_ENDPOINTS_V1.OTHER.VERSION; + const facilityBackendUrlVersion = + facilityBackendUrlCleaned + INGESTOR_API_ENDPOINTS_V1.OTHER.VERSION; // Try to connect to the facility backend/version to check if it is available - console.log('Connecting to facility backend: ' + facilityBackendUrlVersion); + console.log("Connecting to facility backend: " + facilityBackendUrlVersion); this.http.get(facilityBackendUrlVersion).subscribe( - response => { - console.log('Connected to facility backend', response); + (response) => { + console.log("Connected to facility backend", response); // If the connection is successful, store the connected facility backend URL this.connectedFacilityBackend = facilityBackendUrlCleaned; this.connectingToFacilityBackend = false; - this.connectedFacilityBackendVersion = response['version']; + this.connectedFacilityBackendVersion = response["version"]; }, - error => { + (error) => { this.errorMessage += `${new Date().toLocaleString()}: ${error.message}
`; - console.error('Request failed', error); - this.connectedFacilityBackend = ''; + console.error("Request failed", error); + this.connectedFacilityBackend = ""; this.connectingToFacilityBackend = false; this.lastUsedFacilityBackends = this.loadLastUsedFacilityBackends(); - } + }, ); return true; } - apiGetTransferList(page: number, pageSize: number, transferId?: string): void { + apiGetTransferList( + page: number, + pageSize: number, + transferId?: string, + ): void { const params: any = { page: page.toString(), pageSize: pageSize.toString(), @@ -98,72 +113,97 @@ export class IngestorComponent implements OnInit { if (transferId) { params.transferId = transferId; } - this.http.get(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.TRANSFER, { params }).subscribe( - response => { - console.log('Transfer list received', response); - this.transferDataSource = response['transfers']; - }, - error => { - this.errorMessage += `${new Date().toLocaleString()}: ${error.message}]
`; - console.error('Request failed', error); - } - ); + this.http + .get(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.TRANSFER, { + params, + }) + .subscribe( + (response) => { + console.log("Transfer list received", response); + this.transferDataSource = response["transfers"]; + }, + (error) => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}]
`; + console.error("Request failed", error); + }, + ); } apiUpload() { this.loading = true; - const payload: IPostDatasetEndpoint = { + const payload: PostDatasetEndpoint = { metaData: this.createNewTransferData.mergedMetaDataString, }; - this.http.post(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.DATASET, payload).subscribe( - response => { - console.log('Upload successfully started', response); - this.returnValue = JSON.stringify(response); - this.loading = false; - }, - error => { - this.errorMessage += `${new Date().toLocaleString()}: ${error.message}]
`; - console.error('Upload failed', error); - this.loading = false; - } - ); + this.http + .post( + this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.DATASET, + payload, + ) + .subscribe( + (response) => { + console.log("Upload successfully started", response); + this.returnValue = JSON.stringify(response); + this.loading = false; + }, + (error) => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}]
`; + console.error("Upload failed", error); + this.loading = false; + }, + ); } async apiStartMetadataExtraction(): Promise { this.createNewTransferData.apiErrorInformation.metaDataExtraction = false; if (this.createNewTransferData.extractMetaDataRequested) { - console.log(this.createNewTransferData.extractMetaDataRequested, ' already requested'); // Debugging + console.log( + this.createNewTransferData.extractMetaDataRequested, + " already requested", + ); // Debugging return false; } this.createNewTransferData.extractorMetaDataReady = false; this.createNewTransferData.extractMetaDataRequested = true; - const payload: IPostExtractorEndpoint = { + const payload: PostExtractorEndpoint = { filePath: this.createNewTransferData.selectedPath, methodName: this.createNewTransferData.selectedMethod.name, }; return new Promise((resolve) => { - this.http.post(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.EXTRACTOR, payload).subscribe( - response => { - console.log('Metadata extraction result', response); - this.createNewTransferData.extractorMetaData.instrument = (response as any).instrument ?? {}; - this.createNewTransferData.extractorMetaData.acquisition = (response as any).acquisition ?? {}; - this.createNewTransferData.extractorMetaDataReady = true; - resolve(true); - }, - error => { - this.errorMessage += `${new Date().toLocaleString()}: ${error.message}]
`; - console.error('Metadata extraction failed', error); - this.createNewTransferData.extractorMetaDataReady = true; - this.createNewTransferData.apiErrorInformation.metaDataExtraction = true; - resolve(false); - } - ); + this.http + .post( + this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.EXTRACTOR, + payload, + ) + .subscribe( + (response) => { + console.log("Metadata extraction result", response); + const extractedMetadata = response as MetadataExtractorResult; + const extractedScientificMetadata = JSON.parse( + extractedMetadata.result, + ) as ScientificMetadata; + + this.createNewTransferData.extractorMetaData.instrument = + extractedScientificMetadata.instrument ?? {}; + this.createNewTransferData.extractorMetaData.acquisition = + extractedScientificMetadata.acquisition ?? {}; + this.createNewTransferData.extractorMetaDataReady = true; + resolve(true); + }, + (error) => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}]
`; + console.error("Metadata extraction failed", error); + this.createNewTransferData.extractorMetaDataReady = true; + this.createNewTransferData.apiErrorInformation.metaDataExtraction = + true; + resolve(false); + }, + ); }); } @@ -177,15 +217,17 @@ export class IngestorComponent implements OnInit { return; } - this.router.navigate(['/ingestor'], { queryParams: { backendUrl: this.forwardFacilityBackend } }); + this.router.navigate(["/ingestor"], { + queryParams: { backendUrl: this.forwardFacilityBackend }, + }); } } onClickDisconnectIngestor() { - this.returnValue = ''; - this.connectedFacilityBackend = ''; + this.returnValue = ""; + this.connectedFacilityBackend = ""; // Remove the GET parameter 'backendUrl' from the URL - this.router.navigate(['/ingestor']); + this.router.navigate(["/ingestor"]); } // Helper functions @@ -195,7 +237,8 @@ export class IngestorComponent implements OnInit { loadLastUsedFacilityBackends(): string[] { // Load the list from the local Storage - const lastUsedFacilityBackends = '["http://localhost:8000", "http://localhost:8888"]'; + const lastUsedFacilityBackends = + '["http://localhost:8000", "http://localhost:8888"]'; if (lastUsedFacilityBackends) { return JSON.parse(lastUsedFacilityBackends); } @@ -203,7 +246,7 @@ export class IngestorComponent implements OnInit { } clearErrorMessage(): void { - this.errorMessage = ''; + this.errorMessage = ""; } onClickAddIngestion(): void { @@ -221,41 +264,59 @@ export class IngestorComponent implements OnInit { this.createNewTransferData.extractMetaDataRequested = false; this.createNewTransferData.extractorMetaDataReady = false; dialogRef = this.dialog.open(IngestorNewTransferDialogComponent, { - data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData, backendURL: this.connectedFacilityBackend }, - disableClose: true + data: { + onClickNext: this.onClickNext.bind(this), + createNewTransferData: this.createNewTransferData, + backendURL: this.connectedFacilityBackend, + }, + disableClose: true, }); break; case 1: - this.apiStartMetadataExtraction().then((response: boolean) => { - if (response) console.log('Metadata extraction finished'); - else console.error('Metadata extraction failed'); - }).catch(error => { - console.error('Metadata extraction error', error); - }); - - dialogRef = this.dialog.open(IngestorUserMetadataDialog, { - data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData, backendURL: this.connectedFacilityBackend }, - disableClose: true + this.apiStartMetadataExtraction() + .then((response: boolean) => { + if (response) console.log("Metadata extraction finished"); + else console.error("Metadata extraction failed"); + }) + .catch((error) => { + console.error("Metadata extraction error", error); + }); + + dialogRef = this.dialog.open(IngestorUserMetadataDialogComponent, { + data: { + onClickNext: this.onClickNext.bind(this), + createNewTransferData: this.createNewTransferData, + backendURL: this.connectedFacilityBackend, + }, + disableClose: true, }); break; case 2: - dialogRef = this.dialog.open(IngestorExtractorMetadataDialog, { - data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData, backendURL: this.connectedFacilityBackend }, - disableClose: true + dialogRef = this.dialog.open(IngestorExtractorMetadataDialogComponent, { + data: { + onClickNext: this.onClickNext.bind(this), + createNewTransferData: this.createNewTransferData, + backendURL: this.connectedFacilityBackend, + }, + disableClose: true, }); break; case 3: - dialogRef = this.dialog.open(IngestorConfirmTransferDialog, { - data: { onClickNext: this.onClickNext.bind(this), createNewTransferData: this.createNewTransferData, backendURL: this.connectedFacilityBackend }, - disableClose: true + dialogRef = this.dialog.open(IngestorConfirmTransferDialogComponent, { + data: { + onClickNext: this.onClickNext.bind(this), + createNewTransferData: this.createNewTransferData, + backendURL: this.connectedFacilityBackend, + }, + disableClose: true, }); break; case 4: this.apiUpload(); break; default: - console.error('Unknown step', step); + console.error("Unknown step", step); } // Error if the dialog reference is not set @@ -267,16 +328,23 @@ export class IngestorComponent implements OnInit { } onCancelTransfer(transferId: string) { - console.log('Cancel transfer', transferId); - this.http.delete(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.TRANSFER + '/' + transferId).subscribe( - response => { - console.log('Transfer cancelled', response); - this.apiGetTransferList(1, 100); - }, - error => { - this.errorMessage += `${new Date().toLocaleString()}: ${error.message}
`; - console.error('Cancel transfer failed', error); - } - ); + console.log("Cancel transfer", transferId); + this.http + .delete( + this.connectedFacilityBackend + + INGESTOR_API_ENDPOINTS_V1.TRANSFER + + "/" + + transferId, + ) + .subscribe( + (response) => { + console.log("Transfer cancelled", response); + this.apiGetTransferList(1, 100); + }, + (error) => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}
`; + console.error("Cancel transfer failed", error); + }, + ); } -} \ No newline at end of file +} From 52a45e60434ad17f83bd81e76beb5d87bd616c6c Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Thu, 9 Jan 2025 16:10:07 +0000 Subject: [PATCH 074/245] reloading page works as intended --- src/app/datasets/onedep/onedep.component.html | 6 +- src/app/datasets/onedep/onedep.component.ts | 107 +++-- src/app/datasets/onedep/types/methods.enum.ts | 390 +++++++++--------- .../effects/onedep.effects.ts | 2 +- src/app/state-management/models/index.ts | 1 + 5 files changed, 251 insertions(+), 255 deletions(-) diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index 4887573eef..8c33b6b819 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -113,6 +113,7 @@

Enter 16-digit ORCID iD

formControlName="orcidId" orcidFormatter > + - -
-
-

Enter 16-digit ORCID iD

-

Owners of these ORCIDs iDs are - allowed to access this deposition.

- - -
- -
- - Enter 16-digits ORCID iD -
- - - -
-
-
-
-
- - -
- -
- - -

Choose Electron Microscopy - Method

- - experimental method - - - {{ method.viewValue }} - - - - -
- You must specify the experimental method -
-
- - - - - Are you deposing - coordinates with this - submission? (for PDB) - - - - Yes - - - No - - - - - - Has an associated map been deposited to EMDB? - - - - - Yes - - - No - - - - - - - - EMDB Identifier - - - EMDB ID - - - - - - - - Is this a composite map? - - - - - Yes - - - No - - - - -
+
+ + +
+ assignment +
+ General information +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + {{ keyword }} + + + +
Name{{ dataset.datasetName || "-" }}
Description + +
PID + {{ dataset.pid }} +
Type{{dataset.type }}
Creation Time{{ dataset.creationTime | date: "yyyy-MM-dd + HH:mm" }} +
Keywords
+
+
+ + +
+ blur_linear +
+ Administrative and Method Information: +
+
+
+

Obtain OneDep Token

+

Go to OneDep API and choose option to sign in with ORCID. After the authorization you will be redirected back to OneDep. In the left bar below the map, tick the Deposition API option and click "Generate Key". Copy the token in the field below.

+ + Token + + + +

Password

+

You can provide a password, which will be confirmed in the email and sent to all provided ORCID users.

+ + Password + + + +
+
+

Enter 16-digit ORCID iD

+

Owners of these ORCIDs iDs are + allowed to access this deposition. +

+ +
+ +
+ + Enter 16-digits ORCID iD +
+ + +
+
+
+
+
+
- +

Choose Electron Microscopy + Method +

+ + experimental method + + + {{ method.viewValue }} + + + +
+ You must specify the experimental method +
+
+ + + Are you deposing + coordinates with this + submission? (for PDB) + + + + + Yes + + + No + + + + + + Has an associated map been deposited to EMDB? + + + + + Yes + + + No + + + + + + + EMDB Identifier + + + EMDB ID + + + + + + + Is this a composite map? + + + + + Yes + + + No + + + + +
+
+ - - - - - -
- attachment -
- Choose files for deposition -
- - - - - {{ fileType.nameFE}} * - - - -
- -
- attach_file -
- -
- attach_file -
- -
-

Selected File: {{ selectedFile[fileType.emName]?.name }}

-
-
-

Selected File: {{ selectedFile['add-map-' + fileType.id]?.name }}

-
- -
- -
- - - Contour Level - - - - - - - - - Details - - -
-
-
-
- -
-
- - - - - -
-
- - -
+
+ attach_file +
+
+ attach_file +
+ +
+

Selected File: {{ selectedFile[fileType.emName]?.name }}

+
+
+

Selected File: {{ selectedFile['add-map-' + fileType.id]?.name }}

+
+
+
+ + + Contour Level + + + + + + + + Details + + +
+ + +
+ +
+ + + + + + +
- \ No newline at end of file + + \ No newline at end of file From 65219b29dcdf62d976a04091dddd40ae62bab68e Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Tue, 14 Jan 2025 17:04:50 +0000 Subject: [PATCH 078/245] orcid entry shows messages and can be corrected --- src/app/datasets/onedep/onedep.component.html | 54 +++++++++-------- src/app/datasets/onedep/onedep.component.ts | 26 +++++--- src/app/datasets/onedep/onedep.directive.ts | 60 ++++++++++++------- src/assets/config.json | 7 ++- 4 files changed, 91 insertions(+), 56 deletions(-) diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index 99ea99d158..94a216605a 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -96,32 +96,34 @@

Enter 16-digit ORCID iD

- -
- - Enter 16-digits ORCID iD -
- - -
-
-
-
+ +
+ + Enter 16-digits ORCID iD +
+ + +
+
+
+ ORCID ID must contain only numbers. +
+
+
--> - +
attachment @@ -240,90 +241,74 @@

Choose Electron Microscopy Choose files for deposition - - - - {{ fileType.nameFE}} * - - -
- -
- attach_file -
-
- attach_file -
- -
-

Selected File: {{ selectedFile[fileType.emName]?.name }}

-
-
-

Selected File: {{ selectedFile['add-map-' + fileType.id]?.name }}

-
-
-
- - - Contour Level - - - - - - - - Details - - -
-
-
-
- -
+ + + + {{ fileType.nameFE }} + * + + + +
+ +
+ attach_file +
+
+ attach_file +
+ +
+

Selected File: {{ selectedFile[fileType.emName]?.name }}

+
+
+

Selected File: {{ selectedFile['add-map-' + fileType.id]?.name }}

+
+
+
+ + + Contour Level + + + + + Details + + +
+
+
+
+ +
+ +
+

diff --git a/src/app/datasets/onedep/onedep.component.scss b/src/app/datasets/onedep/onedep.component.scss index 1884b5fec2..9978746543 100644 --- a/src/app/datasets/onedep/onedep.component.scss +++ b/src/app/datasets/onedep/onedep.component.scss @@ -244,15 +244,19 @@ mat-card { } mat-form-field { - width: 100%; // Ensure fields take full width of their flex container + width: 100%; margin-bottom: 0; } } } } - + .button-container { + display: flex; + gap: 20px; + } .submitDep { background-color: #B3D9AC; color:black; + // padding: 10px 20px; } } \ No newline at end of file diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index 69ea12fed3..992f83dc4a 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -81,20 +81,21 @@ export class OneDepComponent implements OnInit, OnDestroy { ) { this.config = this.appConfigService.getConfig(); this.form = this.fb.group({ - email: "", - jwtToken: new FormControl(""), + email: ["", [Validators.required, Validators.email]], + jwtToken: ["", Validators.required], password: new FormControl(""), - metadata: "", + metadata: ["", Validators.required], orcid: this.fb.array([ this.fb.group({ orcidId: ["", [Validators.required, this.orcidValidator()]], }), ]), - emMethod: new FormControl(""), - deposingCoordinates: new FormControl(null, Validators.required), - associatedMap: new FormControl(null, Validators.required), - compositeMap: new FormControl(null, Validators.required), + emMethod: ["", Validators.required], + deposingCoordinates: new FormControl("false", Validators.required), + associatedMap: new FormControl("false", Validators.required), + compositeMap: new FormControl("false", Validators.required), emdbId: new FormControl(""), + files: this.fb.array([]), }); } @@ -103,8 +104,8 @@ export class OneDepComponent implements OnInit, OnDestroy { this.fileTypes = []; this.mainContour = 0.0; // connect to the depositor - this.connectingToDepositionBackend = true; - this.connectToDepositionBackend(); + + this.store.dispatch(fromActions.connectToDepositor()); this.store.select(selectCurrentDataset).subscribe((dataset) => { this.dataset = dataset; @@ -166,20 +167,34 @@ export class OneDepComponent implements OnInit, OnDestroy { this.orcidArray().removeAt(index); } } + addFilesToForm(files: DepositionFiles[]) { + const filesArray = this.form.get("files") as FormArray; + filesArray.clear(); + files.forEach((file) => { + filesArray.push( + this.fb.group({ + emName: [file.emName], + id: [file.id], + nameFE: [file.nameFE], + type: [file.type], + fileName: [file.fileName], + file: [file.file], + required: [file.required], + contour: [file.contour], + details: [file.details], + explanation: [file.explanation], + }), + ); + }); + } onMethodChange() { this.methodsList = createMethodsList(); // Reset the methods list to be empty this.fileTypes = this.methodsList.find( (mL) => mL.value === this.form.value["emMethod"], ).files; this.fileTypes.forEach((fT) => { - if ( - fT.emName === this.emFile.MainMap || - fT.emName === this.emFile.Image - ) { - fT.required = true; - } else { - fT.required = false; - } + fT.required = + fT.emName === this.emFile.MainMap || fT.emName === this.emFile.Image; }); switch (this.form.value["emMethod"]) { case "helical": @@ -203,7 +218,15 @@ export class OneDepComponent implements OnInit, OnDestroy { } }); break; + case "tomogram": + this.form.get("deposingCoordinates")?.setValue("false"); + this.form.get("associatedMap")?.setValue("false"); + break; } + this.addFilesToForm(this.fileTypes); + } + get files() { + return (this.form.get("files") as FormArray).controls; } onPDB(event: MatRadioChange) { @@ -215,6 +238,7 @@ export class OneDepComponent implements OnInit, OnDestroy { } }); } + this.addFilesToForm(this.fileTypes); // update the co-cif required status } autoGrow(event: Event) { @@ -240,23 +264,24 @@ export class OneDepComponent implements OnInit, OnDestroy { const input = event.target as HTMLInputElement; if (input.files && input.files.length > 0) { this.selectedFile[controlName] = input.files[0]; - this.fileTypes.forEach((fT) => { - if (fT.emName === controlName) { - fT.file = this.selectedFile[controlName]; - fT.fileName = this.selectedFile[controlName].name; + this.files.forEach((fT) => { + if (fT.value.emName === controlName) { + fT.value.file = this.selectedFile[controlName]; + fT.value.fileName = this.selectedFile[controlName].name; if ( this.mainContour !== 0.0 && - (fT.emName === EmFile.MainMap || - fT.emName === EmFile.HalfMap1 || - fT.emName === EmFile.HalfMap2) + (fT.value.emName === EmFile.MainMap || + fT.value.emName === EmFile.HalfMap1 || + fT.value.emName === EmFile.HalfMap2) ) { - fT.contour = this.mainContour; + fT.value.contour = this.mainContour; } } }); } } + // FIX ME needs refac onFileAddMapSelected(event: Event, id: number) { const input = event.target as HTMLInputElement; if (input.files && input.files.length > 0) { @@ -272,9 +297,9 @@ export class OneDepComponent implements OnInit, OnDestroy { } isRequired(controlName: string): boolean { let value: boolean; - this.fileTypes.forEach((fT) => { - if (fT.emName === controlName) { - value = fT.required; + this.files.forEach((fT) => { + if (fT.value.emName === controlName) { + value = fT.value.required; } }); return value; @@ -285,18 +310,16 @@ export class OneDepComponent implements OnInit, OnDestroy { const parsedValue = parseFloat(normalizedInput); if (!isNaN(parsedValue)) { this.mainContour = parsedValue; - this.fileTypes.forEach((fT) => { + this.files.forEach((fT) => { if ( - fT.file && - (fT.emName === EmFile.MainMap || - fT.emName === EmFile.HalfMap1 || - fT.emName === EmFile.HalfMap2) + fT.value.file && + (fT.value.emName === EmFile.MainMap || + fT.value.emName === EmFile.HalfMap1 || + fT.value.emName === EmFile.HalfMap2) ) { - fT.contour = this.mainContour; + fT.value.contour = this.mainContour; } }); - } else { - console.warn("Invalid number format:", input); } } updateContourLevelAddMap(event: Event, id: number) { @@ -304,13 +327,11 @@ export class OneDepComponent implements OnInit, OnDestroy { const normalizedInput = input.replace(",", "."); const parsedValue = parseFloat(normalizedInput); if (!isNaN(parsedValue)) { - this.fileTypes.forEach((fT) => { - if (fT.emName === this.emFile.AddMap && fT.id === id) { - fT.contour = parsedValue; + this.files.forEach((fT) => { + if (fT.value.emName === this.emFile.AddMap && fT.value.id === id) { + fT.value.contour = parsedValue; } }); - } else { - console.warn("Invalid number format:", input); } } updateContourLevel(event: Event, controlName: EmFile) { @@ -318,38 +339,40 @@ export class OneDepComponent implements OnInit, OnDestroy { const normalizedInput = input.replace(",", "."); const parsedValue = parseFloat(normalizedInput); if (!isNaN(parsedValue)) { - this.fileTypes.forEach((fT) => { - if (fT.emName === controlName) { - fT.contour = parsedValue; + this.files.forEach((fT) => { + if (fT.value.emName === controlName) { + fT.value.contour = parsedValue; } }); - } else { - console.warn("Invalid number format:", input); } } updateDetails(event: Event, controlName: EmFile) { const textarea = event.target as HTMLTextAreaElement; // Cast to HTMLTextAreaElement const value = textarea.value; - this.fileTypes.forEach((fT) => { - if (fT.emName === controlName) { - fT.details = value; + this.files.forEach((fT) => { + if (fT.value.emName === controlName) { + fT.value.details = value; } }); } updateDetailsAddMap(event: Event, id: number) { const textarea = event.target as HTMLTextAreaElement; // Cast to HTMLTextAreaElement const value = textarea.value; - this.fileTypes.forEach((fT) => { - if (fT.emName === this.emFile.AddMap && fT.id === id) { - fT.details = value; + this.files.forEach((fT) => { + if (fT.value.emName === this.emFile.AddMap && fT.value.id === id) { + fT.value.details = value; } }); } addMap() { + // FIX ME const nextId = - this.fileTypes - .filter((file) => file.emName === EmFile.AddMap) - .reduce((maxId, file) => (file.id > maxId ? file.id : maxId), 0) + 1; + this.files + .filter((file) => file.value.emName === EmFile.AddMap) + .reduce( + (maxId, file) => (file.value.id > maxId ? file.value.id : maxId), + 0, + ) + 1; const newMap: DepositionFiles = { emName: EmFile.AddMap, @@ -363,7 +386,7 @@ export class OneDepComponent implements OnInit, OnDestroy { required: false, }; - this.fileTypes.push(newMap); + // this.files.push(newMap); } onDepositClick() { @@ -388,13 +411,13 @@ export class OneDepComponent implements OnInit, OnDestroy { } let metadataAdded = false; - const filesToUpload = this.fileTypes - .filter((fT) => fT.file) + const filesToUpload = this.files + .filter((fT) => fT.value.file) .map((fT) => { const formDataFile = new FormData(); formDataFile.append("jwtToken", this.form.value.jwtToken); - formDataFile.append("file", fT.file); - if (fT.emName === this.emFile.Coordinates) { + formDataFile.append("file", fT.value.file); + if (fT.value.emName === this.emFile.Coordinates) { formDataFile.append( "scientificMetadata", JSON.stringify(this.form.value.metadata), @@ -404,14 +427,14 @@ export class OneDepComponent implements OnInit, OnDestroy { formDataFile.append( "fileMetadata", JSON.stringify({ - name: fT.fileName, - type: fT.type, - contour: fT.contour, - details: fT.details, + name: fT.value.fileName, + type: fT.value.type, + contour: fT.value.contour, + details: fT.value.details, }), ); } - return { form: formDataFile, fileType: fT.emName }; + return { form: formDataFile, fileType: fT.value.emName }; }); // if (!metadataAdded) { // const formDataFile = new FormData(); @@ -434,82 +457,42 @@ export class OneDepComponent implements OnInit, OnDestroy { } onDownloadClick() { if (this.form.value.deposingCoordinates === "true") { - const formDataFile = new FormData(); - const fT = this.fileTypes.find( - (fileType) => fileType.emName === this.emFile.Coordinates, + const fileEntry = this.files.find( + (fileType) => fileType.value.emName === this.emFile.Coordinates, ); - formDataFile.append("file", fT.file); - formDataFile.append( - "scientificMetadata", - JSON.stringify(this.form.value.metadata), - ); - this.http - .post(this.connectedDepositionBackend + "onedep/pdb", formDataFile, { - responseType: "blob", - }) - .subscribe({ - next: (response: Blob) => { - this.triggerDownload(response, "coordinatesWithMmetadata.cif"); - }, - error: (error) => { - console.error("Error downloading file from onedep/pdb", error); - }, - }); + + if (fileEntry) { + const file = fileEntry.value.file; + this.depositor + .downloadCoordinatesWithMetadata(file, this.form.value.metadata) + .subscribe({ + next: (response: Blob) => { + this.triggerDownload(response, "coordinatesWithMetadata.cif"); + }, + // error: (error) => { + // console.error("Error downloading file from /onedep/pdb", error); + // }, + }); + } } else { - const body = JSON.stringify(this.form.value.metadata); - this.http - .post(this.connectedDepositionBackend + "onedep/metadata", body, { - headers: { "Content-Type": "application/json" }, - responseType: "blob", - }) - .subscribe({ - next: (response: Blob) => { - this.triggerDownload(response, "metadata.cif"); - }, - error: (error) => { - console.error("Error downloading file from onedep/metadata", error); - }, - }); + this.depositor.downloadMetadata(this.form.value.metadata).subscribe({ + next: (response: Blob) => { + this.triggerDownload(response, "metadata.cif"); + }, + // error: (error) => { + // console.error("Error downloading file from /onedep/metadata", error); + // }, + }); } } + triggerDownload(response: Blob, filename: string) { const downloadUrl = window.URL.createObjectURL(response); const a = document.createElement("a"); a.href = downloadUrl; - a.download = filename; // Set the file name here + a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); } - - connectToDepositionBackend(): boolean { - const depositionBackendUrl = this.config.depositorURL; - let depositionBackendUrlCleaned = depositionBackendUrl.slice(); - // Check if last symbol is a slash and add version endpoint - if (!depositionBackendUrlCleaned.endsWith("/")) { - depositionBackendUrlCleaned += "/"; - } - - const depositionBackendUrlVersion = depositionBackendUrlCleaned + "version"; - - // Try to connect to the facility backend/version to check if it is available - console.log("Connecting to OneDep backend: " + depositionBackendUrlVersion); - this.http.get(depositionBackendUrlVersion).subscribe( - (response) => { - console.log("Connected to OneDep backend", response); - // If the connection is successful, store the connected facility backend URL - this.connectedDepositionBackend = depositionBackendUrlCleaned; - this.connectingToDepositionBackend = false; - this.connectedDepositionBackendVersion = response["version"]; - }, - (error) => { - this.errorMessage += `${new Date().toLocaleString()}: ${error.message}
`; - console.error("Request failed", error); - this.connectedDepositionBackend = ""; - this.connectingToDepositionBackend = false; - }, - ); - - return true; - } } diff --git a/src/app/shared/sdk/apis/onedep-depositor.service.ts b/src/app/shared/sdk/apis/onedep-depositor.service.ts index 8391e4a3a7..889760c66c 100644 --- a/src/app/shared/sdk/apis/onedep-depositor.service.ts +++ b/src/app/shared/sdk/apis/onedep-depositor.service.ts @@ -1,11 +1,14 @@ import { Injectable } from "@angular/core"; import { Store } from "@ngrx/store"; -import { tap } from "rxjs/operators"; -import { HttpClient, HttpHeaders, HttpResponse } from "@angular/common/http"; +import { HttpClient, HttpHeaders } from "@angular/common/http"; import { Observable } from "rxjs"; import { AppConfigService, AppConfig } from "app-config.service"; -import { OneDepUserInfo, OneDepCreated, UploadedFile } from "../models/OneDep"; -import * as fromActions from "state-management/actions/onedep.actions"; +import { + DepBackendVersion, + OneDepUserInfo, + OneDepCreated, + UploadedFile, +} from "../models/OneDep"; @Injectable({ providedIn: "root", @@ -20,6 +23,11 @@ export class Depositor { this.config = this.appConfigService.getConfig(); } + getVersion(): Observable { + return this.http.get( + `${this.config.depositorURL}/version`, + ); + } createDep(body: OneDepUserInfo): Observable { return this.http.post( `${this.config.depositorURL}/onedep`, @@ -47,4 +55,28 @@ export class Depositor { form, ); } + + downloadCoordinatesWithMetadata(file: File, metadata: any) { + const formDataFile = new FormData(); + formDataFile.append("file", file); + formDataFile.append("scientificMetadata", JSON.stringify(metadata)); + + return this.http.post( + `${this.config.depositorURL}/onedep/pdb`, + formDataFile, + { + responseType: "blob", + }, + ); + } + + downloadMetadata(metadata: any) { + const headers = new HttpHeaders({ "Content-Type": "application/json" }); + const body = JSON.stringify(metadata); + + return this.http.post(`${this.config.depositorURL}/onedep/metadata`, body, { + headers, + responseType: "blob", + }); + } } diff --git a/src/app/shared/sdk/models/OneDep.ts b/src/app/shared/sdk/models/OneDep.ts index cd7ddbb337..d7d0c5c11e 100644 --- a/src/app/shared/sdk/models/OneDep.ts +++ b/src/app/shared/sdk/models/OneDep.ts @@ -1,5 +1,9 @@ import { EmFile } from "../../../datasets/onedep/types/methods.enum"; +export interface DepBackendVersion { + version: string; +} + export interface OneDepCreated { depID: string; } diff --git a/src/app/state-management/actions/onedep.actions.ts b/src/app/state-management/actions/onedep.actions.ts index acffdeb40a..3d381f110b 100644 --- a/src/app/state-management/actions/onedep.actions.ts +++ b/src/app/state-management/actions/onedep.actions.ts @@ -4,8 +4,19 @@ import { OneDepCreated, UploadedFile, FileUpload, + DepBackendVersion, } from "shared/sdk/models/OneDep"; +export const connectToDepositor = createAction("[OneDep] Connect to Depositor"); +export const connectToDepositorSuccess = createAction( + "[OneDep] Connect To Depositor Success", + props<{ depositor: DepBackendVersion }>(), +); + +export const connectToDepositorFailure = createAction( + "[OneDep] Connect To Depositor Failure", + props<{ err: Error }>(), +); export const submitDeposition = createAction( "[OneDep] Submit Deposition", props<{ deposition: OneDepUserInfo; files: FileUpload[] }>(), diff --git a/src/app/state-management/effects/onedep.effects.ts b/src/app/state-management/effects/onedep.effects.ts index 3b02c8e34c..ca1792f4ea 100644 --- a/src/app/state-management/effects/onedep.effects.ts +++ b/src/app/state-management/effects/onedep.effects.ts @@ -11,10 +11,65 @@ import { OneDepUserInfo, OneDepCreated, UploadedFile, + DepBackendVersion, } from "shared/sdk/models/OneDep"; import { EmFile } from "../../datasets/onedep/types/methods.enum"; @Injectable() export class OneDepEffects { + createDeposition$ = createEffect(() => { + return this.actions$.pipe( + ofType(fromActions.connectToDepositor), + switchMap(() => + this.onedepDepositor.getVersion().pipe( + map((res) => + fromActions.connectToDepositorSuccess({ + depositor: res as DepBackendVersion, + }), + ), + catchError((err) => + of(fromActions.connectToDepositorFailure({ err })), + ), + ), + ), + ); + }); + + connectToDepositorSuccess$ = createEffect(() => { + return this.actions$.pipe( + ofType(fromActions.connectToDepositorSuccess), + switchMap(({ depositor }) => { + const message = { + type: MessageType.Success, + content: + "Successfully connected to depositor version " + depositor.version, + duration: 5000, + }; + return of(showMessageAction({ message })); + }), + ); + }); + + connectToDepositorFailure$ = createEffect(() => { + return this.actions$.pipe( + ofType(fromActions.connectToDepositorFailure), + switchMap(({ err }) => { + const errorMessage = + err instanceof HttpErrorResponse + ? (err.error?.message ?? err.message ?? "Unknown error") + : err.message || "Unknown error"; + const message = { + type: MessageType.Error, + content: + "Failed to connect to the depositor service: " + + errorMessage + + " Are you sure service is running?", + duration: 5000, + }; + return of(showMessageAction({ message })); + }), + ); + }); + submitDeposition$ = createEffect(() => { return this.actions$.pipe( ofType(fromActions.submitDeposition), From 065bfa6bb358f1389da6bffadbaabf39932cb189 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 21 Jan 2025 11:39:06 +0000 Subject: [PATCH 080/245] adding more add-maps fixed --- src/app/datasets/onedep/onedep.component.ts | 46 ++++++++++++++++----- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index 992f83dc4a..f159cb4d7f 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -187,6 +187,23 @@ export class OneDepComponent implements OnInit, OnDestroy { ); }); } + addFileToForm(file: DepositionFiles) { + const filesArray = this.form.get("files") as FormArray; + filesArray.push( + this.fb.group({ + emName: [file.emName], + id: [file.id], + nameFE: [file.nameFE], + type: [file.type], + fileName: [file.fileName], + file: [file.file], + required: [file.required], + contour: [file.contour], + details: [file.details], + explanation: [file.explanation], + }), + ); + } onMethodChange() { this.methodsList = createMethodsList(); // Reset the methods list to be empty this.fileTypes = this.methodsList.find( @@ -223,7 +240,11 @@ export class OneDepComponent implements OnInit, OnDestroy { this.form.get("associatedMap")?.setValue("false"); break; } - this.addFilesToForm(this.fileTypes); + const filesArray = this.form.get("files") as FormArray; + filesArray.clear(); + this.fileTypes.forEach((file) => { + this.addFileToForm(file); + }); } get files() { return (this.form.get("files") as FormArray).controls; @@ -234,11 +255,11 @@ export class OneDepComponent implements OnInit, OnDestroy { if (input === "true") { this.fileTypes.forEach((fT) => { if (fT.emName === this.emFile.Coordinates) { - fT.required = true; + fT.required = true; // update the co-cif required status + this.addFileToForm(fT) } }); } - this.addFilesToForm(this.fileTypes); // update the co-cif required status } autoGrow(event: Event) { @@ -281,19 +302,21 @@ export class OneDepComponent implements OnInit, OnDestroy { } } - // FIX ME needs refac + onFileAddMapSelected(event: Event, id: number) { const input = event.target as HTMLInputElement; if (input.files && input.files.length > 0) { // Use the ID to store the file uniquely for each "add-map" this.selectedFile[`add-map-${id}`] = input.files[0]; - this.fileTypes.forEach((fT) => { - if (fT.emName === this.emFile.AddMap && fT.id === id) { - fT.file = this.selectedFile[`add-map-${id}`]; - fT.fileName = this.selectedFile[`add-map-${id}`].name; + this.files.forEach((fT) => { + + if (fT.value.emName === this.emFile.AddMap && fT.value.id === id) { + fT.value.file = this.selectedFile[`add-map-${id}`]; + fT.value.fileName = this.selectedFile[`add-map-${id}`].name; } }); } + console.log(this.files); } isRequired(controlName: string): boolean { let value: boolean; @@ -365,7 +388,7 @@ export class OneDepComponent implements OnInit, OnDestroy { }); } addMap() { - // FIX ME + console.log("addMaps() was called. files before:", this.files); const nextId = this.files .filter((file) => file.value.emName === EmFile.AddMap) @@ -385,8 +408,9 @@ export class OneDepComponent implements OnInit, OnDestroy { details: "", required: false, }; - - // this.files.push(newMap); + // update the co-cif required status + this.addFileToForm(newMap); + console.log("addMaps() files after:", this.files); } onDepositClick() { From e862637de7c2f1baf06cc22cf4219892ef08b477 Mon Sep 17 00:00:00 2001 From: consolethinks Date: Tue, 21 Jan 2025 14:51:02 +0100 Subject: [PATCH 081/245] add auth to the first dialog box for ingestion --- .../ingestor/dialog/ingestor.new-transfer-dialog.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts index 471c8e15f1..4c582288a6 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts @@ -67,7 +67,7 @@ export class IngestorNewTransferDialogComponent implements OnInit { apiGetExtractionMethods(): void { this.http - .get(this.backendURL + INGESTOR_API_ENDPOINTS_V1.EXTRACTOR) + .get(this.backendURL + INGESTOR_API_ENDPOINTS_V1.EXTRACTOR, {withCredentials: true}) .subscribe( (response: any) => { if (response.methods && response.methods.length > 0) { @@ -85,7 +85,7 @@ export class IngestorNewTransferDialogComponent implements OnInit { apiGetAvailableFilePaths(): void { this.http - .get(this.backendURL + INGESTOR_API_ENDPOINTS_V1.DATASET) + .get(this.backendURL + INGESTOR_API_ENDPOINTS_V1.DATASET, {withCredentials: true}) .subscribe( (response: any) => { if (response.datasets && response.datasets.length > 0) { From a36898826ce1d10b28ebd661893e386d66fc7126 Mon Sep 17 00:00:00 2001 From: consolethinks Date: Tue, 21 Jan 2025 16:43:35 +0100 Subject: [PATCH 082/245] add withCredentials to all backend calls --- src/app/ingestor/ingestor/ingestor.component.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/app/ingestor/ingestor/ingestor.component.ts b/src/app/ingestor/ingestor/ingestor.component.ts index 71604d540d..e052fda843 100644 --- a/src/app/ingestor/ingestor/ingestor.component.ts +++ b/src/app/ingestor/ingestor/ingestor.component.ts @@ -81,7 +81,7 @@ export class IngestorComponent implements OnInit { // Try to connect to the facility backend/version to check if it is available console.log("Connecting to facility backend: " + facilityBackendUrlVersion); - this.http.get(facilityBackendUrlVersion).subscribe( + this.http.get(facilityBackendUrlVersion, {withCredentials: true}).subscribe( (response) => { console.log("Connected to facility backend", response); // If the connection is successful, store the connected facility backend URL @@ -115,7 +115,7 @@ export class IngestorComponent implements OnInit { } this.http .get(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.TRANSFER, { - params, + params, withCredentials: true }) .subscribe( (response) => { @@ -139,7 +139,7 @@ export class IngestorComponent implements OnInit { this.http .post( this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.DATASET, - payload, + {payload, withCredentials: true}, ) .subscribe( (response) => { @@ -178,7 +178,7 @@ export class IngestorComponent implements OnInit { this.http .post( this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.EXTRACTOR, - payload, + {payload, withCredentials: true}, ) .subscribe( (response) => { @@ -334,7 +334,8 @@ export class IngestorComponent implements OnInit { this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.TRANSFER + "/" + - transferId, + transferId, + {withCredentials: true} ) .subscribe( (response) => { From 16a3dc68ef77df89cbd1ab3bf11bcf1b035de031 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Tue, 21 Jan 2025 16:29:42 +0000 Subject: [PATCH 083/245] optional number of fsc and add-maps --- src/app/datasets/onedep/onedep.component.html | 575 ++++++++---------- src/app/datasets/onedep/onedep.component.scss | 26 +- src/app/datasets/onedep/onedep.component.ts | 31 +- src/app/datasets/onedep/types/methods.enum.ts | 7 +- 4 files changed, 304 insertions(+), 335 deletions(-) diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index d573c8d76c..7bce94c628 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -1,317 +1,274 @@ -
-
-
- - -
- assignment -
- General information -
- - - - - - - - - - - - - - - - - - - - - - - - - - - {{ keyword }} - - - -
Name{{ dataset.datasetName || "-" }}
Description - -
PID - {{ dataset.pid }} -
Type{{dataset.type }}
Creation Time{{ dataset.creationTime | date: "yyyy-MM-dd - HH:mm" }} -
Keywords
-
-
- - -
- blur_linear -
- Administrative and Method Information: -
-
-
-

Obtain OneDep Token

-

Go to OneDep API and choose option to sign in with ORCID. After the authorization you will be redirected back to OneDep. In the left bar below the map, tick the Deposition API option and click "Generate Key". Copy the token in the field below.

- - Token - - - -

Password

-

You can provide a password, which will be confirmed in the email and sent to all provided ORCID users.

- - Password - - +
+
+
+ + +
+ assignment +
+ General information +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + {{ keyword }} + + + +
Name{{ dataset.datasetName || "-" }}
Description + +
PID + {{ dataset.pid }} +
Type{{dataset.type }}
Creation Time{{ dataset.creationTime | date: "yyyy-MM-dd HH:mm" }}
Keywords
+
+
+ + +
+ blur_linear +
+ Administrative and Method Information: +
+
+
+

Obtain OneDep Token

+

+ Go to OneDep API and choose option to sign in with ORCID. After the authorization you will be redirected back to OneDep. In the left bar below the map, tick the + Deposition API option and click "Generate Key". Copy the token in the field below. +

+ + Token + + + +

Password

+

You can provide a password, which will be confirmed in the email and sent to all provided ORCID users.

+ + Password + + + +
+
+

Enter 16-digit ORCID iD

+

Owners of these ORCIDs iDs are allowed to access this deposition.

+ +
+ +
+ + Enter 16-digits ORCID iD +
+ + +
-
-
-

Enter 16-digit ORCID iD

-

Owners of these ORCIDs iDs are - allowed to access this deposition. -

- -
- -
- - Enter 16-digits ORCID iD -
- - -
-
-
- ORCID ID must contain only numbers. -
-
-
+
+ ORCID ID must contain only numbers.
-
+ +
+ +
+
+ +

Choose Electron Microscopy Method

+ + experimental method + + + {{ method.viewValue }} + + + +
+ You must specify the experimental method +
+
+ + + Are you deposing coordinates with this submission? (for PDB) + + + + Yes + + + No + + + + + + + Has an associated map been deposited to EMDB? + + + + Yes + + + No + + + + + + + EMDB Identifier + + + EMDB ID + + + + + + + Is this a composite map? + + + + Yes + + + No + + + + +
+
+ + + + + +
+ attachment +
+ Choose files for deposition +
+ + + + + + {{ fileType.nameFE }} + * + + + +
+ +
+ attach_file +
+
+ attach_file +
+ +
+

Selected File: {{ selectedFile[fileType.emName]?.name }}

+
+
+

Selected File: {{ selectedFile['add-map-' + fileType.id]?.name }}

+
+
+ +
+ + + Contour Level + + + + + + Short description + + + +
+
+
+
+
-
- -

Choose Electron Microscopy - Method -

- - experimental method - - - {{ method.viewValue }} - - - -
- You must specify the experimental method -
-
- - - Are you deposing - coordinates with this - submission? (for PDB) - - - - - Yes - - - No - - - - - - - Has an associated map been deposited to EMDB? - - - - - Yes - - - No - - - - - - - EMDB Identifier - - - EMDB ID - - - - - - - Is this a composite map? - - - - - Yes - - - No - - - - -
-
- -
- - - -
- attachment +
+
- Choose files for deposition - - - - - - {{ fileType.nameFE }} - * - - - -
- -
- attach_file -
-
- attach_file -
- -
-

Selected File: {{ selectedFile[fileType.emName]?.name }}

-
-
-

Selected File: {{ selectedFile['add-map-' + fileType.id]?.name }}

-
-
-
- - - Contour Level - - - - - Details - - -
-
-
-
- -
-
- - -
- - -
-
- -
-
+ + + + +
+ + +
+ + +
+
\ No newline at end of file diff --git a/src/app/datasets/onedep/onedep.component.scss b/src/app/datasets/onedep/onedep.component.scss index 9978746543..9fec0412ea 100644 --- a/src/app/datasets/onedep/onedep.component.scss +++ b/src/app/datasets/onedep/onedep.component.scss @@ -14,8 +14,7 @@ mat-card { justify-content: space-between; align-items: flex-start; gap: 16px; - padding: 16px; - margin-bottom: 20px; + padding: 8px 16px 8px; } .card-left, @@ -40,7 +39,7 @@ mat-card { h2.password{ margin-top:3rem; } - .file-header { + .file-header {/* space after the main Attachments card */ display: flex; margin-bottom: 16px; } @@ -156,7 +155,7 @@ mat-card { .fileCard { width: 80%; - margin: 10px 0 10px 0; + margin: 16px 0 0 0; /* Space between attachment cards */ border: 1px solid #ddd; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); @@ -175,12 +174,12 @@ mat-card { .file-selection-container { display: flex; // Use flexbox for layout align-items: center; // Center items vertically - gap: 10px; // Space between items + gap: 16px; // Space between items .fileChooser { background-color: #CFE7CB; color: #333; - margin: 5px 0 0 0; // Reset any margin + margin: 16px 0 0 0; // Reset any margin } .fileName { @@ -192,20 +191,18 @@ mat-card { mat-card-content { display: flex; flex-direction: column; - margin-bottom: 40px; - - .fileChooser { - margin: 3px auto; - } + margin-bottom: 0px; + .data-field { width: 350px; min-width: 320px; position: relative; } + .input-container { display: flex; // Use flexbox for layout align-items: flex-end; - gap: 10px; // Space between the fields + gap: 16px; // Space between the fields .contour-level { flex: 0 0 20%; // Set to take 20% of the width @@ -225,7 +222,7 @@ mat-card { .details { - margin-top: 10px; + margin-top: 16px; flex: 1; // Allow this field to take the remaining space min-width: 200px; // Optional: set a minimum width for usability @@ -253,10 +250,11 @@ mat-card { .button-container { display: flex; gap: 20px; + margin-top: 16px; } .submitDep { background-color: #B3D9AC; color:black; - // padding: 10px 20px; } + } \ No newline at end of file diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index f159cb4d7f..8242c813c4 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -251,12 +251,13 @@ export class OneDepComponent implements OnInit, OnDestroy { } onPDB(event: MatRadioChange) { + // fix me : add removal on const input = event.value; if (input === "true") { this.fileTypes.forEach((fT) => { if (fT.emName === this.emFile.Coordinates) { fT.required = true; // update the co-cif required status - this.addFileToForm(fT) + this.addFileToForm(fT); } }); } @@ -302,21 +303,18 @@ export class OneDepComponent implements OnInit, OnDestroy { } } - onFileAddMapSelected(event: Event, id: number) { const input = event.target as HTMLInputElement; if (input.files && input.files.length > 0) { // Use the ID to store the file uniquely for each "add-map" this.selectedFile[`add-map-${id}`] = input.files[0]; this.files.forEach((fT) => { - if (fT.value.emName === this.emFile.AddMap && fT.value.id === id) { fT.value.file = this.selectedFile[`add-map-${id}`]; fT.value.fileName = this.selectedFile[`add-map-${id}`].name; } }); } - console.log(this.files); } isRequired(controlName: string): boolean { let value: boolean; @@ -388,7 +386,6 @@ export class OneDepComponent implements OnInit, OnDestroy { }); } addMap() { - console.log("addMaps() was called. files before:", this.files); const nextId = this.files .filter((file) => file.value.emName === EmFile.AddMap) @@ -410,7 +407,29 @@ export class OneDepComponent implements OnInit, OnDestroy { }; // update the co-cif required status this.addFileToForm(newMap); - console.log("addMaps() files after:", this.files); + } + addFSC() { + const nextId = + this.files + .filter((file) => file.value.emName === EmFile.FSC) + .reduce( + (maxId, file) => (file.value.id > maxId ? file.value.id : maxId), + 0, + ) + 1; + + const newFSC: DepositionFiles = { + emName: EmFile.FSC, + id: nextId, + nameFE: "FSC-XML ( " + (nextId + 1).toString() + " )", + type: "fsc-xml", + fileName: "", + file: null, + contour: 0.0, + details: "", + required: false, + }; + // update the co-cif required status + this.addFileToForm(newFSC); } onDepositClick() { diff --git a/src/app/datasets/onedep/types/methods.enum.ts b/src/app/datasets/onedep/types/methods.enum.ts index a8bc13a2dd..0925e9a764 100644 --- a/src/app/datasets/onedep/types/methods.enum.ts +++ b/src/app/datasets/onedep/types/methods.enum.ts @@ -58,7 +58,6 @@ export const createMethodsList = (): EmMethod[] => { type: "img-emdb", fileName: "", file: null, - details: "", required: false, explanation: "Image of the map (500 x 500 pixels in .jpg, .png, etc. format)", @@ -126,11 +125,11 @@ export const createMethodsList = (): EmMethod[] => { }; const depositionFSC: DepositionFiles = { emName: EmFile.FSC, + id: 0, nameFE: "FSC-XML", type: "fsc-xml", fileName: "", file: null, - details: "", required: false, explanation: "Half-map FSC, Map-model FSC, Cross-validation FSCs", }; @@ -140,7 +139,6 @@ export const createMethodsList = (): EmMethod[] => { type: "layer-lines", fileName: "", file: null, - details: "", required: false, }; const depositionCoordinates: DepositionFiles = { @@ -149,7 +147,6 @@ export const createMethodsList = (): EmMethod[] => { type: "co-cif", fileName: "", file: null, - details: "", required: false, explanation: "mmCIF or PDB format", }; @@ -159,7 +156,6 @@ export const createMethodsList = (): EmMethod[] => { type: "xs-cif", fileName: "", file: null, - details: "", required: false, }; const depositionMTZ: DepositionFiles = { @@ -168,7 +164,6 @@ export const createMethodsList = (): EmMethod[] => { type: "xs-mtz", fileName: "", file: null, - details: "", required: false, }; return [ From 8a168851e30ba69d3de40d86012220e8832e36ce Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Wed, 22 Jan 2025 17:44:39 +0000 Subject: [PATCH 084/245] add many files and clean entered file --- src/app/datasets/onedep/onedep.component.html | 25 +++-- src/app/datasets/onedep/onedep.component.scss | 37 ++++--- src/app/datasets/onedep/onedep.component.ts | 98 ++++++++++++------- 3 files changed, 105 insertions(+), 55 deletions(-) diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index 7bce94c628..049b74eeb2 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -86,7 +86,7 @@

Enter 16-digit ORCID iD

Enter 16-digits ORCID iD
-
@@ -185,7 +185,6 @@

Choose Electron Microscopy Method

- {{ fileType.nameFE }} @@ -200,16 +199,31 @@

Choose Electron Microscopy Method

attach_file
-
+
attach_file
- -
+ +

Selected File: {{ selectedFile[fileType.emName]?.name }}

Selected File: {{ selectedFile['add-map-' + fileType.id]?.name }}

+
+

Selected File: {{ selectedFile['fsc-xml-' + fileType.id]?.name }}

+
+
+ +
@@ -254,7 +268,6 @@

Choose Electron Microscopy Method

add
-
diff --git a/src/app/datasets/onedep/onedep.component.scss b/src/app/datasets/onedep/onedep.component.scss index 9fec0412ea..b131c8afcf 100644 --- a/src/app/datasets/onedep/onedep.component.scss +++ b/src/app/datasets/onedep/onedep.component.scss @@ -73,7 +73,7 @@ mat-card { width:40%; } - .remove-field-btn { + .remove-orcid-btn { position: absolute; top: 50%; right: -40%; @@ -82,6 +82,12 @@ mat-card { color: rgba(0, 0, 0, 0.6); } + .remove-file-btn { + position: absolute; + margin-left: 16px; + color: rgba(0, 0, 0, 0.6); + } + .add-field-btn { margin-top: 5px; color: rgba(0, 0, 0, 0.6); @@ -162,9 +168,9 @@ mat-card { mat-card-header { background-color: #B3D9AC; height: 26px; - display: flex; // Use flexbox to align items - align-items: center; // Center content vertically - padding: 0 16px; // Optional: adjust padding as needed + display: flex; + align-items: center; + padding: 0 16px; } mat-card-title { @@ -172,19 +178,20 @@ mat-card { } .file-selection-container { - display: flex; // Use flexbox for layout - align-items: center; // Center items vertically - gap: 16px; // Space between items + display: flex; + align-items: center; + gap: 16px; .fileChooser { background-color: #CFE7CB; color: #333; - margin: 16px 0 0 0; // Reset any margin + margin: 16px 0 0 0; } .fileName { - font-size: 14px; // Adjust the font size as needed - color: #333; // Adjust the text color if needed + font-size: 16px; + transform: translateY(15%); + color: #333; } } @@ -200,13 +207,13 @@ mat-card { } .input-container { - display: flex; // Use flexbox for layout + display: flex; align-items: flex-end; - gap: 16px; // Space between the fields + gap: 16px; .contour-level { flex: 0 0 20%; // Set to take 20% of the width - min-width: 150px; // Optional: set a minimum width for usability + min-width: 150px; /* Chrome, Safari, Edge, Opera */ input[matinput]::-webkit-outer-spin-button, @@ -223,8 +230,8 @@ mat-card { .details { margin-top: 16px; - flex: 1; // Allow this field to take the remaining space - min-width: 200px; // Optional: set a minimum width for usability + flex: 1; + min-width: 200px; mat-form-field { textarea { diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index 8242c813c4..db2aa083bd 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -167,26 +167,6 @@ export class OneDepComponent implements OnInit, OnDestroy { this.orcidArray().removeAt(index); } } - addFilesToForm(files: DepositionFiles[]) { - const filesArray = this.form.get("files") as FormArray; - filesArray.clear(); - files.forEach((file) => { - filesArray.push( - this.fb.group({ - emName: [file.emName], - id: [file.id], - nameFE: [file.nameFE], - type: [file.type], - fileName: [file.fileName], - file: [file.file], - required: [file.required], - contour: [file.contour], - details: [file.details], - explanation: [file.explanation], - }), - ); - }); - } addFileToForm(file: DepositionFiles) { const filesArray = this.form.get("files") as FormArray; filesArray.push( @@ -204,7 +184,19 @@ export class OneDepComponent implements OnInit, OnDestroy { }), ); } + removeFileFromForm(fileCat: EmFile) { + const filesArray = this.form.get("files") as FormArray; + const index = filesArray.value.findIndex( + (file: DepositionFiles) => file.emName === fileCat, + ); + if (index > -1) { + filesArray.removeAt(index); + } + } onMethodChange() { + const filesArray = this.form.get("files") as FormArray; + filesArray.clear(); + this.methodsList = createMethodsList(); // Reset the methods list to be empty this.fileTypes = this.methodsList.find( (mL) => mL.value === this.form.value["emMethod"], @@ -240,10 +232,10 @@ export class OneDepComponent implements OnInit, OnDestroy { this.form.get("associatedMap")?.setValue("false"); break; } - const filesArray = this.form.get("files") as FormArray; - filesArray.clear(); this.fileTypes.forEach((file) => { - this.addFileToForm(file); + if (file.emName !== this.emFile.Coordinates) { + this.addFileToForm(file); + } }); } get files() { @@ -251,7 +243,6 @@ export class OneDepComponent implements OnInit, OnDestroy { } onPDB(event: MatRadioChange) { - // fix me : add removal on const input = event.value; if (input === "true") { this.fileTypes.forEach((fT) => { @@ -260,6 +251,8 @@ export class OneDepComponent implements OnInit, OnDestroy { this.addFileToForm(fT); } }); + } else { + this.removeFileFromForm(this.emFile.Coordinates); } } @@ -302,18 +295,57 @@ export class OneDepComponent implements OnInit, OnDestroy { }); } } + clearFile(fileCat: EmFile, id?: number) { + const key = + fileCat === "add-map" || fileCat === "fsc-xml" + ? `${fileCat}-${id}` + : fileCat; + this.selectedFile[key] = null; - onFileAddMapSelected(event: Event, id: number) { + let index = -1; + const filesArray = this.form.get("files") as FormArray; + if (fileCat !== "add-map" && fileCat !== "fsc-xml") { + index = filesArray.value.findIndex( + (file: DepositionFiles) => file.emName === fileCat, + ); + } else { + for (let i = 0; i < filesArray.length; i++) { + if ( + filesArray.at(i).value.emName === fileCat && + filesArray.at(i).value.id === id + ) { + index = i; + break; + } + } + } + if (index > -1) { + filesArray.at(index).patchValue({ file: null }); + } + } + onFileAddMore(event: Event, id: number, fileType: string) { const input = event.target as HTMLInputElement; if (input.files && input.files.length > 0) { - // Use the ID to store the file uniquely for each "add-map" - this.selectedFile[`add-map-${id}`] = input.files[0]; - this.files.forEach((fT) => { - if (fT.value.emName === this.emFile.AddMap && fT.value.id === id) { - fT.value.file = this.selectedFile[`add-map-${id}`]; - fT.value.fileName = this.selectedFile[`add-map-${id}`].name; + this.selectedFile[`${fileType}-${id}`] = input.files[0]; + for (let i = 0; i < this.files.length; i++) { + if ( + this.files.at(i).value.emName === this.emFile.AddMap && + this.files.at(i).value.id === id && + fileType === "add-map" + ) { + this.files.at(i).value.file = this.selectedFile[`${fileType}-${id}`]; + this.files.at(i).value.fileName = + this.selectedFile[`${fileType}-${id}`].name; + } else if ( + this.files.at(i).value.emName === this.emFile.FSC && + this.files.at(i).value.id === id && + fileType === "fsc-xml" + ) { + this.files.at(i).value.file = this.selectedFile[`${fileType}-${id}`]; + this.files.at(i).value.fileName = + this.selectedFile[`${fileType}-${id}`].name; } - }); + } } } isRequired(controlName: string): boolean { @@ -424,8 +456,6 @@ export class OneDepComponent implements OnInit, OnDestroy { type: "fsc-xml", fileName: "", file: null, - contour: 0.0, - details: "", required: false, }; // update the co-cif required status From 63fb9dccfb948178a6583bd75fa787b72866d1b6 Mon Sep 17 00:00:00 2001 From: David Wiessner Date: Thu, 23 Jan 2025 10:29:15 +0000 Subject: [PATCH 085/245] Prepare optical improvements and adding control mechanisms --- ...stor.dialog-stepper.component.component.ts | 10 ++++ .../ingestor.dialog-stepper.component.css | 26 +++++++--- .../ingestor.dialog-stepper.component.html | 7 ++- ...tor.extractor-metadata-dialog.component.ts | 3 ++ .../ingestor.extractor-metadata-dialog.html | 8 +++ ...ingestor.user-metadata-dialog.component.ts | 3 ++ .../dialog/ingestor.user-metadata-dialog.html | 51 +++++++++++++------ .../ingestor/ingestor-api-endpoints.ts | 4 ++ .../ingestor/ingestor/ingestor.component.html | 14 ++++- .../ingestor/ingestor/ingestor.component.scss | 8 +++ .../ingestor/ingestor/ingestor.component.ts | 5 ++ 11 files changed, 115 insertions(+), 24 deletions(-) diff --git a/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.component.ts index 1531c5404f..8b665406d5 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.component.ts @@ -7,4 +7,14 @@ import { Component, Input } from "@angular/core"; }) export class IngestorDialogStepperComponent { @Input() activeStep = 0; + + // Save a template of metadata + onSave() { + console.log("Save action triggered"); + } + + // Upload a template of metadata + onUpload() { + console.log("Upload action triggered"); + } } diff --git a/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.css b/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.css index 8e114ace88..215e47f1c4 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.css +++ b/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.css @@ -4,14 +4,28 @@ align-items: center; } -.stepper div { - margin: 5px; +.ingestor-stepper { + max-width: 100%; } -.stepper div.active { - font-weight: bold; +.dialog-control-buttons { + display: flex; + justify-content: flex-end; + gap: 1em; +} + +.control-button-icon { + margin: auto; } -button { - margin: 5px; +@media (max-width: 768px) { + .stepper { + flex-direction: column; + width: 100%; + } + + .stepper div { + width: 100%; + text-align: center; + } } \ No newline at end of file diff --git a/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.html b/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.html index 9902301bb8..f5498dd673 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.html +++ b/src/app/ingestor/ingestor/dialog/ingestor.dialog-stepper.component.html @@ -1,5 +1,10 @@ +
+ + +
+
- + Select your ingestion method diff --git a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts index 9fa86aa138..fd91e68979 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts @@ -23,6 +23,9 @@ export class IngestorExtractorMetadataDialogComponent { extractorMetaDataReady = false; extractorMetaDataError = false; + isAcquisitionMetadataOk = false; // TODO IMPLEMENT VALUE CHECK + isInstrumentMetadataOk = false; // TODO IMPLEMENT VALUE CHECK + isCardContentVisible = { instrument: true, acquisition: true, diff --git a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html index fe985f0d6a..2674b3d860 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html +++ b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.html @@ -39,6 +39,10 @@

Instrument Information + {{ + isInstrumentMetadataOk ? 'check_circle_outline' : 'error_outline'}} {{ isCardContentVisible.instrument ? 'expand_less' : 'expand_more' }} @@ -54,6 +58,10 @@

Acquisition Information + {{ + isAcquisitionMetadataOk ? 'check_circle_outline' : 'error_outline'}} {{ isCardContentVisible.acquisition ? 'expand_less' : 'expand_more' }}
diff --git a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts index 1f57b6ab8a..bf9a2c2d74 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts @@ -23,6 +23,9 @@ export class IngestorUserMetadataDialogComponent { backendURL = ""; uiNextButtonReady = true; // Change to false when dev is ready + isSciCatHeaderOk = false; // TODO IMPLEMENT VALUE CHECK + isOrganizationalMetadataOk = false; // TODO IMPLEMENT VALUE CHECK + isSampleInformationOk = false; // TODO IMPLEMENT VALUE CHECK isCardContentVisible = { scicat: true, diff --git a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html index 39fbf41a49..c124c91feb 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html +++ b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.html @@ -9,51 +9,72 @@

- +
- +
info
SciCat Information - {{ isCardContentVisible.scicat ? 'expand_less' : 'expand_more' }} + {{ + isSciCatHeaderOk ? 'check_circle_outline' : 'error_outline'}} + {{ isCardContentVisible.scicat ? + 'expand_less' : 'expand_more' }}
- +
- +
person
Organizational Information - {{ isCardContentVisible.organizational ? 'expand_less' : 'expand_more' }} + {{ + isOrganizationalMetadataOk ? 'check_circle_outline' : 'error_outline'}} + {{ isCardContentVisible.organizational ? + 'expand_less' : 'expand_more' }}
- +
- +
description
Sample Information - {{ isCardContentVisible.sample ? 'expand_less' : 'expand_more' }} + {{ + isSampleInformationOk ? 'check_circle_outline' : 'error_outline'}} + {{ isCardContentVisible.sample ? + 'expand_less' : 'expand_more' }}
- +
@@ -62,6 +83,6 @@

- + \ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor-api-endpoints.ts b/src/app/ingestor/ingestor/ingestor-api-endpoints.ts index acd08c0177..d237ef4fd9 100644 --- a/src/app/ingestor/ingestor/ingestor-api-endpoints.ts +++ b/src/app/ingestor/ingestor/ingestor-api-endpoints.ts @@ -15,3 +15,7 @@ export interface PostExtractorEndpoint { export interface PostDatasetEndpoint { metaData: string; } + +export const apiGetHealth = () => { + console.log("Health check"); // TODO IMPLEMENT +}; diff --git a/src/app/ingestor/ingestor/ingestor.component.html b/src/app/ingestor/ingestor/ingestor.component.html index 187f6f2f76..454f273f4a 100644 --- a/src/app/ingestor/ingestor/ingestor.component.html +++ b/src/app/ingestor/ingestor/ingestor.component.html @@ -23,7 +23,8 @@ @@ -32,7 +33,7 @@ - @@ -87,6 +88,15 @@ {{ connectedFacilityBackendVersion }} + + Health Status + + + {{ status.title }} + {{ status.value }} + + + diff --git a/src/app/ingestor/ingestor/ingestor.component.scss b/src/app/ingestor/ingestor/ingestor.component.scss index a5c5dcad67..1fb107fe37 100644 --- a/src/app/ingestor/ingestor/ingestor.component.scss +++ b/src/app/ingestor/ingestor/ingestor.component.scss @@ -59,4 +59,12 @@ mat-card { .error-message { color: red; +} + +.error-icon { + color: red; +} + +.success-icon { + color: green; } \ No newline at end of file diff --git a/src/app/ingestor/ingestor/ingestor.component.ts b/src/app/ingestor/ingestor/ingestor.component.ts index e052fda843..d082da9173 100644 --- a/src/app/ingestor/ingestor/ingestor.component.ts +++ b/src/app/ingestor/ingestor/ingestor.component.ts @@ -3,6 +3,7 @@ import { AppConfigService } from "app-config.service"; import { HttpClient } from "@angular/common/http"; import { ActivatedRoute, Router } from "@angular/router"; import { + apiGetHealth, INGESTOR_API_ENDPOINTS_V1, PostDatasetEndpoint, PostExtractorEndpoint, @@ -35,6 +36,7 @@ export class IngestorComponent implements OnInit { connectedFacilityBackend = ""; connectedFacilityBackendVersion = ""; connectingToFacilityBackend = false; + ingestorHealthStatus = []; lastUsedFacilityBackends: string[] = []; @@ -88,6 +90,9 @@ export class IngestorComponent implements OnInit { this.connectedFacilityBackend = facilityBackendUrlCleaned; this.connectingToFacilityBackend = false; this.connectedFacilityBackendVersion = response["version"]; + + // TODO Do Health check + apiGetHealth(); }, (error) => { this.errorMessage += `${new Date().toLocaleString()}: ${error.message}
`; From 6fc8a31de72a189c4fa54e105647bad38d44a50e Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Thu, 23 Jan 2025 11:22:35 +0000 Subject: [PATCH 086/245] wip: error messages on questionnaire --- src/app/datasets/onedep/onedep.component.html | 560 +++++++++--------- src/app/datasets/onedep/onedep.component.scss | 5 + src/app/datasets/onedep/onedep.component.ts | 134 +++-- src/app/datasets/onedep/types/methods.enum.ts | 45 +- 4 files changed, 402 insertions(+), 342 deletions(-) diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index 049b74eeb2..59cef57ede 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -1,287 +1,285 @@ -
-
-
- - -
- assignment -
- General information -
- - - - - - - - - - - - - - - - - - - - - - - - - - - {{ keyword }} - - - -
Name{{ dataset.datasetName || "-" }}
Description - -
PID - {{ dataset.pid }} -
Type{{dataset.type }}
Creation Time{{ dataset.creationTime | date: "yyyy-MM-dd HH:mm" }}
Keywords
-
-
- - -
- blur_linear -
- Administrative and Method Information: -
-
-
-

Obtain OneDep Token

-

- Go to OneDep API and choose option to sign in with ORCID. After the authorization you will be redirected back to OneDep. In the left bar below the map, tick the - Deposition API option and click "Generate Key". Copy the token in the field below. -

- - Token - - - -

Password

-

You can provide a password, which will be confirmed in the email and sent to all provided ORCID users.

- - Password - - - -
-
-

Enter 16-digit ORCID iD

-

Owners of these ORCIDs iDs are allowed to access this deposition.

- -
- -
- - Enter 16-digits ORCID iD -
- - -
-
-
- ORCID ID must contain only numbers. -
-
-
-
- -
-
- -

Choose Electron Microscopy Method

- - experimental method - - - {{ method.viewValue }} - - - -
- You must specify the experimental method -
-
- - - Are you deposing coordinates with this submission? (for PDB) - - - - Yes - - - No - - - - - - - Has an associated map been deposited to EMDB? - - - - Yes - - - No - - - - - - - EMDB Identifier - - - EMDB ID - - - - - - - Is this a composite map? - - - - Yes - - - No - - - - -
-
- -
- - - -
- attachment -
- Choose files for deposition -
- - - - - {{ fileType.nameFE }} - * - - - -
- -
- attach_file -
-
- attach_file -
- -
-

Selected File: {{ selectedFile[fileType.emName]?.name }}

-
-
-

Selected File: {{ selectedFile['add-map-' + fileType.id]?.name }}

-
-
-

Selected File: {{ selectedFile['fsc-xml-' + fileType.id]?.name }}

+
+
+
+ + +
+ assignment +
+ General information +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + {{ keyword }} + + + +
Name{{ dataset.datasetName || "-" }}
Description + +
PID + {{ dataset.pid }} +
Type{{dataset.type }}
Creation Time{{ dataset.creationTime | date: "yyyy-MM-dd HH:mm" }}
Keywords
+
+
+ + +
+ blur_linear +
+ Administrative and Method Information: +
+
+
+

Obtain OneDep Token

+

+ Go to OneDep API and choose option to sign in with ORCID. After the authorization you will be redirected back to OneDep. In the left bar below the map, tick the + Deposition API option and click "Generate Key". Copy the token in the field below. +

+ + Token + + + +

Password

+

You can provide a password, which will be confirmed in the email and sent to all provided ORCID users.

+ + Password + + + +
+
+

Enter 16-digit ORCID iD

+

Owners of these ORCIDs iDs are allowed to access this deposition.

+ +
+ +
+ + Enter 16-digits ORCID iD +
+ + +
+
+
+ ORCID ID must contain only numbers.
-
-
+ +
+
+ +

Choose Electron Microscopy Method

+ + experimental method + + + {{ method.viewValue }} + + + +
+ You must specify the experimental method +
+
+ + + Are you deposing coordinates with this submission? (for PDB) + + + + Yes + + + No + + + + + + + Has an associated map been deposited to EMDB? + + + + Yes + + + No + + + + + + + EMDB Identifier + + + EMDB ID + + + + + + + Is this a composite map? + + + + Yes + + + No + + + + +
+
+ + + + +
+ attachment +
+ Choose files for deposition +
+ + + + + {{ fileType.get('nameFE').value }} + * + + + +
+ +
+ attach_file +
+
+ attach_file +
+ +
+

Selected File: {{ selectedFile[fileType.get('emName').value]?.name }}

+
+
+

Selected File: {{ selectedFile['add-map-' + fileType.get('id').value]?.name }}

+
+
+

Selected File: {{ selectedFile['fsc-xml-' + fileType.get('id').value]?.name }}

+
+
+ -
-
- -
- - - Contour Level - - - - - - Short description - - - -
-
-
-
-
-
- +
+ Invalid file format. Allowed formats: {{ fileType.get('fileFormat').value }} +
+ +
+ +
+ + + Contour Level + + + + + + Short description + + +
-
- - -
- - -
-
-
-
-
- - \ No newline at end of file + + +
+ +
+
+ +
+
+ +
+ + +
+ + +
+
+ diff --git a/src/app/datasets/onedep/onedep.component.scss b/src/app/datasets/onedep/onedep.component.scss index b131c8afcf..fdc7578840 100644 --- a/src/app/datasets/onedep/onedep.component.scss +++ b/src/app/datasets/onedep/onedep.component.scss @@ -263,5 +263,10 @@ mat-card { background-color: #B3D9AC; color:black; } + .error-text { + color: red; + font-size: 0.8rem; + margin-top: 0; + } } \ No newline at end of file diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index db2aa083bd..4f22e13c41 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -31,7 +31,7 @@ import * as fromActions from "state-management/actions/onedep.actions"; import { createMethodsList, EmFile, - DepositionFiles, + DepositionFile, OneDepUserInfo, OneDepCreate, } from "./types/methods.enum"; @@ -60,7 +60,7 @@ export class OneDepComponent implements OnInit, OnDestroy { detailsOverflow = "hidden"; additionalMaps = 0; showPassword = false; - fileTypes: DepositionFiles[]; + fileTypes: DepositionFile[]; mainContour = 0.0; connectedDepositionBackend = ""; connectedDepositionBackendVersion = ""; @@ -167,7 +167,7 @@ export class OneDepComponent implements OnInit, OnDestroy { this.orcidArray().removeAt(index); } } - addFileToForm(file: DepositionFiles) { + addFileToForm(file: DepositionFile) { const filesArray = this.form.get("files") as FormArray; filesArray.push( this.fb.group({ @@ -176,10 +176,11 @@ export class OneDepComponent implements OnInit, OnDestroy { nameFE: [file.nameFE], type: [file.type], fileName: [file.fileName], - file: [file.file], + file: [file.file, [this.correctExtension]], required: [file.required], contour: [file.contour], details: [file.details], + fileFormat: [file.fileFormat], explanation: [file.explanation], }), ); @@ -187,7 +188,7 @@ export class OneDepComponent implements OnInit, OnDestroy { removeFileFromForm(fileCat: EmFile) { const filesArray = this.form.get("files") as FormArray; const index = filesArray.value.findIndex( - (file: DepositionFiles) => file.emName === fileCat, + (file: DepositionFile) => file.emName === fileCat, ); if (index > -1) { filesArray.removeAt(index); @@ -275,27 +276,64 @@ export class OneDepComponent implements OnInit, OnDestroy { onChooseFile(fileInput: HTMLInputElement) { fileInput.click(); } - onFileSelected(event: Event, controlName: EmFile) { + onFileSelected(event: Event, emCat: EmFile) { + // once the file is selected, adds it to the form const input = event.target as HTMLInputElement; if (input.files && input.files.length > 0) { - this.selectedFile[controlName] = input.files[0]; - this.files.forEach((fT) => { - if (fT.value.emName === controlName) { - fT.value.file = this.selectedFile[controlName]; - fT.value.fileName = this.selectedFile[controlName].name; - if ( - this.mainContour !== 0.0 && - (fT.value.emName === EmFile.MainMap || - fT.value.emName === EmFile.HalfMap1 || - fT.value.emName === EmFile.HalfMap2) - ) { - fT.value.contour = this.mainContour; - } + this.selectedFile[emCat] = input.files[0]; + + const filesArray = this.form.get("files") as FormArray; + const fileTypeControl = filesArray.controls.find( + (control) => control.get("emName")?.value === emCat, + ); + if (fileTypeControl) { + fileTypeControl.get("file")?.setValue(this.selectedFile[emCat]); + fileTypeControl + .get("fileName") + ?.setValue(this.selectedFile[emCat].name); + if ( + this.mainContour !== 0.0 && + [EmFile.MainMap, EmFile.HalfMap1, EmFile.HalfMap2].includes(emCat) + ) { + fileTypeControl.get("contour").setValue(this.mainContour); } - }); + } } } + onFileAddMore(event: Event, id: number, fileType: string) { + // once the file is selected, adds it to the form. Only for additional maps and FSC inputs, as they can include multiple files + const input = event.target as HTMLInputElement; + let fileTypeControl; + if (input.files && input.files.length > 0) { + this.selectedFile[`${fileType}-${id}`] = input.files[0]; + + const filesArray = this.form.get("files") as FormArray; + if (fileType === "add-map") { + fileTypeControl = filesArray.controls.find( + (control) => + control.get("emName")?.value === this.emFile.AddMap && + control.get("id")?.value === id, + ); + } else { + fileTypeControl = filesArray.controls.find( + (control) => + control.get("emName")?.value === this.emFile.FSC && + control.get("id")?.value === id, + ); + } + if (fileTypeControl) { + fileTypeControl + .get("file") + ?.setValue(this.selectedFile[`${fileType}-${id}`]); + fileTypeControl + .get("fileName") + ?.setValue(this.selectedFile[`${fileType}-${id}`].name); + } + } + } + clearFile(fileCat: EmFile, id?: number) { + // clear the selected file const key = fileCat === "add-map" || fileCat === "fsc-xml" ? `${fileCat}-${id}` @@ -306,7 +344,7 @@ export class OneDepComponent implements OnInit, OnDestroy { const filesArray = this.form.get("files") as FormArray; if (fileCat !== "add-map" && fileCat !== "fsc-xml") { index = filesArray.value.findIndex( - (file: DepositionFiles) => file.emName === fileCat, + (file: DepositionFile) => file.emName === fileCat, ); } else { for (let i = 0; i < filesArray.length; i++) { @@ -323,32 +361,9 @@ export class OneDepComponent implements OnInit, OnDestroy { filesArray.at(index).patchValue({ file: null }); } } - onFileAddMore(event: Event, id: number, fileType: string) { - const input = event.target as HTMLInputElement; - if (input.files && input.files.length > 0) { - this.selectedFile[`${fileType}-${id}`] = input.files[0]; - for (let i = 0; i < this.files.length; i++) { - if ( - this.files.at(i).value.emName === this.emFile.AddMap && - this.files.at(i).value.id === id && - fileType === "add-map" - ) { - this.files.at(i).value.file = this.selectedFile[`${fileType}-${id}`]; - this.files.at(i).value.fileName = - this.selectedFile[`${fileType}-${id}`].name; - } else if ( - this.files.at(i).value.emName === this.emFile.FSC && - this.files.at(i).value.id === id && - fileType === "fsc-xml" - ) { - this.files.at(i).value.file = this.selectedFile[`${fileType}-${id}`]; - this.files.at(i).value.fileName = - this.selectedFile[`${fileType}-${id}`].name; - } - } - } - } isRequired(controlName: string): boolean { + // for a given file extracts the "required" status for the chosen EM method + // FIX me subject to change let value: boolean; this.files.forEach((fT) => { if (fT.value.emName === controlName) { @@ -426,7 +441,7 @@ export class OneDepComponent implements OnInit, OnDestroy { 0, ) + 1; - const newMap: DepositionFiles = { + const newMap: DepositionFile = { emName: EmFile.AddMap, id: nextId, nameFE: "Additional Map ( " + (nextId + 1).toString() + " )", @@ -436,6 +451,9 @@ export class OneDepComponent implements OnInit, OnDestroy { contour: 0.0, details: "", required: false, + fileFormat: [".mrc", ".ccp4", ".mrc.gz", ".ccp4.gz"], + explanation: + "Difference maps, maps showing alternative conformations and/or compositions, maps with differential processing (e.g. filtering, sharpening and masking)", }; // update the co-cif required status this.addFileToForm(newMap); @@ -449,7 +467,7 @@ export class OneDepComponent implements OnInit, OnDestroy { 0, ) + 1; - const newFSC: DepositionFiles = { + const newFSC: DepositionFile = { emName: EmFile.FSC, id: nextId, nameFE: "FSC-XML ( " + (nextId + 1).toString() + " )", @@ -457,11 +475,31 @@ export class OneDepComponent implements OnInit, OnDestroy { fileName: "", file: null, required: false, + fileFormat: [".xml"], + explanation: "Half-map FSC, Map-model FSC, Cross-validation FSCs", }; // update the co-cif required status this.addFileToForm(newFSC); } + correctExtension(control: AbstractControl) { + console.log("calling validator"); + const fileValue = control.value; + if (!fileValue) { + return null; + } + const allowedExtensions = control.parent?.get("fileFormat")?.value; + const fileName = fileValue.name || fileValue; + const fileExtension = fileName.endsWith(".gz") + ? "." + fileName.split(".").slice(-2).join(".") + : "." + fileName.split(".").pop(); + + if (allowedExtensions && allowedExtensions.includes(fileExtension)) { + return null; + } + return { correctExtension: false }; + } + onDepositClick() { let body: OneDepUserInfo; if (this.form.value.password) { diff --git a/src/app/datasets/onedep/types/methods.enum.ts b/src/app/datasets/onedep/types/methods.enum.ts index 0925e9a764..7f26e06f6d 100644 --- a/src/app/datasets/onedep/types/methods.enum.ts +++ b/src/app/datasets/onedep/types/methods.enum.ts @@ -32,7 +32,7 @@ export interface OneDepUserInfo { export interface OneDepCreate { depID: string; } -export interface DepositionFiles { +export interface DepositionFile { emName: EmFile; id?: number; nameFE: string; @@ -42,27 +42,29 @@ export interface DepositionFiles { contour?: number; details?: string; required: boolean; + fileFormat?: string[]; explanation?: string; } interface EmMethod { value: EmType; viewValue: string; - files: DepositionFiles[]; + files: DepositionFile[]; } export const createMethodsList = (): EmMethod[] => { - const depositionImage: DepositionFiles = { + const depositionImage: DepositionFile = { emName: EmFile.Image, nameFE: "Public Image", type: "img-emdb", fileName: "", file: null, required: false, + fileFormat: [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff"], explanation: "Image of the map (500 x 500 pixels in .jpg, .png, etc. format)", }; - const depositionMainMap: DepositionFiles = { + const depositionMainMap: DepositionFile = { emName: EmFile.MainMap, nameFE: "Main Map", type: "vo-map", @@ -71,10 +73,11 @@ export const createMethodsList = (): EmMethod[] => { contour: 0.0, details: "", required: false, + fileFormat: [".mrc", ".ccp4", ".mrc.gz", ".ccp4.gz"], explanation: "Primary map (.mrc or .ccp4 format, may use gzip or bzip2 compression) along with recommended contour level", }; - const depositionHalfMap1: DepositionFiles = { + const depositionHalfMap1: DepositionFile = { emName: EmFile.HalfMap1, nameFE: "Half Map (1)", type: "half-map", @@ -83,10 +86,11 @@ export const createMethodsList = (): EmMethod[] => { contour: 0.0, details: "", required: false, + fileFormat: [".mrc", ".ccp4", ".mrc.gz", ".ccp4.gz"], explanation: "Half maps (as used for FSC calculation; two maps must be uploaded)", }; - const depositionHalfMap2: DepositionFiles = { + const depositionHalfMap2: DepositionFile = { emName: EmFile.HalfMap2, nameFE: "Half Map (2)", type: "half-map", @@ -95,10 +99,11 @@ export const createMethodsList = (): EmMethod[] => { contour: 0.0, details: "", required: false, + fileFormat: [".mrc", ".ccp4", ".mrc.gz", ".ccp4.gz"], explanation: "Half maps (as used for FSC calculation; two maps must be uploaded)", }; - const depositionMaskMap: DepositionFiles = { + const depositionMaskMap: DepositionFile = { emName: EmFile.MaskMap, nameFE: "Mask Map", type: "mask-map", @@ -107,10 +112,11 @@ export const createMethodsList = (): EmMethod[] => { contour: 0.0, details: "", required: false, + fileFormat: [".mrc", ".ccp4", ".mrc.gz", ".ccp4.gz"], explanation: "Primary/raw map mask, segmentation/focused refinement mask and half-map mask", }; - const depositionAddMap: DepositionFiles = { + const depositionAddMap: DepositionFile = { emName: EmFile.AddMap, id: 0, nameFE: "Additional Map", @@ -120,20 +126,22 @@ export const createMethodsList = (): EmMethod[] => { contour: 0.0, details: "", required: false, + fileFormat: [".mrc", ".ccp4", ".mrc.gz", ".ccp4.gz"], explanation: "Difference maps, maps showing alternative conformations and/or compositions, maps with differential processing (e.g. filtering, sharpening and masking)", }; - const depositionFSC: DepositionFiles = { + const depositionFSC: DepositionFile = { emName: EmFile.FSC, id: 0, nameFE: "FSC-XML", type: "fsc-xml", fileName: "", file: null, + fileFormat: [".xml"], required: false, explanation: "Half-map FSC, Map-model FSC, Cross-validation FSCs", }; - const depositionLayerLines: DepositionFiles = { + const depositionLayerLines: DepositionFile = { emName: EmFile.LayerLines, nameFE: "Other: Layer Lines Data ", type: "layer-lines", @@ -141,24 +149,35 @@ export const createMethodsList = (): EmMethod[] => { file: null, required: false, }; - const depositionCoordinates: DepositionFiles = { + const depositionCoordinates: DepositionFile = { emName: EmFile.Coordinates, nameFE: "Coordinates", type: "co-cif", fileName: "", file: null, required: false, + fileFormat: [ + ".cif", + ".pdb", + ".ent", + ".brk", + ".cif.gz", + ".pdb.gz", + ".ent.gz", + ".brk.gz", + ], explanation: "mmCIF or PDB format", }; - const depositionStructureFactors: DepositionFiles = { + const depositionStructureFactors: DepositionFile = { emName: EmFile.StructureFactors, nameFE: "Structure Factors", type: "xs-cif", fileName: "", file: null, required: false, + fileFormat: [".cif", ".mtz", ".cif.gz", ".mtz.gz"], }; - const depositionMTZ: DepositionFiles = { + const depositionMTZ: DepositionFile = { emName: EmFile.MTZ, nameFE: "MTZ", type: "xs-mtz", From 5f51a5b3fd628313499d4e262d51b4756a8edb37 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Thu, 23 Jan 2025 14:12:01 +0000 Subject: [PATCH 087/245] update function descriptions --- src/app/datasets/onedep/onedep.component.ts | 153 +++++++++++--------- 1 file changed, 86 insertions(+), 67 deletions(-) diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index 4f22e13c41..2932a3750c 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -26,18 +26,16 @@ import { } from "@scicatproject/scicat-sdk-ts"; import { selectCurrentDataset } from "state-management/selectors/datasets.selectors"; import { selectCurrentUser } from "state-management/selectors/user.selectors"; -import { selectDepID } from "state-management/selectors/onedep.selectors"; import * as fromActions from "state-management/actions/onedep.actions"; import { createMethodsList, EmFile, DepositionFile, OneDepUserInfo, - OneDepCreate, } from "./types/methods.enum"; import { Depositor } from "shared/sdk/apis/onedep-depositor.service"; -import { Observable, Subscription, fromEvent } from "rxjs"; -import { filter, map, take } from "rxjs/operators"; +import { Observable, Subscription } from "rxjs"; +import { runInThisContext } from "vm"; @Component({ selector: "onedep", @@ -60,7 +58,7 @@ export class OneDepComponent implements OnInit, OnDestroy { detailsOverflow = "hidden"; additionalMaps = 0; showPassword = false; - fileTypes: DepositionFile[]; + fileTypes: DepositionFile[]; // required to keep the initial set of files based on EM method mainContour = 0.0; connectedDepositionBackend = ""; connectedDepositionBackendVersion = ""; @@ -132,15 +130,11 @@ export class OneDepComponent implements OnInit, OnDestroy { subscription.unsubscribe(); }); } - hasUnsavedChanges() { - return this._hasUnsavedChanges; - } - onHasUnsavedChanges($event: boolean) { - this._hasUnsavedChanges = $event; - } + togglePasswordVisibility() { this.showPassword = !this.showPassword; } + // custom validator of the ORCID ids orcidValidator(): (control: AbstractControl) => ValidationErrors | null { return (control: AbstractControl): ValidationErrors | null => { const value = control.value.replace(/-/g, ""); @@ -155,12 +149,14 @@ export class OneDepComponent implements OnInit, OnDestroy { return this.form.get("orcid") as FormArray; } addOrcidField() { + // adds an empty ORCID field to the form const orcidField = this.fb.group({ orcidId: ["", [Validators.required, this.orcidValidator()]], }); this.orcidArray().push(orcidField); } removeOrcidField(index: number) { + // removes an ORCID field from the form; if it's first entry, just wipes the field if (index === 0) { this.orcidArray().at(0).reset({ orcidId: "" }); } else if (this.orcidArray().length > 1) { @@ -168,6 +164,7 @@ export class OneDepComponent implements OnInit, OnDestroy { } } addFileToForm(file: DepositionFile) { + // adds a depositionFile to the form const filesArray = this.form.get("files") as FormArray; filesArray.push( this.fb.group({ @@ -185,18 +182,20 @@ export class OneDepComponent implements OnInit, OnDestroy { }), ); } - removeFileFromForm(fileCat: EmFile) { + //remove a file from the form; only used for co-cif (yes/no toggle). On method change a new files array will be generated + removeFileFromForm(controlName: EmFile) { const filesArray = this.form.get("files") as FormArray; const index = filesArray.value.findIndex( - (file: DepositionFile) => file.emName === fileCat, + (file: DepositionFile) => file.emName === controlName, ); if (index > -1) { filesArray.removeAt(index); } } onMethodChange() { + // generates a form array with predefined types of depositionFiles with empty peoperties and specified required tag const filesArray = this.form.get("files") as FormArray; - filesArray.clear(); + filesArray.clear(); // clear files form this.methodsList = createMethodsList(); // Reset the methods list to be empty this.fileTypes = this.methodsList.find( @@ -229,6 +228,7 @@ export class OneDepComponent implements OnInit, OnDestroy { }); break; case "tomogram": + // these questions in questionnaire are not relevant for tomogram but required for other methods this.form.get("deposingCoordinates")?.setValue("false"); this.form.get("associatedMap")?.setValue("false"); break; @@ -258,44 +258,44 @@ export class OneDepComponent implements OnInit, OnDestroy { } autoGrow(event: Event) { + // function to auto-grow the textarea const textarea = event.target as HTMLTextAreaElement; const lineHeight = parseInt(getComputedStyle(textarea).lineHeight, 10); const maxLines = 3; - // Reset height to auto to calculate scrollHeight textarea.style.height = "auto"; - // Set the height based on the scrollHeight but limit it const newHeight = Math.min(textarea.scrollHeight, lineHeight * maxLines); textarea.style.height = `${newHeight}px`; - // Update overflow property based on height this.detailsOverflow = textarea.scrollHeight > newHeight ? "auto" : "hidden"; } onChooseFile(fileInput: HTMLInputElement) { fileInput.click(); } - onFileSelected(event: Event, emCat: EmFile) { + onFileSelected(event: Event, controlName: EmFile) { // once the file is selected, adds it to the form const input = event.target as HTMLInputElement; if (input.files && input.files.length > 0) { - this.selectedFile[emCat] = input.files[0]; + this.selectedFile[controlName] = input.files[0]; const filesArray = this.form.get("files") as FormArray; - const fileTypeControl = filesArray.controls.find( - (control) => control.get("emName")?.value === emCat, + const fileControl = filesArray.controls.find( + (control) => control.get("emName")?.value === controlName, ); - if (fileTypeControl) { - fileTypeControl.get("file")?.setValue(this.selectedFile[emCat]); - fileTypeControl + if (fileControl) { + fileControl.get("file")?.setValue(this.selectedFile[controlName]); + fileControl .get("fileName") - ?.setValue(this.selectedFile[emCat].name); + ?.setValue(this.selectedFile[controlName].name); if ( this.mainContour !== 0.0 && - [EmFile.MainMap, EmFile.HalfMap1, EmFile.HalfMap2].includes(emCat) + [EmFile.MainMap, EmFile.HalfMap1, EmFile.HalfMap2].includes( + controlName, + ) ) { - fileTypeControl.get("contour").setValue(this.mainContour); + fileControl.get("contour").setValue(this.mainContour); } } } @@ -303,53 +303,53 @@ export class OneDepComponent implements OnInit, OnDestroy { onFileAddMore(event: Event, id: number, fileType: string) { // once the file is selected, adds it to the form. Only for additional maps and FSC inputs, as they can include multiple files const input = event.target as HTMLInputElement; - let fileTypeControl; + let fileControl; if (input.files && input.files.length > 0) { this.selectedFile[`${fileType}-${id}`] = input.files[0]; const filesArray = this.form.get("files") as FormArray; if (fileType === "add-map") { - fileTypeControl = filesArray.controls.find( + fileControl = filesArray.controls.find( (control) => control.get("emName")?.value === this.emFile.AddMap && control.get("id")?.value === id, ); } else { - fileTypeControl = filesArray.controls.find( + fileControl = filesArray.controls.find( (control) => control.get("emName")?.value === this.emFile.FSC && control.get("id")?.value === id, ); } - if (fileTypeControl) { - fileTypeControl + if (fileControl) { + fileControl .get("file") ?.setValue(this.selectedFile[`${fileType}-${id}`]); - fileTypeControl + fileControl .get("fileName") ?.setValue(this.selectedFile[`${fileType}-${id}`].name); } } } - clearFile(fileCat: EmFile, id?: number) { - // clear the selected file + clearFile(controlName: EmFile, id?: number) { + // clear the selected file from form const key = - fileCat === "add-map" || fileCat === "fsc-xml" - ? `${fileCat}-${id}` - : fileCat; + controlName === "add-map" || controlName === "fsc-xml" + ? `${controlName}-${id}` + : controlName; this.selectedFile[key] = null; let index = -1; const filesArray = this.form.get("files") as FormArray; - if (fileCat !== "add-map" && fileCat !== "fsc-xml") { + if (controlName !== "add-map" && controlName !== "fsc-xml") { index = filesArray.value.findIndex( - (file: DepositionFile) => file.emName === fileCat, + (file: DepositionFile) => file.emName === controlName, ); } else { for (let i = 0; i < filesArray.length; i++) { if ( - filesArray.at(i).value.emName === fileCat && + filesArray.at(i).value.emName === controlName && filesArray.at(i).value.id === id ) { index = i; @@ -365,6 +365,7 @@ export class OneDepComponent implements OnInit, OnDestroy { // for a given file extracts the "required" status for the chosen EM method // FIX me subject to change let value: boolean; + this.files.forEach((fT) => { if (fT.value.emName === controlName) { value = fT.value.required; @@ -395,11 +396,15 @@ export class OneDepComponent implements OnInit, OnDestroy { const normalizedInput = input.replace(",", "."); const parsedValue = parseFloat(normalizedInput); if (!isNaN(parsedValue)) { - this.files.forEach((fT) => { - if (fT.value.emName === this.emFile.AddMap && fT.value.id === id) { - fT.value.contour = parsedValue; - } - }); + const filesArray = this.form.get("files") as FormArray; + const fileControl = filesArray.controls.find( + (control) => + control.get("emName")?.value === this.emFile.AddMap && + control.get("id")?.value === id, + ); + if (fileControl) { + fileControl.get("contour")?.patchValue(parsedValue); + } } } updateContourLevel(event: Event, controlName: EmFile) { @@ -407,32 +412,44 @@ export class OneDepComponent implements OnInit, OnDestroy { const normalizedInput = input.replace(",", "."); const parsedValue = parseFloat(normalizedInput); if (!isNaN(parsedValue)) { - this.files.forEach((fT) => { - if (fT.value.emName === controlName) { - fT.value.contour = parsedValue; - } - }); + const filesArray = this.form.get("files") as FormArray; + const fileControl = filesArray.controls.find( + (control) => control.get("emName")?.value === controlName, + ); + if (fileControl) { + fileControl.get("contour")?.patchValue(parsedValue); + } } } updateDetails(event: Event, controlName: EmFile) { - const textarea = event.target as HTMLTextAreaElement; // Cast to HTMLTextAreaElement + // function to update details for a map + const textarea = event.target as HTMLTextAreaElement; const value = textarea.value; - this.files.forEach((fT) => { - if (fT.value.emName === controlName) { - fT.value.details = value; - } - }); + + const filesArray = this.form.get("files") as FormArray; + const fileControl = filesArray.controls.find( + (control) => control.get("emName")?.value === controlName, + ); + if (fileControl) { + fileControl.get("details")?.patchValue(value); + } } updateDetailsAddMap(event: Event, id: number) { - const textarea = event.target as HTMLTextAreaElement; // Cast to HTMLTextAreaElement + // function to update details for additional maps + const textarea = event.target as HTMLTextAreaElement; const value = textarea.value; - this.files.forEach((fT) => { - if (fT.value.emName === this.emFile.AddMap && fT.value.id === id) { - fT.value.details = value; - } - }); + const filesArray = this.form.get("files") as FormArray; + const fileControl = filesArray.controls.find( + (control) => + control.get("emName")?.value === this.emFile.AddMap && + control.get("id")?.value === id, + ); + if (fileControl) { + fileControl.get("details")?.patchValue(value); + } } addMap() { + // adds an empty DepositionFile of type Add-Map to the form const nextId = this.files .filter((file) => file.value.emName === EmFile.AddMap) @@ -459,6 +476,7 @@ export class OneDepComponent implements OnInit, OnDestroy { this.addFileToForm(newMap); } addFSC() { + // adds an empty DepositionFile of type FSC to the form const nextId = this.files .filter((file) => file.value.emName === EmFile.FSC) @@ -482,13 +500,13 @@ export class OneDepComponent implements OnInit, OnDestroy { this.addFileToForm(newFSC); } - correctExtension(control: AbstractControl) { - console.log("calling validator"); - const fileValue = control.value; + correctExtension(controlFile: AbstractControl) { + // checks if the provided files has a correct extension + const fileValue = controlFile.value; if (!fileValue) { return null; } - const allowedExtensions = control.parent?.get("fileFormat")?.value; + const allowedExtensions = controlFile.parent?.get("fileFormat")?.value; const fileName = fileValue.name || fileValue; const fileExtension = fileName.endsWith(".gz") ? "." + fileName.split(".").slice(-2).join(".") @@ -501,6 +519,7 @@ export class OneDepComponent implements OnInit, OnDestroy { } onDepositClick() { + console.log(this.files); let body: OneDepUserInfo; if (this.form.value.password) { body = { From 6d5182b5c8bb3626bf60cbb74dfb5f09a2bd2665 Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Thu, 23 Jan 2025 17:16:37 +0000 Subject: [PATCH 088/245] validators on required files working --- src/app/datasets/onedep/onedep.component.html | 4 +- src/app/datasets/onedep/onedep.component.scss | 2 +- src/app/datasets/onedep/onedep.component.ts | 40 ++++++++++--------- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index 59cef57ede..8926488e98 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -112,7 +112,7 @@

Choose Electron Microscopy Method

-
+
You must specify the experimental method
@@ -220,7 +220,7 @@

Choose Electron Microscopy Method

clear
-
+
Invalid file format. Allowed formats: {{ fileType.get('fileFormat').value }}
diff --git a/src/app/datasets/onedep/onedep.component.scss b/src/app/datasets/onedep/onedep.component.scss index fdc7578840..a444911664 100644 --- a/src/app/datasets/onedep/onedep.component.scss +++ b/src/app/datasets/onedep/onedep.component.scss @@ -264,7 +264,7 @@ mat-card { color:black; } .error-text { - color: red; + color: #c81919; font-size: 0.8rem; margin-top: 0; } diff --git a/src/app/datasets/onedep/onedep.component.ts b/src/app/datasets/onedep/onedep.component.ts index 2932a3750c..26e042ff67 100644 --- a/src/app/datasets/onedep/onedep.component.ts +++ b/src/app/datasets/onedep/onedep.component.ts @@ -36,6 +36,7 @@ import { import { Depositor } from "shared/sdk/apis/onedep-depositor.service"; import { Observable, Subscription } from "rxjs"; import { runInThisContext } from "vm"; +import { FlexAlignDirective } from "@ngbracket/ngx-layout"; @Component({ selector: "onedep", @@ -165,22 +166,26 @@ export class OneDepComponent implements OnInit, OnDestroy { } addFileToForm(file: DepositionFile) { // adds a depositionFile to the form + const validators = [this.correctExtension]; + if (file.required) { + validators.push(Validators.required); // Add required dynamically + } + const filesArray = this.form.get("files") as FormArray; - filesArray.push( - this.fb.group({ - emName: [file.emName], - id: [file.id], - nameFE: [file.nameFE], - type: [file.type], - fileName: [file.fileName], - file: [file.file, [this.correctExtension]], - required: [file.required], - contour: [file.contour], - details: [file.details], - fileFormat: [file.fileFormat], - explanation: [file.explanation], - }), - ); + const fileGroup = this.fb.group({ + emName: [file.emName], + id: [file.id], + nameFE: [file.nameFE], + type: [file.type], + fileName: [file.fileName], + file: [file.file, validators], + required: [file.required], + contour: [file.contour], + details: [file.details], + fileFormat: [file.fileFormat], + explanation: [file.explanation], + }); + filesArray.push(fileGroup); } //remove a file from the form; only used for co-cif (yes/no toggle). On method change a new files array will be generated removeFileFromForm(controlName: EmFile) { @@ -500,7 +505,7 @@ export class OneDepComponent implements OnInit, OnDestroy { this.addFileToForm(newFSC); } - correctExtension(controlFile: AbstractControl) { + correctExtension(controlFile: AbstractControl): ValidationErrors | null { // checks if the provided files has a correct extension const fileValue = controlFile.value; if (!fileValue) { @@ -515,11 +520,10 @@ export class OneDepComponent implements OnInit, OnDestroy { if (allowedExtensions && allowedExtensions.includes(fileExtension)) { return null; } - return { correctExtension: false }; + return { correctExtension: true }; } onDepositClick() { - console.log(this.files); let body: OneDepUserInfo; if (this.form.value.password) { body = { From 3496fec2c5510fca8900514055c8c770209353cf Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Fri, 24 Jan 2025 08:54:32 +0000 Subject: [PATCH 089/245] wip add validators on contour level --- src/app/datasets/onedep/onedep.component.html | 6 +++--- src/app/datasets/onedep/onedep.component.ts | 18 +++++++++++------ src/app/datasets/onedep/types/methods.enum.ts | 20 ++++++++++++++----- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/app/datasets/onedep/onedep.component.html b/src/app/datasets/onedep/onedep.component.html index 8926488e98..a07f777293 100644 --- a/src/app/datasets/onedep/onedep.component.html +++ b/src/app/datasets/onedep/onedep.component.html @@ -225,9 +225,9 @@

Choose Electron Microscopy Method

- +
- + Contour Level Choose Electron Microscopy Method

/> - + Short description + - + \ No newline at end of file diff --git a/src/app/ingestor/ingestor/helper/ingestor.component-helper.ts b/src/app/ingestor/ingestor/helper/ingestor.component-helper.ts index 910b2fbe35..e53f2967e1 100644 --- a/src/app/ingestor/ingestor/helper/ingestor.component-helper.ts +++ b/src/app/ingestor/ingestor/helper/ingestor.component-helper.ts @@ -70,6 +70,7 @@ export interface DialogDataObject { createNewTransferData: IngestionRequestInformation; backendURL: string; onClickNext: (step: number) => void; + onStartUpload: () => Promise; } export class IngestorHelper { diff --git a/src/app/ingestor/ingestor/ingestor.component.ts b/src/app/ingestor/ingestor/ingestor.component.ts index 6efa0f6144..976da9db0d 100644 --- a/src/app/ingestor/ingestor/ingestor.component.ts +++ b/src/app/ingestor/ingestor/ingestor.component.ts @@ -139,30 +139,37 @@ export class IngestorComponent implements OnInit { ); } - apiUpload() { + apiUpload(): Promise { this.loading = true; const payload: PostDatasetEndpoint = { metaData: this.createNewTransferData.mergedMetaDataString, }; - this.http - .post(this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.DATASET, { - payload, - withCredentials: true, - }) - .subscribe( - (response) => { - //console.log("Upload successfully started", response); - this.returnValue = JSON.stringify(response); - this.loading = false; - }, - (error) => { - this.errorMessage += `${new Date().toLocaleString()}: ${error.message}]
`; - console.error("Upload failed", error); - this.loading = false; - }, - ); + return new Promise((resolve, reject) => { + this.http + .post( + this.connectedFacilityBackend + INGESTOR_API_ENDPOINTS_V1.DATASET, + { + payload, + withCredentials: true, + }, + ) + .subscribe( + (response) => { + //console.log("Upload successfully started", response); + this.returnValue = JSON.stringify(response); + this.loading = false; + resolve(true); + }, + (error) => { + this.errorMessage += `${new Date().toLocaleString()}: ${error.message}]
`; + console.error("Upload failed", error); + this.loading = false; + reject(error); + }, + ); + }); } async apiStartMetadataExtraction(): Promise { @@ -308,6 +315,7 @@ export class IngestorComponent implements OnInit { dialogRef = this.dialog.open(IngestorConfirmTransferDialogComponent, { data: { onClickNext: this.onClickNext.bind(this), + onStartUpload: this.apiUpload.bind(this), createNewTransferData: this.createNewTransferData, backendURL: this.connectedFacilityBackend, }, @@ -315,7 +323,6 @@ export class IngestorComponent implements OnInit { }); break; case 4: - this.apiUpload(); break; default: console.error("Unknown step", step); From 84208dad9e9075a5a420b6e7860934a3ce9f44e9 Mon Sep 17 00:00:00 2001 From: dwiessner-unibe Date: Fri, 31 Jan 2025 17:01:13 +0000 Subject: [PATCH 100/245] Bug Fixes, auto generated models and support of user info --- .../customRenderer/array-renderer.ts | 1 - .../ingestor/ingestor/_ingestor-theme.scss | 15 +- .../ingestor.dialog-stepper.component.ts | 11 +- ...estor.confirm-transfer-dialog.component.ts | 1 - .../ingestor.confirm-transfer-dialog.html | 20 +- ...tor.extractor-metadata-dialog.component.ts | 6 +- .../ingestor.new-transfer-dialog.component.ts | 1 - ...ingestor.user-metadata-dialog.component.ts | 8 +- .../ingestor/helper/ingestor-api-endpoints.ts | 14 +- .../ingestor/helper/ingestor-api-manager.ts | 155 ++++++++++++++ .../ingestor/ingestor/ingestor.component.html | 34 +-- .../ingestor/ingestor/ingestor.component.scss | 34 +-- .../ingestor/ingestor/ingestor.component.ts | 201 +++++++----------- .../ingestor/model/deleteTransferRequest.ts | 18 ++ .../ingestor/model/deleteTransferResponse.ts | 22 ++ src/app/ingestor/model/getDatasetResponse.ts | 19 ++ .../ingestor/model/getExtractorResponse.ts | 23 ++ src/app/ingestor/model/getTransferResponse.ts | 20 ++ src/app/ingestor/model/methodItem.ts | 19 ++ src/app/ingestor/model/modelError.ts | 16 ++ src/app/ingestor/model/models.ts | 16 ++ src/app/ingestor/model/oidcCallbackOk.ts | 18 ++ .../model/oidcCallbackOkOAuth2Token.ts | 22 ++ .../ingestor/model/oidcCallbackOkUserInfo.ts | 24 +++ src/app/ingestor/model/otherHealthResponse.ts | 19 ++ .../ingestor/model/otherVersionResponse.ts | 18 ++ src/app/ingestor/model/postDatasetRequest.ts | 18 ++ src/app/ingestor/model/postDatasetResponse.ts | 22 ++ src/app/ingestor/model/transferItem.ts | 16 ++ src/app/ingestor/model/userInfo.ts | 24 +++ 30 files changed, 641 insertions(+), 194 deletions(-) create mode 100644 src/app/ingestor/ingestor/helper/ingestor-api-manager.ts create mode 100644 src/app/ingestor/model/deleteTransferRequest.ts create mode 100644 src/app/ingestor/model/deleteTransferResponse.ts create mode 100644 src/app/ingestor/model/getDatasetResponse.ts create mode 100644 src/app/ingestor/model/getExtractorResponse.ts create mode 100644 src/app/ingestor/model/getTransferResponse.ts create mode 100644 src/app/ingestor/model/methodItem.ts create mode 100644 src/app/ingestor/model/modelError.ts create mode 100644 src/app/ingestor/model/models.ts create mode 100644 src/app/ingestor/model/oidcCallbackOk.ts create mode 100644 src/app/ingestor/model/oidcCallbackOkOAuth2Token.ts create mode 100644 src/app/ingestor/model/oidcCallbackOkUserInfo.ts create mode 100644 src/app/ingestor/model/otherHealthResponse.ts create mode 100644 src/app/ingestor/model/otherVersionResponse.ts create mode 100644 src/app/ingestor/model/postDatasetRequest.ts create mode 100644 src/app/ingestor/model/postDatasetResponse.ts create mode 100644 src/app/ingestor/model/transferItem.ts create mode 100644 src/app/ingestor/model/userInfo.ts diff --git a/src/app/ingestor/ingestor-metadata-editor/customRenderer/array-renderer.ts b/src/app/ingestor/ingestor-metadata-editor/customRenderer/array-renderer.ts index 49dedb6987..a14d18e241 100644 --- a/src/app/ingestor/ingestor-metadata-editor/customRenderer/array-renderer.ts +++ b/src/app/ingestor/ingestor-metadata-editor/customRenderer/array-renderer.ts @@ -112,7 +112,6 @@ import { `, - changeDetection: ChangeDetectionStrategy.OnPush, }) // eslint-disable-next-line @angular-eslint/component-class-suffix export class ArrayLayoutRendererCustom diff --git a/src/app/ingestor/ingestor/_ingestor-theme.scss b/src/app/ingestor/ingestor/_ingestor-theme.scss index 39875ec92b..8e48d16b6e 100644 --- a/src/app/ingestor/ingestor/_ingestor-theme.scss +++ b/src/app/ingestor/ingestor/_ingestor-theme.scss @@ -8,6 +8,7 @@ $header-2: map-get($color-config, "header-2"); $header-3: map-get($color-config, "header-3"); $accent: map-get($color-config, "accent"); + $warn: map-get($color-config, "warn"); mat-card { .scicat-header { background-color: mat.get-color-from-palette($primary, "lighter"); @@ -29,6 +30,18 @@ background-color: mat.get-color-from-palette($accent, "lighter"); } } + + .error-message { + color: mat.get-color-from-palette($warn, "default"); + } + + .error-icon { + color: mat.get-color-from-palette($warn, "default"); + } + + .success-icon { + color: green; + } } @mixin theme($theme) { @@ -36,4 +49,4 @@ @if $color-config != null { @include color($theme); } -} \ No newline at end of file +} diff --git a/src/app/ingestor/ingestor/dialog/dialog-mounting-components/ingestor.dialog-stepper.component.ts b/src/app/ingestor/ingestor/dialog/dialog-mounting-components/ingestor.dialog-stepper.component.ts index fd3371a4e5..8242dd5b50 100644 --- a/src/app/ingestor/ingestor/dialog/dialog-mounting-components/ingestor.dialog-stepper.component.ts +++ b/src/app/ingestor/ingestor/dialog/dialog-mounting-components/ingestor.dialog-stepper.component.ts @@ -107,12 +107,13 @@ export class IngestorDialogStepperComponent { const input = document.createElement("input"); input.type = "file"; input.accept = ".json"; - input.onchange = (event: any) => { - const file = event.target.files[0]; + input.onchange = (event: Event) => { + const target = event.target as HTMLInputElement; + const file = target.files?.[0]; if (file) { const reader = new FileReader(); reader.onload = (e) => { - const content = e.target.result; + const content = e.target?.result as string; const dialogRef = this.dialog.open( IngestorConfirmationDialogComponent, @@ -120,14 +121,13 @@ export class IngestorDialogStepperComponent { data: { header: "Confirm template", message: "Do you really want to apply the following values?", - //messageComponent: CheckboxComponent, }, }, ); dialogRef.afterClosed().subscribe((result) => { if (result) { try { - const parsedData = JSON.parse(content as string); + const parsedData = JSON.parse(content); this.createNewTransferData = { ...this.createNewTransferData, scicatHeader: { @@ -139,7 +139,6 @@ export class IngestorDialogStepperComponent { ...parsedData.userMetaData, }, }; - console.log(this.createNewTransferData.scicatHeader); this.createNewTransferDataChange.emit( this.createNewTransferData, ); diff --git a/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts index 7dc979cb60..1e61f0894f 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.component.ts @@ -16,7 +16,6 @@ import { IngestorConfirmationDialogComponent } from "./confirmation-dialog/inges @Component({ selector: "ingestor.confirm-transfer-dialog", templateUrl: "ingestor.confirm-transfer-dialog.html", - changeDetection: ChangeDetectionStrategy.OnPush, styleUrls: ["../ingestor.component.scss"], }) export class IngestorConfirmTransferDialogComponent implements OnInit { diff --git a/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.html b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.html index d836e88838..0226b835a5 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.html +++ b/src/app/ingestor/ingestor/dialog/ingestor.confirm-transfer-dialog.html @@ -15,23 +15,29 @@

- -

- + Error message + +
+

+
+

Confirm Metadata

- - - +
+
+    
+ {{ line }} +
+
+
diff --git a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts index aeb389d2e4..bb1ae2f823 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.extractor-metadata-dialog.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Inject } from "@angular/core"; +import { ChangeDetectorRef, Component, Inject } from "@angular/core"; import { MatDialog, MAT_DIALOG_DATA } from "@angular/material/dialog"; import { JsonSchema } from "@jsonforms/core"; import { @@ -11,7 +11,6 @@ import { selector: "ingestor.extractor-metadata-dialog", templateUrl: "ingestor.extractor-metadata-dialog.html", styleUrls: ["../ingestor.component.scss"], - changeDetection: ChangeDetectionStrategy.OnPush, }) export class IngestorExtractorMetadataDialogComponent { metadataSchemaInstrument: JsonSchema; @@ -37,6 +36,7 @@ export class IngestorExtractorMetadataDialogComponent { constructor( public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: DialogDataObject, + private cdr: ChangeDetectorRef, ) { this.createNewTransferData = data.createNewTransferData; this.backendURL = data.backendURL; @@ -93,6 +93,7 @@ export class IngestorExtractorMetadataDialogComponent { } }); this.validateNextButton(); + this.cdr.detectChanges(); } acquisitionErrorsHandler(errors: any[]) { @@ -105,6 +106,7 @@ export class IngestorExtractorMetadataDialogComponent { } }); this.validateNextButton(); + this.cdr.detectChanges(); } validateNextButton(): void { diff --git a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts index 1d93906636..60df4e0005 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.new-transfer-dialog.component.ts @@ -18,7 +18,6 @@ import { IngestorMetadataEditorHelper } from "ingestor/ingestor-metadata-editor/ @Component({ selector: "ingestor.new-transfer-dialog", templateUrl: "ingestor.new-transfer-dialog.html", - changeDetection: ChangeDetectionStrategy.OnPush, styleUrls: ["../ingestor.component.scss"], }) export class IngestorNewTransferDialogComponent implements OnInit { diff --git a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts index 709b145d70..58f1e74726 100644 --- a/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts +++ b/src/app/ingestor/ingestor/dialog/ingestor.user-metadata-dialog.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Inject } from "@angular/core"; +import { ChangeDetectorRef, Component, Inject } from "@angular/core"; import { MAT_DIALOG_DATA, MatDialog } from "@angular/material/dialog"; import { JsonSchema } from "@jsonforms/core"; import { @@ -11,7 +11,6 @@ import { @Component({ selector: "ingestor.user-metadata-dialog", templateUrl: "ingestor.user-metadata-dialog.html", - changeDetection: ChangeDetectionStrategy.OnPush, styleUrls: ["../ingestor.component.scss"], }) export class IngestorUserMetadataDialogComponent { @@ -39,6 +38,7 @@ export class IngestorUserMetadataDialogComponent { constructor( public dialog: MatDialog, @Inject(MAT_DIALOG_DATA) public data: DialogDataObject, + private cdr: ChangeDetectorRef, ) { this.createNewTransferData = data.createNewTransferData; this.backendURL = data.backendURL; @@ -50,7 +50,6 @@ export class IngestorUserMetadataDialogComponent { .sample; this.metadataSchemaOrganizational = organizationalSchema; - this.metadataSchemaSample = sampleSchema; this.scicatHeaderSchema = SciCatHeader_Schema; } @@ -97,6 +96,7 @@ export class IngestorUserMetadataDialogComponent { } }); this.validateNextButton(); + this.cdr.detectChanges(); } organizationalErrorsHandler(errors: any[]) { @@ -109,6 +109,7 @@ export class IngestorUserMetadataDialogComponent { } }); this.validateNextButton(); + this.cdr.detectChanges(); } sampleErrorsHandler(errors: any[]) { @@ -121,6 +122,7 @@ export class IngestorUserMetadataDialogComponent { } }); this.validateNextButton(); + this.cdr.detectChanges(); } validateNextButton(): void { diff --git a/src/app/ingestor/ingestor/helper/ingestor-api-endpoints.ts b/src/app/ingestor/ingestor/helper/ingestor-api-endpoints.ts index 98a98c484f..753d3710f7 100644 --- a/src/app/ingestor/ingestor/helper/ingestor-api-endpoints.ts +++ b/src/app/ingestor/ingestor/helper/ingestor-api-endpoints.ts @@ -14,15 +14,5 @@ export const INGESTOR_API_ENDPOINTS_V1 = { METADATA: "metadata", }; -export interface PostExtractorEndpoint { - filePath: string; - methodName: string; -} - -export interface PostDatasetEndpoint { - metaData: string; -} - -export const apiGetHealth = () => { - console.log("Health check"); // TODO IMPLEMENT -}; +export const LAST_USED_FALLBACK = + '["http://localhost:8000", "http://localhost:8888"]'; diff --git a/src/app/ingestor/ingestor/helper/ingestor-api-manager.ts b/src/app/ingestor/ingestor/helper/ingestor-api-manager.ts new file mode 100644 index 0000000000..6f2a76ce73 --- /dev/null +++ b/src/app/ingestor/ingestor/helper/ingestor-api-manager.ts @@ -0,0 +1,155 @@ +import { HttpClient, HttpParams } from "@angular/common/http"; +import { Injectable } from "@angular/core"; +import { INGESTOR_API_ENDPOINTS_V1 } from "./ingestor-api-endpoints"; +import { TransferDataListEntry } from "./ingestor.component-helper"; +import { + DeleteTransferResponse, + OtherHealthResponse, + OtherVersionResponse, + PostDatasetRequest, + UserInfo, +} from "ingestor/model/models"; + +@Injectable({ + providedIn: "root", +}) +export class IngestorAPIManager { + private connectUrl: string; + private connectOptions: object; + + constructor(private http: HttpClient) { } + + public connect(url: string, withCredentials = true): void { + this.connectUrl = url; + this.connectOptions = { withCredentials: withCredentials }; + } + + public getVersion(): Promise { + const params = new HttpParams(); + return new Promise((resolve, reject) => { + this.http + .get(this.connectUrl + INGESTOR_API_ENDPOINTS_V1.OTHER.VERSION, { + params, + ...this.connectOptions, + }) + .subscribe( + (response) => { + resolve(response as OtherVersionResponse); + }, + (error) => { + reject(error); + }, + ); + }); + } + + public getUserInfo(): Promise { + const params = new HttpParams(); + return new Promise((resolve, reject) => { + this.http + .get(this.connectUrl + INGESTOR_API_ENDPOINTS_V1.AUTH.USERINFO, { + params, + ...this.connectOptions, + }) + .subscribe( + (response) => { + resolve(response as UserInfo); + }, + (error) => { + reject(error); + }, + ); + }); + } + + public getHealth(): Promise { + const params = new HttpParams(); + return new Promise((resolve, reject) => { + this.http + .get(this.connectUrl + INGESTOR_API_ENDPOINTS_V1.OTHER.HEALTH, { + params, + ...this.connectOptions, + }) + .subscribe( + (response) => { + resolve(response as OtherHealthResponse); + }, + (error) => { + reject(error); + }, + ); + }); + } + + public cancelTransfer(transferId: string): Promise { + const params = new HttpParams().set("transferId", transferId); + + console.log("Cancel transfer", transferId); + return new Promise((resolve, reject) => { + this.http + .delete(this.connectUrl + INGESTOR_API_ENDPOINTS_V1.TRANSFER, { + params, + ...this.connectOptions, + }) + .subscribe( + (response) => { + resolve(response as DeleteTransferResponse); + }, + (error) => { + reject(error); + }, + ); + }); + } + + public getTransferList( + page: number, + pageSize: number, + transferId?: string, + ): Promise { + const params: any = { + page: page.toString(), + pageSize: pageSize.toString(), + }; + if (transferId) { + params.transferId = transferId; + } + return new Promise((resolve, reject) => { + this.http + .get(this.connectUrl + INGESTOR_API_ENDPOINTS_V1.TRANSFER, { + params, + ...this.connectOptions, + }) + .subscribe( + (response) => { + const transferDataList: TransferDataListEntry[] = + response["transfers"]; + resolve(transferDataList); + }, + (error) => { + reject(error); + }, + ); + }); + } + + public startIngestion(payload: PostDatasetRequest): Promise { + return new Promise((resolve, reject) => { + this.http + .post(this.connectUrl + INGESTOR_API_ENDPOINTS_V1.DATASET, { + payload, + ...this.connectOptions, + }) + .subscribe( + (response) => { + const returnValue = JSON.stringify(response); + resolve(returnValue); + }, + (error) => { + console.error("Upload failed", error); + reject(error); + }, + ); + }); + } +} diff --git a/src/app/ingestor/ingestor/ingestor.component.html b/src/app/ingestor/ingestor/ingestor.component.html index 15131a2579..597aa00a5a 100644 --- a/src/app/ingestor/ingestor/ingestor.component.html +++ b/src/app/ingestor/ingestor/ingestor.component.html @@ -32,14 +32,14 @@ -