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 6956393a6..bef0238b8 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
@@ -10,6 +10,7 @@ import { DatasetDetailComponent } from "datasets/dataset-detail/dataset-detail.c
import { DatasetFileUploaderComponent } from "datasets/dataset-file-uploader/dataset-file-uploader.component";
import { DatasetLifecycleComponent } from "datasets/dataset-lifecycle/dataset-lifecycle.component";
import { ReduceComponent } from "datasets/reduce/reduce.component";
+import { RelatedDatasetsComponent } from "datasets/related-datasets/related-datasets.component";
import { LogbooksDashboardComponent } from "logbooks/logbooks-dashboard/logbooks-dashboard.component";
const routes: Routes = [
{
@@ -22,6 +23,10 @@ const routes: Routes = [
component: DatafilesComponent,
},
+ {
+ 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
// Expected behavior should be that ServiceGuard return false should have higher priority than AuthGuard therefore it shoulds navigate to /404 instead of /login
{
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 1513bfda1..876868266 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -81,4 +81,4 @@ const appThemeInitializerFn = (appTheme: AppThemeService) => {
],
bootstrap: [AppComponent],
})
-export class AppModule {}
+export class AppModule { }
diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.ts b/src/app/datasets/dataset-detail/dataset-detail.component.ts
index 00fe9165d..be9a06f3a 100644
--- a/src/app/datasets/dataset-detail/dataset-detail.component.ts
+++ b/src/app/datasets/dataset-detail/dataset-detail.component.ts
@@ -1,4 +1,4 @@
-import { Component, Inject, OnInit, OnDestroy } from "@angular/core";
+import { Component, OnInit, OnDestroy } from "@angular/core";
import { Dataset, Proposal, Sample } from "shared/sdk/models";
import { ENTER, COMMA, SPACE } from "@angular/cdk/keycodes";
import { MatChipInputEvent } from "@angular/material/chips";
@@ -74,7 +74,7 @@ export class DatasetDetailComponent
public dialog: MatDialog,
private store: Store,
private router: Router
- ) { }
+ ) {}
ngOnInit() {
this.subscriptions.push(
diff --git a/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.spec.ts b/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.spec.ts
index f3d390ce2..59204da37 100644
--- a/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.spec.ts
+++ b/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.spec.ts
@@ -32,38 +32,37 @@ describe("DetailsDashboardComponent", () => {
editMetadataEnabled: true,
});
- beforeEach(
- waitForAsync(() => {
- TestBed.configureTestingModule({
- schemas: [NO_ERRORS_SCHEMA],
- declarations: [DatasetDetailsDashboardComponent],
- imports: [
- MatButtonModule,
- MatIconModule,
- MatSlideToggleModule,
- MatTabsModule,
- SharedScicatFrontendModule,
- StoreModule.forRoot({}),
- ],
- });
- TestBed.overrideComponent(DatasetDetailsDashboardComponent, {
- set: {
- providers: [
- { provide: Router, useValue: router },
- {
- provide: AppConfigService,
- useValue: {
- getConfig,
- },
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ schemas: [NO_ERRORS_SCHEMA],
+ declarations: [DatasetDetailsDashboardComponent],
+ imports: [
+ MatButtonModule,
+ MatIconModule,
+ MatSlideToggleModule,
+ MatTabsModule,
+ SharedScicatFrontendModule,
+ StoreModule.forRoot({}),
+ ],
+ providers: [],
+ });
+ TestBed.overrideComponent(DatasetDetailsDashboardComponent, {
+ set: {
+ providers: [
+ { provide: Router, useValue: router },
+ {
+ provide: AppConfigService,
+ useValue: {
+ getConfig,
},
- { provide: ActivatedRoute, useClass: MockActivatedRoute },
- { provide: UserApi, useClass: MockUserApi },
- ],
- },
- });
- TestBed.compileComponents();
- })
- );
+ },
+ { provide: ActivatedRoute, useClass: MockActivatedRoute },
+ { provide: UserApi, useClass: MockUserApi },
+ ],
+ },
+ });
+ TestBed.compileComponents();
+ }));
beforeEach(() => {
fixture = TestBed.createComponent(DatasetDetailsDashboardComponent);
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 5249c8a99..e04223589 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
@@ -16,13 +16,14 @@ import {
} from "state-management/selectors/user.selectors";
import { ActivatedRoute } from "@angular/router";
import { Subscription, Observable, combineLatest } from "rxjs";
-import { map, pluck, takeWhile } from "rxjs/operators";
+import { map, pluck } from "rxjs/operators";
import {
clearCurrentDatasetStateAction,
fetchAttachmentsAction,
fetchDatablocksAction,
fetchDatasetAction,
fetchOrigDatablocksAction,
+ fetchRelatedDatasetsAction,
} from "state-management/actions/datasets.actions";
import {
clearLogbookAction,
@@ -51,6 +52,7 @@ export interface FileObject {
enum TAB {
details = "Details",
datafiles = "Datafiles",
+ relatedDatasets = "Related Datasets",
reduce = "Reduce",
logbook = "Logbook",
attachments = "Attachments",
@@ -81,6 +83,10 @@ export class DatasetDetailsDashboardComponent
}[] = [];
fetchDataActions: { [tab: string]: { action: any; loaded: boolean } } = {
[TAB.details]: { action: fetchDatasetAction, loaded: false },
+ [TAB.relatedDatasets]: {
+ action: fetchRelatedDatasetsAction,
+ loaded: false,
+ },
[TAB.datafiles]: { action: fetchOrigDatablocksAction, loaded: false },
[TAB.logbook]: { action: fetchLogbookAction, loaded: false },
[TAB.attachments]: { action: fetchAttachmentsAction, loaded: false },
@@ -99,23 +105,24 @@ export class DatasetDetailsDashboardComponent
private store: Store,
private userApi: UserApi,
public dialog: MatDialog
- ) {}
+ ) { }
ngOnInit() {
- this.route.params
- .pipe(pluck("id"))
- .subscribe((id: string) => {
+ this.subscriptions.push(
+ this.route.params.pipe(pluck("id")).subscribe((id: string) => {
if (id) {
+ this.resetTabs();
// Fetch dataset details
this.store.dispatch(fetchDatasetAction({ pid: id }));
this.fetchDataActions[TAB.details].loaded = true;
}
})
- .unsubscribe();
+ );
+
const datasetSub = this.dataset$
- .pipe(takeWhile((dataset) => !dataset, true))
.subscribe((dataset) => {
- if (dataset) {
+ // Only run this code when dataset.pid is different from this.dataset.pid or this.dataset = null
+ if (dataset && (!this.dataset || this.dataset && (dataset.pid != this.dataset.pid))) {
this.dataset = dataset;
combineLatest([this.accessGroups$, this.isAdmin$, this.loggedIn$])
.subscribe(([groups, isAdmin, isLoggedIn]) => {
@@ -134,6 +141,12 @@ export class DatasetDetailsDashboardComponent
icon: "cloud_download",
enabled: true,
},
+ {
+ location: "./related-datasets",
+ label: TAB.relatedDatasets,
+ icon: "folder",
+ enabled: true,
+ },
{
location: "./reduce",
label: TAB.reduce,
@@ -181,28 +194,17 @@ export class DatasetDetailsDashboardComponent
})
.unsubscribe();
- if ("proposalId" in dataset) {
- this.store.dispatch(
- fetchProposalAction({ proposalId: dataset["proposalId"] })
- );
- } else {
- this.store.dispatch(clearLogbookAction());
- }
- if ("sampleId" in dataset) {
- this.store.dispatch(
- fetchSampleAction({ sampleId: dataset["sampleId"] })
- );
- }
- if ("instrumentId" in dataset) {
- this.store.dispatch(
- fetchInstrumentAction({ pid: dataset["instrumentId"] })
- );
- }
+ this.fetchDatasetRelatedDocuments();
}
});
this.subscriptions.push(datasetSub);
this.jwt$ = this.userApi.jwt();
- };
+ }
+ resetTabs() {
+ Object.values(this.fetchDataActions).forEach(tab => {
+ tab.loaded = false;
+ });
+ }
onTabSelected(tab: string) {
this.fetchDataForTab(tab);
}
@@ -239,6 +241,29 @@ export class DatasetDetailsDashboardComponent
}
}
}
+
+ fetchDatasetRelatedDocuments(): void {
+ if (this.dataset) {
+ if ("proposalId" in this.dataset) {
+ this.store.dispatch(
+ fetchProposalAction({ proposalId: this.dataset["proposalId"] })
+ );
+ } else {
+ this.store.dispatch(clearLogbookAction());
+ }
+ if ("sampleId" in this.dataset) {
+ this.store.dispatch(
+ fetchSampleAction({ sampleId: this.dataset["sampleId"] })
+ );
+ }
+ if ("instrumentId" in this.dataset) {
+ this.store.dispatch(
+ fetchInstrumentAction({ pid: this.dataset["instrumentId"] })
+ );
+ }
+ }
+ }
+
ngAfterViewChecked() {
this.cdRef.detectChanges();
}
diff --git a/src/app/datasets/dataset-lifecycle/dataset-lifecycle.component.ts b/src/app/datasets/dataset-lifecycle/dataset-lifecycle.component.ts
index 4251f6e71..730fbd31e 100644
--- a/src/app/datasets/dataset-lifecycle/dataset-lifecycle.component.ts
+++ b/src/app/datasets/dataset-lifecycle/dataset-lifecycle.component.ts
@@ -59,7 +59,7 @@ export class DatasetLifecycleComponent implements OnInit, OnChanges {
) {}
private parseHistoryItems(): HistoryItem[] {
- if (this.dataset) {
+ if (this.dataset && this.dataset.history) {
const history = this.dataset.history.map(
({ updatedAt, updatedBy, id, ...properties }) =>
Object.keys(properties).map(
diff --git a/src/app/datasets/datasets.module.ts b/src/app/datasets/datasets.module.ts
index 610922a0d..74d3c78a4 100644
--- a/src/app/datasets/datasets.module.ts
+++ b/src/app/datasets/datasets.module.ts
@@ -79,6 +79,7 @@ import { DatasetFileUploaderComponent } from "./dataset-file-uploader/dataset-fi
import { AdminTabComponent } from "./admin-tab/admin-tab.component";
import { instrumentsReducer } from "state-management/reducers/instruments.reducer";
import { InstrumentEffects } from "state-management/effects/instruments.effects";
+import { RelatedDatasetsComponent } from "./related-datasets/related-datasets.component";
@NgModule({
imports: [
@@ -155,6 +156,7 @@ import { InstrumentEffects } from "state-management/effects/instruments.effects"
ShareDialogComponent,
DatasetFileUploaderComponent,
AdminTabComponent,
+ RelatedDatasetsComponent,
],
providers: [
ArchivingService,
diff --git a/src/app/datasets/related-datasets/related-datasets.component.html b/src/app/datasets/related-datasets/related-datasets.component.html
new file mode 100644
index 000000000..d3b8ba930
--- /dev/null
+++ b/src/app/datasets/related-datasets/related-datasets.component.html
@@ -0,0 +1,12 @@
+
diff --git a/src/app/datasets/related-datasets/related-datasets.component.scss b/src/app/datasets/related-datasets/related-datasets.component.scss
new file mode 100644
index 000000000..e13431329
--- /dev/null
+++ b/src/app/datasets/related-datasets/related-datasets.component.scss
@@ -0,0 +1,3 @@
+.dataset-table {
+ margin-top: 5em;
+}
\ No newline at end of file
diff --git a/src/app/datasets/related-datasets/related-datasets.component.spec.ts b/src/app/datasets/related-datasets/related-datasets.component.spec.ts
new file mode 100644
index 000000000..023d499b0
--- /dev/null
+++ b/src/app/datasets/related-datasets/related-datasets.component.spec.ts
@@ -0,0 +1,98 @@
+import { DatePipe } from "@angular/common";
+import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { Router } from "@angular/router";
+import { Store } from "@ngrx/store";
+import { provideMockStore } from "@ngrx/store/testing";
+import { PageChangeEvent } from "shared/modules/table/table.component";
+import { Dataset } from "shared/sdk";
+import {
+ changeRelatedDatasetsPageAction,
+ fetchRelatedDatasetsAction,
+} from "state-management/actions/datasets.actions";
+import { selectRelatedDatasetsPageViewModel } from "state-management/selectors/datasets.selectors";
+
+import { RelatedDatasetsComponent } from "./related-datasets.component";
+
+describe("RelatedDatasetsComponent", () => {
+ let component: RelatedDatasetsComponent;
+ let fixture: ComponentFixture;
+
+ const router = {
+ navigateByUrl: jasmine.createSpy("navigateByUrl"),
+ };
+ let store: Store;
+ let dispatchSpy;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [RelatedDatasetsComponent],
+ providers: [
+ DatePipe,
+ provideMockStore({
+ selectors: [
+ {
+ selector: selectRelatedDatasetsPageViewModel,
+ value: {
+ relatedDatasets: [],
+ relatedDatasetsCount: 0,
+ relatedDatasetsFilters: {
+ skip: 0,
+ limit: 25,
+ sortField: "creationTime:desc",
+ },
+ },
+ },
+ ],
+ }),
+ { provide: Router, useValue: router },
+ ],
+ }).compileComponents();
+
+ store = TestBed.inject(Store);
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(RelatedDatasetsComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it("should create", () => {
+ expect(component).toBeTruthy();
+ });
+
+ describe("#onPageChange", () => {
+ it("should dispatch a changeRelatedDatasetsPageAction and a fetchRelatedDatasetsAction", () => {
+ dispatchSpy = spyOn(store, "dispatch");
+
+ const event: PageChangeEvent = {
+ pageIndex: 0,
+ pageSize: 25,
+ length: 25,
+ };
+
+ component.onPageChange(event);
+
+ expect(dispatchSpy).toHaveBeenCalledTimes(2);
+ expect(dispatchSpy).toHaveBeenCalledWith(
+ changeRelatedDatasetsPageAction({
+ page: event.pageIndex,
+ limit: event.pageSize,
+ })
+ );
+ expect(dispatchSpy).toHaveBeenCalledWith(fetchRelatedDatasetsAction());
+ });
+ });
+
+ describe("#onRowClick()", () => {
+ it("should navigate to a dataset", () => {
+ const dataset = new Dataset();
+
+ component.onRowClick(dataset);
+
+ expect(router.navigateByUrl).toHaveBeenCalledOnceWith(
+ "/datasets/" + encodeURIComponent(dataset.pid)
+ );
+ });
+ });
+});
diff --git a/src/app/datasets/related-datasets/related-datasets.component.ts b/src/app/datasets/related-datasets/related-datasets.component.ts
new file mode 100644
index 000000000..4a6a4eded
--- /dev/null
+++ b/src/app/datasets/related-datasets/related-datasets.component.ts
@@ -0,0 +1,109 @@
+import { DatePipe } from "@angular/common";
+import { Component } from "@angular/core";
+import { Router } from "@angular/router";
+import { Store } from "@ngrx/store";
+import { map } from "rxjs/operators";
+import {
+ PageChangeEvent,
+ TableColumn,
+} from "shared/modules/table/table.component";
+import { Dataset } from "shared/sdk";
+import {
+ changeRelatedDatasetsPageAction,
+ fetchRelatedDatasetsAction,
+} from "state-management/actions/datasets.actions";
+import { selectRelatedDatasetsPageViewModel } from "state-management/selectors/datasets.selectors";
+
+@Component({
+ selector: "app-related-datasets",
+ templateUrl: "./related-datasets.component.html",
+ styleUrls: ["./related-datasets.component.scss"],
+})
+export class RelatedDatasetsComponent {
+ vm$ = this.store.select(selectRelatedDatasetsPageViewModel).pipe(
+ map((vm) => ({
+ ...vm,
+ relatedDatasets: this.formatTableData(vm.relatedDatasets),
+ }))
+ );
+
+ tablePaginate = true;
+ tableColumns: TableColumn[] = [
+ {
+ name: "name",
+ icon: "portrait",
+ sort: true,
+ inList: true,
+ },
+ {
+ name: "sourceFolder",
+ icon: "explore",
+ sort: true,
+ inList: true,
+ },
+ {
+ name: "size",
+ icon: "save",
+ sort: true,
+ inList: true,
+ },
+ {
+ name: "type",
+ icon: "bubble_chart",
+ sort: true,
+ inList: true,
+ },
+ {
+ name: "creationTime",
+ icon: "calendar_today",
+ sort: true,
+ inList: true,
+ },
+ {
+ name: "owner",
+ icon: "face",
+ sort: true,
+ inList: true,
+ },
+ ];
+
+ constructor(
+ private datePipe: DatePipe,
+ private router: Router,
+ private store: Store
+ ) { }
+
+ formatTableData(datasets: Dataset[]): Record[] {
+ if (!datasets) {
+ return [];
+ }
+
+ return datasets.map((dataset) => ({
+ pid: dataset.pid,
+ name: dataset.datasetName,
+ sourceFolder: dataset.sourceFolder,
+ size: dataset.size,
+ type: dataset.type,
+ creationTime: this.datePipe.transform(
+ dataset.creationTime,
+ "yyyy-MM-dd, hh:mm"
+ ),
+ owner: dataset.owner,
+ }));
+ }
+
+ onPageChange(event: PageChangeEvent): void {
+ this.store.dispatch(
+ changeRelatedDatasetsPageAction({
+ page: event.pageIndex,
+ limit: event.pageSize,
+ })
+ );
+ this.store.dispatch(fetchRelatedDatasetsAction());
+ }
+
+ onRowClick(dataset: Dataset): void {
+ const pid = encodeURIComponent(dataset.pid);
+ this.router.navigateByUrl("/datasets/" + pid);
+ }
+}
diff --git a/src/app/state-management/actions/datasets.actions.spec.ts b/src/app/state-management/actions/datasets.actions.spec.ts
index 37e3de096..aa888ddd3 100644
--- a/src/app/state-management/actions/datasets.actions.spec.ts
+++ b/src/app/state-management/actions/datasets.actions.spec.ts
@@ -126,6 +126,75 @@ describe("Dataset Actions", () => {
});
});
+ describe("fetchRelatedDatasetsAction", () => {
+ it("should create an action", () => {
+ const action = fromActions.fetchRelatedDatasetsAction();
+ expect({ ...action }).toEqual({
+ type: "[Dataset] Fetch Related Datasets",
+ });
+ });
+ });
+
+ describe("fetchRelatedDatasetsCompleteAction", () => {
+ it("should create an action", () => {
+ const relatedDatasets = [new Dataset()];
+ const action = fromActions.fetchRelatedDatasetsCompleteAction({
+ relatedDatasets,
+ });
+ expect({ ...action }).toEqual({
+ type: "[Dataset] Fetch Related Datasets Complete",
+ relatedDatasets,
+ });
+ });
+ });
+
+ describe("fetchRelatedDatasetsFailedAction", () => {
+ it("should create an action", () => {
+ const action = fromActions.fetchRelatedDatasetsFailedAction();
+ expect({ ...action }).toEqual({
+ type: "[Datasets] Fetch Related Datasets Failed",
+ });
+ });
+ });
+
+ describe("fetchRelatedDatasetsCountCompleteAction", () => {
+ it("should create an action", () => {
+ const count = 0;
+ const action = fromActions.fetchRelatedDatasetsCountCompleteAction({
+ count,
+ });
+ expect({ ...action }).toEqual({
+ type: "[Dataset] Fetch Related Datasets Count Complete",
+ count,
+ });
+ });
+ });
+
+ describe("fetchRelatedDatasetsCountFailedAction", () => {
+ it("should create an action", () => {
+ const action = fromActions.fetchRelatedDatasetsCountFailedAction();
+ expect({ ...action }).toEqual({
+ type: "[Datasets] Fetch Related Datasets Count Failed",
+ });
+ });
+ });
+
+ describe("changeRelatedDatasetsPageAction", () => {
+ it("should create an action", () => {
+ const page = 0;
+ const limit = 25;
+ const action = fromActions.changeRelatedDatasetsPageAction({
+ page,
+ limit,
+ });
+ expect({ ...action }).toEqual({
+ type: "[Dataset] Change Related Datasets Page",
+ page,
+ limit,
+ });
+ });
+ });
+
describe("prefillBatchAction", () => {
it("should create an action", () => {
const action = fromActions.prefillBatchAction();
diff --git a/src/app/state-management/actions/datasets.actions.ts b/src/app/state-management/actions/datasets.actions.ts
index bb63b0b38..40c202036 100644
--- a/src/app/state-management/actions/datasets.actions.ts
+++ b/src/app/state-management/actions/datasets.actions.ts
@@ -1,5 +1,11 @@
import { createAction, props } from "@ngrx/store";
-import { Dataset, Attachment, DerivedDataset, OrigDatablock, Datablock } from "shared/sdk";
+import {
+ Dataset,
+ Attachment,
+ DerivedDataset,
+ OrigDatablock,
+ Datablock,
+} from "shared/sdk";
import { FacetCounts } from "state-management/state/datasets.store";
import {
ArchViewMode,
@@ -86,6 +92,30 @@ export const fetchAttachmentsFailedAction = createAction(
"[Dataset] Fetch Attachments Failed"
);
+export const fetchRelatedDatasetsAction = createAction(
+ "[Dataset] Fetch Related Datasets"
+);
+export const fetchRelatedDatasetsCompleteAction = createAction(
+ "[Dataset] Fetch Related Datasets Complete",
+ props<{ relatedDatasets: Dataset[] }>()
+);
+export const fetchRelatedDatasetsFailedAction = createAction(
+ "[Datasets] Fetch Related Datasets Failed"
+);
+
+export const fetchRelatedDatasetsCountCompleteAction = createAction(
+ "[Dataset] Fetch Related Datasets Count Complete",
+ props<{ count: number }>()
+);
+export const fetchRelatedDatasetsCountFailedAction = createAction(
+ "[Datasets] Fetch Related Datasets Count Failed"
+);
+
+export const changeRelatedDatasetsPageAction = createAction(
+ "[Dataset] Change Related Datasets Page",
+ props<{ page: number; limit: number }>()
+);
+
export const prefillBatchAction = createAction("[Dataset] Prefill Batch");
export const prefillBatchCompleteAction = createAction(
"[Dataset] Prefill Batch Complete",
@@ -222,7 +252,7 @@ export const setArchiveViewModeAction = createAction(
);
export const setPublicViewModeAction = createAction(
"[Dataset] Set Public View Mode",
- props<{ isPublished: boolean | ""}>()
+ props<{ isPublished: boolean | "" }>()
);
export const prefillFiltersAction = createAction(
@@ -288,4 +318,6 @@ export const removeScientificConditionAction = createAction(
export const clearDatasetsStateAction = createAction("[Dataset] Clear State");
-export const clearCurrentDatasetStateAction = createAction("[Dataset] Clear Current Dataset State");
+export const clearCurrentDatasetStateAction = createAction(
+ "[Dataset] Clear Current Dataset State"
+);
diff --git a/src/app/state-management/effects/datasets.effects.spec.ts b/src/app/state-management/effects/datasets.effects.spec.ts
index ca33226c0..fea8c849b 100644
--- a/src/app/state-management/effects/datasets.effects.spec.ts
+++ b/src/app/state-management/effects/datasets.effects.spec.ts
@@ -17,6 +17,8 @@ import { FacetCounts } from "state-management/state/datasets.store";
import {
selectFullqueryParams,
selectFullfacetParams,
+ selectCurrentDataset,
+ selectRelatedDatasetsFilters,
} from "state-management/selectors/datasets.selectors";
import {
loadingAction,
@@ -63,6 +65,11 @@ describe("DatasetEffects", () => {
provideMockActions(() => actions),
provideMockStore({
selectors: [
+ { selector: selectCurrentDataset, value: dataset },
+ {
+ selector: selectRelatedDatasetsFilters,
+ value: { skip: 0, limit: 25, sortField: "creationTime:desc" },
+ },
{
selector: selectFullqueryParams,
value: {
@@ -80,6 +87,7 @@ describe("DatasetEffects", () => {
"fullquery",
"fullfacet",
"metadataKeys",
+ "find",
"findOne",
"updateAttributes",
"createAttachments",
@@ -306,6 +314,62 @@ describe("DatasetEffects", () => {
});
});
+ describe("fetchRelatedDatasets$", () => {
+ it("should result in a fetchRelatedDatasetsCompleteAction", () => {
+ const relatedDatasets = [dataset];
+ const action = fromActions.fetchRelatedDatasetsAction();
+ const outcome = fromActions.fetchRelatedDatasetsCompleteAction({
+ relatedDatasets,
+ });
+
+ actions = hot("-a", { a: action });
+ const response = cold("-a|", { a: relatedDatasets });
+ datasetApi.find.and.returnValue(response);
+
+ const expected = cold("--b", { b: outcome });
+ expect(effects.fetchRelatedDatasets$).toBeObservable(expected);
+ });
+ it("should result in a fetchRelatedDatasetsFailedAction", () => {
+ const action = fromActions.fetchRelatedDatasetsAction();
+ const outcome = fromActions.fetchRelatedDatasetsFailedAction();
+
+ actions = hot("-a", { a: action });
+ const response = cold("-#", {});
+ datasetApi.find.and.returnValue(response);
+
+ const expected = cold("--b", { b: outcome });
+ expect(effects.fetchRelatedDatasets$).toBeObservable(expected);
+ });
+ });
+
+ describe("fetchRelatedDatasetsCount$", () => {
+ it("should result in a fetchRelatedDatasetsCountCompleteAction", () => {
+ const relatedDatasets = [dataset];
+ const action = fromActions.fetchRelatedDatasetsAction();
+ const outcome = fromActions.fetchRelatedDatasetsCountCompleteAction({
+ count: relatedDatasets.length,
+ });
+
+ actions = hot("-a", { a: action });
+ const response = cold("-a|", { a: relatedDatasets });
+ datasetApi.find.and.returnValue(response);
+
+ const expected = cold("--b", { b: outcome });
+ expect(effects.fetchRelatedDatasetsCount$).toBeObservable(expected);
+ });
+ it("should result in a fetchRelatedDatasetsCountFailedAction", () => {
+ const action = fromActions.fetchRelatedDatasetsAction();
+ const outcome = fromActions.fetchRelatedDatasetsCountFailedAction();
+
+ actions = hot("-a", { a: action });
+ const response = cold("-#", {});
+ datasetApi.find.and.returnValue(response);
+
+ const expected = cold("--b", { b: outcome });
+ expect(effects.fetchRelatedDatasetsCount$).toBeObservable(expected);
+ });
+ });
+
describe("addDataset$", () => {
it("should result in an addDatasetCompleteAction, a fetchDatasetsAction and a fetchDatasetAction", () => {
const action = fromActions.addDatasetAction({ dataset: derivedDataset });
diff --git a/src/app/state-management/effects/datasets.effects.ts b/src/app/state-management/effects/datasets.effects.ts
index e1833a13e..d0562f578 100644
--- a/src/app/state-management/effects/datasets.effects.ts
+++ b/src/app/state-management/effects/datasets.effects.ts
@@ -1,11 +1,21 @@
import { Injectable } from "@angular/core";
import { Actions, concatLatestFrom, createEffect, ofType } from "@ngrx/effects";
-import { DatasetApi, Dataset, LoopBackFilter, OrigDatablock, Attachment, Datablock } from "shared/sdk";
+import {
+ DatasetApi,
+ Dataset,
+ LoopBackFilter,
+ OrigDatablock,
+ Attachment,
+ Datablock,
+ DerivedDataset,
+} from "shared/sdk";
import { Store } from "@ngrx/store";
import {
selectFullqueryParams,
selectFullfacetParams,
selectDatasetsInBatch,
+ selectCurrentDataset,
+ selectRelatedDatasetsFilters,
} from "state-management/selectors/datasets.selectors";
import * as fromActions from "state-management/actions/datasets.actions";
import {
@@ -29,6 +39,8 @@ import {
@Injectable()
export class DatasetEffects {
+ currentDataset$ = this.store.select(selectCurrentDataset);
+ relatedDatasetsFilters$ = this.store.select(selectRelatedDatasetsFilters);
fullqueryParams$ = this.store.select(selectFullqueryParams);
fullfacetParams$ = this.store.select(selectFullfacetParams);
datasetsInBatch$ = this.store.select(selectDatasetsInBatch);
@@ -123,7 +135,7 @@ export class DatasetEffects {
ofType(fromActions.fetchDatasetAction),
switchMap(({ pid, filters }) => {
const datasetFilter: LoopBackFilter = {
- where: { pid }
+ where: { pid },
};
if (filters) {
@@ -134,7 +146,7 @@ export class DatasetEffects {
return this.datasetApi.findOne(datasetFilter).pipe(
map((dataset: Dataset) =>
- fromActions.fetchDatasetCompleteAction({ dataset })
+ fromActions.fetchDatasetCompleteAction({ dataset })
),
catchError(() => of(fromActions.fetchDatasetFailedAction()))
);
@@ -145,12 +157,14 @@ export class DatasetEffects {
return this.actions$.pipe(
ofType(fromActions.fetchDatablocksAction),
switchMap(({ pid, filters }) => {
- return this.datasetApi.getDatablocks(encodeURIComponent(pid), filters).pipe(
- map((datablocks: Datablock[]) =>
- fromActions.fetchDatablocksCompleteAction({ datablocks })
- ),
- catchError(() => of(fromActions.fetchDatablocksFailedAction()))
- );
+ return this.datasetApi
+ .getDatablocks(encodeURIComponent(pid), filters)
+ .pipe(
+ map((datablocks: Datablock[]) =>
+ fromActions.fetchDatablocksCompleteAction({ datablocks })
+ ),
+ catchError(() => of(fromActions.fetchDatablocksFailedAction()))
+ );
})
);
});
@@ -159,12 +173,14 @@ export class DatasetEffects {
return this.actions$.pipe(
ofType(fromActions.fetchOrigDatablocksAction),
switchMap(({ pid, filters }) => {
- return this.datasetApi.getOrigdatablocks(encodeURIComponent(pid), {}).pipe(
- map((origdatablocks: OrigDatablock[]) =>
- fromActions.fetchOrigDatablocksCompleteAction({ origdatablocks })
- ),
- catchError(() => of(fromActions.fetchOrigDatablocksFailedAction()))
- );
+ return this.datasetApi
+ .getOrigdatablocks(encodeURIComponent(pid), {})
+ .pipe(
+ map((origdatablocks: OrigDatablock[]) =>
+ fromActions.fetchOrigDatablocksCompleteAction({ origdatablocks })
+ ),
+ catchError(() => of(fromActions.fetchOrigDatablocksFailedAction()))
+ );
})
);
});
@@ -173,11 +189,81 @@ export class DatasetEffects {
return this.actions$.pipe(
ofType(fromActions.fetchAttachmentsAction),
switchMap(({ pid, filters }) => {
- return this.datasetApi.getAttachments(encodeURIComponent(pid), filters).pipe(
- map((attachments: Attachment[]) =>
- fromActions.fetchAttachmentsCompleteAction({ attachments })
+ return this.datasetApi
+ .getAttachments(encodeURIComponent(pid), filters)
+ .pipe(
+ map((attachments: Attachment[]) =>
+ fromActions.fetchAttachmentsCompleteAction({ attachments })
+ ),
+ catchError(() => of(fromActions.fetchAttachmentsFailedAction()))
+ );
+ })
+ );
+ });
+
+ fetchRelatedDatasets$ = createEffect(() => {
+ return this.actions$.pipe(
+ ofType(fromActions.fetchRelatedDatasetsAction),
+ concatLatestFrom(() => [
+ this.currentDataset$,
+ this.relatedDatasetsFilters$,
+ ]),
+ switchMap(([_, dataset, filters]) => {
+ const queryFilter: LoopBackFilter = {
+ where: {},
+ skip: filters.skip,
+ limit: filters.limit,
+ order: filters.sortField,
+ };
+ if (dataset.type === "raw") {
+ queryFilter.where = {
+ type: "derived",
+ inputDatasets: dataset.pid,
+ };
+ }
+ if (dataset.type === "derived") {
+ queryFilter.where = {
+ pid: { $in: (dataset as unknown as DerivedDataset).inputDatasets },
+ };
+ }
+ return this.datasetApi.find(queryFilter).pipe(
+ map((relatedDatasets: Dataset[]) =>
+ fromActions.fetchRelatedDatasetsCompleteAction({ relatedDatasets })
),
- catchError(() => of(fromActions.fetchAttachmentsFailedAction()))
+ catchError(() => of(fromActions.fetchRelatedDatasetsFailedAction()))
+ );
+ })
+ );
+ });
+
+ fetchRelatedDatasetsCount$ = createEffect(() => {
+ return this.actions$.pipe(
+ ofType(fromActions.fetchRelatedDatasetsAction),
+ concatLatestFrom(() => [this.currentDataset$]),
+ switchMap(([_, dataset]) => {
+ const queryFilter: LoopBackFilter = {
+ where: {},
+ };
+ if (dataset.type === "raw") {
+ queryFilter.where = {
+ type: "derived",
+ inputDatasets: dataset.pid,
+ };
+ }
+ if (dataset.type === "derived") {
+ queryFilter.where = {
+ pid: { $in: (dataset as unknown as DerivedDataset).inputDatasets },
+ };
+ }
+ return this.datasetApi.find(queryFilter).pipe(
+ map((datasets) =>
+ fromActions.fetchRelatedDatasetsCountCompleteAction({
+ count: datasets.length,
+ })
+ ),
+ catchError(() =>
+ of(fromActions.fetchRelatedDatasetsCountFailedAction())
+ )
);
})
);
diff --git a/src/app/state-management/reducers/datasets.reducer.spec.ts b/src/app/state-management/reducers/datasets.reducer.spec.ts
index 7a94f0a2b..3269a78af 100644
--- a/src/app/state-management/reducers/datasets.reducer.spec.ts
+++ b/src/app/state-management/reducers/datasets.reducer.spec.ts
@@ -1,6 +1,12 @@
import * as fromDatasets from "./datasets.reducer";
import * as fromActions from "../actions/datasets.actions";
-import { Dataset, DatasetInterface, Attachment, DerivedDatasetInterface, DerivedDataset } from "shared/sdk/models";
+import {
+ Dataset,
+ DatasetInterface,
+ Attachment,
+ DerivedDatasetInterface,
+ DerivedDataset,
+} from "shared/sdk/models";
import {
FacetCounts,
initialDatasetState,
@@ -78,6 +84,46 @@ describe("DatasetsReducer", () => {
});
});
+ describe("on fetchRelatedDatasetsCompleteAction", () => {
+ it("should set relatedDatasets property", () => {
+ const relatedDatasets = [dataset];
+ const action = fromActions.fetchRelatedDatasetsCompleteAction({
+ relatedDatasets,
+ });
+ const state = fromDatasets.datasetsReducer(initialDatasetState, action);
+
+ expect(state.relatedDatasets).toEqual(relatedDatasets);
+ });
+ });
+
+ describe("on fetchRelatedDatasetsCountCompleteAction", () => {
+ it("should set relatedDatasetsCount property", () => {
+ const count = 0;
+ const action = fromActions.fetchRelatedDatasetsCountCompleteAction({
+ count,
+ });
+ const state = fromDatasets.datasetsReducer(initialDatasetState, action);
+
+ expect(state.relatedDatasetsCount).toEqual(count);
+ });
+ });
+
+ describe("on changeRelatedDatasetsPageAction", () => {
+ it("should set relatedDatasetsFilters skip and limit property", () => {
+ const page = 0;
+ const limit = 25;
+ const skip = page * limit;
+ const action = fromActions.changeRelatedDatasetsPageAction({
+ page,
+ limit,
+ });
+ const state = fromDatasets.datasetsReducer(initialDatasetState, action);
+
+ expect(state.relatedDatasetsFilters.skip).toEqual(skip);
+ expect(state.relatedDatasetsFilters.limit).toEqual(limit);
+ });
+ });
+
describe("on prefillBatchCompleteAction", () => {
it("should set batch property", () => {
const batch: Dataset[] = [];
@@ -132,10 +178,12 @@ describe("DatasetsReducer", () => {
describe("on addDatasetCompleteAction", () => {
it("should set currentSet", () => {
- const action = fromActions.addDatasetCompleteAction({ dataset: derivedDataset });
+ const action = fromActions.addDatasetCompleteAction({
+ dataset: derivedDataset,
+ });
const state = fromDatasets.datasetsReducer(initialDatasetState, action);
- expect(state.currentSet).toEqual((derivedDataset as unknown) as Dataset);
+ expect(state.currentSet).toEqual(derivedDataset as unknown as Dataset);
});
});
diff --git a/src/app/state-management/reducers/datasets.reducer.ts b/src/app/state-management/reducers/datasets.reducer.ts
index 5f1672a1d..5e2278cfb 100644
--- a/src/app/state-management/reducers/datasets.reducer.ts
+++ b/src/app/state-management/reducers/datasets.reducer.ts
@@ -38,33 +38,67 @@ const reducer = createReducer(
})
),
- on(fromActions.fetchDatablocksCompleteAction, (state, { datablocks }) =>{
+ on(fromActions.fetchDatablocksCompleteAction, (state, { datablocks }) => {
return {
- ...state,
- currentSet: {
- ...state.currentSet,
- datablocks
- }};
+ ...state,
+ currentSet: {
+ ...state.currentSet,
+ datablocks,
+ },
+ };
}),
- on(fromActions.fetchOrigDatablocksCompleteAction, (state, { origdatablocks }) =>{
- return {
- ...state,
- currentSet: {
- ...state.currentSet,
- origdatablocks
- }};
- }),
+ on(
+ fromActions.fetchOrigDatablocksCompleteAction,
+ (state, { origdatablocks }) => {
+ return {
+ ...state,
+ currentSet: {
+ ...state.currentSet,
+ origdatablocks,
+ },
+ };
+ }
+ ),
- on(fromActions.fetchAttachmentsCompleteAction, (state, { attachments }) =>{
+ on(fromActions.fetchAttachmentsCompleteAction, (state, { attachments }) => {
return {
- ...state,
- currentSet: {
- ...state.currentSet,
- attachments
- }};
+ ...state,
+ currentSet: {
+ ...state.currentSet,
+ attachments,
+ },
+ };
}),
+ on(
+ fromActions.fetchRelatedDatasetsCompleteAction,
+ (state, { relatedDatasets }): DatasetState => ({
+ ...state,
+ relatedDatasets,
+ })
+ ),
+ on(
+ fromActions.fetchRelatedDatasetsCountCompleteAction,
+ (state, { count }): DatasetState => ({
+ ...state,
+ relatedDatasetsCount: count,
+ })
+ ),
+
+ on(
+ fromActions.changeRelatedDatasetsPageAction,
+ (state, { page, limit }): DatasetState => {
+ const skip = page * limit;
+ const relatedDatasetsFilters = {
+ ...state.relatedDatasetsFilters,
+ skip,
+ limit,
+ };
+ return { ...state, relatedDatasetsFilters };
+ }
+ ),
+
on(fromActions.prefillBatchCompleteAction, (state, { batch }) => ({
...state,
batch,
@@ -147,9 +181,10 @@ const reducer = createReducer(
})
),
- on(fromActions.clearCurrentDatasetStateAction, (state) => ({
+ on(fromActions.clearCurrentDatasetStateAction, (state): DatasetState => ({
...state,
- currentSet: undefined
+ currentSet: undefined,
+ relatedDatasets: [],
})),
on(fromActions.selectDatasetAction, (state, { dataset }) => {
diff --git a/src/app/state-management/selectors/datasets.selectors.spec.ts b/src/app/state-management/selectors/datasets.selectors.spec.ts
index e7b3c3842..31c4f1a86 100644
--- a/src/app/state-management/selectors/datasets.selectors.spec.ts
+++ b/src/app/state-management/selectors/datasets.selectors.spec.ts
@@ -8,6 +8,8 @@ const initialDatasetState: DatasetState = {
datasets: [],
selectedSets: [],
currentSet: dataset,
+ relatedDatasets: [],
+ relatedDatasetsCount: 0,
totalCount: 0,
facetCounts: {},
@@ -37,6 +39,11 @@ const initialDatasetState: DatasetState = {
scientific: [],
isPublished: false,
},
+ relatedDatasetsFilters: {
+ skip: 0,
+ limit: 25,
+ sortField: "creationTime:desc",
+ },
};
describe("test dataset selectors", () => {
@@ -377,4 +384,22 @@ describe("test dataset selectors", () => {
).toEqual({});
});
});
+
+ describe("selectRelatedDatasets", () => {
+ it("should return the current related datasets", () => {
+ expect(
+ fromDatasetSelectors.selectRelatedDatasetsPageViewModel.projector(
+ initialDatasetState
+ )
+ ).toEqual({
+ relatedDatasets: [],
+ relatedDatasetsCount: 0,
+ relatedDatasetsFilters: {
+ skip: 0,
+ limit: 25,
+ sortField: "creationTime:desc",
+ },
+ });
+ });
+ });
});
diff --git a/src/app/state-management/selectors/datasets.selectors.ts b/src/app/state-management/selectors/datasets.selectors.ts
index 19aed03a3..d4af5050d 100644
--- a/src/app/state-management/selectors/datasets.selectors.ts
+++ b/src/app/state-management/selectors/datasets.selectors.ts
@@ -237,3 +237,17 @@ export const selectOpenwhiskResult = createSelector(
selectDatasetState,
(state) => state.openwhiskResult
);
+
+export const selectRelatedDatasetsPageViewModel = createSelector(
+ selectDatasetState,
+ ({ relatedDatasets, relatedDatasetsCount, relatedDatasetsFilters }) => ({
+ relatedDatasets,
+ relatedDatasetsCount,
+ relatedDatasetsFilters,
+ })
+);
+
+export const selectRelatedDatasetsFilters = createSelector(
+ selectDatasetState,
+ (state) => state.relatedDatasetsFilters
+);
diff --git a/src/app/state-management/state/datasets.store.ts b/src/app/state-management/state/datasets.store.ts
index 980bb23fd..7f299aa5e 100644
--- a/src/app/state-management/state/datasets.store.ts
+++ b/src/app/state-management/state/datasets.store.ts
@@ -19,6 +19,8 @@ export interface DatasetState {
datasets: Dataset[];
selectedSets: Dataset[];
currentSet: Dataset | undefined;
+ relatedDatasets: Dataset[];
+ relatedDatasetsCount: number;
totalCount: number;
facetCounts: FacetCounts;
@@ -28,6 +30,12 @@ export interface DatasetState {
keywordsTerms: string;
filters: DatasetFilters;
+ relatedDatasetsFilters: {
+ skip: number;
+ limit: number;
+ sortField: string;
+ };
+
batch: Dataset[];
openwhiskResult: Record | undefined;
@@ -37,6 +45,8 @@ export const initialDatasetState: DatasetState = {
datasets: [],
selectedSets: [],
currentSet: undefined,
+ relatedDatasets: [],
+ relatedDatasetsCount: 0,
totalCount: 0,
facetCounts: {},
@@ -57,10 +67,15 @@ export const initialDatasetState: DatasetState = {
sortField: "creationTime:desc",
keywords: [],
scientific: [],
- isPublished: ""
+ isPublished: "",
+ },
+ relatedDatasetsFilters: {
+ skip: 0,
+ limit: 25,
+ sortField: "creationTime:desc",
},
batch: [],
- openwhiskResult: undefined
+ openwhiskResult: undefined,
};