Skip to content

Commit

Permalink
Merge pull request #1662 from SciCatProject/SWAP-4300-proposal-metadata
Browse files Browse the repository at this point in the history
feat: proposal metadata and e2e tests
  • Loading branch information
martin-trajanovski authored Nov 20, 2024
2 parents 2f46db8 + 45a6307 commit b5b364e
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 32 deletions.
60 changes: 60 additions & 0 deletions cypress/e2e/proposals/proposals-general.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,65 @@ describe("Proposals general", () => {

cy.get('[data-cy="proposal-type"]').contains(newProposalType);
});

it("proposal should have metadata and if not it should be able to add", () => {
const metadataName = "Proposal Metadata Name";
const metadataValue = "proposal metadata value";
const newProposal = {
...testData.proposal,
proposalId: Math.floor(100000 + Math.random() * 900000).toString(),
};
cy.createProposal(newProposal);

cy.visit(`/proposals/${newProposal.proposalId}`);

cy.finishedLoading();

cy.contains(newProposal.title);

cy.finishedLoading();

cy.get('[data-cy="proposal-metadata-card"]').should("exist");

cy.get('[data-cy="proposal-metadata-card"] [role="tab"]')
.contains("Edit")
.click();

cy.get('[data-cy="add-new-row"]').click();

// simulate click event on the drop down
cy.get("mat-select[data-cy=field-type-input]").last().click(); // opens the drop down

// simulate click event on the drop down item (mat-option)
cy.get("mat-option")
.contains("string")
.then((option) => {
option[0].click();
});

cy.get("[data-cy=metadata-name-input]")
.last()
.type(`${metadataName}{enter}`);
cy.get("[data-cy=metadata-value-input]")
.last()
.type(`${metadataValue}{enter}`);

cy.get("button[data-cy=save-changes-button]").click();

cy.finishedLoading();

cy.reload();

cy.finishedLoading();

cy.contains(newProposal.title);

cy.get('[data-cy="proposal-metadata-card"]').contains(metadataName, {
matchCase: true,
});
cy.get('[data-cy="proposal-metadata-card"]').contains(metadataValue, {
matchCase: true,
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,6 @@ describe("DatasetTableActionsComponent", () => {
describe("#onModeChange()", () => {
it("should dispatch a SetViewModeAction", () => {
dispatchSpy = spyOn(store, "dispatch");

const event = "test";
const modeToggle = ArchViewMode.all;

component.onModeChange(modeToggle);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,10 @@ import {
deselectDatasetAction,
selectAllDatasetsAction,
clearSelectionAction,
changePageAction,
sortByColumnAction,
} from "state-management/actions/datasets.actions";
import { PageChangeEvent } from "shared/modules/table/table.component";
import { provideMockStore } from "@ngrx/store/testing";
import { selectDatasets } from "state-management/selectors/datasets.selectors";
import {
selectColumnAction,
deselectColumnAction,
} from "state-management/actions/user.actions";
import { MatTableModule } from "@angular/material/table";
import {
MatCheckboxChange,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const labelMaps = {
PidFilterStartsWith: "PID filter (Starts With)- Not implemented",
};
export class MockStoreWithFilters extends MockStore {
public select(selector: any) {
public select(selector) {
if (selector === selectFilters) {
return of(filterConfigs);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@ import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { removeScientificConditionAction } from "state-management/actions/datasets.actions";
import { of } from "rxjs";
import {
deselectColumnAction,
deselectAllCustomColumnsAction,
} from "state-management/actions/user.actions";
import { deselectColumnAction } from "state-management/actions/user.actions";
import { ScientificCondition } from "state-management/models";
import { SharedScicatFrontendModule } from "shared/shared.module";
import { MatAutocompleteModule } from "@angular/material/autocomplete";
Expand Down
72 changes: 64 additions & 8 deletions src/app/proposals/proposal-detail/proposal-detail.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,71 @@
</mat-card-content>
</mat-card>

<mat-card *ngIf="appConfig.jsonMetadataEnabled">
<mat-card-content>
<button mat-stroked-button (click)="show = !show">
{{ show ? "Hide MetaData" : "Show Metadata" }}
</button>
<br />
<div *ngIf="show">
<ngx-json-viewer [json]="proposal" [expanded]="false"></ngx-json-viewer>
<mat-card
*ngIf="appConfig.jsonMetadataEnabled"
data-cy="proposal-metadata-card"
>
<mat-card-header class="scientific-header">
<div mat-card-avatar class="section-icon">
<mat-icon> science </mat-icon>
</div>
Metadata
</mat-card-header>
<mat-card-content>
<ng-template [ngIf]="appConfig.tableSciDataEnabled" [ngIfElse]="jsonView">
<ng-template #metadataView>
<div [ngSwitch]="appConfig.metadataStructure">
<tree-view
*ngSwitchCase="'tree'"
[metadata]="proposal['metadata']"
></tree-view>
<metadata-view *ngSwitchDefault [metadata]="proposal['metadata']">
</metadata-view>
</div>
</ng-template>
<mat-tab-group
class="metadataGroup"
*ngIf="editingAllowed; else metadataView"
>
<mat-tab>
<ng-template mat-tab-label>
<mat-icon> list </mat-icon> View
</ng-template>
<ng-container *ngTemplateOutlet="metadataView"> </ng-container>
</mat-tab>
<mat-tab
class="editTab"
*ngIf="editingAllowed"
[hidden]="!appConfig.editMetadataEnabled"
>
<ng-template mat-tab-label>
<mat-icon> edit </mat-icon> Edit
</ng-template>
<div [ngSwitch]="appConfig.metadataStructure">
<tree-edit
*ngSwitchCase="'tree'"
[metadata]="proposal['metadata']"
(save)="onSaveMetadata($event)"
(hasUnsavedChanges)="onHasUnsavedChanges($event)"
>
</tree-edit>
<metadata-edit
*ngSwitchDefault
[metadata]="proposal['metadata']"
(save)="onSaveMetadata($event)"
>
</metadata-edit>
</div>
</mat-tab>
</mat-tab-group>
</ng-template>

<ng-template #jsonView>
<ngx-json-viewer
[json]="proposal['metadata']"
[expanded]="false"
></ngx-json-viewer>
</ng-template>
</mat-card-content>
</mat-card>
</div>
86 changes: 83 additions & 3 deletions src/app/proposals/proposal-detail/proposal-detail.component.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,44 @@
import { Component, Input } from "@angular/core";
import { Component, Input, OnDestroy, OnInit } from "@angular/core";
import { Proposal } from "state-management/models";
import { AppConfigService } from "app-config.service";
import { Store } from "@ngrx/store";
import { selectParentProposal } from "state-management/selectors/proposals.selectors";
import {
selectCurrentProposal,
selectParentProposal,
} from "state-management/selectors/proposals.selectors";
import { Router } from "@angular/router";
import { updateProposalPropertyAction } from "state-management/actions/proposals.actions";
import {
Observable,
Subscription,
combineLatest,
fromEvent,
map,
switchMap,
} from "rxjs";
import {
selectIsAdmin,
selectProfile,
} from "state-management/selectors/user.selectors";
import { clearProposalsStateAction } from "state-management/actions/proposals.actions";

@Component({
selector: "proposal-detail",
templateUrl: "proposal-detail.component.html",
styleUrls: ["proposal-detail.component.scss"],
})
export class ProposalDetailComponent {
export class ProposalDetailComponent implements OnInit, OnDestroy {
private subscriptions: Subscription[] = [];
private _hasUnsavedChanges = false;
@Input() proposal: Proposal;
parentProposal: Proposal | undefined;
parentProposal$ = this.store.select(selectParentProposal);
editingAllowed = false;
userProfile$ = this.store.select(selectProfile);
isAdmin$ = this.store.select(selectIsAdmin);
accessGroups$: Observable<string[]> = this.userProfile$.pipe(
map((profile) => (profile ? profile.accessGroups : [])),
);

appConfig = this.appConfigService.getConfig();

Expand All @@ -26,9 +50,65 @@ export class ProposalDetailComponent {
private router: Router,
) {}

ngOnInit(): void {
// 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(selectCurrentProposal)
.pipe(
switchMap((proposal) => {
this.proposal = proposal;
return combineLatest([this.accessGroups$, this.isAdmin$]).pipe(
map(([groups, isAdmin]) => ({
proposal,
groups,
isAdmin,
})),
);
}),
map(({ proposal, groups, isAdmin }) => {
this.editingAllowed =
groups.indexOf(proposal?.ownerGroup) !== -1 || isAdmin;
}),
)
.subscribe(),
);
}

hasUnsavedChanges() {
return this._hasUnsavedChanges;
}

onClickProposal(proposalId: string): void {
this.store.dispatch(clearProposalsStateAction());
const id = encodeURIComponent(proposalId);
this.router.navigateByUrl("/proposals/" + id);
}

onSaveMetadata(metadata: Record<string, any>) {
if (this.proposal) {
const { proposalId } = this.proposal;
const property = { metadata };

this.store.dispatch(
updateProposalPropertyAction({ proposalId, property }),
);
}
}

onHasUnsavedChanges($event: boolean) {
this._hasUnsavedChanges = $event;
}

ngOnDestroy() {
this.subscriptions.forEach((subscription) => subscription.unsubscribe());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,16 @@

<!-- Name Column -->
<ng-container matColumnDef="name" style="align-content: center">
<mat-header-cell *matHeaderCellDef> Name </mat-header-cell>
<mat-cell *matCellDef="let metadata">
{{ metadata.name | replaceUnderscore | titlecase }}
</mat-cell>
<mat-header-cell *matHeaderCellDef>Name</mat-header-cell>
<mat-cell *matCellDef="let metadata">{{
metadata.name | replaceUnderscore | titlecase
}}</mat-cell>
</ng-container>

<!-- Value Column -->
<ng-container matColumnDef="value" style="align-content: center">
<mat-header-cell *matHeaderCellDef> Value </mat-header-cell>
<mat-cell *matCellDef="let metadata">
{{ metadata.value }}
</mat-cell>
<mat-header-cell *matHeaderCellDef>Value</mat-header-cell>
<mat-cell *matCellDef="let metadata">{{ metadata.value }}</mat-cell>
</ng-container>

<!-- Unit Column -->
Expand Down
12 changes: 12 additions & 0 deletions src/app/state-management/actions/proposals.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,18 @@ export const updateAttachmentCaptionFailedAction = createAction(
"[Proposal] Update Attachment Caption Failed",
);

export const updateProposalPropertyAction = createAction(
"[Proposal] Update Proposal Property",
// TODO: Most probably with the new sdk the property should be of type UpdateProposalDto or something similar
props<{ proposalId: string; property: Record<string, unknown> }>(),
);
export const updateProposalPropertyCompleteAction = createAction(
"[Proposal] Update Proposal Property Complete",
);
export const updateProposalPropertyFailedAction = createAction(
"[Proposal] Update Proposal Property Failed",
);

export const removeAttachmentAction = createAction(
"[Proposal] Remove Attachment",
props<{ proposalId: string; attachmentId: string }>(),
Expand Down
Loading

0 comments on commit b5b364e

Please sign in to comment.