From 139081aee2eee9426a837620606be2a960ad214a Mon Sep 17 00:00:00 2001 From: David Wiessner Date: Mon, 12 Aug 2024 08:42:38 +0000 Subject: [PATCH 001/242] 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 b844e8659..88baab409 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 3310f2c05..2f95ca27e 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 000000000..8796c9c34 --- /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 000000000..c1a5b047d --- /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 000000000..d5ac4b712 --- /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 000000000..e69de29bb 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 000000000..19b7d76c4 --- /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 000000000..a693ac724 --- /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 000000000..151391c67 --- /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 000000000..b85eb9646 --- /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 000000000..52186b107 --- /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 000000000..d300b87a0 --- /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/242] 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 31ea6b31b..c4c8d31d1 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 3bd83d36a..f5f9be20e 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 5ff1f6c9d..a5d14bc91 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 65eb04968..0753fc83c 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 e09b06506..6640cd2b0 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 ca6147ce5..c447e2055 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 692833eb3..727782e5b 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 3a59efee6..5956ab44f 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 151391c67..61ab5ab39 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 d300b87a0..e3722a06d 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 9f8cf2b64..398216a63 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 c32cf212b..e893ad62f 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/242] 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 e3722a06d..e2931bc54 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/242] 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 61ab5ab39..3d19f732d 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 b85eb9646..96cdeec1b 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 e2931bc54..82213f95c 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/242] 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 ccd2e6df4..223e5bca8 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 a5d14bc91..5ff1f6c9d 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 7b8210795..3d875e105 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 59f2aaafc..e19f1c8b9 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 c447e2055..ca6147ce5 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 727782e5b..692833eb3 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 e3310ebca..a043999e8 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 398216a63..9f8cf2b64 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 d2a42303f..c41fd6c04 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/242] 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 f5f9be20e..3bd83d36a 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 a043999e8..4538eea22 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 c41fd6c04..759ea9507 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/242] 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 12e3624f5..aa0ee1e75 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/242] 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 82213f95c..99cf89e0f 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/242] 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 19b7d76c4..d9cd5b12b 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 bd0400815..febb5625c 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 a61b5ca5b..b458d1edd 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 99cf89e0f..55d4200fc 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/242] 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 3ec659024..0a28ad197 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 000000000..632419f9c --- /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 000000000..8ed4d8d0e --- /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 d9cd5b12b..56b2a9fc3 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 febb5625c..c2248030e 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 000000000..45d7bcc19 --- /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 000000000..4e5521049 --- /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 000000000..8e114ace8 --- /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 000000000..9902301bb --- /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 000000000..cda2927a1 --- /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 000000000..ef83ca5d4 --- /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 000000000..c636b2654 --- /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 000000000..a054ec9e3 --- /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 000000000..09464dc94 --- /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 000000000..f39a46a2b --- /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 000000000..3de23c296 --- /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 aa0ee1e75..6cd586080 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 b458d1edd..0f94f8fe9 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 96cdeec1b..685d6ee35 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 55d4200fc..8daab0e07 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/242] 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 632419f9c..edecaf865 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 8ed4d8d0e..05b606f41 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 56b2a9fc3..65b7f330c 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 c2248030e..359e3dd50 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 000000000..d214fed38 --- /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 45d7bcc19..52dfdddc6 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 4e5521049..000000000 --- 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 000000000..e37ebd7e7 --- /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 ef83ca5d4..6366b1d43 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 c636b2654..000000000 --- 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 000000000..352663e37 --- /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 a054ec9e3..16675bd3d 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 09464dc94..000000000 --- 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 3de23c296..c2fc72286 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 f39a46a2b..4ec7ae4c1 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 0f94f8fe9..187f6f2f7 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 685d6ee35..defee924f 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 8daab0e07..80dfd0772 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/242] 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 edecaf865..df2d8bc0a 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 05b606f41..630a06f27 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 000000000..39875ec92 --- /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 d214fed38..76637b90f 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 e37ebd7e7..d09447139 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 6366b1d43..c3283d3e0 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 c2fc72286..e5532f221 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 4ec7ae4c1..abf714cf8 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 defee924f..6daf14159 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 80dfd0772..a6c7def86 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 9aee94e72..150dc3ab4 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/242] 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 0a28ad197..d7eeff7c9 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/242] 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 000000000..91085fbaf --- /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 000000000..4add5848a --- /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 000000000..8b807f113 --- /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 000000000..efc546ae4 --- /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 df2d8bc0a..5208f602b 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 65b7f330c..db5348c19 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 76637b90f..1968c6742 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 d09447139..fae49b798 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 c3283d3e0..15298bee8 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 352663e37..0433f6122 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 16675bd3d..6bd1a6d73 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 e5532f221..cbf4cacd8 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 abf714cf8..8e3fd1914 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 6daf14159..bbfccd1e5 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 a6c7def86..ba3431335 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/242] 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 4add5848a..3e212da8d 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 efc546ae4..42665d5c6 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 5208f602b..b1b4ff58b 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 db5348c19..d6921ccbb 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 359e3dd50..eb79e0e64 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 fae49b798..cdae97e55 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 15298bee8..3178a15a5 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 cbf4cacd8..c2087642f 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/242] 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 3e212da8d..530d76219 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 b1b4ff58b..65c4d640d 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 630a06f27..5874d74d4 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 d6921ccbb..308a64143 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 1968c6742..52285fca6 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 cdae97e55..97ba8e811 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 3178a15a5..fe985f0d6 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 0433f6122..198b11fdf 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 6bd1a6d73..a80e8c4d8 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 c2087642f..427bacd80 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 8e3fd1914..39fbf41a4 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 6cd586080..df2d08833 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 000000000..1d31525d5 --- /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 bbfccd1e5..a5c5dcad6 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 ba3431335..2b49f2f90 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/242] 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 fcf68c4a1..99cfb094f 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 46e362e2b..0919c32cc 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 d807d0914..91da0d4b1 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 611ee66ce..fb6689b8c 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 0084ef2f6..4e50368af 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 2033ad245..cc1ae02dd 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 ac4678930..42b3234ba 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 2687093ee..2d1be484f 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 4124c88a0..735cd8512 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/242] 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 b844e8659..88baab409 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 3310f2c05..2f95ca27e 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 000000000..8796c9c34 --- /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 000000000..c1a5b047d --- /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 000000000..2e2b11f4e --- /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 000000000..89fe8050d --- /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 000000000..19b7d76c4 --- /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 000000000..bd0400815 --- /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 000000000..aa0ee1e75 --- /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 000000000..a61b5ca5b --- /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 000000000..96cdeec1b --- /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 000000000..52186b107 --- /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 000000000..99cf89e0f --- /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/242] 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 88baab409..195563a3d 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 2f95ca27e..85e9660f0 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 000000000..194e47f54 --- /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 000000000..6b4116ea0 --- /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 000000000..d54b86fe8 --- /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 000000000..6bdd48eea --- /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 000000000..c81532751 --- /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 000000000..ea5afea2c --- /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 000000000..7fe6bedc3 --- /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 000000000..e69de29bb diff --git a/src/app/emexport/empiar/empiar.component.ts b/src/app/emexport/empiar/empiar.component.ts new file mode 100644 index 000000000..231735c2d --- /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 000000000..0fcef09a3 --- /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 000000000..e69de29bb diff --git a/src/app/emexport/onedep/onedep.component.ts b/src/app/emexport/onedep/onedep.component.ts new file mode 100644 index 000000000..053355af0 --- /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/242] 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 195563a3d..dced69ad4 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 85e9660f0..2f95ca27e 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 d1637dbd5..14feaf694 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 194e47f54..000000000 --- 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 6b4116ea0..000000000 --- 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 d049b4fe8..f9c575b4f 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 14d17981e..5eb33c71c 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 c13db28b0..f34393667 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 f9092cc4b..8f8d905a1 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 000000000..346cccafb --- /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 000000000..b862d7d64 --- /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 000000000..93ed41e1d --- /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 d54b86fe8..000000000 --- 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 6bdd48eea..000000000 --- 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 c81532751..000000000 --- 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 ea5afea2c..000000000 --- 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 0fcef09a3..000000000 --- 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 e69de29bb..000000000 diff --git a/src/app/emexport/onedep/onedep.component.ts b/src/app/emexport/onedep/onedep.component.ts deleted file mode 100644 index 053355af0..000000000 --- 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 231735c2d..ae9b3eba2 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/242] 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 f9c575b4f..ba5b6e7f2 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 5eb33c71c..efb4f1bdc 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 f34393667..c223b518b 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 aa2b8dcec..c85709486 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 346cccafb..822153b38 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 93ed41e1d..b1f0a3e4e 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/242] 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 ba5b6e7f2..dd98f6e24 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 efb4f1bdc..4ceac4289 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 822153b38..db0a8ec7a 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 b1f0a3e4e..76f308fe1 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/242] 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 dd98f6e24..c5c6d5833 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 b862d7d64..71d65ed58 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 76f308fe1..16434c83f 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/242] 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 3a3db36fd..30f76fa82 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 7148f5362..62bf4c166 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 16434c83f..bb9f31e05 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/242] 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 30f76fa82..67f706479 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 62bf4c166..e784b686a 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 bb9f31e05..1cbb0ed07 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 000000000..043208781 --- /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/242] 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 e784b686a..77003ab2d 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 1cbb0ed07..3d506a841 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 043208781..5cefb55be 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/242] 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 77003ab2d..5a8b368d5 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 71d65ed58..617a0715a 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 3d506a841..ac61cf2f6 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 5cefb55be..fee023970 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/242] 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 5a8b368d5..ca292ea3a 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 ac61cf2f6..d47c5b0f0 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/242] 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 d47c5b0f0..ad3e6059f 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/242] 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 ca292ea3a..0d9cc8efe 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 ad3e6059f..ba92fbc1f 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 96518ad9d..a71270c0e 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/242] 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 8f8d905a1..2c007619c 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 0d9cc8efe..99c0aa40e 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 617a0715a..ad555adaf 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 ba92fbc1f..5cea0d5a7 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 000000000..d54b0a250 --- /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/242] 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 99c0aa40e..c96828f1d 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 5f2bd34f1..8b16ccae5 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 68ada646e..050444632 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 fee023970..008503e18 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/242] 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 54aecdb54..abb7adc38 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 8b16ccae5..2118ed5cd 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 050444632..ffbbcd77e 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 008503e18..388d089b3 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/242] 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 abb7adc38..b02eaa101 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 388d089b3..ab39c9a87 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/242] 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 a85036002..aece4d008 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/242] 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 2118ed5cd..b81300843 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/242] 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 b02eaa101..85dbf9397 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 d1efd128d..1884b5fec 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 34c1b2232..0557ace15 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/242] 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 2f22b5624..862f21e38 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 da87bca32..633624b99 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 7dec6effd..ea5714c92 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 0f3f81e43..1bf09da06 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 14feaf694..d1637dbd5 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 fddb5db54..5f3ec55dc 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 c5c6d5833..0df999dc2 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 c85709486..9a8326095 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 0557ace15..df0ad5422 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 d54b0a250..7928e6552 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 971433d61..e5748c9f0 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 7fe6bedc3..000000000 --- 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 e69de29bb..000000000 diff --git a/src/app/empiar/empiar.component.ts b/src/app/empiar/empiar.component.ts deleted file mode 100644 index ae9b3eba2..000000000 --- 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 a71270c0e..b5c7e35a6 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/242] 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 c8e12289a..163cf8ab6 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 df0ad5422..65f2cbbf8 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 e5748c9f0..f2c8b8bdf 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/242] 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 216eae775..dfba4fb67 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/242] 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 2c007619c..7b11366ef 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/242] 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 17b798591..eca8887ea 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 dfba4fb67..150f79dcd 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/242] 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 d1637dbd5..14feaf694 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 0df999dc2..f9c575b4f 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 14d17981e..5eb33c71c 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 eca8887ea..3a0e3b061 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 7b11366ef..9f2314495 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 000000000..e69de29bb From a0682c00cdefd53b66b3fc4416a82de8d69f92fa Mon Sep 17 00:00:00 2001 From: sofyalaski Date: Thu, 17 Oct 2024 17:56:02 +0200 Subject: [PATCH 050/242] 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 f9c575b4f..ba5b6e7f2 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 5eb33c71c..efb4f1bdc 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 3a0e3b061..3da1109e8 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/242] 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 ba5b6e7f2..aabc10c2d 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 3da1109e8..9b801fd7d 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 163cf8ab6..294e0d39f 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/242] 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 9b801fd7d..58830f8c2 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/242] 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 58830f8c2..2f4724678 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/242] 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 9f2314495..d1d05db71 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 294e0d39f..163cf8ab6 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/242] 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 14feaf694..d1637dbd5 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 efb4f1bdc..14d17981e 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 2f4724678..308b7f684 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 e69de29bb..000000000 From c3d30af3aab32cd4c855dcdb620aaf8673dafcc7 Mon Sep 17 00:00:00 2001 From: Spencer Bliven Date: Tue, 17 Dec 2024 15:42:10 +0100 Subject: [PATCH 056/242] 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 19b77893c..e612e490a 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 8d1598332..683992c73 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 fcf68c4a1..99cfb094f 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 46e362e2b..0919c32cc 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 d807d0914..91da0d4b1 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 611ee66ce..11dee7b73 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 0084ef2f6..4e50368af 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 2033ad245..3a8b41ff3 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 ac4678930..42b3234ba 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 2687093ee..2d1be484f 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 4124c88a0..735cd8512 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 000000000..778938bec --- /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 139074016..b4e673e37 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 4a0157e02..8eb342020 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 7f866bd6f..a2c3098b4 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/242] 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 163cf8ab6..4887573ee 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/242] 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 e612e490a..878cfdf0d 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 683992c73..0ad6e1a77 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 99cfb094f..eb4374a44 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 91da0d4b1..de9f38d88 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 11dee7b73..210586cfa 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 4e50368af..ee5315f7e 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 3a8b41ff3..8c374bfec 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 910febb14..8142f27e9 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 3060e7ce8..600de0b6b 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 778938bec..ba6e9ad14 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 993a05777..fdeecbd8a 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 b4e673e37..ac902ae6a 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 e1e32fb2f..b8e139bf9 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 8eb342020..80981a7e7 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 a2c3098b4..040366483 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 365e0ede3..d9e891e83 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/242] 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 862f21e38..2f22b5624 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 dced69ad4..b844e8659 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 2f95ca27e..3310f2c05 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 8796c9c34..000000000 --- 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 c1a5b047d..000000000 --- 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 99cfb094f..fcf68c4a1 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 0919c32cc..46e362e2b 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 91da0d4b1..d807d0914 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 aabc10c2d..0df999dc2 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 9a8326095..ae48fc545 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 d1d05db71..2c007619c 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 2e2b11f4e..000000000 --- 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 89fe8050d..000000000 --- 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 19b7d76c4..000000000 --- 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 bd0400815..000000000 --- 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 aa0ee1e75..000000000 --- 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 a61b5ca5b..000000000 --- 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 96cdeec1b..000000000 --- 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 52186b107..000000000 --- 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 99cf89e0f..000000000 --- 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 fb6689b8c..611ee66ce 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 4e50368af..f3c32b511 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 cc1ae02dd..2033ad245 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 42b3234ba..a41a60800 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 2d1be484f..8ce5c5b90 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 735cd8512..4124c88a0 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 150f79dcd..216eae775 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/242] 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 093937260..73a9c0b92 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 878cfdf0d..123910ced 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 0ad6e1a77..0f6824a6b 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 9acc15820..701a6f3a4 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 ee5315f7e..31efaf81c 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 8c374bfec..8eeb995db 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 8142f27e9..a803b8fa6 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 600de0b6b..88e7f8950 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 000000000..0dd46a908 --- /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 216a5f85d..00dcd5792 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 fdeecbd8a..4a5ca645d 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 ac902ae6a..93cbd2581 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 b8e139bf9..16251775d 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 80981a7e7..f9f5738de 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 040366483..3fdae792a 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 d9e891e83..cc5017089 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/242] 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 0dd46a908..b39d7a015 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/242] 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 65f2cbbf8..aeee5e3e5 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/242] 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 96518ad9d..996dd1a93 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/242] 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 aeee5e3e5..af13dc9f0 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/242] =?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 91c79ee62..6404306d1 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 b844e8659..88baab409 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 3310f2c05..2f95ca27e 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 000000000..8796c9c34 --- /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 000000000..c1a5b047d --- /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 000000000..91085fbaf --- /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 000000000..530d76219 --- /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 000000000..8b807f113 --- /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 000000000..42665d5c6 --- /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 000000000..65c4d640d --- /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 000000000..5874d74d4 --- /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 000000000..2e2b11f4e --- /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 000000000..89fe8050d --- /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 000000000..308a64143 --- /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 000000000..eb79e0e64 --- /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 000000000..39875ec92 --- /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 000000000..52285fca6 --- /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 000000000..52dfdddc6 --- /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 000000000..cda2927a1 --- /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 000000000..8e114ace8 --- /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 000000000..9902301bb --- /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 000000000..97ba8e811 --- /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 000000000..fe985f0d6 --- /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 000000000..198b11fdf --- /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 000000000..a80e8c4d8 --- /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 000000000..427bacd80 --- /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 000000000..39fbf41a4 --- /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 000000000..df2d08833 --- /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 000000000..1d31525d5 --- /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 000000000..187f6f2f7 --- /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 000000000..a5c5dcad6 --- /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 000000000..52186b107 --- /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 000000000..2b49f2f90 --- /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 9aee94e72..150dc3ab4 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/242] 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 2c007619c..bf79aab65 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 000000000..4f527d88c --- /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 000000000..e69de29bb 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 000000000..24c4b1d59 --- /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 000000000..c7e535f5d --- /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 af13dc9f0..7e9809130 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 000000000..39c765d51 --- /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 b39d7a015..a762e12af 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 000000000..ff57d2901 --- /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 000000000..aa10a96f9 --- /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 000000000..2d68b5d7a --- /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 000000000..8ed164759 --- /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/242] 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 ab451f69c..b4978fa09 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/242] 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 ab451f69c..b4978fa09 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/242] 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 91085fbaf..0eeefb679 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 530d76219..1df486fff 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 8b807f113..67f76f65d 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 42665d5c6..3f863aa3b 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 65c4d640d..5eb5a4964 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 5874d74d4..000000000 --- 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 308a64143..d4bf2a72e 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 eb79e0e64..1dfd044bf 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 52285fca6..36b22feb0 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 cda2927a1..1531c5404 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 97ba8e811..9fa86aa13 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 198b11fdf..ba4cae3be 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 427bacd80..896033c8a 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 df2d08833..acd08c017 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 1d31525d5..8191b9069 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 52186b107..dd7090033 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 2b49f2f90..33210529e 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/242] 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 bf79aab65..34a955f4c 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 7e9809130..3c0d393ba 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 f2c8b8bdf..fce5bc7ee 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 ff57d2901..bbf5cd569 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 aa10a96f9..238284709 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 000000000..1721eedc4 --- /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 2d68b5d7a..0f8910e0d 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 8ed164759..169b9c796 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/242] 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 0eeefb679..bc0df73f5 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 1df486fff..663d69321 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 000000000..49dedb698 --- /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 67f76f65d..04b118182 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 3f863aa3b..76324cab2 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 5eb5a4964..404a614e9 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 89fe8050d..b41bf4ab3 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 1dfd044bf..9e9b54d6e 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 896033c8a..39f629852 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 8191b9069..c022782ea 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 33210529e..637cf7f94 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/242] 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 3c0d393ba..f2280d8b4 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 fce5bc7ee..1895a716c 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 238284709..8391e4a3a 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 1721eedc4..cd7ddbb33 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 0f8910e0d..acffdeb40 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 000000000..5264d83aa --- /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 169b9c796..d1f654d2c 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 000000000..e69de29bb 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 000000000..516e8bf8b --- /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 000000000..0b58a2587 --- /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 000000000..32aa33be3 --- /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/242] 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 91085fbaf..bc0df73f5 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 530d76219..663d69321 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 000000000..49dedb698 --- /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 8b807f113..04b118182 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 42665d5c6..76324cab2 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 65c4d640d..404a614e9 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 89fe8050d..b41bf4ab3 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 308a64143..d4bf2a72e 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 eb79e0e64..9e9b54d6e 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 52285fca6..36b22feb0 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 cda2927a1..1531c5404 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 97ba8e811..9fa86aa13 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 198b11fdf..ba4cae3be 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 427bacd80..39f629852 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 df2d08833..acd08c017 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 1d31525d5..c022782ea 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 52186b107..dd7090033 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 2b49f2f90..637cf7f94 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/242] 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 4887573ee..8c33b6b81 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/242] 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 99ea99d15..94a216605 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 1884b5fec..997874654 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 69ea12fed..992f83dc4 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 8391e4a3a..889760c66 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 cd7ddbb33..d7d0c5c11 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 acffdeb40..3d381f110 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 3b02c8e34..ca1792f4e 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/242] 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 992f83dc4..f159cb4d7 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/242] 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 471c8e15f..4c582288a 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/242] 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 71604d540..e052fda84 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/242] 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 d573c8d76..7bce94c62 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 997874654..9fec0412e 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 f159cb4d7..8242c813c 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 a8bc13a2d..0925e9a76 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/242] 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 7bce94c62..049b74eeb 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 9fec0412e..b131c8afc 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 8242c813c..db2aa083b 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/242] 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 1531c5404..8b665406d 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 8e114ace8..215e47f1c 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 9902301bb..f5498dd67 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 9fa86aa13..fd91e6897 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 fe985f0d6..2674b3d86 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 1f57b6ab8..bf9a2c2d7 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 39fbf41a4..c124c91fe 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 acd08c017..d237ef4fd 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 187f6f2f7..454f273f4 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 a5c5dcad6..1fb107fe3 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 e052fda84..d082da917 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/242] 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 049b74eeb..59cef57ed 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 b131c8afc..fdc757884 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 db2aa083b..4f22e13c4 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 0925e9a76..7f26e06f6 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/242] 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 4f22e13c4..2932a3750 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/242] 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 59cef57ed..8926488e9 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 fdc757884..a44491166 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 2932a3750..26e042ff6 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/242] 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 8926488e9..a07f77729 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 910b2fbe3..e53f2967e 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 6efa0f614..976da9db0 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/242] 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 49dedb698..a14d18e24 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 39875ec92..8e48d16b6 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 fd3371a4e..8242dd5b5 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 7dc979cb6..1e61f0894 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 d836e8883..0226b835a 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 aeb389d2e..bb1ae2f82 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 1d9390663..60df4e000 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 709b145d7..58f1e7472 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 98a98c484..753d3710f 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 000000000..6f2a76ce7 --- /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 15131a257..597aa00a5 100644 --- a/src/app/ingestor/ingestor/ingestor.component.html +++ b/src/app/ingestor/ingestor/ingestor.component.html @@ -32,14 +32,14 @@ -