From 05c10928baa453c11573780bd571f8bbe5560491 Mon Sep 17 00:00:00 2001
From: armandobermudezmartinez
Date: Fri, 21 Feb 2025 15:50:28 +0100
Subject: [PATCH 1/4] added changes to help, about, and config service
---
.../app-main-layout.component.scss | 2 +-
src/app/about/about/about.component.html | 9 +-
src/app/about/about/about.component.ts | 40 +-
src/app/app-config.service.spec.ts | 11 +
src/app/app-config.service.ts | 22 +-
...ataset-details-dashboard.routing.module.ts | 9 +-
.../instruments.routing.module.ts | 4 +-
.../samples-routing/samples.routing.module.ts | 4 +-
src/app/app-routing/pending-changes.guard.ts | 47 +-
src/app/app.module.ts | 10 -
.../batch-view/batch-view.component.html | 2 +-
.../dataset-detail/_dataset-detail-theme.scss | 44 ++
.../dataset-detail.component.html | 464 ++++++++++++++++++
.../dataset-detail.component.scss | 67 +++
.../dataset-detail.component.spec.ts | 378 ++++++++++++++
.../dataset-detail.component.ts | 323 ++++++++++++
.../dataset-details-dashboard.component.ts | 2 +-
.../dataset-lifecycle.component.html | 10 +-
.../dataset-table.component.html | 3 +-
src/app/datasets/datasets.module.ts | 10 +-
src/app/datasets/reduce/reduce.component.html | 2 +-
.../sample-edit/sample-edit.component.html | 2 +-
src/app/help/help.module.ts | 3 +-
src/app/help/help/help.component.html | 44 +-
src/app/help/help/help.component.spec.ts | 3 +-
src/app/help/help/help.component.ts | 15 +
.../jobs-detail/jobs-detail.component.html | 10 +-
.../logbooks-detail.component.html | 2 +-
.../logbooks-table.component.html | 5 +-
.../proposal-dashboard.component.html | 2 +-
.../proposal-dashboard.component.spec.ts | 13 +-
.../proposal-dashboard.component.ts | 9 +-
.../proposal-detail.component.html | 56 +--
.../proposal-detail.component.spec.ts | 24 +-
.../proposal-detail.component.ts | 6 +-
.../proposal-logbook.component.html | 2 +-
.../proposal-logbook.component.ts | 16 +-
src/app/proposals/proposals.module.ts | 6 -
.../view-proposal-page.component.html | 52 +-
.../view-proposal-page.component.spec.ts | 100 +++-
.../view-proposal-page.component.ts | 107 +++-
.../publisheddata-details.component.html | 2 +-
.../sample-detail.component.html | 2 +-
.../shared/loaders/custom-translate.loader.ts | 14 +-
.../tree-view/tree-view.component.html | 6 +-
.../tree-view/tree-view.component.scss | 16 +-
.../metadata-edit/metadata-edit.component.ts | 2 +-
.../metadata-view.component.html | 16 +-
.../metadata-view.component.scss | 21 +-
.../shared-table/_shared-table-theme.scss | 9 -
src/app/shared/pipes/pipes.module.ts | 3 -
src/app/shared/shared.module.ts | 6 -
.../actions/proposals.actions.spec.ts | 37 +-
.../actions/proposals.actions.ts | 42 +-
.../effects/datasets.effects.ts | 2 -
.../effects/proposals.effects.spec.ts | 135 ++++-
.../effects/proposals.effects.ts | 40 +-
.../state-management/effects/user.effects.ts | 34 +-
src/app/state-management/models/index.ts | 52 +-
.../reducers/proposals.reducer.spec.ts | 46 ++
.../reducers/proposals.reducer.ts | 52 +-
.../selectors/proposals.selectors.spec.ts | 8 -
.../selectors/proposals.selectors.ts | 23 -
.../state-management/state/proposals.store.ts | 16 -
64 files changed, 1944 insertions(+), 580 deletions(-)
create mode 100644 src/app/datasets/dataset-detail/_dataset-detail-theme.scss
create mode 100644 src/app/datasets/dataset-detail/dataset-detail.component.html
create mode 100644 src/app/datasets/dataset-detail/dataset-detail.component.scss
create mode 100644 src/app/datasets/dataset-detail/dataset-detail.component.spec.ts
create mode 100644 src/app/datasets/dataset-detail/dataset-detail.component.ts
diff --git a/src/app/_layout/app-main-layout/app-main-layout.component.scss b/src/app/_layout/app-main-layout/app-main-layout.component.scss
index f0fd0d86c..7db0bd823 100644
--- a/src/app/_layout/app-main-layout/app-main-layout.component.scss
+++ b/src/app/_layout/app-main-layout/app-main-layout.component.scss
@@ -1,5 +1,5 @@
.main-app {
height: 100%;
- min-height: calc(100vh - 6.5rem);
+ min-height: calc(100vh - 3.5rem);
padding: 1.5rem;
}
diff --git a/src/app/about/about/about.component.html b/src/app/about/about/about.component.html
index 5ae708167..27a48c372 100644
--- a/src/app/about/about/about.component.html
+++ b/src/app/about/about/about.component.html
@@ -10,7 +10,6 @@
SciCat Github Project.
Access Conditions
- {{ accessText }}
+
+
+
@@ -32,12 +33,12 @@
Terms of Use
- {{ termsText }}
+
Open Research Data.
- {{ termsTextContinued }}
+
Data Policy.
diff --git a/src/app/about/about/about.component.ts b/src/app/about/about/about.component.ts
index 2572765b8..913181215 100644
--- a/src/app/about/about/about.component.ts
+++ b/src/app/about/about/about.component.ts
@@ -31,42 +31,10 @@ export class AboutComponent implements OnInit {
this.SNFLink =
"http://www.snf.ch/en/theSNSF/research-policies/open_research_data/Pages/default.aspx#Guidelines%20and%20Regulations";
this.PSIDataPolicy = "https://www.psi.ch/en/science/psi-data-policy";
- this.aboutText =
- "Scicat allows users to access data and metadata from experiments";
- this.accessText = "Users must comply with access policy of instruments";
- this.termsText = "Data can be used freely under the CC-BY-4.0 licence";
- if (this.facility === "ESS") {
- this.aboutText =
- "Scicat is a metadata catalogue allows users to access information about experimental results, " +
- "measured at the European Spallation Source, " +
- "(https://esss.se/). " +
- "Scientific datasets are linked to proposals and samples. " +
- "Scientific datasets are linked to publications (DOI, PID). " +
- "SciCat helps to keep track of data provenance (i.e. the steps leading to the final results). " +
- "Scicat allows users to find data based on the metadata (both your own data and other peoples’ public data). " +
- "In the long term, SciCat will help to automate scientific analysis workflows.";
- this.accessText =
- "Access to the online catalogue of open data will be given to a user," +
- " providing the user registers with ESS " +
- " and accepts the terms of the ESS scientific data policy ";
- this.termsText =
- "All scientific datasets are licensed under the CC-BY-4.0 license ";
- } else if (this.facility === "PSI") {
- this.aboutText =
- "SciCat is a metadata catalog. At PSI, SciCat works in conjunction with a PetaByte archive and remote accesss system." +
- " Together these components provide users with the ability to store, search and access their data." +
- " SciCat is an open source project in collaboration with ESS and Max IV";
+ this.aboutText = this.appConfig.aboutText || "Scicat allows users to access data and metadata from experiments";
+ this.accessText = this.appConfig.accessText || "Users must comply with access policy of instruments";
+ this.termsText = this.appConfig.termsText || "Data can be used freely under the CC-BY-4.0 license";
+ this.termsTextContinued = this.appConfig.termsTextContinued || "";
- this.accessText =
- "Access to SciCat is granted to users by the Digital User Office.";
- this.termsTextContinued = "Additionally, PSI defines it's own ";
- this.termsText =
- "The Swiss National Science Foundation describes policy and guidelines on ";
- } else if (this.facility === "MAX IV") {
- this.aboutText =
- "Scicat allows users to access data and metadata from experiments at MAX IV.";
- this.accessText = "Users must comply with access policy of instruments";
- this.termsText = "Data can be used freely under the CC-BY-4.0 licence";
- }
}
}
diff --git a/src/app/app-config.service.spec.ts b/src/app/app-config.service.spec.ts
index e276c8dec..7c49913bf 100644
--- a/src/app/app-config.service.spec.ts
+++ b/src/app/app-config.service.spec.ts
@@ -45,6 +45,17 @@ const appConfig: AppConfig = {
multipleDownloadAction: "http://localhost:3012/zip",
multipleDownloadEnabled: true,
multipleDownloadUseAuthToken: false,
+ aboutText: "Configured about text",
+ accessText: "Configured access policy",
+ termsText: "Configured terms of use",
+ termsTextContinued: "Configured additional terms text",
+ docText: "Configured documentation Text",
+ gettingStartedExtraText: "Configured gettingStarted Text",
+ whereIsMyDataText: "Configured whereIsMyDataText",
+ howToPublishDataText: "Configured howToPublishDataText",
+ ingestManualExtraText = "Configured ingestManualExtraText",
+ whereAreMyProposalsExtraText = "Configured whereAreMyProposalsExtraText",
+ whereAreMySamplesExtraText = "Configured whereAreMySamplesExtraText",
oAuth2Endpoints: [],
policiesEnabled: true,
retrieveDestinations: [],
diff --git a/src/app/app-config.service.ts b/src/app/app-config.service.ts
index c906f8a98..551cfd791 100644
--- a/src/app/app-config.service.ts
+++ b/src/app/app-config.service.ts
@@ -2,10 +2,9 @@ import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { timeout } from "rxjs/operators";
import {
- DatasetDetailComponentConfig,
+ datasetDetailViewLabelOption,
DatasetsListSettings,
LabelMaps,
- LabelsLocalization,
TableColumn,
} from "state-management/models";
@@ -103,9 +102,18 @@ export interface AppConfig {
labelMaps: LabelMaps;
thumbnailFetchLimitPerPage: number;
maxFileUploadSizeInMb?: string;
- datasetDetailComponent?: DatasetDetailComponentConfig;
- labelsLocalization?: LabelsLocalization;
- dateFormat?: string;
+ datasetDetailViewLabelOption?: datasetDetailViewLabelOption;
+ aboutText?: string;
+ accessText?: string;
+ termsText?: string;
+ termsTextContinued?: string;
+ docText?: string;
+ gettingStartedExtraText?: string;
+ whereIsMyDataText?: string;
+ howToPublishDataText?: string;
+ ingestManualExtraText?: string;
+ whereAreMyProposalsExtraText?: string;
+ whereAreMySamplesExtraText?: string;
}
@Injectable()
@@ -133,10 +141,6 @@ export class AppConfigService {
}
getConfig(): AppConfig {
- if (!this.appConfig) {
- console.error("AppConfigService: Configuration not loaded!");
- }
-
return this.appConfig as AppConfig;
}
}
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 b3b29d2a4..0f3f81e43 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
@@ -2,21 +2,22 @@ import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AdminGuard } from "app-routing/admin.guard";
import { AuthGuard } from "app-routing/auth.guard";
+import { LeavingPageGuard } from "app-routing/pending-changes.guard";
import { ServiceGuard } from "app-routing/service.guard";
import { AdminTabComponent } from "datasets/admin-tab/admin-tab.component";
import { DatafilesComponent } from "datasets/datafiles/datafiles.component";
import { JsonScientificMetadataComponent } from "datasets/jsonScientificMetadata/jsonScientificMetadata.component";
+import { DatasetDetailComponent } from "datasets/dataset-detail/dataset-detail.component";
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 { RelatedDatasetsComponent } from "datasets/related-datasets/related-datasets.component";
import { LogbooksDashboardComponent } from "logbooks/logbooks-dashboard/logbooks-dashboard.component";
-import { DatasetDetailWrapperComponent } from "datasets/dataset-detail/dataset-detail-wrapper.component";
-
const routes: Routes = [
{
path: "",
- component: DatasetDetailWrapperComponent,
+ component: DatasetDetailComponent,
+ canDeactivate: [LeavingPageGuard],
},
{
path: "jsonScientificMetadata",
@@ -27,7 +28,7 @@ const routes: Routes = [
component: DatafilesComponent,
},
{
- path: "relatedDatasets",
+ path: "related-datasets",
component: RelatedDatasetsComponent,
},
// For reduce && logbook this is a work around because guard priority somehow doesn't work and this work around make guards excuted sequencial
diff --git a/src/app/app-routing/lazy/instruments-routing/instruments.routing.module.ts b/src/app/app-routing/lazy/instruments-routing/instruments.routing.module.ts
index 646787b7c..fdbcbe998 100644
--- a/src/app/app-routing/lazy/instruments-routing/instruments.routing.module.ts
+++ b/src/app/app-routing/lazy/instruments-routing/instruments.routing.module.ts
@@ -1,7 +1,7 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuard } from "app-routing/auth.guard";
-import { leavingPageGuard } from "app-routing/pending-changes.guard";
+import { LeavingPageGuard } from "app-routing/pending-changes.guard";
import { InstrumentDetailsComponent } from "instruments/instrument-details/instrument-details.component";
import { InstrumentsDashboardComponent } from "instruments/instruments-dashboard/instruments-dashboard.component";
@@ -15,7 +15,7 @@ const routes: Routes = [
path: ":id",
component: InstrumentDetailsComponent,
canActivate: [AuthGuard],
- canDeactivate: [leavingPageGuard],
+ canDeactivate: [LeavingPageGuard],
},
];
@NgModule({
diff --git a/src/app/app-routing/lazy/samples-routing/samples.routing.module.ts b/src/app/app-routing/lazy/samples-routing/samples.routing.module.ts
index 3e3a2ed9f..cd1dd22df 100644
--- a/src/app/app-routing/lazy/samples-routing/samples.routing.module.ts
+++ b/src/app/app-routing/lazy/samples-routing/samples.routing.module.ts
@@ -1,7 +1,7 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuard } from "app-routing/auth.guard";
-import { leavingPageGuard } from "app-routing/pending-changes.guard";
+import { LeavingPageGuard } from "app-routing/pending-changes.guard";
import { SampleDashboardComponent } from "samples/sample-dashboard/sample-dashboard.component";
import { SampleDetailComponent } from "samples/sample-detail/sample-detail.component";
@@ -15,7 +15,7 @@ const routes: Routes = [
path: ":id",
component: SampleDetailComponent,
canActivate: [AuthGuard],
- canDeactivate: [leavingPageGuard],
+ canDeactivate: [LeavingPageGuard],
},
];
@NgModule({
diff --git a/src/app/app-routing/pending-changes.guard.ts b/src/app/app-routing/pending-changes.guard.ts
index d5b8e15d3..991b03de1 100644
--- a/src/app/app-routing/pending-changes.guard.ts
+++ b/src/app/app-routing/pending-changes.guard.ts
@@ -1,36 +1,29 @@
-import { CanDeactivateFn } from "@angular/router";
+import { Injectable } from "@angular/core";
+import { CanDeactivate } from "@angular/router";
import { Observable } from "rxjs";
export interface EditableComponent {
hasUnsavedChanges: () => boolean | Observable;
+ // openLeavingPageGuardDialog(): () => void;
}
-
-export const leavingPageGuard: CanDeactivateFn = (
- component,
-) => {
- const hasUnsavedChanges = component.hasUnsavedChanges();
-
- if (typeof hasUnsavedChanges === "boolean") {
- return hasUnsavedChanges
+/**
+ * Ensure that the user knowns about pending changes before leaving the page
+ * @export
+ * @class LeavingPageGuard
+ * @implements {CanDeactivate}
+ */
+@Injectable({
+ providedIn: "root",
+})
+export class LeavingPageGuard implements CanDeactivate {
+ /**
+ * Needs to return either a boolean or an observable that maps to a boolean
+ */
+ canDeactivate(component: EditableComponent): Observable | boolean {
+ return component.hasUnsavedChanges()
? confirm(
- "You have unsaved changes. Press Cancel to go back and save these changes, or OK to leave without saving.",
+ "You have unsaved changes. Press Cancel to go back and save these changes, or OK to leave without saving",
)
: true;
}
-
- if (hasUnsavedChanges instanceof Observable) {
- return new Observable((subscriber) => {
- hasUnsavedChanges.subscribe((unsaved) => {
- subscriber.next(
- !unsaved ||
- confirm(
- "You have unsaved changes. Press Cancel to go back and save these changes, or OK to leave without saving.",
- ),
- );
- subscriber.complete();
- });
- });
- }
-
- return true;
-};
+}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 596e13037..729f25ab8 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -121,16 +121,6 @@ const apiConfigurationFn = (
subscriptSizing: "dynamic",
},
},
- {
- provide: DATE_PIPE_DEFAULT_OPTIONS,
- useFactory: (appConfigService: AppConfigService) => {
- return {
- dateFormat:
- appConfigService.getConfig().dateFormat || "yyyy-MM-dd HH:mm",
- };
- },
- deps: [AppConfigService],
- },
AuthService,
AppThemeService,
Title,
diff --git a/src/app/datasets/batch-view/batch-view.component.html b/src/app/datasets/batch-view/batch-view.component.html
index cce14bc9f..c84f89a60 100644
--- a/src/app/datasets/batch-view/batch-view.component.html
+++ b/src/app/datasets/batch-view/batch-view.component.html
@@ -116,7 +116,7 @@
- {{ dataset.creationTime | date }}
+ {{ dataset.creationTime | date: "yyyy-MM-dd HH:mm" }}
diff --git a/src/app/datasets/dataset-detail/_dataset-detail-theme.scss b/src/app/datasets/dataset-detail/_dataset-detail-theme.scss
new file mode 100644
index 000000000..a040faf23
--- /dev/null
+++ b/src/app/datasets/dataset-detail/_dataset-detail-theme.scss
@@ -0,0 +1,44 @@
+@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");
+ $accent: map.get($color-config, "accent");
+ $header-2: map.get($color-config, "header-2");
+ $header-3: map.get($color-config, "header-3");
+ $header-4: map.get($color-config, "header-4");
+ mat-card {
+ .general-header {
+ background-color: mat.get-color-from-palette($primary, "lighter");
+ }
+
+ .creator-header {
+ background-color: mat.get-color-from-palette($header-1, "lighter");
+ }
+
+ .file-header {
+ background-color: mat.get-color-from-palette($accent, "lighter");
+ }
+
+ .related-header {
+ background-color: mat.get-color-from-palette($header-2, "lighter");
+ }
+
+ .derived-header {
+ background-color: mat.get-color-from-palette($header-3, "lighter");
+ }
+
+ .scientific-header {
+ background-color: mat.get-color-from-palette($header-4, "lighter");
+ }
+ }
+}
+
+@mixin theme($theme) {
+ $color-config: mat.get-color-config($theme);
+ @if $color-config != null {
+ @include color($theme);
+ }
+}
diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.html b/src/app/datasets/dataset-detail/dataset-detail.component.html
new file mode 100644
index 000000000..3c3161ffe
--- /dev/null
+++ b/src/app/datasets/dataset-detail/dataset-detail.component.html
@@ -0,0 +1,464 @@
+
+
+
+
+
0 ? '90%' : '100%'">
+
+
+
+
+
+
+
+ edit Edit
+
+
+ Close
+
+
+ save Save changes
+
+
+
+
+
+
+
+
+
+
+ {{ "Owner" | translate }}
+ {{ value }}
+
+
+ {{ "Principal Investigator" | translate }}
+
+
+
+ {{ "Investigator" | translate }}
+ {{ value }}
+
+
+ {{ "Orcid" | translate }}
+
+
+
+ {{ "Contact Email" | translate }}
+
+
+
+ {{ "Owner Group" | translate }}
+ {{ value }}
+
+
+ {{ "Access Groups" | translate }}
+ {{ value }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ "Source Folder" | translate }}
+ {{ value }}
+
+
+ {{ "Size" | translate }}
+ {{ value | filesize }}
+
+
+ {{ "Data Format" | translate }}
+ {{ value }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ "Proposal" | translate }}
+
+ {{
+ proposal.title
+ }}
+
+
+
+ {{ "Proposal Id" | translate }}
+ {{ dataset["proposalId"] }}
+
+
+ {{ "Sample" | translate }}
+
+
+ {{ sample.description }}
+
+
+
+
+ {{ "Instrument" | translate }}
+
+
+ {{ instrument.name }}
+
+
+
+
+ {{ "Creation Location" | translate }}
+ {{ value }}
+
+ 0">
+ {{ "Techniques" | translate }}
+
+
+
+ {{ technique.name }}
+
+ ,
+
+
+
+
+ 0
+ "
+ >
+ {{ "Input Datasets" | translate }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ "Software Used" | translate }}
+ {{ value }}
+
+
+ {{ "Job Parameters" | translate }}
+ {{ value | json }}
+
+
+ {{ "Job Log Data" | translate }}
+ {{ value }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ list View
+
+
+
+
+
+ edit Edit
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ (show ? "Hide Metadata" : "Show Metadata") | translate }}
+
+
+
+
+
+
+
+
+
+
+
+
+
0 ? '10%' : '0'"
+ *ngIf="attachments$ | async as attachments"
+ >
+
+
+
+ {{
+ da.caption
+ }}
+
+
+
+
+
diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.scss b/src/app/datasets/dataset-detail/dataset-detail.component.scss
new file mode 100644
index 000000000..14d17981e
--- /dev/null
+++ b/src/app/datasets/dataset-detail/dataset-detail.component.scss
@@ -0,0 +1,67 @@
+mat-card {
+ margin: 1em;
+
+ .section-icon {
+ height: auto !important;
+ width: auto !important;
+
+ mat-icon {
+ vertical-align: middle;
+ }
+ }
+ .caption-text {
+ word-break: break-all;
+ }
+
+ 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;
+ }
+ }
+
+ .sample-edit:hover {
+ cursor: pointer;
+ }
+ }
+
+ .full-width {
+ width: 100%;
+ }
+
+ .keywords-row {
+ .add-keyword-chip:hover {
+ cursor: pointer;
+ }
+ }
+ }
+}
+.jupyter-button {
+ margin: 1em 0 0 1em;
+}
+.public-toggle {
+ margin: 1em 1em 0 0;
+ float: right;
+}
+.attachment-card {
+ display: flex;
+ align-items: center;
+}
+.thumbnail-image {
+ cursor: pointer;
+ max-width: 14em; /* Set maximum width */
+ width: 100%; /* Make it responsive within the card */
+ height: auto; /* Maintain aspect ratio */
+}
diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.spec.ts b/src/app/datasets/dataset-detail/dataset-detail.component.spec.ts
new file mode 100644
index 000000000..0a3ee66de
--- /dev/null
+++ b/src/app/datasets/dataset-detail/dataset-detail.component.spec.ts
@@ -0,0 +1,378 @@
+import { DatafilesComponent } from "../../datasets/datafiles/datafiles.component";
+import { DatasetDetailComponent } from "./dataset-detail.component";
+import { LinkyPipe } from "ngx-linky";
+import { NO_ERRORS_SCHEMA } from "@angular/core";
+import {
+ ComponentFixture,
+ inject,
+ TestBed,
+ waitForAsync,
+} from "@angular/core/testing";
+import { SharedScicatFrontendModule } from "shared/shared.module";
+import { MatTableModule } from "@angular/material/table";
+import { MatChipInputEvent, MatChipsModule } from "@angular/material/chips";
+import { Observable, of } from "rxjs";
+import { MatDialogRef } from "@angular/material/dialog";
+import { MatCardModule } from "@angular/material/card";
+import { MatIconModule } from "@angular/material/icon";
+import { MatInputModule } from "@angular/material/input";
+import { MatFormFieldModule } from "@angular/material/form-field";
+import { MatButtonModule } from "@angular/material/button";
+import { MatTabsModule } from "@angular/material/tabs";
+import { NgxJsonViewerModule } from "ngx-json-viewer";
+import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
+import { MockStore } from "@ngrx/store/testing";
+import { Store, StoreModule } from "@ngrx/store";
+import {
+ addKeywordFilterAction,
+ clearFacetsAction,
+ updatePropertyAction,
+} from "state-management/actions/datasets.actions";
+import {
+ MatSlideToggle,
+ MatSlideToggleChange,
+} from "@angular/material/slide-toggle";
+import { ActivatedRoute, Router } from "@angular/router";
+import { MockActivatedRoute, mockDataset } from "shared/MockStubs";
+import { DialogComponent } from "shared/modules/dialog/dialog.component";
+import { AppConfigService } from "app-config.service";
+import { AttachmentService } from "shared/services/attachment.service";
+import { OutputDatasetObsoleteDto } from "@scicatproject/scicat-sdk-ts-angular";
+import {
+ TranslateLoader,
+ TranslateModule,
+ TranslationObject,
+} from "@ngx-translate/core";
+class MockTranslateLoader implements TranslateLoader {
+ getTranslation(): Observable {
+ return of({});
+ }
+}
+
+describe("DatasetDetailComponent", () => {
+ let component: DatasetDetailComponent;
+ let fixture: ComponentFixture;
+
+ const router = {
+ navigateByUrl: jasmine.createSpy("navigateByUrl"),
+ };
+
+ const getConfig = () => ({
+ datasetDetailViewLabelOption: {
+ currentLabel: "test",
+ },
+ });
+
+ let store: MockStore;
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ schemas: [NO_ERRORS_SCHEMA],
+ imports: [
+ BrowserAnimationsModule,
+ MatButtonModule,
+ MatCardModule,
+ MatChipsModule,
+ MatFormFieldModule,
+ MatIconModule,
+ MatInputModule,
+ MatTableModule,
+ MatTabsModule,
+ NgxJsonViewerModule,
+ SharedScicatFrontendModule,
+ TranslateModule.forRoot({
+ loader: {
+ provide: TranslateLoader,
+ useClass: MockTranslateLoader,
+ },
+ }),
+ StoreModule.forRoot({}),
+ ],
+ providers: [AttachmentService],
+ declarations: [DatasetDetailComponent, DatafilesComponent, LinkyPipe],
+ });
+ TestBed.overrideComponent(DatasetDetailComponent, {
+ set: {
+ providers: [
+ { provide: Router, useValue: router },
+ {
+ provide: AppConfigService,
+ useValue: {
+ getConfig,
+ },
+ },
+ { provide: ActivatedRoute, useClass: MockActivatedRoute },
+ ],
+ },
+ });
+ TestBed.compileComponents();
+ }));
+
+ beforeEach(inject([Store], (mockStore: MockStore) => {
+ store = mockStore;
+ fixture = TestBed.createComponent(DatasetDetailComponent);
+ component = fixture.componentInstance;
+ component.dataset = {
+ pid: "testPid",
+ isPublished: false,
+ } as unknown as OutputDatasetObsoleteDto;
+ fixture.detectChanges();
+ }));
+ afterEach(() => {
+ fixture.destroy();
+ });
+
+ it("should create", () => {
+ expect(component).toBeTruthy();
+ });
+
+ describe("#onClickKeyword()", () => {
+ it("should update datasets keyword filter and navigate to datasets table", () => {
+ const dispatchSpy = spyOn(store, "dispatch");
+ const keyword = "test";
+ component.dataset = mockDataset;
+ component.onClickKeyword(keyword);
+
+ expect(dispatchSpy).toHaveBeenCalledTimes(2);
+ expect(dispatchSpy).toHaveBeenCalledWith(clearFacetsAction());
+ expect(dispatchSpy).toHaveBeenCalledWith(
+ addKeywordFilterAction({ keyword }),
+ );
+ expect(router.navigateByUrl).toHaveBeenCalledWith("/datasets");
+ });
+ });
+
+ describe("#onAddKeyword()", () => {
+ it("should add property keywords if it does not exist already", () => {
+ const event = {
+ chipInput: {
+ inputElement: {
+ value: "test",
+ },
+ },
+ value: "test",
+ };
+ const pid = "testPid";
+ component.dataset = mockDataset;
+ component.dataset.pid = pid;
+ component.onEditModeEnable();
+ component.onAddKeyword(event as MatChipInputEvent);
+
+ expect(component.keywords).toBeTruthy();
+ expect(component.keywords.length).toBe(1);
+ expect(component.form.value.keywords.length).toBe(1);
+ });
+
+ it("should do nothing if keyword already exists", () => {
+ const event = {
+ chipInput: {
+ inputElement: {
+ value: "test",
+ },
+ },
+ value: "test",
+ };
+ component.dataset = mockDataset;
+ component.dataset.keywords = ["test"];
+ component.onEditModeEnable();
+ expect(component.keywords.value.length).toBe(1);
+ expect(component.form.value.keywords.length).toBe(1);
+ component.onAddKeyword(event as MatChipInputEvent);
+
+ expect(component.keywords.value.length).toBe(1);
+ expect(component.form.value.keywords.length).toBe(1);
+ });
+
+ it("should add a keyword if the keyword does not exist", () => {
+ const event = {
+ chipInput: {
+ inputElement: {
+ value: "test",
+ },
+ },
+ value: "test",
+ };
+ const pid = "testPid";
+ component.dataset = mockDataset;
+ component.dataset.pid = pid;
+ component.dataset.keywords = [];
+ component.onEditModeEnable();
+ expect(component.keywords.value.length).toBe(0);
+ expect(component.form.value.keywords.length).toBe(0);
+ component.onAddKeyword(event as MatChipInputEvent);
+
+ expect(component.keywords.value.length).toBe(1);
+ expect(component.form.value.keywords.length).toBe(1);
+ });
+ });
+
+ describe("#onRemoveKeyword()", () => {
+ it("should do nothing if the keyword does not exist", () => {
+ const dispatchSpy = spyOn(store, "dispatch");
+
+ const keyword = "test";
+ component.dataset = mockDataset;
+ component.dataset.keywords = [];
+ component.onRemoveKeyword(keyword);
+
+ expect(dispatchSpy).toHaveBeenCalledTimes(0);
+ });
+
+ it("should dispatch an updatePropertyAction if the keyword does exist", () => {
+ const keyword = "test";
+ const pid = "testPid";
+ component.dataset = mockDataset;
+ component.dataset.pid = pid;
+ component.dataset.keywords = [keyword];
+ component.onEditModeEnable();
+ expect(component.keywords.value.length).toBe(1);
+ expect(component.form.value.keywords.length).toBe(1);
+ component.onRemoveKeyword(keyword);
+
+ expect(component.keywords.value.length).toBe(0);
+ expect(component.form.value.keywords.length).toBe(0);
+ });
+ });
+
+ describe("#onSaveGeneralInformationChanges()", () => {
+ it("should dispatch an updatePropertyAction", () => {
+ const dispatchSpy = spyOn(store, "dispatch");
+
+ const keyword = "test";
+ const pid = "testPid";
+ component.dataset = mockDataset;
+ component.dataset.pid = pid;
+ component.dataset.keywords = [keyword];
+ component.dataset.datasetName = "Test dataset name";
+ component.dataset.description = "Test dataset description";
+ component.onEditModeEnable();
+ expect(component.keywords.value.length).toBe(1);
+ expect(component.form.value.keywords.length).toBe(1);
+ component.onSaveGeneralInformationChanges();
+
+ const property = {
+ keywords: component.dataset.keywords,
+ datasetName: component.dataset.datasetName,
+ description: component.dataset.description,
+ };
+
+ expect(dispatchSpy).toHaveBeenCalledTimes(1);
+ expect(dispatchSpy).toHaveBeenCalledWith(
+ updatePropertyAction({ pid, property }),
+ );
+ });
+ });
+
+ describe("#onSlidePublic()", () => {
+ it("should dispatch a updatePropertyAction", () => {
+ const dispatchSpy = spyOn(store, "dispatch");
+ const pid = "testPid";
+ component.dataset = mockDataset;
+ component.dataset.pid = pid;
+ const event = new MatSlideToggleChange({} as MatSlideToggle, true);
+ const property = { isPublished: true };
+ component.onSlidePublic(event);
+
+ expect(dispatchSpy).toHaveBeenCalledTimes(1);
+ expect(dispatchSpy).toHaveBeenCalledWith(
+ updatePropertyAction({ pid, property }),
+ );
+ });
+ });
+
+ describe("#onRemoveShare()", () => {
+ it("should do nothing if dataset is undefined", () => {
+ const dispatchSpy = spyOn(store, "dispatch");
+
+ component.dataset = undefined;
+ const share = "test";
+ component.onRemoveShare(share);
+
+ expect(dispatchSpy).toHaveBeenCalledTimes(0);
+ });
+
+ it("should do nothing if dataset is defined and group does not exist", () => {
+ const dispatchSpy = spyOn(store, "dispatch");
+
+ component.dataset = mockDataset;
+ component.dataset.sharedWith = [];
+ const share = "test";
+ component.onRemoveShare(share);
+
+ expect(dispatchSpy).toHaveBeenCalledTimes(0);
+ });
+
+ it("should dispatch an updatePropertyAction if dataset is defined and group exists", () => {
+ const dispatchSpy = spyOn(store, "dispatch");
+
+ const pid = "testPid";
+ const share = "test";
+ component.dataset = {
+ pid,
+ isPublished: false,
+ sharedWith: [share],
+ } as unknown as OutputDatasetObsoleteDto;
+ const dialogOpenSpy = spyOn(component.dialog, "open").and.returnValue({
+ afterClosed: () => of("ok"),
+ } as MatDialogRef);
+ component.onRemoveShare(share);
+
+ const property = { sharedWith: [] };
+ expect(dialogOpenSpy).toHaveBeenCalledTimes(1);
+ expect(dispatchSpy).toHaveBeenCalledTimes(1);
+ expect(dispatchSpy).toHaveBeenCalledWith(
+ updatePropertyAction({ pid, property }),
+ );
+ });
+ });
+
+ describe("#onClickInstrument()", () => {
+ it("should navigate to an instrument", () => {
+ const instrumentId = "testId";
+
+ component.onClickInstrument(instrumentId);
+
+ expect(router.navigateByUrl).toHaveBeenCalledWith(
+ "/instruments/" + instrumentId,
+ );
+ });
+ });
+
+ describe("#onClickProposal()", () => {
+ it("should navigate to a proposal", () => {
+ const proposalId = "ABC123";
+ component.onClickProposal(proposalId);
+
+ expect(router.navigateByUrl).toHaveBeenCalledWith(
+ "/proposals/" + proposalId,
+ );
+ });
+ });
+
+ describe("#onClickSample()", () => {
+ it("should navigate to a sample", () => {
+ const sampleId = "testId";
+ component.onClickSample(sampleId);
+
+ expect(router.navigateByUrl).toHaveBeenCalledWith("/samples/" + sampleId);
+ });
+ });
+
+ describe("#onSaveMetadata()", () => {
+ it("should dispatch an updatePropertyAction", () => {
+ const dispatchSpy = spyOn(store, "dispatch");
+
+ const pid = "testPid";
+ component.dataset = mockDataset;
+ component.dataset.pid = pid;
+ const metadata = {};
+ const property = { scientificMetadata: metadata };
+ component.onSaveMetadata(metadata);
+
+ expect(dispatchSpy).toHaveBeenCalledTimes(1);
+ expect(dispatchSpy).toHaveBeenCalledWith(
+ updatePropertyAction({ pid, property }),
+ );
+ });
+ });
+});
diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.ts b/src/app/datasets/dataset-detail/dataset-detail.component.ts
new file mode 100644
index 000000000..ca5287d33
--- /dev/null
+++ b/src/app/datasets/dataset-detail/dataset-detail.component.ts
@@ -0,0 +1,323 @@
+import { Component, OnInit, OnDestroy, Inject } from "@angular/core";
+import { ENTER, COMMA, SPACE } from "@angular/cdk/keycodes";
+import { MatChipInputEvent } from "@angular/material/chips";
+
+import { MatDialog } from "@angular/material/dialog";
+// import { SampleEditComponent } from "datasets/sample-edit/sample-edit.component";
+import { DialogComponent } from "shared/modules/dialog/dialog.component";
+import { combineLatest, fromEvent, Observable, Subscription } from "rxjs";
+import { Store } from "@ngrx/store";
+
+import { showMessageAction } from "state-management/actions/user.actions";
+import {
+ selectCurrentAttachments,
+ selectCurrentDataset,
+ selectCurrentDatasetWithoutFileInfo,
+} from "state-management/selectors/datasets.selectors";
+import {
+ selectCurrentUser,
+ selectIsAdmin,
+ selectIsLoading,
+ selectProfile,
+} from "state-management/selectors/user.selectors";
+import { map } from "rxjs/operators";
+import {
+ addKeywordFilterAction,
+ clearFacetsAction,
+ updatePropertyAction,
+} from "state-management/actions/datasets.actions";
+import { Router } from "@angular/router";
+import { selectCurrentProposal } from "state-management/selectors/proposals.selectors";
+import { MatSlideToggleChange } from "@angular/material/slide-toggle";
+import { EditableComponent } from "app-routing/pending-changes.guard";
+import { AppConfigService } from "app-config.service";
+import { selectCurrentSample } from "state-management/selectors/samples.selectors";
+import { selectCurrentInstrument } from "state-management/selectors/instruments.selectors";
+import {
+ FormArray,
+ FormBuilder,
+ FormControl,
+ FormGroup,
+ Validators,
+} from "@angular/forms";
+import { Message, MessageType } from "state-management/models";
+import { DOCUMENT } from "@angular/common";
+import {
+ Instrument,
+ OutputDatasetObsoleteDto,
+ ProposalClass,
+ ReturnedUserDto,
+ SampleClass,
+} from "@scicatproject/scicat-sdk-ts-angular";
+import { AttachmentService } from "shared/services/attachment.service";
+import { TranslateService } from "@ngx-translate/core";
+
+/**
+ * Component to show details for a data set, using the
+ * form component
+ * @export
+ * @class DatasetDetailComponent
+ */
+@Component({
+ selector: "dataset-detail",
+ templateUrl: "./dataset-detail.component.html",
+ styleUrls: ["./dataset-detail.component.scss"],
+ standalone: false,
+})
+export class DatasetDetailComponent
+ implements OnInit, OnDestroy, EditableComponent
+{
+ private subscriptions: Subscription[] = [];
+ private _hasUnsavedChanges = false;
+ form: FormGroup;
+ userProfile$ = this.store.select(selectProfile);
+ isAdmin$ = this.store.select(selectIsAdmin);
+ accessGroups$: Observable = this.userProfile$.pipe(
+ map((profile) => (profile ? profile.accessGroups : [])),
+ );
+
+ appConfig = this.appConfigService.getConfig();
+
+ dataset: OutputDatasetObsoleteDto | undefined;
+ datasetWithout$ = this.store.select(selectCurrentDatasetWithoutFileInfo);
+ attachments$ = this.store.select(selectCurrentAttachments);
+ loading$ = this.store.select(selectIsLoading);
+ instrument: Instrument | undefined;
+ proposal: ProposalClass | undefined;
+ sample: SampleClass | undefined;
+ user: ReturnedUserDto | undefined;
+ editingAllowed = false;
+ editEnabled = false;
+ show = false;
+ readonly separatorKeyCodes: number[] = [ENTER, COMMA, SPACE];
+
+ constructor(
+ @Inject(DOCUMENT) private document: Document,
+ public appConfigService: AppConfigService,
+ public dialog: MatDialog,
+ private attachmentService: AttachmentService,
+ private translateService: TranslateService,
+ private store: Store,
+ private router: Router,
+ private fb: FormBuilder,
+ ) {
+ this.translateService.use(
+ this.appConfig.datasetDetailViewLabelOption?.currentLabelSet,
+ );
+ }
+
+ 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;
+ if (this.dataset) {
+ combineLatest([this.accessGroups$, this.isAdmin$]).subscribe(
+ ([groups, isAdmin]) => {
+ this.editingAllowed =
+ groups.indexOf(this.dataset.ownerGroup) !== -1 || isAdmin;
+ },
+ );
+ }
+ }),
+ );
+
+ this.subscriptions.push(
+ this.store.select(selectCurrentInstrument).subscribe((instrument) => {
+ this.instrument = instrument;
+ }),
+ );
+
+ this.subscriptions.push(
+ this.store.select(selectCurrentProposal).subscribe((proposal) => {
+ this.proposal = proposal;
+ }),
+ );
+
+ this.subscriptions.push(
+ this.store.select(selectCurrentSample).subscribe((sample) => {
+ this.sample = sample;
+ }),
+ );
+
+ // Prevent user from reloading page if there are unsave changes
+ this.subscriptions.push(
+ fromEvent(window, "beforeunload").subscribe((event) => {
+ if (this.hasUnsavedChanges()) {
+ event.preventDefault();
+ }
+ }),
+ );
+
+ this.subscriptions.push(
+ this.store.select(selectCurrentUser).subscribe((user) => {
+ if (user) {
+ this.user = user;
+ }
+ }),
+ );
+ }
+
+ onEditModeEnable() {
+ this.form = this.fb.group({
+ datasetName: this.dataset.datasetName || "",
+ description: this.dataset.description || "",
+ keywords: this.fb.array(this.dataset.keywords || []),
+ });
+ this.editEnabled = true;
+ }
+
+ hasUnsavedChanges() {
+ return this._hasUnsavedChanges;
+ }
+
+ onClickKeyword(keyword: string) {
+ this.store.dispatch(clearFacetsAction());
+ this.store.dispatch(addKeywordFilterAction({ keyword }));
+ this.router.navigateByUrl("/datasets");
+ }
+
+ get keywords(): FormArray {
+ return this.form.controls.keywords as FormArray;
+ }
+
+ onAddKeyword(event: MatChipInputEvent): void {
+ const input = event.chipInput.inputElement;
+ const value = event.value;
+
+ if ((value || "").trim() && this.dataset) {
+ const keyword = value.trim().toLowerCase();
+ if (this.keywords.value.indexOf(keyword) === -1) {
+ this.keywords.push(this.fb.control(keyword));
+
+ // Reset the input value
+ if (input) {
+ input.value = "";
+ }
+ }
+ }
+ }
+
+ onRemoveKeyword(keyword: string): void {
+ const index = this.keywords.value.indexOf(keyword);
+ if (index >= 0) {
+ this.keywords.removeAt(index);
+ }
+ }
+
+ onSaveGeneralInformationChanges() {
+ const pid = this.dataset.pid;
+
+ if (pid) {
+ const property = {
+ datasetName: this.form.value.datasetName,
+ description: this.form.value.description,
+ keywords: this.keywords.value,
+ };
+
+ this.store.dispatch(updatePropertyAction({ pid, property }));
+ }
+
+ this.editEnabled = false;
+ }
+
+ onRemoveShare(share: string): void {
+ const dialogRef = this.dialog.open(DialogComponent, {
+ width: "auto",
+ data: {
+ title: `Really remove ${share}?`,
+ question: `If you click 'Ok', ${share} will no longer be able to access this Dataset.`,
+ },
+ });
+ dialogRef.afterClosed().subscribe((result) => {
+ if (result && this.dataset) {
+ const index = this.dataset.sharedWith.indexOf(share);
+ if (index >= 0) {
+ const pid = this.dataset.pid;
+ const sharedWith: string[] = [...this.dataset.sharedWith];
+ sharedWith.splice(index, 1);
+ const property = { sharedWith };
+ this.store.dispatch(updatePropertyAction({ pid, property }));
+ }
+ }
+ });
+ }
+
+ onClickInstrument(instrumentId: string): void {
+ const pid = encodeURIComponent(instrumentId);
+ this.router.navigateByUrl("/instruments/" + pid);
+ }
+
+ onClickProposal(proposalId: string): void {
+ const id = encodeURIComponent(proposalId);
+ this.router.navigateByUrl("/proposals/" + id);
+ }
+
+ onClickSample(sampleId: string): void {
+ const id = encodeURIComponent(sampleId);
+ this.router.navigateByUrl("/samples/" + id);
+ }
+
+ onSlidePublic(event: MatSlideToggleChange) {
+ if (this.dataset) {
+ const pid = this.dataset.pid;
+ const property = { isPublished: event.checked };
+ this.store.dispatch(updatePropertyAction({ pid, property }));
+ }
+ }
+
+ onSaveMetadata(metadata: Record) {
+ if (this.dataset) {
+ const pid = this.dataset.pid;
+ const property = { scientificMetadata: metadata };
+ this.store.dispatch(updatePropertyAction({ pid, property }));
+ }
+ }
+
+ onHasUnsavedChanges($event: boolean) {
+ this._hasUnsavedChanges = $event;
+ }
+
+ ngOnDestroy() {
+ this.subscriptions.forEach((subscription) => {
+ subscription.unsubscribe();
+ });
+ }
+
+ onCopy(pid: string) {
+ const selectionBox = this.document.createElement("textarea");
+ selectionBox.style.position = "fixed";
+ selectionBox.style.left = "0";
+ selectionBox.style.top = "0";
+ selectionBox.style.opacity = "0";
+ selectionBox.value = pid;
+ this.document.body.appendChild(selectionBox);
+ selectionBox.focus();
+ selectionBox.select();
+ this.document.execCommand("copy");
+ this.document.body.removeChild(selectionBox);
+
+ const message = new Message(
+ "Dataset PID has been copied to your clipboard",
+ MessageType.Success,
+ 5000,
+ );
+ this.store.dispatch(showMessageAction({ message }));
+ }
+ base64MimeType(encoded: string): string {
+ return this.attachmentService.base64MimeType(encoded);
+ }
+
+ getImageUrl(encoded: string) {
+ return this.attachmentService.getImageUrl(encoded);
+ }
+
+ openAttachment(encoded: string) {
+ this.attachmentService.openAttachment(encoded);
+ }
+}
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 5daece4b2..fb703f500 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
@@ -167,7 +167,7 @@ export class DatasetDetailsDashboardComponent
enabled: true,
},
{
- location: "./relatedDatasets",
+ location: "./related-datasets",
label: TAB.relatedDatasets,
icon: "folder",
enabled: true,
diff --git a/src/app/datasets/dataset-lifecycle/dataset-lifecycle.component.html b/src/app/datasets/dataset-lifecycle/dataset-lifecycle.component.html
index 845dde9e0..56d6d6506 100644
--- a/src/app/datasets/dataset-lifecycle/dataset-lifecycle.component.html
+++ b/src/app/datasets/dataset-lifecycle/dataset-lifecycle.component.html
@@ -13,7 +13,7 @@
Creation Date
- {{ value | date }}
+ {{ value | date: "yyyy-MM-dd" }}
End of Embargo Period
- {{ value | date }}
+ {{ value | date: "yyyy-MM-dd" }}
Publication Date
- {{ value | date }}
+ {{ value | date: "yyyy-MM-dd" }}
Data Deletion Date
- {{ value | date }}
+ {{ value | date: "yyyy-MM-dd" }}
Archive Retention Date
- {{ value | date }}
+ {{ value | date: "yyyy-MM-dd" }}
diff --git a/src/app/datasets/dataset-table/dataset-table.component.html b/src/app/datasets/dataset-table/dataset-table.component.html
index fe8299015..9aa6f3dd9 100644
--- a/src/app/datasets/dataset-table/dataset-table.component.html
+++ b/src/app/datasets/dataset-table/dataset-table.component.html
@@ -176,7 +176,8 @@
-
{{ dataset.creationTime | date }}
+
{{ dataset.creationTime | date: "yyyy-MM-dd" }}
+
{{ dataset.creationTime | date: "EEE HH:mm" }}
diff --git a/src/app/datasets/datasets.module.ts b/src/app/datasets/datasets.module.ts
index 2cebf8218..b0330d5c5 100644
--- a/src/app/datasets/datasets.module.ts
+++ b/src/app/datasets/datasets.module.ts
@@ -51,7 +51,7 @@ import { DashboardComponent } from "./dashboard/dashboard.component";
import { DatablocksComponent } from "./datablocks-table/datablocks-table.component";
import { DatafilesComponent } from "./datafiles/datafiles.component";
import { JsonScientificMetadataComponent } from "./jsonScientificMetadata/jsonScientificMetadata.component";
-import { DatasetDetailComponent } from "./dataset-detail/dataset-detail/dataset-detail.component";
+import { DatasetDetailComponent } from "./dataset-detail/dataset-detail.component";
import { DatasetTableComponent } from "./dataset-table/dataset-table.component";
import { DatasetsFilterComponent } from "./datasets-filter/datasets-filter.component";
import { AddDatasetDialogComponent } from "./add-dataset-dialog/add-dataset-dialog.component";
@@ -90,8 +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 { DatasetDetailDynamicComponent } from "./dataset-detail/dataset-detail-dynamic/dataset-detail-dynamic.component";
-import { DatasetDetailWrapperComponent } from "./dataset-detail/dataset-detail-wrapper.component";
+import { TranslateModule } from "@ngx-translate/core";
@NgModule({
imports: [
CommonModule,
@@ -154,6 +153,7 @@ import { DatasetDetailWrapperComponent } from "./dataset-detail/dataset-detail-w
CdkDrag,
CdkDragHandle,
FiltersModule,
+ TranslateModule,
],
declarations: [
BatchViewComponent,
@@ -161,9 +161,7 @@ import { DatasetDetailWrapperComponent } from "./dataset-detail/dataset-detail-w
DatablocksComponent,
JsonScientificMetadataComponent,
DatafilesComponent,
- DatasetDetailWrapperComponent,
DatasetDetailComponent,
- DatasetDetailDynamicComponent,
DatasetTableComponent,
DatasetsFilterComponent,
PublishComponent,
@@ -202,7 +200,7 @@ import { DatasetDetailWrapperComponent } from "./dataset-detail/dataset-detail-w
DatablocksComponent,
JsonScientificMetadataComponent,
DatafilesComponent,
- DatasetDetailWrapperComponent,
+ DatasetDetailComponent,
DatasetTableComponent,
DatasetsFilterComponent,
],
diff --git a/src/app/datasets/reduce/reduce.component.html b/src/app/datasets/reduce/reduce.component.html
index d70d7e645..9236d3d8d 100644
--- a/src/app/datasets/reduce/reduce.component.html
+++ b/src/app/datasets/reduce/reduce.component.html
@@ -195,7 +195,7 @@ Description
- {{ dataset.createdAt | date }}
+ {{ dataset.createdAt | date: "yyyy-MM-dd, EEE HH:mm" }}
diff --git a/src/app/datasets/sample-edit/sample-edit.component.html b/src/app/datasets/sample-edit/sample-edit.component.html
index ad37628f8..cffc792b7 100644
--- a/src/app/datasets/sample-edit/sample-edit.component.html
+++ b/src/app/datasets/sample-edit/sample-edit.component.html
@@ -56,7 +56,7 @@ Edit Dataset Sample
Creation Time
- {{ row.createdAt | date }}
+ {{ row.createdAt | date: "yyyy-MM-dd, hh:mm" }}
diff --git a/src/app/help/help.module.ts b/src/app/help/help.module.ts
index 86524c575..7421c82cd 100644
--- a/src/app/help/help.module.ts
+++ b/src/app/help/help.module.ts
@@ -3,9 +3,10 @@ import { CommonModule } from "@angular/common";
import { HelpComponent } from "./help/help.component";
import { MatCardModule } from "@angular/material/card";
import { RouterModule } from "@angular/router";
+import { LinkyModule } from "ngx-linky";
@NgModule({
declarations: [HelpComponent],
- imports: [CommonModule, MatCardModule, RouterModule],
+ imports: [CommonModule, MatCardModule, LinkyModule, RouterModule],
})
export class HelpModule {}
diff --git a/src/app/help/help/help.component.html b/src/app/help/help/help.component.html
index 4474f5343..016fe7112 100644
--- a/src/app/help/help/help.component.html
+++ b/src/app/help/help/help.component.html
@@ -4,11 +4,7 @@
Where is the official documentation?
- This is our
- documentation homepage . Following the documentation link (blue button at top left) you will
- find detailed information for Users, Developers, Operators and Ingestors
+
@@ -23,39 +19,19 @@
Getting Started Guide
{{ helpMessages.gettingStarted }}
-
-
-
-
-
-
- Where is my data?
-
- Your data is stored and can be accessed by logging into
- {{ facility }} using sftp to download a file. Alternatively, individual
- datafiles can be downloaded by clicking on a datafiles tab for a given
- dataset, however this is not recommended for large datasets
+
-
+
Where is my data?
- The data catalog keeps the *metadata* for your raw and derived datasets.
- The actual files comprising the datasets are however stored outside the
- catalog. Often the data will reside initially on disk. The data catalog
- allows you to archive these files. In this way the files on disk will be
- copied to tape and can be retrieved later to a location of your choice.
- Here are the
- details on the archive and retrieve workflows
- see
+
@@ -66,8 +42,7 @@
How can I publish a dataset?
- Select datasets in the dataset table and add to Cart . Once in
- Cart , click Publish and follow instructions.
+
@@ -82,6 +57,9 @@
Ingest Manual
{{ helpMessages.ingestManual }}
+
+
+
@@ -94,6 +72,9 @@
Once logged in, you can see your proposals at
Proposals
+
+
+
@@ -106,5 +87,8 @@
Once logged in, you can see your samples at
Samples
+
+
+
diff --git a/src/app/help/help/help.component.spec.ts b/src/app/help/help/help.component.spec.ts
index 9e6ed8ba1..c317fa8fd 100644
--- a/src/app/help/help/help.component.spec.ts
+++ b/src/app/help/help/help.component.spec.ts
@@ -1,6 +1,7 @@
import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing";
import { HelpComponent } from "./help.component";
import { MatCardModule } from "@angular/material/card";
+import { LinkyModule } from "ngx-linky";
import { AppConfigService, HelpMessages } from "app-config.service";
const getConfig = () => ({
@@ -18,7 +19,7 @@ describe("HelpComponent", () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [HelpComponent],
- imports: [MatCardModule],
+ imports: [MatCardModule, LinkyModule],
});
TestBed.overrideComponent(HelpComponent, {
set: {
diff --git a/src/app/help/help/help.component.ts b/src/app/help/help/help.component.ts
index 58823a5f8..0ed090977 100644
--- a/src/app/help/help/help.component.ts
+++ b/src/app/help/help/help.component.ts
@@ -13,6 +13,13 @@ export class HelpComponent implements OnInit {
gettingStarted: string | null = null;
shoppingCartEnabled = false;
helpMessages: HelpMessages;
+ docText = "";
+ gettingStartedExtraText = "";
+ whereIsMyDataText = "";
+ howToPublishDataText = "";
+ ingestManualExtraText = "";
+ whereAreMyProposalsExtraText = "";
+ whereAreMySamplesExtraText = "";
constructor(public appConfigService: AppConfigService) {}
ngOnInit() {
@@ -24,5 +31,13 @@ export class HelpComponent implements OnInit {
);
this.gettingStarted = this.appConfig.gettingStarted;
this.shoppingCartEnabled = this.appConfig.shoppingCartEnabled;
+ this.docText = this.appConfig.docText || "This is our documentation homepage https://scicatproject.github.io/. Following the documentation link (blue button at top left) you will find detailed information for Users, Developers, Operators and Ingestors.";
+ this.gettingStartedExtraText = this.appConfig.gettingStartedExtraText || "";
+ let rawText = this.appConfig.whereIsMyDataText || "Your data is stored and can be accessed by logging into {{ facility }} using sftp to download a file. Alternatively, individual datafiles can be downloaded by clicking on a datafiles tab for a given dataset, however this is not recommended for large datasets.";
+ this.whereIsMyDataText = rawText.replace("{{ facility }}", this.facility);
+ this.howToPublishDataText = this.appConfig.howToPublishDataText || "Select datasets in the dataset table and add to Cart . Once in Cart , click Publish and follow instructions.";
+ this.ingestManualExtraText = this.appConfig.ingestManualExtraText || "";
+ this.whereAreMyProposalsExtraText = this.appConfig.whereAreMyProposalsExtraText || "";
+ this.whereAreMySamplesExtraText = this.appConfig.whereAreMySamplesExtraText || "";
}
}
diff --git a/src/app/jobs/jobs-detail/jobs-detail.component.html b/src/app/jobs/jobs-detail/jobs-detail.component.html
index e7eacda48..ac4678930 100644
--- a/src/app/jobs/jobs-detail/jobs-detail.component.html
+++ b/src/app/jobs/jobs-detail/jobs-detail.component.html
@@ -21,14 +21,14 @@
brightness_high
Creation Time
- {{ value | date }}
+ {{ value | date: "yyyy-MM-dd HH:mm" }}
gavel
Execution Time
- {{ value | date }}
+ {{ value | date: "yyyy-MM-dd HH:mm" }}
@@ -42,7 +42,7 @@
markunread
Date Of Last Message
- {{ value | date }}
+ {{ value | date: "yyyy-MM-dd HH:mm" }}
@@ -58,14 +58,14 @@
calendar_today
Created At
- {{ value | date }}
+ {{ value | date: "yyyy-MM-dd HH:mm" }}
calendar_today
Updated At
- {{ value | date }}
+ {{ value | date: "yyyy-MM-dd HH:mm" }}
diff --git a/src/app/logbooks/logbooks-detail/logbooks-detail.component.html b/src/app/logbooks/logbooks-detail/logbooks-detail.component.html
index 904a49666..6dea84c13 100644
--- a/src/app/logbooks/logbooks-detail/logbooks-detail.component.html
+++ b/src/app/logbooks/logbooks-detail/logbooks-detail.component.html
@@ -28,7 +28,7 @@
- {{ message.origin_server_ts | date }}
+ {{ message.origin_server_ts | date: "yyyy-MM-dd, EEE HH:mm" }}
diff --git a/src/app/logbooks/logbooks-table/logbooks-table.component.html b/src/app/logbooks/logbooks-table/logbooks-table.component.html
index 5a83d3ca0..8095e4e12 100644
--- a/src/app/logbooks/logbooks-table/logbooks-table.component.html
+++ b/src/app/logbooks/logbooks-table/logbooks-table.component.html
@@ -28,7 +28,10 @@
0">
- {{ logbook.messages[0].origin_server_ts | date }}
+ {{
+ logbook.messages[0].origin_server_ts
+ | date: "yyyy-MM-dd, EEE HH:mm"
+ }}
No entries.
diff --git a/src/app/proposals/proposal-dashboard/proposal-dashboard.component.html b/src/app/proposals/proposal-dashboard/proposal-dashboard.component.html
index fc59f5856..43f7b09fe 100644
--- a/src/app/proposals/proposal-dashboard/proposal-dashboard.component.html
+++ b/src/app/proposals/proposal-dashboard/proposal-dashboard.component.html
@@ -24,4 +24,4 @@
style="height: 70vh"
class="mat-elevation-z2"
>
-
+
diff --git a/src/app/proposals/proposal-dashboard/proposal-dashboard.component.spec.ts b/src/app/proposals/proposal-dashboard/proposal-dashboard.component.spec.ts
index e04620099..77fcac220 100644
--- a/src/app/proposals/proposal-dashboard/proposal-dashboard.component.spec.ts
+++ b/src/app/proposals/proposal-dashboard/proposal-dashboard.component.spec.ts
@@ -19,18 +19,19 @@ import { EffectsModule } from "@ngrx/effects";
import { HttpClient } from "@angular/common/http";
import { ScicatDataService } from "shared/services/scicat-data-service";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
-import {
- DatasetsService,
- ProposalClass,
-} from "@scicatproject/scicat-sdk-ts-angular";
-import { BehaviorSubject } from "rxjs";
+import { DatasetsService } from "@scicatproject/scicat-sdk-ts-angular";
describe("ProposalDashboardComponent", () => {
let component: ProposalDashboardComponent;
let fixture: ComponentFixture
;
const getConfig = () => ({});
- const dataSource = new BehaviorSubject([]);
+ const dataSource = new MockScicatDataSource(
+ new MockAppConfigService(null) as unknown as AppConfigService,
+ null,
+ null,
+ { collections: null, columns: null },
+ );
beforeEach(async () => {
await TestBed.configureTestingModule({
diff --git a/src/app/proposals/proposal-dashboard/proposal-dashboard.component.ts b/src/app/proposals/proposal-dashboard/proposal-dashboard.component.ts
index cf6ccf955..6dfcf55c4 100644
--- a/src/app/proposals/proposal-dashboard/proposal-dashboard.component.ts
+++ b/src/app/proposals/proposal-dashboard/proposal-dashboard.component.ts
@@ -1,7 +1,3 @@
-import { Component, EventEmitter, OnInit, Output } from "@angular/core";
-import { BehaviorSubject } from "rxjs";
-import { PageChangeEvent } from "shared/modules/table/table.component";
-import { TableField } from "shared/modules/dynamic-material-table/models/table-field.model";
import {
ITableSetting,
TableSettingEventType,
@@ -129,7 +125,10 @@ export class ProposalDashboardComponent implements OnInit {
@Output() pageChange = new EventEmitter();
constructor(
- private store: Store,
+ private appConfigService: AppConfigService,
+ private cdRef: ChangeDetectorRef,
+ private dataService: ScicatDataService,
+ private exportService: ExportExcelService,
private router: Router,
private route: ActivatedRoute,
) {}
diff --git a/src/app/proposals/proposal-detail/proposal-detail.component.html b/src/app/proposals/proposal-detail/proposal-detail.component.html
index b250619a5..6c07a0962 100644
--- a/src/app/proposals/proposal-detail/proposal-detail.component.html
+++ b/src/app/proposals/proposal-detail/proposal-detail.component.html
@@ -4,41 +4,23 @@
description
- {{ "General Information" | translate }}
+ General Information
- {{ "Title" | translate }}
+ Title
{{ proposal.title }}
- {{ "Abstract" | translate }}
+ Abstract
{{ proposal.abstract }}
- {{ "Proposal Id" | translate }}
+ Identifier
{{ proposal.proposalId }}
-
- {{ "Proposal Type" | translate }}
-
- {{ proposal["type"] }}
-
-
- {{ "Parent proposal" | translate }}
-
- {{
- parentProposal.title
- }}
-
-
- {{ "Start Time" | translate }}
+ Start Time
{{ period.start | date }}
@@ -67,11 +49,29 @@
let-last="last"
>
- {{ "End Time" | translate }}
+ End Time
{{ period.end | date }}
+
+ Type
+
+ {{ proposal["type"] }}
+
+
+ Parent proposal
+
+ {{
+ parentProposal.title
+ }}
+
+
@@ -81,13 +81,13 @@
person
- {{ "Creator Information" | translate }}
+ Creator Information
- {{ "Main proposer" | translate }}
+ Main proposer
{{ proposal.firstname }} {{ proposal.lastname }}
- {{ "Principal Investigator" | translate }}
+ Principal investigator
{{ proposal.pi_firstname }} {{ proposal.pi_lastname }}
science
- {{ "Metadata" | translate }}
+ Metadata
diff --git a/src/app/proposals/proposal-detail/proposal-detail.component.spec.ts b/src/app/proposals/proposal-detail/proposal-detail.component.spec.ts
index dedd01543..890919ca7 100644
--- a/src/app/proposals/proposal-detail/proposal-detail.component.spec.ts
+++ b/src/app/proposals/proposal-detail/proposal-detail.component.spec.ts
@@ -7,17 +7,6 @@ import { MatButtonModule } from "@angular/material/button";
import { NgxJsonViewerModule } from "ngx-json-viewer";
import { AppConfigService } from "app-config.service";
import { StoreModule } from "@ngrx/store";
-import {
- TranslateLoader,
- TranslateModule,
- TranslationObject,
-} from "@ngx-translate/core";
-import { Observable, of } from "rxjs";
-class MockTranslateLoader implements TranslateLoader {
- getTranslation(): Observable {
- return of({});
- }
-}
const getConfig = () => ({
jsonMetadataEnabled: true,
@@ -36,23 +25,12 @@ describe("ProposalsDetailComponent", () => {
MatIconModule,
NgxJsonViewerModule,
StoreModule.forRoot({}),
- TranslateModule.forRoot({
- loader: {
- provide: TranslateLoader,
- useClass: MockTranslateLoader,
- },
- }),
],
declarations: [ProposalDetailComponent],
});
TestBed.overrideComponent(ProposalDetailComponent, {
set: {
- providers: [
- {
- provide: AppConfigService,
- useValue: { getConfig },
- },
- ],
+ providers: [{ provide: AppConfigService, useValue: { getConfig } }],
},
});
TestBed.compileComponents();
diff --git a/src/app/proposals/proposal-detail/proposal-detail.component.ts b/src/app/proposals/proposal-detail/proposal-detail.component.ts
index d6d0577fa..fdeaa28a8 100644
--- a/src/app/proposals/proposal-detail/proposal-detail.component.ts
+++ b/src/app/proposals/proposal-detail/proposal-detail.component.ts
@@ -21,7 +21,6 @@ import {
selectProfile,
} from "state-management/selectors/user.selectors";
import { clearProposalsStateAction } from "state-management/actions/proposals.actions";
-import { TranslateService } from "@ngx-translate/core";
@Component({
selector: "proposal-detail",
@@ -47,12 +46,9 @@ export class ProposalDetailComponent implements OnInit, OnDestroy {
constructor(
public appConfigService: AppConfigService,
- private translateService: TranslateService,
private store: Store,
private router: Router,
- ) {
- this.translateService.use("proposalDefault");
- }
+ ) {}
ngOnInit(): void {
// Prevent user from reloading page if there are unsave changes
diff --git a/src/app/proposals/proposal-logbook/proposal-logbook.component.html b/src/app/proposals/proposal-logbook/proposal-logbook.component.html
index 7b1c2afa6..e4334ed93 100644
--- a/src/app/proposals/proposal-logbook/proposal-logbook.component.html
+++ b/src/app/proposals/proposal-logbook/proposal-logbook.component.html
@@ -1,4 +1,4 @@
-
+
diff --git a/src/app/proposals/proposal-logbook/proposal-logbook.component.ts b/src/app/proposals/proposal-logbook/proposal-logbook.component.ts
index 286bba2d1..a251957f5 100644
--- a/src/app/proposals/proposal-logbook/proposal-logbook.component.ts
+++ b/src/app/proposals/proposal-logbook/proposal-logbook.component.ts
@@ -7,8 +7,8 @@ import {
AfterViewChecked,
} from "@angular/core";
import { Store } from "@ngrx/store";
-import { Subscription } from "rxjs";
-import { selectLogbooksDashboardPageViewModel } from "state-management/selectors/logbooks.selectors";
+import { Observable, Subscription, take } from "rxjs";
+import { selectCurrentLogbook } from "state-management/selectors/logbooks.selectors";
import {
fetchLogbookAction,
setTextFilterAction,
@@ -41,11 +41,11 @@ export interface LogbookData {
export class ProposalLogbookComponent
implements OnInit, OnDestroy, AfterViewChecked
{
- logbook$ = this.store.select(selectLogbooksDashboardPageViewModel);
+ logbook$: Observable
= this.store.select(selectCurrentLogbook);
appConfig = this.appConfigService.getConfig();
subscriptions: Subscription[] = [];
- @Input() proposalId: string;
+ @Input() logbook: LogbookData | null = null; // Still accepting input from parent if provided
constructor(
public appConfigService: AppConfigService,
@@ -87,7 +87,13 @@ export class ProposalLogbookComponent
}
ngOnInit() {
- this.store.dispatch(fetchLogbookAction({ name: this.proposalId }));
+ if (!this.logbook) {
+ this.logbook$.pipe(take(1)).subscribe((logbook) => {
+ if (logbook && logbook.name) {
+ this.store.dispatch(fetchLogbookAction({ name: logbook.name }));
+ }
+ });
+ }
}
ngAfterViewChecked() {
diff --git a/src/app/proposals/proposals.module.ts b/src/app/proposals/proposals.module.ts
index 71bb4e59a..c983f10cb 100644
--- a/src/app/proposals/proposals.module.ts
+++ b/src/app/proposals/proposals.module.ts
@@ -35,9 +35,6 @@ import { MatNativeDateModule } from "@angular/material/core";
import { LogbookEffects } from "state-management/effects/logbooks.effects";
import { logbooksReducer } from "state-management/reducers/logbooks.reducer";
import { ProposalLogbookComponent } from "./proposal-logbook/proposal-logbook.component";
-import { RelatedProposalsComponent } from "./related-proposals/related-proposals.component";
-import { ProposalDatasetsComponent } from "./proposal-datasets/proposal-datasets.component";
-import { TranslateModule } from "@ngx-translate/core";
@NgModule({
imports: [
@@ -63,7 +60,6 @@ import { TranslateModule } from "@ngx-translate/core";
SharedScicatFrontendModule,
StoreModule.forFeature("proposals", proposalsReducer),
StoreModule.forFeature("logbooks", logbooksReducer),
- TranslateModule,
],
declarations: [
ViewProposalPageComponent,
@@ -71,8 +67,6 @@ import { TranslateModule } from "@ngx-translate/core";
ProposalFilterComponent,
ProposalDashboardComponent,
ProposalLogbookComponent,
- RelatedProposalsComponent,
- ProposalDatasetsComponent,
],
exports: [],
providers: [DatePipe, FileSizePipe, SlicePipe],
diff --git a/src/app/proposals/view-proposal-page/view-proposal-page.component.html b/src/app/proposals/view-proposal-page/view-proposal-page.component.html
index 8e27c1e71..13acc0cce 100644
--- a/src/app/proposals/view-proposal-page/view-proposal-page.component.html
+++ b/src/app/proposals/view-proposal-page/view-proposal-page.component.html
@@ -1,54 +1,38 @@
-
+
details
- {{ "Details" | translate }}
+ Details
-
+
folder
- {{ "Datasets" | translate }}
+ Datasets
-
-
-
-
-
-
-
- folder
- {{ "Related Proposals" | translate }}
-
-
-
-
-
+
book
- {{ "Logbook" | translate }}
+ Logbook
-
-
-
+
diff --git a/src/app/proposals/view-proposal-page/view-proposal-page.component.spec.ts b/src/app/proposals/view-proposal-page/view-proposal-page.component.spec.ts
index 66d6730e9..5684ff2b6 100644
--- a/src/app/proposals/view-proposal-page/view-proposal-page.component.spec.ts
+++ b/src/app/proposals/view-proposal-page/view-proposal-page.component.spec.ts
@@ -1,27 +1,32 @@
import { ViewProposalPageComponent } from "./view-proposal-page.component";
-import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing";
+import {
+ ComponentFixture,
+ TestBed,
+ inject,
+ waitForAsync,
+} from "@angular/core/testing";
import { NO_ERRORS_SCHEMA } from "@angular/core";
-import { MockActivatedRoute } from "shared/MockStubs";
+import {
+ MockStore,
+ MockActivatedRoute,
+ createMock,
+ mockDataset,
+ mockProposal,
+} from "shared/MockStubs";
import { Router, ActivatedRoute } from "@angular/router";
-import { StoreModule } from "@ngrx/store";
+import { StoreModule, Store } from "@ngrx/store";
import { DatePipe, SlicePipe } from "@angular/common";
import { FileSizePipe } from "shared/pipes/filesize.pipe";
+import {
+ changeDatasetsPageAction,
+ fetchProposalDatasetsAction,
+} from "state-management/actions/proposals.actions";
+import { PageChangeEvent } from "shared/modules/table/table.component";
import { MatTabsModule } from "@angular/material/tabs";
import { MatIconModule } from "@angular/material/icon";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { AppConfigService } from "app-config.service";
-import {
- TranslateLoader,
- TranslateModule,
- TranslationObject,
-} from "@ngx-translate/core";
-import { Observable, of } from "rxjs";
-
-class MockTranslateLoader implements TranslateLoader {
- getTranslation(): Observable {
- return of({});
- }
-}
+import { DatasetClass } from "@scicatproject/scicat-sdk-ts-angular";
const getConfig = () => ({
logbookEnabled: true,
@@ -34,6 +39,8 @@ describe("ViewProposalPageComponent", () => {
const router = {
navigateByUrl: jasmine.createSpy("navigateByUrl"),
};
+ let store: MockStore;
+ let dispatchSpy;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
@@ -43,12 +50,6 @@ describe("ViewProposalPageComponent", () => {
BrowserAnimationsModule,
MatIconModule,
MatTabsModule,
- TranslateModule.forRoot({
- loader: {
- provide: TranslateLoader,
- useClass: MockTranslateLoader,
- },
- }),
StoreModule.forRoot({}),
],
providers: [DatePipe, FileSizePipe, SlicePipe],
@@ -71,6 +72,10 @@ describe("ViewProposalPageComponent", () => {
fixture.detectChanges();
});
+ beforeEach(inject([Store], (mockStore: MockStore) => {
+ store = mockStore;
+ }));
+
afterEach(() => {
fixture.destroy();
});
@@ -78,4 +83,57 @@ describe("ViewProposalPageComponent", () => {
it("should create", () => {
expect(component).toBeTruthy();
});
+
+ describe("#formatTableData()", () => {
+ it("should return empty array if there are no datasets", () => {
+ const data = component.formatTableData(null);
+
+ expect(data).toEqual([]);
+ });
+
+ it("should return an array of data objects if there are datasets", () => {
+ const datasets = [mockDataset];
+ const data = component.formatTableData(datasets);
+
+ expect(data.length).toEqual(1);
+ });
+ });
+
+ describe("#onPageChange()", () => {
+ it("should dispatch a changeDatasetsPageAction and a fetchProposalDatasetsAction", () => {
+ dispatchSpy = spyOn(store, "dispatch");
+
+ const proposal = mockProposal;
+ proposal.proposalId = "testId";
+ component.proposal = proposal;
+ const event: PageChangeEvent = {
+ pageIndex: 0,
+ pageSize: 25,
+ length: 25,
+ };
+ component.onPageChange(event);
+
+ expect(dispatchSpy).toHaveBeenCalledTimes(2);
+ expect(dispatchSpy).toHaveBeenCalledWith(
+ changeDatasetsPageAction({
+ page: event.pageIndex,
+ limit: event.pageSize,
+ }),
+ );
+ expect(dispatchSpy).toHaveBeenCalledWith(
+ fetchProposalDatasetsAction({ proposalId: proposal.proposalId }),
+ );
+ });
+ });
+
+ describe("#onRowClick()", () => {
+ it("should navigate to a dataset", () => {
+ const dataset = createMock({});
+ const pid = encodeURIComponent(dataset.pid);
+ component.onRowClick(dataset);
+
+ expect(router.navigateByUrl).toHaveBeenCalledTimes(1);
+ expect(router.navigateByUrl).toHaveBeenCalledWith("/datasets/" + pid);
+ });
+ });
});
diff --git a/src/app/proposals/view-proposal-page/view-proposal-page.component.ts b/src/app/proposals/view-proposal-page/view-proposal-page.component.ts
index 33093da55..bc93a91b4 100644
--- a/src/app/proposals/view-proposal-page/view-proposal-page.component.ts
+++ b/src/app/proposals/view-proposal-page/view-proposal-page.component.ts
@@ -1,16 +1,39 @@
import { Component, OnInit, OnDestroy } from "@angular/core";
-import { ActivatedRoute } from "@angular/router";
+import { ActivatedRoute, Router } from "@angular/router";
import { Store } from "@ngrx/store";
import { Subscription } from "rxjs";
import {
fetchProposalAction,
+ fetchProposalDatasetsAction,
+ changeDatasetsPageAction,
fetchParentProposalAction,
clearProposalsStateAction,
} from "state-management/actions/proposals.actions";
import { selectViewProposalPageViewModel } from "state-management/selectors/proposals.selectors";
+import {
+ TableColumn,
+ PageChangeEvent,
+} from "shared/modules/table/table.component";
+import { DatePipe, SlicePipe } from "@angular/common";
+import { FileSizePipe } from "shared/pipes/filesize.pipe";
+import { fetchLogbookAction } from "state-management/actions/logbooks.actions";
import { AppConfigService } from "app-config.service";
-import { ProposalClass } from "@scicatproject/scicat-sdk-ts-angular";
-import { TranslateService } from "@ngx-translate/core";
+import { selectLogbooksDashboardPageViewModel } from "state-management/selectors/logbooks.selectors";
+import {
+ DatasetClass,
+ OutputDatasetObsoleteDto,
+ ProposalClass,
+} from "@scicatproject/scicat-sdk-ts-angular";
+
+export interface TableData {
+ pid: string;
+ name: string;
+ sourceFolder: string;
+ size: string;
+ creationTime: string | null;
+ owner: string;
+ location: string;
+}
@Component({
selector: "view-proposal-page",
@@ -19,18 +42,69 @@ import { TranslateService } from "@ngx-translate/core";
})
export class ViewProposalPageComponent implements OnInit, OnDestroy {
vm$ = this.store.select(selectViewProposalPageViewModel);
+ logbook$ = this.store.select(selectLogbooksDashboardPageViewModel);
appConfig = this.appConfigService.getConfig();
+
proposal: ProposalClass;
+
subscriptions: Subscription[] = [];
- public selectedTabIndex = 0;
+
+ tablePaginate = true;
+ tableData: TableData[] = [];
+ tableColumns: TableColumn[] = [
+ { name: "name", icon: "portrait", sort: false, inList: true },
+ { name: "sourceFolder", icon: "explore", sort: false, inList: true },
+ { name: "size", icon: "save", sort: false, inList: true },
+ { name: "creationTime", icon: "calendar_today", sort: false, inList: true },
+ { name: "owner", icon: "face", sort: false, inList: true },
+ { name: "location", icon: "explore", sort: false, inList: true },
+ ];
constructor(
public appConfigService: AppConfigService,
+ private datePipe: DatePipe,
+ private filesizePipe: FileSizePipe,
private route: ActivatedRoute,
+ private router: Router,
+ private slicePipe: SlicePipe,
private store: Store,
- private translateService: TranslateService,
- ) {
- this.translateService.use("proposalDefault");
+ ) {}
+
+ formatTableData(datasets: OutputDatasetObsoleteDto[]): TableData[] {
+ let tableData: TableData[] = [];
+ if (datasets) {
+ tableData = datasets.map((dataset: any) => ({
+ pid: dataset.pid,
+ name: dataset.datasetName,
+ sourceFolder:
+ "..." + this.slicePipe.transform(dataset.sourceFolder, -14),
+ size: this.filesizePipe.transform(dataset.size),
+ creationTime: this.datePipe.transform(
+ dataset.creationTime,
+ "yyyy-MM-dd HH:mm",
+ ),
+ owner: dataset.owner,
+ location: dataset.creationLocation,
+ }));
+ }
+ return tableData;
+ }
+
+ onPageChange(event: PageChangeEvent) {
+ this.store.dispatch(
+ changeDatasetsPageAction({
+ page: event.pageIndex,
+ limit: event.pageSize,
+ }),
+ );
+ this.store.dispatch(
+ fetchProposalDatasetsAction({ proposalId: this.proposal.proposalId }),
+ );
+ }
+
+ onRowClick(dataset: DatasetClass) {
+ const pid = encodeURIComponent(dataset.pid);
+ this.router.navigateByUrl("/datasets/" + pid);
}
ngOnInit() {
@@ -41,7 +115,7 @@ export class ViewProposalPageComponent implements OnInit, OnDestroy {
if (
this.proposal["parentProposalId"] &&
- this.selectedTabIndex === 0
+ this.proposal["parentProposalId"] !== ""
) {
this.fetchProposalRelatedDocuments();
}
@@ -52,17 +126,18 @@ export class ViewProposalPageComponent implements OnInit, OnDestroy {
this.subscriptions.push(
this.route.params.subscribe((params) => {
this.store.dispatch(fetchProposalAction({ proposalId: params.id }));
- this.resetTabs();
+ this.store.dispatch(
+ fetchProposalDatasetsAction({ proposalId: params.id }),
+ );
+ this.store.dispatch(fetchLogbookAction({ name: params.id }));
}),
);
- }
- onTabChanged(newIndex: number): void {
- this.selectedTabIndex = newIndex;
- }
-
- resetTabs(): void {
- this.selectedTabIndex = 0;
+ this.subscriptions.push(
+ this.vm$.subscribe((vm) => {
+ this.tableData = this.formatTableData(vm.datasets);
+ }),
+ );
}
fetchProposalRelatedDocuments(): void {
diff --git a/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html b/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html
index 03c53a276..2e04f39e9 100644
--- a/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html
+++ b/src/app/publisheddata/publisheddata-details/publisheddata-details.component.html
@@ -20,7 +20,7 @@
>
Registered Time
- {{ value | date }}
+ {{ value | date: "yyyy-MM-dd, HH:mm" }}
diff --git a/src/app/samples/sample-detail/sample-detail.component.html b/src/app/samples/sample-detail/sample-detail.component.html
index cae3733f1..16fc9e192 100644
--- a/src/app/samples/sample-detail/sample-detail.component.html
+++ b/src/app/samples/sample-detail/sample-detail.component.html
@@ -25,7 +25,7 @@
Creation Time
- {{ value | date }}
+ {{ value | date: "yyyy-MM-dd HH:mm" }}
diff --git a/src/app/shared/loaders/custom-translate.loader.ts b/src/app/shared/loaders/custom-translate.loader.ts
index 86394b0ce..7e9a25ec7 100644
--- a/src/app/shared/loaders/custom-translate.loader.ts
+++ b/src/app/shared/loaders/custom-translate.loader.ts
@@ -7,15 +7,13 @@ export class CustomTranslateLoader implements TranslateLoader {
constructor(private appConfigService: AppConfigService) {}
- getTranslation(lang: string): Observable {
- if (
- this.appConfig.labelsLocalization &&
- this.appConfig.labelsLocalization[lang]
- ) {
- return of(this.appConfig.labelsLocalization[lang]);
- }
+ getTranslation(): Observable {
+ const { currentLabelSet = "", labelSets = {} } =
+ this.appConfig?.datasetDetailViewLabelOption || {};
- console.warn(`Translation for "${lang}" not found.`);
+ if (currentLabelSet in labelSets) {
+ return of(labelSets[currentLabelSet]);
+ }
return of({});
}
}
diff --git a/src/app/shared/modules/scientific-metadata-tree/tree-view/tree-view.component.html b/src/app/shared/modules/scientific-metadata-tree/tree-view/tree-view.component.html
index a45e78550..e243697ac 100644
--- a/src/app/shared/modules/scientific-metadata-tree/tree-view/tree-view.component.html
+++ b/src/app/shared/modules/scientific-metadata-tree/tree-view/tree-view.component.html
@@ -36,7 +36,11 @@
>
- {{ node.key }}
+
+
+
+
+ {{ node.key }}
{{ getValueRepresentation(node) }}
diff --git a/src/app/shared/modules/scientific-metadata-tree/tree-view/tree-view.component.scss b/src/app/shared/modules/scientific-metadata-tree/tree-view/tree-view.component.scss
index 9bf1daf21..13e86c996 100644
--- a/src/app/shared/modules/scientific-metadata-tree/tree-view/tree-view.component.scss
+++ b/src/app/shared/modules/scientific-metadata-tree/tree-view/tree-view.component.scss
@@ -60,19 +60,15 @@ mat-form-field {
padding-left: 0px;
}
section {
- display: grid;
- grid-template-columns: 40% 60%;
- gap: 1em;
+ display: table;
width: 100%;
- margin: 0 2em;
}
-
.key-cell {
- grid-column: 1;
- word-wrap: break-word;
+ display: table-cell;
+ margin-left: 8px;
+ width: 40%;
}
-
.value-cell {
- grid-column: 2;
- word-wrap: break-word;
+ display: table-cell;
+ width: 60%;
}
diff --git a/src/app/shared/modules/scientific-metadata/metadata-edit/metadata-edit.component.ts b/src/app/shared/modules/scientific-metadata/metadata-edit/metadata-edit.component.ts
index 4010a73d7..82821861e 100644
--- a/src/app/shared/modules/scientific-metadata/metadata-edit/metadata-edit.component.ts
+++ b/src/app/shared/modules/scientific-metadata/metadata-edit/metadata-edit.component.ts
@@ -133,7 +133,7 @@ export class MetadataEditComponent implements OnInit, OnChanges {
typeof this.metadata[key] === "object" &&
"value" in (this.metadata[key] as ScientificMetadata)
) {
- if (this.metadata[key].unit?.length > 0) {
+ if (this.metadata[key]["unit"].length > 0) {
field = this.formBuilder.group({
fieldType: this.formControlFields["fieldType"]("quantity"),
fieldName: this.formControlFields["fieldName"](key),
diff --git a/src/app/shared/modules/scientific-metadata/metadata-view/metadata-view.component.html b/src/app/shared/modules/scientific-metadata/metadata-view/metadata-view.component.html
index 95f1e1440..983cd16ac 100644
--- a/src/app/shared/modules/scientific-metadata/metadata-view/metadata-view.component.html
+++ b/src/app/shared/modules/scientific-metadata/metadata-view/metadata-view.component.html
@@ -4,9 +4,7 @@
-
+ Name
{{
metadata.name | replaceUnderscore | titlecase
}}
@@ -14,23 +12,19 @@
-
+ Value
{{ metadata.value }}
-
+ Unit
- 0" [ngIfElse]="noUnit">
+ 0" [ngIfElse]="noUnit">
{{ metadata.unit | prettyUnit }}
{
describe("fetchProposalsAction", () => {
it("should create an action", () => {
- const action = fromActions.fetchProposalsAction({});
+ const action = fromActions.fetchProposalsAction();
expect({ ...action }).toEqual({ type: "[Proposal] Fetch Proposals" });
});
});
@@ -36,7 +36,7 @@ describe("Proposal Actions", () => {
describe("fetchCountAction", () => {
it("should create an action", () => {
- const action = fromActions.fetchCountAction({});
+ const action = fromActions.fetchCountAction();
expect({ ...action }).toEqual({ type: "[Proposal] Fetch Count" });
});
});
@@ -304,6 +304,26 @@ describe("Proposal Actions", () => {
});
});
+ describe("clearFacetsAction", () => {
+ it("should create an action", () => {
+ const action = fromActions.clearFacetsAction();
+ expect({ ...action }).toEqual({ type: "[Proposal] Clear Facets" });
+ });
+ });
+
+ describe("changePageAction", () => {
+ it("should create an action", () => {
+ const page = 0;
+ const limit = 25;
+ const action = fromActions.changePageAction({ page, limit });
+ expect({ ...action }).toEqual({
+ type: "[Proposal] Change Page",
+ page,
+ limit,
+ });
+ });
+ });
+
describe("changeDatasetsPageAction", () => {
it("should create an action", () => {
const page = 0;
@@ -317,6 +337,19 @@ describe("Proposal Actions", () => {
});
});
+ describe("sortByColumnAction", () => {
+ it("should create an action", () => {
+ const column = "test";
+ const direction = "asc";
+ const action = fromActions.sortByColumnAction({ column, direction });
+ expect({ ...action }).toEqual({
+ type: "[Proposal] Sort By Column",
+ column,
+ direction,
+ });
+ });
+ });
+
describe("clearProposalsStateAction", () => {
it("should create an action", () => {
const action = fromActions.clearProposalsStateAction();
diff --git a/src/app/state-management/actions/proposals.actions.ts b/src/app/state-management/actions/proposals.actions.ts
index a60450ba4..f6440e760 100644
--- a/src/app/state-management/actions/proposals.actions.ts
+++ b/src/app/state-management/actions/proposals.actions.ts
@@ -27,10 +27,7 @@ export const fetchProposalsFailedAction = createAction(
"[Proposal] Fetch Proposals Failed",
);
-export const fetchCountAction = createAction(
- "[Proposal] Fetch Count",
- props<{ fields?: Record }>(),
-);
+export const fetchCountAction = createAction("[Proposal] Fetch Count");
export const fetchCountCompleteAction = createAction(
"[Proposal] Fetch Count Complete",
props<{ count: number }>(),
@@ -155,38 +152,23 @@ export const setDateRangeFilterAction = createAction(
props<{ begin: string; end: string }>(),
);
+export const clearFacetsAction = createAction("[Proposal] Clear Facets");
+
+export const changePageAction = createAction(
+ "[Proposal] Change Page",
+ props<{ page: number; limit: number }>(),
+);
export const changeDatasetsPageAction = createAction(
"[Proposal] Change Datasets Page",
props<{ page: number; limit: number }>(),
);
+export const sortByColumnAction = createAction(
+ "[Proposal] Sort By Column",
+ props<{ column: string; direction: string }>(),
+);
+
export const clearProposalsStateAction = createAction("[Proposal] Clear State");
export const clearCurrentProposalStateAction = createAction(
"[Proposal] Clear Current Proposal State",
);
-
-export const fetchRelatedProposalsAction = createAction(
- "[Proposal] Fetch Related Proposals",
-);
-export const fetchRelatedProposalsCompleteAction = createAction(
- "[Proposal] Fetch Related Proposals Complete",
- props<{
- relatedProposals: (ProposalClass & { relation: string })[];
- }>(),
-);
-export const fetchRelatedProposalsFailedAction = createAction(
- "[Proposal] Fetch Related Proposals Failed",
-);
-
-export const fetchRelatedProposalsCountCompleteAction = createAction(
- "[Proposal] Fetch Related Proposals Count Complete",
- props<{ count: number }>(),
-);
-export const fetchRelatedProposalsCountFailedAction = createAction(
- "[Proposal] Fetch Related Proposals Count Failed",
-);
-
-export const changeRelatedProposalsPageAction = createAction(
- "[Proposal] Change Related Proposals Page",
- props<{ page: number; limit: number }>(),
-);
diff --git a/src/app/state-management/effects/datasets.effects.ts b/src/app/state-management/effects/datasets.effects.ts
index d6d544cba..25d80aa51 100644
--- a/src/app/state-management/effects/datasets.effects.ts
+++ b/src/app/state-management/effects/datasets.effects.ts
@@ -408,8 +408,6 @@ export class DatasetEffects {
ofType(
fromActions.fetchDatasetsCompleteAction,
fromActions.fetchDatasetsFailedAction,
- fromActions.fetchRelatedDatasetsCompleteAction,
- fromActions.fetchRelatedDatasetsFailedAction,
fromActions.fetchFacetCountsCompleteAction,
fromActions.fetchFacetCountsFailedAction,
fromActions.fetchMetadataKeysCompleteAction,
diff --git a/src/app/state-management/effects/proposals.effects.spec.ts b/src/app/state-management/effects/proposals.effects.spec.ts
index d00745f52..407fad3b1 100644
--- a/src/app/state-management/effects/proposals.effects.spec.ts
+++ b/src/app/state-management/effects/proposals.effects.spec.ts
@@ -69,7 +69,7 @@ describe("ProposalEffects", () => {
provide: ProposalsService,
useValue: jasmine.createSpyObj("proposalApi", [
"proposalsControllerFullquery",
- "proposalsControllerCount",
+ "proposalsControllerFullfacet",
"proposalsControllerFindById",
"proposalsControllerFindByIdAccess",
"proposalsControllerCreateAttachment",
@@ -81,7 +81,6 @@ describe("ProposalEffects", () => {
provide: DatasetsService,
useValue: jasmine.createSpyObj("datasetApi", [
"datasetsControllerFindAll",
- "datasetsControllerCount",
]),
},
],
@@ -99,13 +98,44 @@ describe("ProposalEffects", () => {
describe("ofType fetchProposalsAction", () => {
it("should result in a fetchProposalsCompleteAction and a fetchCountAction", () => {
const proposals = [proposal];
- const action = fromActions.fetchProposalsAction({});
+ const action = fromActions.fetchProposalsAction();
const outcome1 = fromActions.fetchProposalsCompleteAction({
proposals,
});
- const outcome2 = fromActions.fetchCountAction({
- fields: { text: undefined },
+ const outcome2 = fromActions.fetchCountAction();
+
+ actions = hot("-a", { a: action });
+ const response = cold("-a|", { a: proposals });
+ proposalApi.proposalsControllerFullquery.and.returnValue(response);
+
+ const expected = cold("--(bc)", { b: outcome1, c: outcome2 });
+ expect(effects.fetchProposals$).toBeObservable(expected);
+ });
+
+ it("should result in a fetchProposalsFailedAction", () => {
+ const action = fromActions.fetchProposalsAction();
+ const outcome = fromActions.fetchProposalsFailedAction();
+
+ actions = hot("-a", { a: action });
+ const response = cold("-#", {});
+ proposalApi.proposalsControllerFullquery.and.returnValue(response);
+
+ const expected = cold("--b", { b: outcome });
+ expect(effects.fetchProposals$).toBeObservable(expected);
+ });
+ });
+
+ describe("ofType changePageAction", () => {
+ const page = 1;
+ const limit = 25;
+
+ it("should result in a fetchProposalsCompleteAction and a fetchCountAction", () => {
+ const proposals = [proposal];
+ const action = fromActions.changePageAction({ page, limit });
+ const outcome1 = fromActions.fetchProposalsCompleteAction({
+ proposals,
});
+ const outcome2 = fromActions.fetchCountAction();
actions = hot("-a", { a: action });
const response = cold("-a|", { a: proposals });
@@ -116,7 +146,70 @@ describe("ProposalEffects", () => {
});
it("should result in a fetchProposalsFailedAction", () => {
- const action = fromActions.fetchProposalsAction({});
+ const action = fromActions.changePageAction({ page, limit });
+ const outcome = fromActions.fetchProposalsFailedAction();
+
+ actions = hot("-a", { a: action });
+ const response = cold("-#", {});
+ proposalApi.proposalsControllerFullquery.and.returnValue(response);
+
+ const expected = cold("--b", { b: outcome });
+ expect(effects.fetchProposals$).toBeObservable(expected);
+ });
+ });
+
+ describe("ofType sortByColumnAction", () => {
+ const column = "test";
+ const direction = "desc";
+
+ it("should result in a fetchProposalsCompleteAction and a fetchCountAction", () => {
+ const proposals = [proposal];
+ const action = fromActions.sortByColumnAction({ column, direction });
+ const outcome1 = fromActions.fetchProposalsCompleteAction({
+ proposals,
+ });
+ const outcome2 = fromActions.fetchCountAction();
+
+ actions = hot("-a", { a: action });
+ const response = cold("-a|", { a: proposals });
+ proposalApi.proposalsControllerFullquery.and.returnValue(response);
+
+ const expected = cold("--(bc)", { b: outcome1, c: outcome2 });
+ expect(effects.fetchProposals$).toBeObservable(expected);
+ });
+
+ it("should result in a fetchProposalsFailedAction", () => {
+ const action = fromActions.sortByColumnAction({ column, direction });
+ const outcome = fromActions.fetchProposalsFailedAction();
+
+ actions = hot("-a", { a: action });
+ const response = cold("-#", {});
+ proposalApi.proposalsControllerFullquery.and.returnValue(response);
+
+ const expected = cold("--b", { b: outcome });
+ expect(effects.fetchProposals$).toBeObservable(expected);
+ });
+ });
+
+ describe("ofType clearFacetsAction", () => {
+ it("should result in a fetchProposalsCompleteAction and a fetchCountAction", () => {
+ const proposals = [proposal];
+ const action = fromActions.clearFacetsAction();
+ const outcome1 = fromActions.fetchProposalsCompleteAction({
+ proposals,
+ });
+ const outcome2 = fromActions.fetchCountAction();
+
+ actions = hot("-a", { a: action });
+ const response = cold("-a|", { a: proposals });
+ proposalApi.proposalsControllerFullquery.and.returnValue(response);
+
+ const expected = cold("--(bc)", { b: outcome1, c: outcome2 });
+ expect(effects.fetchProposals$).toBeObservable(expected);
+ });
+
+ it("should result in a fetchProposalsFailedAction", () => {
+ const action = fromActions.clearFacetsAction();
const outcome = fromActions.fetchProposalsFailedAction();
actions = hot("-a", { a: action });
@@ -131,27 +224,32 @@ describe("ProposalEffects", () => {
describe("fetchCount$", () => {
it("should result in a fetchCountCompleteAction", () => {
- const count = 1;
- const action = fromActions.fetchCountAction({});
+ const proposals = [proposal];
+ const action = fromActions.fetchCountAction();
const outcome = fromActions.fetchCountCompleteAction({
- count,
+ count: proposals.length,
});
+ const responseArray = [
+ {
+ all: [{ totalSets: proposals.length }],
+ },
+ ];
actions = hot("-a", { a: action });
- const response = cold("-a|", { a: { count } });
- proposalApi.proposalsControllerCount.and.returnValue(response);
+ const response = cold("-a|", { a: responseArray });
+ proposalApi.proposalsControllerFullfacet.and.returnValue(response);
const expected = cold("--b", { b: outcome });
expect(effects.fetchCount$).toBeObservable(expected);
});
it("should result in a fetchCountFailedAction", () => {
- const action = fromActions.fetchCountAction({});
+ const action = fromActions.fetchCountAction();
const outcome = fromActions.fetchCountFailedAction();
actions = hot("-a", { a: action });
const response = cold("-#", {});
- proposalApi.proposalsControllerCount.and.returnValue(response);
+ proposalApi.proposalsControllerFullfacet.and.returnValue(response);
const expected = cold("--b", { b: outcome });
expect(effects.fetchCount$).toBeObservable(expected);
@@ -268,6 +366,7 @@ describe("ProposalEffects", () => {
const proposalId = "testId";
it("should result in a fetchProposalDatasetsCountCompleteAction", () => {
+ const datasets = [dataset];
const count = 1;
const action = fromActions.fetchProposalDatasetsCountAction({
proposalId,
@@ -277,8 +376,8 @@ describe("ProposalEffects", () => {
});
actions = hot("-a", { a: action });
- const response = cold("-a|", { a: { count } });
- datasetApi.datasetsControllerCount.and.returnValue(response);
+ const response = cold("-a|", { a: datasets });
+ datasetApi.datasetsControllerFindAll.and.returnValue(response);
const expected = cold("--b", { b: outcome });
expect(effects.fetchProposalDatasetsCount$).toBeObservable(expected);
@@ -292,7 +391,7 @@ describe("ProposalEffects", () => {
actions = hot("-a", { a: action });
const response = cold("-#", {});
- datasetApi.datasetsControllerCount.and.returnValue(response);
+ datasetApi.datasetsControllerFindAll.and.returnValue(response);
const expected = cold("--b", { b: outcome });
expect(effects.fetchProposalDatasetsCount$).toBeObservable(expected);
@@ -413,7 +512,7 @@ describe("ProposalEffects", () => {
describe("loading$", () => {
describe("ofType fetchProposalsAction", () => {
it("should dispatch a loadingAction", () => {
- const action = fromActions.fetchProposalsAction({});
+ const action = fromActions.fetchProposalsAction();
const outcome = loadingAction();
actions = hot("-a", { a: action });
@@ -425,7 +524,7 @@ describe("ProposalEffects", () => {
describe("ofType fetchCountAction", () => {
it("should dispatch a loadingAction", () => {
- const action = fromActions.fetchCountAction({});
+ const action = fromActions.fetchCountAction();
const outcome = loadingAction();
actions = hot("-a", { a: action });
diff --git a/src/app/state-management/effects/proposals.effects.ts b/src/app/state-management/effects/proposals.effects.ts
index fa3b87c7c..7e18fedc0 100644
--- a/src/app/state-management/effects/proposals.effects.ts
+++ b/src/app/state-management/effects/proposals.effects.ts
@@ -10,8 +10,6 @@ import * as fromActions from "state-management/actions/proposals.actions";
import {
selectFullqueryParams,
selectDatasetsQueryParams,
- selectCurrentProposal,
- selectRelatedProposalsFilters,
} from "state-management/selectors/proposals.selectors";
import { map, mergeMap, catchError, switchMap, filter } from "rxjs/operators";
import { ObservableInput, of } from "rxjs";
@@ -22,8 +20,6 @@ import {
@Injectable()
export class ProposalEffects {
- currentProposal$ = this.store.select(selectCurrentProposal);
- relatedProposalFilters$ = this.store.select(selectRelatedProposalsFilters);
fullqueryParams$ = this.store.select(selectFullqueryParams);
datasetQueryParams$ = this.store.select(selectDatasetsQueryParams);
@@ -51,27 +47,29 @@ export class ProposalEffects {
.pipe(
mergeMap((proposals) => [
fromActions.fetchProposalsCompleteAction({ proposals }),
- // TODO: Maybe this part should be refactored. Now we need to send 2 separate requests to get the data and count
- fromActions.fetchCountAction({
- fields: queryParam,
- }),
+ fromActions.fetchCountAction(),
]),
catchError(() => of(fromActions.fetchProposalsFailedAction())),
- );
- }),
+ ),
+ ),
);
});
fetchCount$ = createEffect(() => {
return this.actions$.pipe(
ofType(fromActions.fetchCountAction),
- switchMap(({ fields }) =>
- this.proposalsService
- .proposalsControllerCount(JSON.stringify(fields))
- .pipe(
- map(({ count }) => fromActions.fetchCountCompleteAction({ count })),
- catchError(() => of(fromActions.fetchCountFailedAction())),
- ),
+ concatLatestFrom(() => this.fullqueryParams$),
+ map(([action, params]) => params),
+ switchMap(({ query }) =>
+ this.proposalsService.proposalsControllerFullfacet(query).pipe(
+ map((res) => {
+ const { all } = res[0];
+ const allCounts = all && all.length > 0 ? all[0].totalSets : 0;
+
+ return fromActions.fetchCountCompleteAction({ count: allCounts });
+ }),
+ catchError(() => of(fromActions.fetchCountFailedAction())),
+ ),
),
);
});
@@ -122,11 +120,11 @@ export class ProposalEffects {
ofType(fromActions.fetchProposalDatasetsCountAction),
switchMap(({ proposalId }) =>
this.datasetsService
- .datasetsControllerCount(JSON.stringify({ where: { proposalId } }))
+ .datasetsControllerFindAll(JSON.stringify({ where: { proposalId } }))
.pipe(
- map(({ count }) =>
+ map((datasets) =>
fromActions.fetchProposalDatasetsCountCompleteAction({
- count,
+ count: datasets.length,
}),
),
catchError(() =>
@@ -335,8 +333,6 @@ export class ProposalEffects {
fromActions.updateProposalPropertyFailedAction,
fromActions.removeAttachmentCompleteAction,
fromActions.removeAttachmentFailedAction,
- fromActions.fetchRelatedProposalsCompleteAction,
- fromActions.fetchRelatedProposalsFailedAction,
),
switchMap(() => of(loadingCompleteAction())),
);
diff --git a/src/app/state-management/effects/user.effects.ts b/src/app/state-management/effects/user.effects.ts
index b68513b7d..8c5d01c2e 100644
--- a/src/app/state-management/effects/user.effects.ts
+++ b/src/app/state-management/effects/user.effects.ts
@@ -216,23 +216,25 @@ export class UserEffects {
return this.actions$.pipe(
ofType(fromActions.logoutAction),
filter(() => this.authService.isAuthenticated()),
- switchMap(() => {
- this.authService.clear();
- return this.sharedAuthService.authControllerLogout().pipe(
- switchMap(({ logoutURL }) => [
- clearDatasetsStateAction(),
- clearInstrumentsStateAction(),
- clearJobsStateAction(),
- clearLogbooksStateAction(),
- clearPoliciesStateAction(),
- clearProposalsStateAction(),
- clearPublishedDataStateAction(),
- clearSamplesStateAction(),
- fromActions.logoutCompleteAction({ logoutURL }),
- ]),
+ switchMap(() =>
+ this.sharedAuthService.authControllerLogout().pipe(
+ switchMap(({ logoutURL }) => {
+ this.authService.clear();
+ return [
+ clearDatasetsStateAction(),
+ clearInstrumentsStateAction(),
+ clearJobsStateAction(),
+ clearLogbooksStateAction(),
+ clearPoliciesStateAction(),
+ clearProposalsStateAction(),
+ clearPublishedDataStateAction(),
+ clearSamplesStateAction(),
+ fromActions.logoutCompleteAction({ logoutURL }),
+ ];
+ }),
catchError(() => of(fromActions.logoutFailedAction())),
- );
- }),
+ ),
+ ),
);
});
diff --git a/src/app/state-management/models/index.ts b/src/app/state-management/models/index.ts
index ccc3d1180..4c5eb9aa5 100644
--- a/src/app/state-management/models/index.ts
+++ b/src/app/state-management/models/index.ts
@@ -21,55 +21,13 @@ export interface LabelMaps {
[key: string]: Record;
}
-export interface LabelsLocalization {
- datasetDefault: Record;
- datasetCustom: Record;
- proposalDefault: Record;
+export interface datasetDetailViewLabelOption {
+ currentLabelSet: string;
+ labelSets: {
+ [key: string]: Record;
+ };
}
-export interface DatasetDetailComponentConfig {
- enableCustomizedComponent: boolean;
- customization: CustomizationItem[];
-}
-export enum DatasetViewFieldType {
- TEXT = "text",
- DATE = "date",
- LINKY = "linky",
- COPY = "copy",
- TAG = "tag",
-}
-
-interface AttachmentOptions {
- limit: number;
- size: "small" | "medium" | "large";
-}
-type viewModeOptions = "table" | "json" | "tree";
-
-export interface CustomizationItem {
- type: CustomizationType;
- label: string;
- order: number;
- fields?: Field[];
- options?: AttachmentOptions;
- viewMode?: viewModeOptions;
-}
-
-export interface Field {
- element: FieldType;
- source: string;
- order: number;
-}
-
-// Type alias for allowed customization types
-type CustomizationType =
- | "regular"
- | "scientificMetadata"
- | "datasetJsonView"
- | "attachments";
-
-// Type alias for allowed field types
-type FieldType = "text" | "copy" | "linky" | "tag" | "date";
-
export interface DatasetsListSettings {
columns: TableColumn[];
filters: FilterConfig[];
diff --git a/src/app/state-management/reducers/proposals.reducer.spec.ts b/src/app/state-management/reducers/proposals.reducer.spec.ts
index 037131145..ff2185783 100644
--- a/src/app/state-management/reducers/proposals.reducer.spec.ts
+++ b/src/app/state-management/reducers/proposals.reducer.spec.ts
@@ -180,6 +180,39 @@ describe("ProposalsReducer", () => {
});
});
+ describe("on clearFacetsAction", () => {
+ it("should clear filters while saving the filters limit", () => {
+ const limit = 10;
+ const page = 1;
+ const skip = limit * page;
+
+ const act = fromActions.changePageAction({ page, limit });
+ const sta = proposalsReducer(initialProposalsState, act);
+
+ expect(sta.proposalFilters.skip).toEqual(skip);
+
+ const action = fromActions.clearFacetsAction();
+ const state = proposalsReducer(sta, action);
+
+ expect(state.proposalFilters.skip).toEqual(0);
+ expect(state.proposalFilters.limit).toEqual(limit);
+ expect(state.proposalFilters.text).toEqual("");
+ });
+ });
+
+ describe("on changePageAction", () => {
+ it("should set skip and limit filters", () => {
+ const page = 1;
+ const limit = 25;
+ const skip = page * limit;
+ const action = fromActions.changePageAction({ page, limit });
+ const state = proposalsReducer(initialProposalsState, action);
+
+ expect(state.proposalFilters.skip).toEqual(skip);
+ expect(state.proposalFilters.limit).toEqual(limit);
+ });
+ });
+
describe("on changeDatasetsPageAction", () => {
it("should set skip and limit dataset filters", () => {
const page = 1;
@@ -193,6 +226,19 @@ describe("ProposalsReducer", () => {
});
});
+ describe("on sortByColumnAction", () => {
+ it("should set sortField filter and set skip to 0", () => {
+ const column = "test";
+ const direction = "asc";
+ const sortField = column + ":" + direction;
+ const action = fromActions.sortByColumnAction({ column, direction });
+ const state = proposalsReducer(initialProposalsState, action);
+
+ expect(state.proposalFilters.sortField).toEqual(sortField);
+ expect(state.proposalFilters.skip).toEqual(0);
+ });
+ });
+
describe("on clearProposalsStateAction", () => {
it("it should set proposals state to initialProposState", () => {
const action = fromActions.clearProposalsStateAction();
diff --git a/src/app/state-management/reducers/proposals.reducer.ts b/src/app/state-management/reducers/proposals.reducer.ts
index 7350dccc4..f79431054 100644
--- a/src/app/state-management/reducers/proposals.reducer.ts
+++ b/src/app/state-management/reducers/proposals.reducer.ts
@@ -119,6 +119,21 @@ const reducer = createReducer(
},
),
+ on(fromActions.clearFacetsAction, (state): ProposalsState => {
+ const limit = state.proposalFilters.limit; // Save limit
+ const proposalFilters = {
+ ...initialProposalsState.proposalFilters,
+ skip: 0,
+ limit,
+ };
+ return { ...state, proposalFilters };
+ }),
+
+ on(fromActions.changePageAction, (state, { page, limit }): ProposalsState => {
+ const skip = page * limit;
+ const proposalFilters = { ...state.proposalFilters, skip, limit };
+ return { ...state, proposalFilters };
+ }),
on(
fromActions.changeDatasetsPageAction,
(state, { page, limit }): ProposalsState => {
@@ -128,6 +143,15 @@ const reducer = createReducer(
},
),
+ on(
+ fromActions.sortByColumnAction,
+ (state, { column, direction }): ProposalsState => {
+ const sortField = column + (direction ? ":" + direction : "");
+ const proposalFilters = { ...state.proposalFilters, sortField, skip: 0 };
+ return { ...state, proposalFilters };
+ },
+ ),
+
on(fromActions.clearProposalsStateAction, () => ({
...initialProposalsState,
})),
@@ -136,34 +160,6 @@ const reducer = createReducer(
...state,
currentProposal: undefined,
})),
-
- on(
- fromActions.fetchRelatedProposalsCompleteAction,
- (state, { relatedProposals }): ProposalsState => ({
- ...state,
- relatedProposals,
- }),
- ),
- on(
- fromActions.fetchRelatedProposalsCountCompleteAction,
- (state, { count }): ProposalsState => ({
- ...state,
- relatedProposalsCount: count,
- }),
- ),
-
- on(
- fromActions.changeRelatedProposalsPageAction,
- (state, { page, limit }): ProposalsState => {
- const skip = page * limit;
- const relatedProposalsFilters = {
- ...state.relatedProposalsFilters,
- skip,
- limit,
- };
- return { ...state, relatedProposalsFilters };
- },
- ),
);
export const proposalsReducer = (
diff --git a/src/app/state-management/selectors/proposals.selectors.spec.ts b/src/app/state-management/selectors/proposals.selectors.spec.ts
index bd7f3070f..405e7b565 100644
--- a/src/app/state-management/selectors/proposals.selectors.spec.ts
+++ b/src/app/state-management/selectors/proposals.selectors.spec.ts
@@ -36,14 +36,6 @@ const initialProposalsState: ProposalsState = {
parentProposal: parentProposal,
datasets: [],
- relatedProposals: [],
- relatedProposalsCount: 0,
- relatedProposalsFilters: {
- skip: 0,
- limit: 25,
- sortField: "creationTime:desc",
- },
-
proposalsCount: 0,
datasetsCount: 0,
diff --git a/src/app/state-management/selectors/proposals.selectors.ts b/src/app/state-management/selectors/proposals.selectors.ts
index 8ecf76d2b..73ed093b9 100644
--- a/src/app/state-management/selectors/proposals.selectors.ts
+++ b/src/app/state-management/selectors/proposals.selectors.ts
@@ -111,29 +111,6 @@ export const selectViewProposalPageViewModel = createSelector(
}),
);
-export const selectRelatedProposalsPageViewModel = createSelector(
- selectProposalsState,
- ({ relatedProposals, relatedProposalsCount }) => ({
- relatedProposals,
- relatedProposalsCount,
- }),
-);
-
-export const selectRelatedProposalsFilters = createSelector(
- selectProposalsState,
- (state) => state.relatedProposalsFilters,
-);
-
-export const selectRelatedProposalsCurrentPage = createSelector(
- selectRelatedProposalsFilters,
- (filters) => filters.skip / filters.limit,
-);
-
-export const selectRelatedProposalsPerPage = createSelector(
- selectRelatedProposalsFilters,
- (filters) => filters.limit,
-);
-
const restrictFilter = (filter: any, allowedKeys?: string[]) => {
const isNully = (value: any) => {
const hasLength = typeof value === "string" || Array.isArray(value);
diff --git a/src/app/state-management/state/proposals.store.ts b/src/app/state-management/state/proposals.store.ts
index 2bb02cf1f..cc89f413b 100644
--- a/src/app/state-management/state/proposals.store.ts
+++ b/src/app/state-management/state/proposals.store.ts
@@ -27,8 +27,6 @@ export interface ProposalsState {
proposals: ProposalClass[];
currentProposal: ProposalClass | undefined;
parentProposal: ProposalClass | undefined;
- relatedProposals: (ProposalClass & { relation: string })[];
- relatedProposalsCount: number;
datasets: OutputDatasetObsoleteDto[];
proposalsCount: number;
@@ -37,20 +35,12 @@ export interface ProposalsState {
hasPrefilledFilters: boolean;
proposalFilters: ProposalFilters;
datasetFilters: ProposalDatesetFilters;
-
- relatedProposalsFilters: {
- skip: number;
- limit: number;
- sortField: string;
- };
}
export const initialProposalsState: ProposalsState = {
proposals: [],
currentProposal: undefined,
parentProposal: undefined,
- relatedProposals: [],
- relatedProposalsCount: 0,
datasets: [],
proposalsCount: 0,
@@ -75,10 +65,4 @@ export const initialProposalsState: ProposalsState = {
limit: 25,
sortField: "creationTime:desc",
},
-
- relatedProposalsFilters: {
- skip: 0,
- limit: 25,
- sortField: "creationTime:desc",
- },
};
From dc4eccdf2a475ce33ecd17f8415c26ebfe4b7c18 Mon Sep 17 00:00:00 2001
From: Armando Bermudez Martinez
Date: Thu, 13 Mar 2025 10:50:35 +0100
Subject: [PATCH 2/4] Update about.component.ts
---
src/app/about/about/about.component.ts | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/src/app/about/about/about.component.ts b/src/app/about/about/about.component.ts
index 913181215..230779f8e 100644
--- a/src/app/about/about/about.component.ts
+++ b/src/app/about/about/about.component.ts
@@ -31,10 +31,13 @@ export class AboutComponent implements OnInit {
this.SNFLink =
"http://www.snf.ch/en/theSNSF/research-policies/open_research_data/Pages/default.aspx#Guidelines%20and%20Regulations";
this.PSIDataPolicy = "https://www.psi.ch/en/science/psi-data-policy";
- this.aboutText = this.appConfig.aboutText || "Scicat allows users to access data and metadata from experiments";
- this.accessText = this.appConfig.accessText || "Users must comply with access policy of instruments";
- this.termsText = this.appConfig.termsText || "Data can be used freely under the CC-BY-4.0 license";
- this.termsTextContinued = this.appConfig.termsTextContinued || "";
-
+ this.aboutText =
+ this.appConfig.aboutText || "Scicat allows users to access data and metadata from experiments";
+ this.accessText =
+ this.appConfig.accessText || "Users must comply with access policy of instruments";
+ this.termsText =
+ this.appConfig.termsText || "Data can be used freely under the CC-BY-4.0 license";
+ this.termsTextContinued =
+ this.appConfig.termsTextContinued || "";
}
}
From 213f5d0910f296dc8ac0a5c30deda09cda03b198 Mon Sep 17 00:00:00 2001
From: Armando Bermudez Martinez
Date: Thu, 13 Mar 2025 10:52:03 +0100
Subject: [PATCH 3/4] Update about.component.ts
---
src/app/about/about/about.component.ts | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/src/app/about/about/about.component.ts b/src/app/about/about/about.component.ts
index 230779f8e..41c7af87f 100644
--- a/src/app/about/about/about.component.ts
+++ b/src/app/about/about/about.component.ts
@@ -32,12 +32,16 @@ export class AboutComponent implements OnInit {
"http://www.snf.ch/en/theSNSF/research-policies/open_research_data/Pages/default.aspx#Guidelines%20and%20Regulations";
this.PSIDataPolicy = "https://www.psi.ch/en/science/psi-data-policy";
this.aboutText =
- this.appConfig.aboutText || "Scicat allows users to access data and metadata from experiments";
+ this.appConfig.aboutText ||
+ "Scicat allows users to access data and metadata from experiments";
this.accessText =
- this.appConfig.accessText || "Users must comply with access policy of instruments";
+ this.appConfig.accessText ||
+ "Users must comply with access policy of instruments";
this.termsText =
- this.appConfig.termsText || "Data can be used freely under the CC-BY-4.0 license";
+ this.appConfig.termsText ||
+ "Data can be used freely under the CC-BY-4.0 license";
this.termsTextContinued =
- this.appConfig.termsTextContinued || "";
+ this.appConfig.termsTextContinued ||
+ "";
}
}
From d5ce3ca42a24c73e4bb80152d9ad11d908d0bf9f Mon Sep 17 00:00:00 2001
From: Armando Bermudez Martinez
Date: Thu, 13 Mar 2025 10:53:54 +0100
Subject: [PATCH 4/4] Update help.component.ts
---
src/app/help/help/help.component.ts | 32 +++++++++++++++++++++--------
1 file changed, 24 insertions(+), 8 deletions(-)
diff --git a/src/app/help/help/help.component.ts b/src/app/help/help/help.component.ts
index 0ed090977..2f264a60d 100644
--- a/src/app/help/help/help.component.ts
+++ b/src/app/help/help/help.component.ts
@@ -31,13 +31,29 @@ export class HelpComponent implements OnInit {
);
this.gettingStarted = this.appConfig.gettingStarted;
this.shoppingCartEnabled = this.appConfig.shoppingCartEnabled;
- this.docText = this.appConfig.docText || "This is our documentation homepage https://scicatproject.github.io/. Following the documentation link (blue button at top left) you will find detailed information for Users, Developers, Operators and Ingestors.";
- this.gettingStartedExtraText = this.appConfig.gettingStartedExtraText || "";
- let rawText = this.appConfig.whereIsMyDataText || "Your data is stored and can be accessed by logging into {{ facility }} using sftp to download a file. Alternatively, individual datafiles can be downloaded by clicking on a datafiles tab for a given dataset, however this is not recommended for large datasets.";
- this.whereIsMyDataText = rawText.replace("{{ facility }}", this.facility);
- this.howToPublishDataText = this.appConfig.howToPublishDataText || "Select datasets in the dataset table and add to Cart . Once in Cart , click Publish and follow instructions.";
- this.ingestManualExtraText = this.appConfig.ingestManualExtraText || "";
- this.whereAreMyProposalsExtraText = this.appConfig.whereAreMyProposalsExtraText || "";
- this.whereAreMySamplesExtraText = this.appConfig.whereAreMySamplesExtraText || "";
+ this.docText =
+ this.appConfig.docText ||
+ "This is our documentation homepage https://scicatproject.github.io/. Following the documentation link (blue button at top left) you will find detailed information for Users, Developers, Operators and Ingestors.";
+ this.gettingStartedExtraText =
+ this.appConfig.gettingStartedExtraText ||
+ "";
+ let rawText =
+ this.appConfig.whereIsMyDataText ||
+ "Your data is stored and can be accessed by logging into {{ facility }} using sftp to download a file. Alternatively, individual datafiles can be downloaded by clicking on a datafiles tab for a given dataset, however this is not recommended for large datasets.";
+ this.whereIsMyDataText = rawText.replace(
+ "{{ facility }}", this.facility
+ );
+ this.howToPublishDataText =
+ this.appConfig.howToPublishDataText ||
+ "Select datasets in the dataset table and add to Cart . Once in Cart , click Publish and follow instructions.";
+ this.ingestManualExtraText =
+ this.appConfig.ingestManualExtraText ||
+ "";
+ this.whereAreMyProposalsExtraText =
+ this.appConfig.whereAreMyProposalsExtraText ||
+ "";
+ this.whereAreMySamplesExtraText =
+ this.appConfig.whereAreMySamplesExtraText ||
+ "";
}
}