Skip to content

feat: replace instruments table with the new dynamic material table #1793

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 206 additions & 0 deletions cypress/e2e/instruments/instruments-general.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
const path = require("path");

import { testData } from "../../fixtures/testData";
import { testConfig } from "../../fixtures/testData";
import { getFormattedFileNamingDate, mergeConfig } from "../../support/utils";

describe("Instruments general", () => {
beforeEach(() => {
cy.login(Cypress.env("username"), Cypress.env("password"));
});

after(() => {
cy.removeInstruments();
});

describe("Instruments table and details", () => {
it("should be able to see instrument in a table and visit the instrument details page", () => {
const instrument = {
...testData.instrument,
name: "Cypress test instrument",
uniqueName: `Instrument-${Math.floor(1000 + Math.random() * 9000).toString()}`,
};
cy.createInstrument(instrument);

cy.visit("/instruments");

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

cy.finishedLoading();

cy.get("mat-table mat-row").should("contain", instrument.uniqueName);

cy.get("mat-cell")
.contains(instrument.uniqueName)
.closest("mat-row")
.contains(instrument.name)
.click();

cy.url().should("include", `/instruments`);

cy.contains(instrument.uniqueName);
});

it("instrument should have metadata and if not it should be able to add", () => {
const metadataName = "Instrument Metadata Name";
const metadataValue = "instrument metadata value";
const instrument = {
...testData.instrument,
name: "Cypress test instrument",
uniqueName: `Instrument-${Math.floor(1000 + Math.random() * 9000).toString()}`,
};
cy.createInstrument(instrument);

cy.visit("/instruments");

cy.finishedLoading();

cy.get("mat-cell")
.contains(instrument.uniqueName)
.closest("mat-row")
.contains(instrument.name)
.click();

cy.finishedLoading();

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

cy.get('[data-cy="instrument-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()
.focus()
.type(`${metadataName}{enter}`);
cy.get("[data-cy=metadata-value-input]")
.last()
.focus()
.type(`${metadataValue}{enter}`);

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

cy.finishedLoading();

cy.reload();

cy.finishedLoading();

cy.contains(instrument.name);

cy.get('[data-cy="instrument-metadata-card"]').contains(metadataName, {
matchCase: true,
});
cy.get('[data-cy="instrument-metadata-card"]').contains(metadataValue, {
matchCase: true,
});
});
});

describe("Instruments dynamic material table", () => {
it("should be able to sort for instrument in the column sort", () => {
const newInstrument = {
...testData.instrument,
name: "000 Cypress test instrument",
uniqueName: `Instrument-${Math.floor(1000 + Math.random() * 9000).toString()}`,
};

const newInstrument2 = {
...testData.instrument,
name: "001 Cypress test instrument",
uniqueName: `Instrument-${Math.floor(1000 + Math.random() * 9000).toString()}`,
};

cy.createInstrument(newInstrument2);
cy.createInstrument(newInstrument);

cy.visit("/instruments");

cy.get("mat-table mat-row")
.first()
.should("not.contain", newInstrument.name);

cy.get(".mat-sort-header-container").contains("Name").click();

cy.get("mat-table mat-row").first().should("contain", newInstrument.name);

cy.reload();

cy.get("mat-table mat-row").first().should("contain", newInstrument.name);
});

it("should be able to download table data as a json", () => {
const instrument = {
...testData.instrument,
uniqueName: `Instrument-${Math.floor(1000 + Math.random() * 9000).toString()}`,
};

cy.createInstrument(instrument);

cy.visit("/instruments");

cy.get("dynamic-mat-table table-menu button").click();

cy.get('[role="menu"] button').contains("Save data").click();

cy.get('[role="menu"] button').contains("Json file").click();

const downloadsFolder = Cypress.config("downloadsFolder");
const tableName = "instrumentsTable";

cy.readFile(
path.join(
downloadsFolder,
`${tableName}${getFormattedFileNamingDate()}.json`,
),
).then((actualExport) => {
const foundInstrument = actualExport.find(
(instrument) => instrument.uniqueName === instrument.uniqueName,
);

expect(foundInstrument).to.exist;
});
});

it("should be able to download table data as a csv", () => {
const instrument = {
...testData.instrument,
uniqueName: `Instrument-${Math.floor(1000 + Math.random() * 9000).toString()}`,
};

cy.createInstrument(instrument);

cy.visit("/instruments");

cy.get("dynamic-mat-table table-menu button").click();

cy.get('[role="menu"] button').contains("Save data").click();

cy.get('[role="menu"] button').contains("CSV file").click();

const downloadsFolder = Cypress.config("downloadsFolder");
const tableName = "instrumentsTable";

cy.readFile(
path.join(
downloadsFolder,
`${tableName}${getFormattedFileNamingDate()}.csv`,
),
).then((actualExport) => {
expect(actualExport).to.contain(instrument.uniqueName);
});
});
});
});
9 changes: 9 additions & 0 deletions cypress/fixtures/testData.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ export const testData = {
ownerGroup: "20170251-group",
accessGroups: [],
},
instrument: {
uniqueName: "ESS1-1",
name: "ESS1",
customMetadata: {
institute: "An immaginary intitution #1",
department: "An immaginary department #1",
main_user: "ESS",
},
},
rawDataset: {
principalInvestigator: "string",
endTime: "2019-10-31T14:44:46.143Z",
Expand Down
63 changes: 63 additions & 0 deletions cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,29 @@ Cypress.Commands.add("createProposal", (proposal) => {
});
});
});

Cypress.Commands.add("createInstrument", (instrument) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider flattening the nested .then calls in createInstrument and removeInstruments by returning promises to improve readability and maintainability of the code..

Actionable Comment:

The additional nested .then() calls can be flattened by returning each promise to chain them instead of nesting callbacks. For example, refactor createInstrument as follows:

Cypress.Commands.add("createInstrument", (instrument) => {
  return cy.getCookie("user")
    .then((userCookie) => {
      const user = JSON.parse(decodeURIComponent(userCookie.value));
      return user; // pass the user along
    })
    .then((user) =>
      cy.getToken().then((token) => ({ user, token }))
    )
    .then(({ user, token }) => {
      cy.log("Instrument: " + JSON.stringify(instrument, null, 2));
      cy.log("User: " + JSON.stringify(user, null, 2));
      return cy.request({
        method: "POST",
        url: lbBaseUrl + "/Instruments",
        headers: {
          Authorization: token,
          Accept: "application/json",
          "Content-Type": "application/json",
        },
        body: instrument,
      });
    });
});

Similarly, in removeInstruments, avoid nesting by returning promises:

Cypress.Commands.add("removeInstruments", () => {
  cy.login(Cypress.env("username"), Cypress.env("password"));
  return cy.getToken()
    .then((token) =>
      cy.request({
        method: "GET",
        url: lbBaseUrl + "/instruments",
        headers: {
          Authorization: token,
          Accept: "application/json",
          "Content-Type": "application/json",
        },
      })
    )
    .its("body")
    .then((instruments) => {
      cy.login(
        Cypress.env("secondaryUsername"),
        Cypress.env("secondaryPassword")
      );
      return cy.getToken()
        .then((token) => {
          // Use Cypress._.each if you want to maintain chainability
          return Cypress._.each(instruments, (instrument) => {
            cy.request({
              method: "DELETE",
              url: lbBaseUrl + "/instruments/" + encodeURIComponent(instrument.pid),
              headers: {
                Authorization: token,
                Accept: "application/json",
                "Content-Type": "application/json",
              },
            });
          });
        });
    });
});

Steps to Reduce Complexity:

  1. Return Promises: Ensure each .then() returns a promise so that you can chain further operations rather than nesting.
  2. Flatten Callbacks: Combine sequential asynchronous operations into a single chain.
  3. Optional: Consider using helper functions for repeated logic (like setting headers).

These changes keep the functionality intact while making the code easier to read and maintain.

return cy.getCookie("user").then((userCookie) => {
const user = JSON.parse(decodeURIComponent(userCookie.value));

cy.getToken().then((token) => {
cy.log("Instrument: " + JSON.stringify(instrument, null, 2));
cy.log("User: " + JSON.stringify(user, null, 2));

cy.request({
method: "POST",
url: lbBaseUrl + "/Instruments",
headers: {
Authorization: token,
Accept: "application/json",
"Content-Type": "application/json",
},
body: instrument,
});
});
});
});

Cypress.Commands.add("updateProposal", (proposalId, updateProposalDto) => {
return cy.getCookie("user").then((userCookie) => {
const user = JSON.parse(decodeURIComponent(userCookie.value));
Expand Down Expand Up @@ -314,6 +337,46 @@ Cypress.Commands.add("removeProposals", () => {
});
});

Cypress.Commands.add("removeInstruments", () => {
cy.login(Cypress.env("username"), Cypress.env("password"));
cy.getToken().then((token) => {
cy.request({
method: "GET",
url: lbBaseUrl + "/instruments",
headers: {
Authorization: token,
Accept: "application/json",
"Content-Type": "application/json",
},
})
.its("body")
.as("instruments");

cy.get("@instruments").then((instruments) => {
cy.login(
Cypress.env("secondaryUsername"),
Cypress.env("secondaryPassword"),
);
cy.getToken().then((token) => {
instruments.forEach((instrument) => {
cy.request({
method: "DELETE",
url:
lbBaseUrl +
"/instruments/" +
encodeURIComponent(instrument.pid),
headers: {
Authorization: token,
Accept: "application/json",
"Content-Type": "application/json",
},
});
});
});
});
});
});

Cypress.Commands.add("removeSamples", () => {
cy.login(Cypress.env("username"), Cypress.env("password"));
cy.getToken().then((token) => {
Expand Down
9 changes: 4 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"@ngrx/router-store": "^16",
"@ngrx/store": "^16",
"@ngx-translate/core": "^16.0.4",
"@scicatproject/scicat-sdk-ts-angular": "^4.13.0",
"@scicatproject/scicat-sdk-ts-angular": "^4.14.0",
"autolinker": "^4.0.0",
"deep-equal": "^2.0.5",
"exceljs": "^4.3.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@
</mat-card-content>
</mat-card>

<mat-card *ngIf="instrument.customMetadata as value">
<mat-card
data-cy="instrument-metadata-card"
*ngIf="instrument.customMetadata as value"
>
<mat-card-header class="customMetadata-header">
<div mat-card-avatar class="section-icon">
<mat-icon> science </mat-icon>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
<ng-container *ngIf="vm$ | async as vm">
<div fxLayout="row" fxLayout.lt-lg="column">
<div fxFlex="100">
<app-table
[data]="vm.instruments"
[columns]="tableColumns"
[paginate]="tablePaginate"
[currentPage]="vm.currentPage"
[dataCount]="vm.instrumentsCount"
[dataPerPage]="vm.instrumentsPerPage"
(pageChange)="onPageChange($event)"
(sortChange)="onSortChange($event)"
(rowClick)="onRowClick($event)"
></app-table>
</div>
</div>
</ng-container>
<dynamic-mat-table
[tableName]="tableName"
[columns]="columns"
[pending]="pending"
[setting]="setting"
[pagingMode]="paginationMode"
[dataSource]="dataSource"
[pagination]="pagination"
[rowSelectionMode]="rowSelectionMode"
[showGlobalTextSearch]="showGlobalTextSearch"
(paginationChange)="onPaginationChange($event)"
(onRowEvent)="onRowClick($event)"
(settingChange)="onSettingChange($event)"
(onTableEvent)="onTableEvent($event)"
style="height: 70vh"
class="mat-elevation-z2"
>
</dynamic-mat-table>
Loading
Loading