Skip to content

Commit bcb3789

Browse files
authored
Merge pull request #289 from ComPlat/231-display-lcms-data
Add LC/MS visualization support for OpenLab and Chemstation data
2 parents c2bc3c5 + 933df56 commit bcb3789

274 files changed

Lines changed: 114908 additions & 3906 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/e2e_testing.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ jobs:
1010
- name: Cypress run
1111
uses: cypress-io/github-action@v6
1212
with:
13-
build: npm run build
14-
start: npm start
13+
build: npm run --max_old_space_size=12288 build
14+
start: npm --max_old_space_size=12288 start
1515
browser: chrome

.github/workflows/testing.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ jobs:
1010
- uses: actions/setup-node@v4
1111
with:
1212
node-version: 22.17.1
13-
- run: yarn install
13+
- run: yarn install --frozen-lockfile
1414
- run: yarn test

cypress.config.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import { defineConfig } from "cypress";
22

33
export default defineConfig({
4+
pageLoadTimeout: 100000,
5+
requestTimeout: 100000,
6+
responseTimeout: 100000,
47
e2e: {
58
setupNodeEvents(on, config) {
69
// implement node event listeners here
710
},
8-
experimentalStudio: true
11+
experimentalStudio: true,
12+
pageLoadTimeout: 100000,
13+
requestTimeout: 100000,
14+
responseTimeout: 100000,
915
},
1016
});

cypress/e2e/lcms_spec.cy.ts

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/// <reference types="cypress" />
2+
3+
describe('LC/MS layouts', () => {
4+
const getAppState = () => cy.window().then((win) => (win as any).__spectraStore.getState())
5+
6+
const openLayout = (buttonText: string) => {
7+
cy.viewport(2000, 2000)
8+
cy.visit('http://localhost:3000/')
9+
cy.contains('button', buttonText).click()
10+
}
11+
12+
const assertNoNaNInPaths = (selector: string) => {
13+
cy.get(`${selector} path[d]`).each(($path) => {
14+
const d = $path.attr('d') || ''
15+
expect(d).to.not.contain('NaN')
16+
})
17+
}
18+
19+
const assertLcmsViewerIsStable = () => {
20+
cy.get('.d3Line .d3Svg').should('exist')
21+
cy.get('.d3Multi .d3SvgMulti').should('exist')
22+
cy.get('.d3Rect .d3SvgRect').should('exist')
23+
24+
assertNoNaNInPaths('.d3Line .d3Svg')
25+
assertNoNaNInPaths('.d3Multi .d3SvgMulti')
26+
assertNoNaNInPaths('.d3Rect .d3SvgRect')
27+
}
28+
29+
it('OpenLab renders LC/MS line, multi and rect graphs', () => {
30+
openLayout('LC/MS OpenLab')
31+
assertLcmsViewerIsStable()
32+
})
33+
34+
it('Chemstation renders LC/MS line, multi and rect graphs', () => {
35+
openLayout('LC/MS Chemstation')
36+
assertLcmsViewerIsStable()
37+
})
38+
39+
it('requests MS page on initial load and TIC click in standalone simulation', () => {
40+
openLayout('LC/MS OpenLab')
41+
assertLcmsViewerIsStable()
42+
43+
// Waits until React has run componentDidUpdate (initial_load on __lcmsDemoRequests).
44+
cy.window().its('__lcmsDemoRequests').should('have.length.above', 0)
45+
46+
cy.window().then((win) => {
47+
(win as any).__spectraStore.dispatch({
48+
type: 'DISPLAY_VIEWER_AT',
49+
payload: { x: 0.8, y: 0, graphIndex: 1 },
50+
})
51+
})
52+
53+
cy.wait(400)
54+
cy.window().its('__lcmsDemoRequests').should('have.length.above', 1)
55+
cy.window().then((win) => {
56+
const requests = (win as any).__lcmsDemoRequests || []
57+
const latestRequest = requests[requests.length - 1]
58+
expect(latestRequest.trigger).to.equal('user_click')
59+
expect(Number.isFinite(latestRequest.retentionTime)).to.equal(true)
60+
})
61+
62+
getAppState().then((stateAfter) => {
63+
expect(Number.isFinite(stateAfter.hplcMs.tic.currentPageValue)).to.equal(true)
64+
})
65+
})
66+
67+
it('updates reducer when adding peak and integration action', () => {
68+
openLayout('LC/MS OpenLab')
69+
assertLcmsViewerIsStable()
70+
71+
cy.window().then((win) => {
72+
getAppState().then((stateBefore) => {
73+
const peaksBefore = stateBefore.hplcMs.uvvis.currentSpectrum?.peaks?.length || 0
74+
const integrationsBefore = stateBefore.hplcMs.uvvis.currentSpectrum?.integrations?.length || 0
75+
76+
cy.get('.btn-sv-bar-addpeak').click()
77+
cy.get('.d3Line .d3Svg').trigger('click', 600, 350, { which: 1, view: win })
78+
79+
const selectedWaveLength = stateBefore.hplcMs.uvvis.selectedWaveLength
80+
const spectrumData = stateBefore.hplcMs.uvvis.currentSpectrum?.data
81+
const xValues = Array.isArray(spectrumData?.x) ? spectrumData.x : []
82+
const yValues = Array.isArray(spectrumData?.y) ? spectrumData.y : []
83+
const mid = Math.floor(xValues.length / 2)
84+
const xL = xValues[Math.max(0, mid - 20)]
85+
const xU = xValues[Math.min(xValues.length - 1, mid + 20)]
86+
const hasIntegrationPayload = Number.isFinite(xL)
87+
&& Number.isFinite(xU)
88+
&& xU !== xL
89+
&& xValues.length > 0
90+
&& yValues.length > 0
91+
92+
if (hasIntegrationPayload) {
93+
(win as any).__spectraStore.dispatch({
94+
type: 'UPDATE_HPLCMS_INTEGRATIONS',
95+
payload: {
96+
spectrumId: selectedWaveLength,
97+
integration: {
98+
xExtent: { xL, xU },
99+
data: spectrumData,
100+
},
101+
},
102+
})
103+
}
104+
105+
getAppState().then((stateAfter) => {
106+
const peaksAfter = stateAfter.hplcMs.uvvis.currentSpectrum?.peaks?.length || 0
107+
const integrationsAfter = stateAfter.hplcMs.uvvis.currentSpectrum?.integrations?.length || 0
108+
expect(peaksAfter).to.be.greaterThan(peaksBefore)
109+
expect(integrationsAfter).to.be.at.least(integrationsBefore)
110+
})
111+
})
112+
})
113+
})
114+
115+
it('updates reducer when switching wavelength', () => {
116+
openLayout('LC/MS OpenLab')
117+
assertLcmsViewerIsStable()
118+
119+
getAppState().then((stateBefore) => {
120+
const wavelengths = stateBefore.hplcMs.uvvis.listWaveLength || []
121+
expect(wavelengths.length).to.be.greaterThan(1)
122+
const initialWaveLength = stateBefore.hplcMs.uvvis.selectedWaveLength
123+
const nextWaveLength = wavelengths.find((value) => value !== initialWaveLength)
124+
expect(nextWaveLength).to.not.equal(undefined)
125+
126+
cy.contains('.MuiFormControl-root', 'Wavelength (nm)')
127+
.find('.MuiSelect-select')
128+
.click({ force: true })
129+
cy.get('li[role="option"]')
130+
.contains(new RegExp(`^\\s*${nextWaveLength}\\s*$`))
131+
.click({ force: true })
132+
133+
getAppState().then((stateAfter) => {
134+
expect(stateAfter.hplcMs.uvvis.selectedWaveLength).to.equal(nextWaveLength)
135+
expect(stateAfter.hplcMs.uvvis.currentSpectrum).to.not.equal(null)
136+
})
137+
})
138+
})
139+
140+
it('updates TIC polarity on Chemstation', () => {
141+
openLayout('LC/MS Chemstation')
142+
assertLcmsViewerIsStable()
143+
144+
getAppState().then((stateBefore) => {
145+
const available = stateBefore.hplcMs.tic?.available || {}
146+
const currentPolarity = stateBefore.hplcMs.tic?.polarity
147+
const polarityOrder = ['positive', 'negative', 'neutral']
148+
const nextPolarity = polarityOrder.find((value) => available[value] && value !== currentPolarity)
149+
if (!nextPolarity) return
150+
151+
const uiLabelByPolarity: Record<string, string> = {
152+
positive: 'PLUS',
153+
negative: 'MINUS',
154+
neutral: 'NEUTRAL',
155+
}
156+
const nextLabel = uiLabelByPolarity[nextPolarity]
157+
158+
cy.contains('.MuiFormControl-root', 'TIC')
159+
.find('.MuiSelect-select')
160+
.click({ force: true })
161+
cy.get('li[role="option"]').contains(new RegExp(`^\\s*${nextLabel}\\s*$`)).click({ force: true })
162+
163+
getAppState().then((stateAfter) => {
164+
expect(stateAfter.hplcMs.tic.polarity).to.equal(nextPolarity)
165+
})
166+
})
167+
})
168+
169+
it('keeps zoom usable after leaving LC/MS', () => {
170+
openLayout('LC/MS OpenLab')
171+
assertLcmsViewerIsStable()
172+
173+
cy.contains('button', 'NMR 1H').click()
174+
cy.get('.d3Line .d3Svg').should('exist')
175+
176+
cy.window().then((win) => {
177+
cy.get('.btn-sv-bar-zoomin').click()
178+
cy.get('.d3Line .brush').should('exist')
179+
cy.get('.d3Line .d3Svg')
180+
.trigger('mousedown', 400, 100, { which: 1, view: win, force: true })
181+
.trigger('mousemove', { clientX: 800, clientY: 1000, view: win, force: true })
182+
.trigger('mouseup', { view: win, force: true })
183+
184+
cy.get('.btn-sv-bar-zoomreset').click()
185+
})
186+
})
187+
})

cypress/e2e/nmr1h_spec.cy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ describe('NMR 1H', () => {
7070
cy.get('.btn-sv-bar-spctrum').click()
7171
cy.get('.input-sv-bar-layout').click()
7272
cy.get('.option-sv-bar-layout').should($li => {
73-
expect($li).to.have.length(24)
73+
expect($li).to.have.length(25)
7474
})
7575
cy.get('ul li:nth-child(9)').click()
7676

dist/actions/axes.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"use strict";
2+
3+
Object.defineProperty(exports, "__esModule", {
4+
value: true
5+
});
6+
exports.updateYAxis = exports.updateXAxis = void 0;
7+
var _action_type = require("../constants/action_type");
8+
const updateXAxis = payload => ({
9+
type: _action_type.AXES.UPDATE_X_AXIS,
10+
payload
11+
});
12+
exports.updateXAxis = updateXAxis;
13+
const updateYAxis = payload => ({
14+
type: _action_type.AXES.UPDATE_Y_AXIS,
15+
payload
16+
});
17+
18+
// eslint-disable-line
19+
exports.updateYAxis = updateYAxis;

dist/actions/curve.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"use strict";
2+
3+
Object.defineProperty(exports, "__esModule", {
4+
value: true
5+
});
6+
exports.toggleShowAllCurves = exports.setAllCurves = exports.selectCurve = void 0;
7+
var _action_type = require("../constants/action_type");
8+
const selectCurve = payload => ({
9+
type: _action_type.CURVE.SELECT_WORKING_CURVE,
10+
payload
11+
});
12+
exports.selectCurve = selectCurve;
13+
const setAllCurves = (payload, meta) => ({
14+
type: _action_type.CURVE.SET_ALL_CURVES,
15+
payload,
16+
...(meta && typeof meta === 'object' ? {
17+
meta
18+
} : {})
19+
});
20+
exports.setAllCurves = setAllCurves;
21+
const toggleShowAllCurves = payload => ({
22+
type: _action_type.CURVE.SET_SHOULD_SHOW_ALL_CURVES,
23+
payload
24+
});
25+
exports.toggleShowAllCurves = toggleShowAllCurves;

dist/actions/cyclic_voltammetry.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
"use strict";
2+
3+
Object.defineProperty(exports, "__esModule", {
4+
value: true
5+
});
6+
exports.toggleCyclicVoltaDensity = exports.setWorkWithMaxPeak = exports.setCylicVoltaRefFactor = exports.setCylicVoltaRef = exports.setCyclicVoltaAreaValue = exports.setCyclicVoltaAreaUnit = exports.selectRefPeaks = exports.selectPairPeak = exports.removeCylicVoltaPecker = exports.removeCylicVoltaPairPeak = exports.removeCylicVoltaMinPeak = exports.removeCylicVoltaMaxPeak = exports.addNewCylicVoltaPairPeak = exports.addCylicVoltaPecker = exports.addCylicVoltaMinPeak = exports.addCylicVoltaMaxPeak = void 0;
7+
var _action_type = require("../constants/action_type");
8+
const addNewCylicVoltaPairPeak = payload => ({
9+
type: _action_type.CYCLIC_VOLTA_METRY.ADD_PAIR_PEAKS,
10+
payload
11+
});
12+
exports.addNewCylicVoltaPairPeak = addNewCylicVoltaPairPeak;
13+
const removeCylicVoltaPairPeak = payload => ({
14+
type: _action_type.CYCLIC_VOLTA_METRY.REMOVE_PAIR_PEAKS,
15+
payload
16+
});
17+
exports.removeCylicVoltaPairPeak = removeCylicVoltaPairPeak;
18+
const addCylicVoltaMaxPeak = payload => ({
19+
type: _action_type.CYCLIC_VOLTA_METRY.ADD_MAX_PEAK,
20+
payload
21+
});
22+
exports.addCylicVoltaMaxPeak = addCylicVoltaMaxPeak;
23+
const removeCylicVoltaMaxPeak = payload => ({
24+
type: _action_type.CYCLIC_VOLTA_METRY.REMOVE_MAX_PEAK,
25+
payload
26+
});
27+
exports.removeCylicVoltaMaxPeak = removeCylicVoltaMaxPeak;
28+
const addCylicVoltaMinPeak = payload => ({
29+
type: _action_type.CYCLIC_VOLTA_METRY.ADD_MIN_PEAK,
30+
payload
31+
});
32+
exports.addCylicVoltaMinPeak = addCylicVoltaMinPeak;
33+
const removeCylicVoltaMinPeak = payload => ({
34+
type: _action_type.CYCLIC_VOLTA_METRY.REMOVE_MIN_PEAK,
35+
payload
36+
});
37+
exports.removeCylicVoltaMinPeak = removeCylicVoltaMinPeak;
38+
const setWorkWithMaxPeak = payload => ({
39+
type: _action_type.CYCLIC_VOLTA_METRY.WORK_WITH_MAX_PEAK,
40+
payload
41+
});
42+
exports.setWorkWithMaxPeak = setWorkWithMaxPeak;
43+
const selectPairPeak = payload => ({
44+
type: _action_type.CYCLIC_VOLTA_METRY.SELECT_PAIR_PEAK,
45+
payload
46+
});
47+
exports.selectPairPeak = selectPairPeak;
48+
const addCylicVoltaPecker = payload => ({
49+
type: _action_type.CYCLIC_VOLTA_METRY.ADD_PECKER,
50+
payload
51+
});
52+
exports.addCylicVoltaPecker = addCylicVoltaPecker;
53+
const removeCylicVoltaPecker = payload => ({
54+
type: _action_type.CYCLIC_VOLTA_METRY.REMOVE_PECKER,
55+
payload
56+
});
57+
exports.removeCylicVoltaPecker = removeCylicVoltaPecker;
58+
const selectRefPeaks = payload => ({
59+
type: _action_type.CYCLIC_VOLTA_METRY.SELECT_REF_PEAK,
60+
payload
61+
});
62+
exports.selectRefPeaks = selectRefPeaks;
63+
const setCylicVoltaRefFactor = payload => ({
64+
type: _action_type.CYCLIC_VOLTA_METRY.SET_FACTOR,
65+
payload
66+
});
67+
exports.setCylicVoltaRefFactor = setCylicVoltaRefFactor;
68+
const setCylicVoltaRef = payload => ({
69+
type: _action_type.CYCLIC_VOLTA_METRY.SET_REF,
70+
payload
71+
});
72+
exports.setCylicVoltaRef = setCylicVoltaRef;
73+
const setCyclicVoltaAreaValue = value => ({
74+
type: _action_type.CYCLIC_VOLTA_METRY.SET_AREA_VALUE,
75+
payload: {
76+
value
77+
}
78+
});
79+
exports.setCyclicVoltaAreaValue = setCyclicVoltaAreaValue;
80+
const setCyclicVoltaAreaUnit = unit => ({
81+
type: _action_type.CYCLIC_VOLTA_METRY.SET_AREA_UNIT,
82+
payload: {
83+
unit
84+
}
85+
});
86+
exports.setCyclicVoltaAreaUnit = setCyclicVoltaAreaUnit;
87+
const toggleCyclicVoltaDensity = payload => ({
88+
type: _action_type.CYCLIC_VOLTA_METRY.TOGGLE_DENSITY,
89+
payload
90+
});
91+
exports.toggleCyclicVoltaDensity = toggleCyclicVoltaDensity;

dist/actions/detector.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"use strict";
2+
3+
Object.defineProperty(exports, "__esModule", {
4+
value: true
5+
});
6+
exports.updateDetector = void 0;
7+
var _action_type = require("../constants/action_type");
8+
/* eslint-disable import/prefer-default-export */
9+
10+
const updateDetector = payload => ({
11+
type: _action_type.SEC.UPDATE_DETECTOR,
12+
payload
13+
});
14+
exports.updateDetector = updateDetector;

0 commit comments

Comments
 (0)