Skip to content

Commit

Permalink
feat: related proposals and table improvements (#1703)
Browse files Browse the repository at this point in the history
## Description
This PR adds the related proposals tab and table into the proposals
view.

## Motivation
The idea is to collect all the proposals that are related to a proposal
either they are a parent or a child of the current one.


## Fixes:
Please provide a list of the fixes implemented in this PR

* https://jira.ess.eu/browse/SWAP-4357


## Changes:
Please provide a list of the changes implemented by this PR

* Added related proposals component and tests

## Tests included
- [x] Included for each change/fix?
- [x] Passing? (Merge will not be approved unless this is checked) 

## Documentation
- [ ] swagger documentation updated \[required\]
- [ ] official documentation updated \[nice-to-have\]

### official documentation info
If you have updated the official documentation, please provide PR # and
URL of the pages where the updates are included

## Backend version
- [ ] Does it require a specific version of the backend
- which version of the backend is required:

## Summary by Sourcery

Update the route and location paths for related datasets.

Bug Fixes:
- Fix routing and navigation issues with related datasets.

Enhancements:
- Improve the loading of related datasets.

## Summary by Sourcery

Add a "Related Proposals" tab to the proposal details view, displaying
related proposals and their relationship to the current proposal.

New Features:
- Display related proposals in a new tab within the proposal details
view. The tab shows the relationship between the proposals (parent or
child).

Tests:
- Add tests for the related proposals component.
  • Loading branch information
martin-trajanovski authored Feb 3, 2025
1 parent 14f4407 commit aed9710
Show file tree
Hide file tree
Showing 28 changed files with 802 additions and 232 deletions.
42 changes: 42 additions & 0 deletions cypress/e2e/proposals/proposals-general.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,5 +167,47 @@ describe("Proposals general", () => {
matchCase: true,
});
});

it("should be able to see and click related proposals on a proposal", () => {
const newProposal = {
...testData.proposal,
proposalId: Math.floor(100000 + Math.random() * 900000).toString(),
parentProposalId: proposal.proposalId,
};
const newProposal2 = {
...testData.proposal,
proposalId: Math.floor(100000 + Math.random() * 900000).toString(),
parentProposalId: newProposal.proposalId,
};
cy.createProposal(newProposal);
cy.createProposal(newProposal2);

cy.visit("/proposals");

cy.get("mat-table mat-header-row").should("exist");

cy.finishedLoading();

cy.get("mat-table mat-row").should("contain", newProposal.proposalId);
cy.get("mat-table mat-row").should("contain", newProposal2.proposalId);

cy.get("mat-row")
.contains(newProposal.proposalId)
.parent()
.contains(newProposal.title)
.click();

cy.url().should("include", `/proposals/${newProposal.proposalId}`);

cy.get('[data-cy="related-proposals"]').click();

cy.get('[data-cy="related-proposals-table"] mat-row')
.contains(newProposal2.title)
.parent()
.contains("child");
cy.get('[data-cy="related-proposals-table"] mat-row')
.contains(proposal.title)
.click();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const routes: Routes = [
component: DatafilesComponent,
},
{
path: "related-datasets",
path: "relatedDatasets",
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export class DatasetDetailsDashboardComponent
enabled: true,
},
{
location: "./related-datasets",
location: "./relatedDatasets",
label: TAB.relatedDatasets,
icon: "folder",
enabled: true,
Expand Down
2 changes: 0 additions & 2 deletions src/app/datasets/datasets.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ 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 { TranslateModule } from "@ngx-translate/core";
@NgModule({
imports: [
CommonModule,
Expand Down Expand Up @@ -153,7 +152,6 @@ import { TranslateModule } from "@ngx-translate/core";
CdkDrag,
CdkDragHandle,
FiltersModule,
TranslateModule,
],
declarations: [
BatchViewComponent,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<shared-table
[dataSource]="dataSource"
[columnsdef]="columns"
[pageSize]="5"
[pageSize]="10"
(rowClick)="onRowClick($event)"
>
</shared-table>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<div class="datasets-table" *ngIf="vm$ | async as vm">
<app-table
[data]="tableData"
[columns]="tableColumns"
[paginate]="tablePaginate"
[currentPage]="vm.currentPage"
[dataCount]="vm.datasetCount"
[dataPerPage]="vm.datasetsPerPage"
(pageChange)="onPageChange($event)"
(rowClick)="onRowClick($event)"
></app-table>
</div>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import {
ComponentFixture,
TestBed,
inject,
waitForAsync,
} from "@angular/core/testing";
import { NO_ERRORS_SCHEMA } from "@angular/core";
import {
MockStore,
MockActivatedRoute,
createMock,
mockDataset,
} from "shared/MockStubs";
import { Router, ActivatedRoute } from "@angular/router";
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 { DatasetClass } from "@scicatproject/scicat-sdk-ts-angular";
import { ProposalDatasetsComponent } from "./proposal-datasets.component";

const getConfig = () => ({
logbookEnabled: true,
});

describe("ViewProposalPageComponent", () => {
let component: ProposalDatasetsComponent;
let fixture: ComponentFixture<ProposalDatasetsComponent>;

const router = {
navigateByUrl: jasmine.createSpy("navigateByUrl"),
};
let store: MockStore;
let dispatchSpy;

beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
schemas: [NO_ERRORS_SCHEMA],
declarations: [ProposalDatasetsComponent],
imports: [
BrowserAnimationsModule,
MatIconModule,
MatTabsModule,
StoreModule.forRoot({}),
],
providers: [DatePipe, FileSizePipe, SlicePipe],
});
TestBed.overrideComponent(ProposalDatasetsComponent, {
set: {
providers: [
{ provide: Router, useValue: router },
{ provide: ActivatedRoute, useClass: MockActivatedRoute },
{ provide: AppConfigService, useValue: { getConfig } },
],
},
});
TestBed.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(ProposalDatasetsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

beforeEach(inject([Store], (mockStore: MockStore) => {
store = mockStore;
}));

afterEach(() => {
fixture.destroy();
});

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 proposalId = "testId";
component.proposalId = proposalId;
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 }),
);
});
});

describe("#onRowClick()", () => {
it("should navigate to a dataset", () => {
const dataset = createMock<DatasetClass>({});
const pid = encodeURIComponent(dataset.pid);
component.onRowClick(dataset);

expect(router.navigateByUrl).toHaveBeenCalledTimes(1);
expect(router.navigateByUrl).toHaveBeenCalledWith("/datasets/" + pid);
});
});
});
111 changes: 111 additions & 0 deletions src/app/proposals/proposal-datasets/proposal-datasets.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { DatePipe, SlicePipe } from "@angular/common";
import { Component, Input, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { Store } from "@ngrx/store";
import {
DatasetClass,
OutputDatasetObsoleteDto,
} from "@scicatproject/scicat-sdk-ts-angular";
import { Subscription } from "rxjs";
import {
PageChangeEvent,
TableColumn,
} from "shared/modules/table/table.component";
import { FileSizePipe } from "shared/pipes/filesize.pipe";
import {
changeDatasetsPageAction,
fetchProposalDatasetsAction,
} from "state-management/actions/proposals.actions";
import { selectViewProposalPageViewModel } from "state-management/selectors/proposals.selectors";

export interface TableData {
pid: string;
name: string;
sourceFolder: string;
size: string;
creationTime: string | null;
owner: string;
location: string;
}

@Component({
selector: "app-proposal-datasets",
templateUrl: "./proposal-datasets.component.html",
styleUrls: ["./proposal-datasets.component.scss"],
})
export class ProposalDatasetsComponent implements OnInit {
vm$ = this.store.select(selectViewProposalPageViewModel);

subscriptions: Subscription[] = [];
@Input() proposalId: string;

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(
private datePipe: DatePipe,
private filesizePipe: FileSizePipe,
private slicePipe: SlicePipe,
private router: Router,
private store: Store,
) {}

ngOnInit(): void {
this.subscriptions.push(
this.vm$.subscribe((vm) => {
this.tableData = this.formatTableData(vm.datasets);
}),
);

if (!this.tableData.length) {
this.store.dispatch(
fetchProposalDatasetsAction({ proposalId: this.proposalId }),
);
}
}

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.proposalId }),
);
}

onRowClick(dataset: DatasetClass) {
const pid = encodeURIComponent(dataset.pid);
this.router.navigateByUrl("/datasets/" + pid);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<ng-container *ngIf="logbook">
<ng-container *ngIf="logbook$ | async as logbook">
<div
*ngIf="(logbook.logbook | keyvalue)?.length !== 0 && logbook.logbook?.name"
>
Expand Down
16 changes: 5 additions & 11 deletions src/app/proposals/proposal-logbook/proposal-logbook.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import {
AfterViewChecked,
} from "@angular/core";
import { Store } from "@ngrx/store";
import { Observable, Subscription, take } from "rxjs";
import { selectCurrentLogbook } from "state-management/selectors/logbooks.selectors";
import { Subscription } from "rxjs";
import { selectLogbooksDashboardPageViewModel } from "state-management/selectors/logbooks.selectors";
import {
fetchLogbookAction,
setTextFilterAction,
Expand Down Expand Up @@ -41,11 +41,11 @@ export interface LogbookData {
export class ProposalLogbookComponent
implements OnInit, OnDestroy, AfterViewChecked
{
logbook$: Observable<any | null> = this.store.select(selectCurrentLogbook);
logbook$ = this.store.select(selectLogbooksDashboardPageViewModel);
appConfig = this.appConfigService.getConfig();
subscriptions: Subscription[] = [];

@Input() logbook: LogbookData | null = null; // Still accepting input from parent if provided
@Input() proposalId: string;

constructor(
public appConfigService: AppConfigService,
Expand Down Expand Up @@ -87,13 +87,7 @@ export class ProposalLogbookComponent
}

ngOnInit() {
if (!this.logbook) {
this.logbook$.pipe(take(1)).subscribe((logbook) => {
if (logbook && logbook.name) {
this.store.dispatch(fetchLogbookAction({ name: logbook.name }));
}
});
}
this.store.dispatch(fetchLogbookAction({ name: this.proposalId }));
}

ngAfterViewChecked() {
Expand Down
Loading

0 comments on commit aed9710

Please sign in to comment.