diff --git a/cypress/e2e/pages/visiumQC.cy.ts b/cypress/e2e/pages/visiumQC.cy.ts index 34b43251d..2dc75175f 100644 --- a/cypress/e2e/pages/visiumQC.cy.ts +++ b/cypress/e2e/pages/visiumQC.cy.ts @@ -11,7 +11,6 @@ import { } from '../../../src/types/sdk'; import { shouldDisplyProjectAndUserNameForWorkNumber } from '../shared/workNumberExtraInfo.cy'; import { - getAllSelect, selectOption, selectOptionForMultiple, selectSGPNumber, @@ -45,7 +44,7 @@ describe('Visium QC Page', () => { describe('On Visium QCType as Slide Processing', () => { context('When user scans in a slide ', () => { before(() => { - cy.get('#labwareScanInput').type('STAN-811EA{enter}'); + cy.get('#labwareScanInput').clear().type('STAN-811EA{enter}'); }); it('shows it on the page', () => { cy.findByText('STAN-811EA').should('be.visible'); @@ -71,7 +70,7 @@ describe('Visium QC Page', () => { context('costing field check', () => { context('when user enters a labware which has already assigned a costing', () => { before(() => { - cy.get('#labwareScanInput').type('STAN-2101{enter}'); + cy.get('#labwareScanInput').clear().type('STAN-2101{enter}'); }); it('disables Slide costing drop down', () => { shouldBeDisabled('slide-costing'); @@ -85,7 +84,7 @@ describe('Visium QC Page', () => { }); context('when user enters a labware with invalid barcode', () => { before(() => { - cy.get('#labwareScanInput').type('aaa{enter}'); + cy.get('#labwareScanInput').clear().type('aaa{enter}'); }); it('shows barcode not found message', () => { cy.findByText('Invalid barcode: aaa').should('be.visible'); @@ -117,8 +116,10 @@ describe('Visium QC Page', () => { }); }); it('has all comment dropdowns enabled', () => { - getAllSelect('comment').forEach((elem: any) => { - cy.wrap(elem).should('be.enabled'); + cy.findAllByTestId('comment').each(($elem) => { + cy.wrap($elem).within(() => { + cy.findByRole('combobox').should('be.enabled'); + }); }); }); }); @@ -135,8 +136,10 @@ describe('Visium QC Page', () => { }); }); it('enables all the comment dropdowns', () => { - getAllSelect('comment').forEach((elem: any) => { - cy.wrap(elem).should('be.enabled'); + cy.findAllByTestId('comment').each(($elem) => { + cy.wrap($elem).within(() => { + cy.findByRole('combobox').should('be.enabled'); + }); }); }); }); @@ -153,7 +156,7 @@ describe('Visium QC Page', () => { context('When slide costing field is empty, all other fields are valid', () => { before(() => { cy.reload(); - cy.get('#labwareScanInput').type('STAN-811EA{enter}'); + cy.get('#labwareScanInput').clear().type('STAN-811EA{enter}'); selectOption('slide-costing', ''); selectSGPNumber('SGP1008'); cy.findByTestId('formInput').type('123456'); @@ -235,7 +238,7 @@ describe('Visium QC Page', () => { }); it('shows an error', () => { - cy.get('#labwareScanInput').type('STAN-2100{enter}'); + cy.get('#labwareScanInput').clear().type('STAN-2100{enter}'); selectOption('slide-costing', 'SGP'); selectSGPNumber('SGP1008'); selectOption('slide-costing', 'Faculty'); @@ -249,7 +252,7 @@ describe('Visium QC Page', () => { before(() => { cy.reload(); cy.get('#labwareScanInput').clear().type('STAN-811EA{enter}'); - cy.get('#labwareScanInput').type('STAN-811FA{enter}'); + cy.get('#labwareScanInput').clear().type('STAN-811FA{enter}'); }); it('should display both labwares', () => { cy.findAllByTestId('passFailComments').should('have.length', 2); @@ -267,7 +270,7 @@ describe('Visium QC Page', () => { context('When user scans in a 96 well plate ', () => { before(() => { - cy.get('#labwareScanInput').type('STAN-811EA{enter}'); + cy.get('#labwareScanInput').clear().type('STAN-811EA{enter}'); }); it('displays the labware layout on the page', () => { cy.findByText('STAN-811EA').should('be.visible'); @@ -314,7 +317,7 @@ describe('Visium QC Page', () => { }); cy.reload(); selectOption('qcType', 'Amplification'); - cy.get('#labwareScanInput').type('STAN-811EA{enter}'); + cy.get('#labwareScanInput').clear().type('STAN-811EA{enter}'); }); it('displays error message', () => { cy.findByText('No Cq values associated with the labware slots').should('be.visible'); @@ -332,8 +335,8 @@ describe('Visium QC Page', () => { before(() => { cy.reload(); selectOption('qcType', 'Amplification'); - cy.get('#labwareScanInput').type('STAN-811EA{enter}'); - cy.findByTestId('all-Cycles').type('3'); + cy.get('#labwareScanInput').clear().type('STAN-811EA{enter}'); + cy.findByTestId('all-Cycles').clear().type('3'); }); it('Save button should be disabled when there is no SGP number', () => { selectSGPNumber(''); @@ -341,9 +344,9 @@ describe('Visium QC Page', () => { }); it('shows a success message', () => { selectSGPNumber('SGP1008'); - cy.findByTestId('all-Cycles').type('4'); + cy.findByTestId('all-Cycles').clear().type('4'); cy.findByRole('button', { name: /Save/i }).should('not.be.disabled').click(); - cy.findByText('Amplification complete').should('be.visible'); + cy.findByText(/Amplification\s*complete/i, { timeout: 30000 }).should('be.visible'); }); }); }); @@ -356,7 +359,7 @@ describe('Visium QC Page', () => { }); context('On load', () => { before(() => { - cy.get('#labwareScanInput').type('STAN-811EA{enter}'); + cy.get('#labwareScanInput').clear().type('STAN-811EA{enter}'); }); it('shows measurementType dropdown with no option selected', () => { shouldDisplaySelectedValue('measurementType', ''); @@ -396,7 +399,7 @@ describe('Visium QC Page', () => { }); context('When user scans in a slide ', () => { before(() => { - cy.get('#labwareScanInput').type('STAN-2100{enter}'); + cy.get('#labwareScanInput').clear().type('STAN-2100{enter}'); }); it('shows it on the page', () => { cy.findByText('STAN-2100').should('be.visible'); @@ -480,7 +483,7 @@ describe('Visium QC Page', () => { }); selectSGPNumber('SGP1008'); selectOption('qcType', 'SPRI clean up'); - cy.get('#labwareScanInput').type('STAN-811EA{enter}'); + cy.get('#labwareScanInput').clear().type('STAN-811EA{enter}'); selectOptionForMultiple('comment', 'Beads cracked during drying step', 0); saveButton().click(); }); @@ -492,7 +495,7 @@ describe('Visium QC Page', () => { context('When user scans in a 96 well plate ', () => { before(() => { cy.findByTestId('removeButton').click(); - cy.get('#labwareScanInput').type('STAN-410{enter}'); + cy.get('#labwareScanInput').clear().type('STAN-410{enter}'); }); it('shows it on the page', () => { cy.findByText('STAN-410').should('be.visible'); @@ -572,7 +575,7 @@ describe('Visium QC Page', () => { }); selectSGPNumber('SGP1008'); selectOption('qcType', 'SPRI clean up'); - cy.get('#labwareScanInput').type('STAN-811EA{enter}'); + cy.get('#labwareScanInput').clear().type('STAN-811EA{enter}'); selectOptionForMultiple('comment', 'Beads cracked during drying step', 0); saveButton().click(); }); @@ -587,7 +590,7 @@ describe('Visium QC Page', () => { before(() => { cy.visit('/lab/visium_qc'); selectOption('qcType', 'qPCR results'); - cy.get('#labwareScanInput').type('STAN-811EA{enter}'); + cy.get('#labwareScanInput').clear().type('STAN-811EA{enter}'); }); context('When a user enters a global CQ value', () => { @@ -642,7 +645,7 @@ describe('Visium QC Page', () => { cy.reload(); selectSGPNumber('SGP1008'); selectOption('qcType', 'qPCR results'); - cy.get('#labwareScanInput').type('STAN-811EA{enter}'); + cy.get('#labwareScanInput').clear().type('STAN-811EA{enter}'); cy.findByTestId('all-Cq value').type('5'); cy.findByRole('button', { name: /Save/i }).click(); }); diff --git a/cypress/e2e/pages/xeniumAnalyser.cy.ts b/cypress/e2e/pages/xeniumAnalyser.cy.ts index b7e2464ae..98cde70e6 100644 --- a/cypress/e2e/pages/xeniumAnalyser.cy.ts +++ b/cypress/e2e/pages/xeniumAnalyser.cy.ts @@ -29,7 +29,7 @@ describe('Xenium Analyser', () => { }); }); it('should display a warning message', () => { - cy.get('#labwareScanInput').type('STAN-3111{enter}'); + cy.get('#labwareScanInput').clear().type('STAN-3111{enter}'); cy.findByText('No probe hybridisation recorded for STAN-3111').should('be.visible'); cy.findByText('Analyser Details').should('not.exist'); }); @@ -39,7 +39,7 @@ describe('Xenium Analyser', () => { }); describe('When a labware is scanned', () => { before(() => { - cy.get('#labwareScanInput').type('STAN-3111{enter}'); + cy.get('#labwareScanInput').clear().type('STAN-3111{enter}'); }); it('shows labware table', () => { cy.findAllByRole('table').eq(0).should('have.length.above', 0); @@ -140,7 +140,7 @@ describe('Xenium Analyser', () => { }); describe('When two labware are scanned ', () => { before(() => { - cy.get('#labwareScanInput').type('STAN-3112{enter}'); //scan second labware + cy.get('#labwareScanInput').clear().type('STAN-3112{enter}'); //scan second labware }); it('should display Analyser Details for STAN-3112', () => { cy.findAllByRole('table').eq(1).contains('STAN-3112'); @@ -169,7 +169,7 @@ describe('Xenium Analyser', () => { describe('On save', () => { before(() => { - cy.get('#labwareScanInput').type('STAN-3111{enter}'); + cy.get('#labwareScanInput').clear().type('STAN-3111{enter}'); fillInForm(); }); context('When there is a server error', () => { @@ -219,7 +219,7 @@ describe('Xenium Analyser', () => { selectOption('STAN-3111-position', 'Left'); for (let indx = 0; indx < 4; indx++) { cy.findByTestId(`STAN-3111-${indx}-roi`).clear().type('123456789').blur(); - cy.findByTestId('closeBarcodeDisplayer').click(); + cy.findByTestId('closeBarcodeDisplayer').should('be.visible').click(); } } }); diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json index 92fad8ba2..a9d931fbe 100644 --- a/cypress/tsconfig.json +++ b/cypress/tsconfig.json @@ -2,8 +2,8 @@ "extends": "../tsconfig.json", "compilerOptions": { "allowJs": true, - "target": "es5", - "lib": ["es5", "dom", "es2015.promise"], + "target": "es2015", + "lib": ["es2015", "dom", "es2015.promise"], "types": ["cypress", "@testing-library/cypress"], "isolatedModules": false }, diff --git a/package.json b/package.json index f5637ffc3..a3a4583d2 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "client", "version": "4.1.4", "private": true, - "proxy": "http://localhost:8080", + "proxy": "http://localhost:8080/", "dependencies": { "@react-aria/tabs": "^3.9.7", "@sanger/ui-styling": "^4.3.0", diff --git a/src/components/Pill.tsx b/src/components/Pill.tsx index 97b0dfa39..a6b6d72ea 100644 --- a/src/components/Pill.tsx +++ b/src/components/Pill.tsx @@ -1,13 +1,14 @@ import React from 'react'; import classNames from 'classnames'; -interface PillProps { +type PillProps = { color: 'pink' | 'blue'; children: React.ReactNode; className?: string; -} + dataTestId?: string; +}; -const Pill = ({ color, children, className }: PillProps) => { +const Pill = ({ color, children, className, dataTestId }: PillProps) => { const spanClassName = classNames( { 'bg-sp text-gray-100': color === 'pink', @@ -17,7 +18,11 @@ const Pill = ({ color, children, className }: PillProps) => { className ); - return {children}; + return ( + + {children} + + ); }; export default Pill; diff --git a/src/components/workAllocation/WorkAllocation.tsx b/src/components/workAllocation/WorkAllocation.tsx index 91bb71449..325716261 100644 --- a/src/components/workAllocation/WorkAllocation.tsx +++ b/src/components/workAllocation/WorkAllocation.tsx @@ -43,7 +43,8 @@ const initialValues: WorkAllocationFormValues = { numOriginalSamples: undefined, ssStudyId: '', studyName: undefined, - facultyLead: '' + facultyLead: '', + treatmentTypes: [] }; export const MAX_NUM_BLOCKANDSLIDES = 200; @@ -58,6 +59,7 @@ const tableColumnFieldInfo = [ { key: 'Priority', path: ['work', 'priority'] }, { key: 'SGP Number', path: ['work', 'workNumber'] }, { key: 'Work Type', path: ['work', 'workType', 'name'] }, + { key: 'Treatment Types', path: ['work', 'treatmentTypes', 'name'] }, { key: 'Work Requester', path: ['work', 'workRequester', 'username'] }, { key: 'Project', path: ['work', 'project', 'name'] }, { key: 'Omero Project', path: ['work', 'omeroProject', 'name'] }, @@ -111,9 +113,16 @@ export default function WorkAllocation() { facultyLeads, requestError, successMessage, - allocatedWorkNumber + allocatedWorkNumber, + treatmentTypes } = current.context; + const treatmentTypeOptions = selectOptionValues( + (treatmentTypes || []).filter((t) => t.enabled), + 'name', + 'name' + ); + /**Hook to sort table*/ const { sortedTableData, sort, sortConfig } = useTableSort(workWithComments, { sortFieldName: 'SGP Number', @@ -133,7 +142,11 @@ export default function WorkAllocation() { }, entries: workWithComments.map((data) => { return tableColumnFieldInfo.map((columnInfo) => { - return String(getPropertyValue(data, columnInfo.path)); + if (columnInfo.key === 'Treatment Types') { + return data.work.treatmentTypes.map((t) => t.name).join(', '); + } + const value = getPropertyValue(data, columnInfo.path); + return String(value); }); }) }; @@ -197,6 +210,11 @@ export default function WorkAllocation() { .oneOf(workTypes.map((wt) => wt.name)) .required() .label('Work Type'), + // Validate that selected treatment types are valid or empty array + treatmentTypes: Yup.array() + .of(Yup.string().oneOf(treatmentTypeOptions.map((t) => t.value))) + .required() + .label('Treatment Types'), workRequester: Yup.string() .oneOf(workRequesters.map((wr) => wr.username)) .required() @@ -285,293 +303,313 @@ export default function WorkAllocation() { validateOnBlur={true} validateOnChange={false} > - {({ setFieldValue, values }) => ( -
-
-
- -
- -
- -
- {addNewProjectCodeCode && isComponentVisible ? ( - { - return stanCore.AddProject({ name: value }); - }} - onCancel={() => { - setAddNewProjectCodeCode(false); - }} - onSuccess={(object) => { - const name = object.name; - send({ - type: 'ADD_NEWLY_CREATED_PROJECT', - project: projectFactory.build({ name: name }) - }); - setFieldValue('project', name); - }} - onFinish={() => { - setAddNewProjectCodeCode(false); - }} - configLabel="Add New Project" - configName="project" - /> - ) : ( + {({ setFieldValue, values }) => { + // ...existing code... + return ( + +
{ - setAddNewProjectCodeCode(true); - setIsComponentVisible(true); - }, - className: 'mt-4' + dataTestId={'workType'} + options={selectOptionValues(workTypes, 'name', 'name')} + /> +
+ +
+ { + setFieldValue( + 'treatmentTypes', + Array.isArray(selected) ? selected.map((opt) => opt.value) : [] + ); }} />
- )} - {addNewOmeroProject && isComponentVisible ? ( - { - return stanCore.AddOmeroProject({ name: value }); - }} - onCancel={() => { - setAddNewOmeroProject(false); - }} - onSuccess={async (object) => { - const name = object.name; - send({ - type: 'ADD_NEWLY_CREATED_OMERO_PROJECT', - project: omeroProjectFactory.build({ name: name }) - }); - await setFieldValue('omeroProject', name); - }} - onFinish={() => { - setAddNewOmeroProject(false); - }} - configLabel="Add New Omero Project" - configName="Omero Project" - /> - ) : ( +
{ - setAddNewOmeroProject(true); - setIsComponentVisible(true); - }, - className: 'mt-4' - }} />
- )} - -
- -
+ {addNewProjectCodeCode && isComponentVisible ? ( + { + return stanCore.AddProject({ name: value }); + }} + onCancel={() => { + setAddNewProjectCodeCode(false); + }} + onSuccess={(object) => { + const name = object.name; + send({ + type: 'ADD_NEWLY_CREATED_PROJECT', + project: projectFactory.build({ name: name }) + }); + setFieldValue('project', name); + }} + onFinish={() => { + setAddNewProjectCodeCode(false); + }} + configLabel="Add New Project" + configName="project" + /> + ) : ( +
+ { + setAddNewProjectCodeCode(true); + setIsComponentVisible(true); + }, + className: 'mt-4' + }} + /> +
+ )} + {addNewOmeroProject && isComponentVisible ? ( + { + return stanCore.AddOmeroProject({ name: value }); + }} + onCancel={() => { + setAddNewOmeroProject(false); + }} + onSuccess={async (object) => { + const name = object.name; + send({ + type: 'ADD_NEWLY_CREATED_OMERO_PROJECT', + project: omeroProjectFactory.build({ name: name }) + }); + await setFieldValue('omeroProject', name); + }} + onFinish={() => { + setAddNewOmeroProject(false); + }} + configLabel="Add New Omero Project" + configName="Omero Project" + /> + ) : ( +
+ { + setAddNewOmeroProject(true); + setIsComponentVisible(true); + }, + className: 'mt-4' + }} + /> +
+ )} - {addNewCostCode && isComponentVisible ? ( - { - return stanCore.AddCostCode({ code: value }); - }} - onCancel={() => { - setAddNewCostCode(false); - }} - onSuccess={(object) => { - const code = object.code; - send({ - type: 'ADD_NEWLY_CREATED_COST_CODE', - costCode: costCodeFactory.build({ code: code }) - }); - setFieldValue('costCode', code); - }} - onFinish={() => { - setAddNewCostCode(false); - }} - configLabel="Add New Cost Code" - configName="costCode" - /> - ) : (
{ - setAddNewCostCode(true); - setIsComponentVisible(true); - }, - className: 'mt-4' - }} + options={selectOptionValues(programs, 'name', 'name')} />
- )} -
- -
-
- -
-
- -
-
- ) => { - const ssId = Number(e.currentTarget.value); - stanCore - .GetDnapStudy({ ssId: ssId }) - .then((study) => { - if (study && study.dnapStudy) { - setFieldValue('studyName', study.dnapStudy.name); - } else { - setFieldValue('studyName', 'undefined'); - } - }) - .catch((e) => { - setFieldValue('studyName', 'undefined'); + {addNewCostCode && isComponentVisible ? ( + { + return stanCore.AddCostCode({ code: value }); + }} + onCancel={() => { + setAddNewCostCode(false); + }} + onSuccess={(object) => { + const code = object.code; + send({ + type: 'ADD_NEWLY_CREATED_COST_CODE', + costCode: costCodeFactory.build({ code: code }) }); - setFieldValue('ssStudyId', e.currentTarget.value); - }} - /> - {values.studyName && values.ssStudyId && ( -
- {values.studyName === 'undefined' ? ( - Unknown Sequencescape study id - ) : ( - {values.studyName} - )} + setFieldValue('costCode', code); + }} + onFinish={() => { + setAddNewCostCode(false); + }} + configLabel="Add New Cost Code" + configName="costCode" + /> + ) : ( +
+ { + setAddNewCostCode(true); + setIsComponentVisible(true); + }, + className: 'mt-4' + }} + />
)} -
-
- ) => { - const ssId = Number(e.currentTarget.value); - stanCore - .GetDnapStudy({ ssId: ssId }) - .then((study) => { - if (study && study.dnapStudy) { - setFieldValue('xeniumStudyName', study.dnapStudy.name); - } else { + +
+ +
+
+ +
+
+ +
+
+ ) => { + const ssId = Number(e.currentTarget.value); + stanCore + .GetDnapStudy({ ssId: ssId }) + .then((study) => { + if (study && study.dnapStudy) { + setFieldValue('studyName', study.dnapStudy.name); + } else { + setFieldValue('studyName', 'undefined'); + } + }) + .catch((e) => { + setFieldValue('studyName', 'undefined'); + }); + setFieldValue('ssStudyId', e.currentTarget.value); + }} + /> + {values.studyName && values.ssStudyId && ( +
+ {values.studyName === 'undefined' ? ( + Unknown Sequencescape study id + ) : ( + {values.studyName} + )} +
+ )} +
+
+ ) => { + const ssId = Number(e.currentTarget.value); + stanCore + .GetDnapStudy({ ssId: ssId }) + .then((study) => { + if (study && study.dnapStudy) { + setFieldValue('xeniumStudyName', study.dnapStudy.name); + } else { + setFieldValue('xeniumStudyName', 'undefined'); + } + }) + .catch((e) => { setFieldValue('xeniumStudyName', 'undefined'); - } - }) - .catch((e) => { - setFieldValue('xeniumStudyName', 'undefined'); - }); - setFieldValue('xeniumStudyId', e.currentTarget.value); - }} - /> - {values.xeniumStudyName && values.xeniumStudyId && ( -
- {values.xeniumStudyName === 'undefined' ? ( - Unknown Sequencescape study id - ) : ( - {values.xeniumStudyName} - )} -
- )} -
-
- + }); + setFieldValue('xeniumStudyId', e.currentTarget.value); + }} + /> + {values.xeniumStudyName && values.xeniumStudyId && ( +
+ {values.xeniumStudyName === 'undefined' ? ( + Unknown Sequencescape study id + ) : ( + {values.xeniumStudyName} + )} +
+ )} +
+
+ +
-
-
- - - Submit - -
- - )} +
+ + + Submit + +
+ + ); + }}
@@ -640,6 +678,7 @@ export default function WorkAllocation() { SGP Number Work Type + Treatment Types Work Requester Project (cost code description) diff --git a/src/components/workAllocation/WorkRow.tsx b/src/components/workAllocation/WorkRow.tsx index d1ba18a27..c14153ff7 100644 --- a/src/components/workAllocation/WorkRow.tsx +++ b/src/components/workAllocation/WorkRow.tsx @@ -357,6 +357,18 @@ export default function WorkRow({ {work.workNumber} {work.workType.name} + + {work.treatmentTypes.length > 0 && ( +
+ {work.treatmentTypes.map((tt) => ( + + {tt.name} + + ))} +
+ )} +
+ {work.workRequester?.username} {work.project.name} {rendeWorkOmeroProjectField(work.workNumber, work.omeroProject?.name)} diff --git a/src/components/workAllocation/workAllocation.machine.ts b/src/components/workAllocation/workAllocation.machine.ts index fae0f6932..5881c2de2 100644 --- a/src/components/workAllocation/workAllocation.machine.ts +++ b/src/components/workAllocation/workAllocation.machine.ts @@ -14,7 +14,8 @@ import { UserRole, WorkStatus, WorkTypeFieldsFragment, - WorkWithCommentFieldsFragment + WorkWithCommentFieldsFragment, + TreatmentTypeFieldsFragment } from '../../types/sdk'; import { stanCore } from '../../lib/sdk'; import { WorkAllocationUrlParams } from './WorkAllocation'; @@ -91,6 +92,11 @@ export type WorkAllocationFormValues = { * Sequencescape study name corresponding to xeniumStudyId */ xeniumStudyName?: string; + + /** + * Treatment types for this Work (multi-select) + */ + treatmentTypes: string[]; }; type WorkAllocationEvent = @@ -174,6 +180,11 @@ type WorkAllocationContext = { */ facultyLeads: Array; + /** + * List of enabled Treatment Types + */ + treatmentTypes: Array; + /** * Notification to show to the user when something good happens */ @@ -231,6 +242,7 @@ export default function createWorkAllocationMachine({ urlParams }: CreateWorkAll omeroProjects: [], availableComments: [], facultyLeads: [], + treatmentTypes: [], urlParams }, initial: 'loading', @@ -276,6 +288,7 @@ export default function createWorkAllocationMachine({ urlParams }: CreateWorkAll src: fromPromise(({ input }) => { const { workType, + treatmentTypes, workRequester, project, program, @@ -291,6 +304,7 @@ export default function createWorkAllocationMachine({ urlParams }: CreateWorkAll } = input.values; return stanCore.CreateWork({ workType, + treatmentTypes, workRequester, project, program, @@ -336,7 +350,8 @@ export default function createWorkAllocationMachine({ urlParams }: CreateWorkAll workTypes, costCodes, releaseRecipients, - releaseDestinations + releaseDestinations, + treatmentTypes } = workAllocation; if (currentUser.user && currentUser.user.role === UserRole.Enduser) { releaseRecipients.push({ @@ -355,7 +370,8 @@ export default function createWorkAllocationMachine({ urlParams }: CreateWorkAll workTypes, costCodes, workRequesters: releaseRecipients, - facultyLeads: releaseDestinations + facultyLeads: releaseDestinations, + treatmentTypes: treatmentTypes }; }), @@ -375,6 +391,7 @@ export default function createWorkAllocationMachine({ urlParams }: CreateWorkAll workNumber, workRequester, workType, + treatmentTypes, project, program, costCode, @@ -394,17 +411,20 @@ export default function createWorkAllocationMachine({ urlParams }: CreateWorkAll .filter((msg) => msg) .join(' and '); + const treatmentTypesMsg = + treatmentTypes.length > 0 ? `, treatment types ${treatmentTypes.map((t) => t.name).join(', ')}` : ''; + return produce(context, (draft) => { draft.allocatedWorkNumber = workNumber; draft.successMessage = `Assigned ${workNumber} (${ workType.name - } - ${blockSlideSampleMsg}) to project (cost code description) ${project.name.trim()}${ + }${treatmentTypesMsg} - ${blockSlideSampleMsg}) to project (cost code description) ${project.name.trim()}${ omeroProject ? `, Omero project ${omeroProject.name}` : '' }${dnapStudy ? `, DNAP study name '${dnapStudy.name}'` : ''}${ xeniumStudy ? `, Xenium study name '${xeniumStudy.name}'` : '' - }, program ${program.name} - ${facultyLead ? `and faculty lead ${facultyLead.name}` : ''} - using cost code ${costCode.code} with the work requester ${workRequester?.username}`; + }, program ${program.name}${facultyLead ? ` and faculty lead ${facultyLead.name}` : ''} using cost code ${ + costCode.code + } with the work requester ${workRequester?.username}`; }); }), diff --git a/src/graphql/fragments/TreatmentTypeFields.graphql b/src/graphql/fragments/TreatmentTypeFields.graphql new file mode 100644 index 000000000..c74293e86 --- /dev/null +++ b/src/graphql/fragments/TreatmentTypeFields.graphql @@ -0,0 +1,4 @@ +fragment TreatmentTypeFields on TreatmentType { + name + enabled +} diff --git a/src/graphql/fragments/WorkFields.graphql b/src/graphql/fragments/WorkFields.graphql index 36cd7f738..e1552b8d6 100644 --- a/src/graphql/fragments/WorkFields.graphql +++ b/src/graphql/fragments/WorkFields.graphql @@ -32,4 +32,7 @@ fragment WorkFields on Work { numSlides numOriginalSamples priority + treatmentTypes { + ...TreatmentTypeFields + } } diff --git a/src/graphql/mutations/AddTreatmentType.graphql b/src/graphql/mutations/AddTreatmentType.graphql new file mode 100644 index 000000000..9842ed4f5 --- /dev/null +++ b/src/graphql/mutations/AddTreatmentType.graphql @@ -0,0 +1,5 @@ +mutation AddTreatmentType($name: String!) { + addTreatmentType(name: $name) { + ...TreatmentTypeFields + } +} diff --git a/src/graphql/mutations/CreateWork.graphql b/src/graphql/mutations/CreateWork.graphql index 447a64ddb..3669fffa7 100644 --- a/src/graphql/mutations/CreateWork.graphql +++ b/src/graphql/mutations/CreateWork.graphql @@ -11,6 +11,7 @@ mutation CreateWork( $omeroProject: String $ssStudyId: Int $xeniumStudyId: Int + $treatmentTypes: [String!] $facultyLead: String! ) { createWork( @@ -26,6 +27,7 @@ mutation CreateWork( omeroProject: $omeroProject ssStudyId: $ssStudyId xeniumStudyId: $xeniumStudyId + treatmentTypes: $treatmentTypes facultyLead: $facultyLead ) { ...WorkFields diff --git a/src/graphql/mutations/SetTreatmentTypeEnabled.graphql b/src/graphql/mutations/SetTreatmentTypeEnabled.graphql new file mode 100644 index 000000000..4c79b7651 --- /dev/null +++ b/src/graphql/mutations/SetTreatmentTypeEnabled.graphql @@ -0,0 +1,5 @@ +mutation SetTreatmentTypeEnabled($name: String!, $enabled: Boolean!) { + setTreatmentTypeEnabled(name: $name, enabled: $enabled) { + ...TreatmentTypeFields + } +} diff --git a/src/graphql/mutations/UpdateWorkTreatmentTypes.graphql b/src/graphql/mutations/UpdateWorkTreatmentTypes.graphql new file mode 100644 index 000000000..cb905697c --- /dev/null +++ b/src/graphql/mutations/UpdateWorkTreatmentTypes.graphql @@ -0,0 +1,5 @@ +mutation UpdateWorkTreatmentTypes($workNumber: String!, $treatmentTypes: [String!]!) { + updateWorkTreatmentTypes(workNumber: $workNumber, treatmentTypes: $treatmentTypes) { + ...WorkFields + } +} diff --git a/src/graphql/queries/GetConfiguration.graphql b/src/graphql/queries/GetConfiguration.graphql index dd95e1840..71c2c6d20 100644 --- a/src/graphql/queries/GetConfiguration.graphql +++ b/src/graphql/queries/GetConfiguration.graphql @@ -68,4 +68,7 @@ query GetConfiguration { proteinPanels(includeDisabled: true) { ...ProteinPanelFields } + treatmentTypes(includeDisabled: true) { + ...TreatmentTypeFields + } } diff --git a/src/graphql/queries/GetWorkAllocationInfo.graphql b/src/graphql/queries/GetWorkAllocationInfo.graphql index 019927364..43e7bfed4 100644 --- a/src/graphql/queries/GetWorkAllocationInfo.graphql +++ b/src/graphql/queries/GetWorkAllocationInfo.graphql @@ -29,4 +29,7 @@ query GetWorkAllocationInfo($commentCategory: String!, $workStatuses: [WorkStatu releaseDestinations(includeDisabled: false) { ...ReleaseDestinationFields } + treatmentTypes(includeDisabled: false) { + ...TreatmentTypeFields + } } diff --git a/src/lib/factories/treatmentTypeFactory.ts b/src/lib/factories/treatmentTypeFactory.ts new file mode 100644 index 000000000..0c31b5ef5 --- /dev/null +++ b/src/lib/factories/treatmentTypeFactory.ts @@ -0,0 +1,8 @@ +import { Factory } from 'fishery'; +import { TreatmentType } from '../../types/sdk'; + +export default Factory.define(({ params, sequence }) => ({ + __typename: 'TreatmentType', + name: params.name ?? `Treatment Type ${sequence}`, + enabled: params.enabled ?? true +})); diff --git a/src/lib/factories/workFactory.ts b/src/lib/factories/workFactory.ts index 10ba50926..f5f3da325 100644 --- a/src/lib/factories/workFactory.ts +++ b/src/lib/factories/workFactory.ts @@ -24,13 +24,17 @@ export default Factory.define( costCode: associations.costCode ?? costCodeFactory.build(), project: associations.project ?? projectFactory.build(), program: associations.program ?? programFactory.build(), + facultyLead: associations.facultyLead ?? undefined, status: params.status ?? WorkStatus.Unstarted, numBlocks: params.numBlocks, numSlides: params.numSlides, numOriginalSamples: params.numOriginalSamples, workNumber: workNumber, omeroProject: associations.omeroProject ?? omeroProjectFactory.build(), - dnapStudy: associations.dnapStudy ?? dnapStudyFactory.build() + dnapStudy: associations.dnapStudy ?? dnapStudyFactory.build(), + xeniumStudy: associations.xeniumStudy ?? undefined, + priority: params.priority ?? undefined, + treatmentTypes: params.treatmentTypes ?? [] }; } ); diff --git a/src/mocks/handlers/configurationHandlers.ts b/src/mocks/handlers/configurationHandlers.ts index 8a9ffe6f5..349a70a20 100644 --- a/src/mocks/handlers/configurationHandlers.ts +++ b/src/mocks/handlers/configurationHandlers.ts @@ -21,6 +21,7 @@ import bioRiskRepository from '../repositories/bioRiskRepository'; import tissueTypeRepository from '../repositories/tissueTypeRepository'; import cellClassRepository from '../repositories/cellClassRepository'; import proteinPanelRepository from '../repositories/proteinPanelRepository'; +import treatmentTypeRepository from '../repositories/treatmentTypeRepository'; const configurationHandlers = [ graphql.query('GetConfiguration', () => { @@ -48,7 +49,8 @@ const configurationHandlers = [ bioRisks: bioRiskRepository.findAll(), tissueTypes: tissueTypeRepository.findAll(), cellClasses: cellClassRepository.findAll(), - proteinPanels: proteinPanelRepository.findAll() + proteinPanels: proteinPanelRepository.findAll(), + treatmentTypes: treatmentTypeRepository.findAll() } }); }) diff --git a/src/mocks/handlers/workHandlers.ts b/src/mocks/handlers/workHandlers.ts index cca7d86dc..4487f20b6 100644 --- a/src/mocks/handlers/workHandlers.ts +++ b/src/mocks/handlers/workHandlers.ts @@ -79,6 +79,7 @@ const workHandlers = [ : undefined }; }), + treatmentTypes: [], workTypes: workTypeRepository.findAll().filter(isEnabled), releaseRecipients: releaseRecipientRepository.findAll().filter(isEnabled), dnapStudies: dnapStudyRepository.findAll().filter(isEnabled), diff --git a/src/mocks/repositories/treatmentTypeRepository.ts b/src/mocks/repositories/treatmentTypeRepository.ts new file mode 100644 index 000000000..6423be5fb --- /dev/null +++ b/src/mocks/repositories/treatmentTypeRepository.ts @@ -0,0 +1,14 @@ +import { TreatmentTypeFieldsFragment } from '../../types/sdk'; +import { createSessionStorageRepository } from './index'; + +const seeds: Array = [ + { __typename: 'TreatmentType', name: 'Fresh frozen', enabled: true }, + { __typename: 'TreatmentType', name: 'FFPE', enabled: true }, + { __typename: 'TreatmentType', name: 'Fixed frozen', enabled: true }, + { __typename: 'TreatmentType', name: 'Paxgene', enabled: true }, + { __typename: 'TreatmentType', name: 'Mixed', enabled: true } +]; + +const treatmentTypeRepository = createSessionStorageRepository('TREATMENT_TYPE', 'name', seeds); + +export default treatmentTypeRepository; diff --git a/src/mocks/repositories/workRepository.ts b/src/mocks/repositories/workRepository.ts index 3861dc5e4..3600753e8 100644 --- a/src/mocks/repositories/workRepository.ts +++ b/src/mocks/repositories/workRepository.ts @@ -1,13 +1,15 @@ import { WorkFieldsFragment, WorkStatus } from '../../types/sdk'; import workFactory from '../../lib/factories/workFactory'; import { createSessionStorageRepository } from './index'; +import treatmentTypeRepository from './treatmentTypeRepository'; const workSeeds: Array = [ ...workFactory.buildList(4), workFactory.build(undefined, { transient: { isRnD: true } }), - workFactory.build({ status: WorkStatus.Paused }), - workFactory.build({ status: WorkStatus.Failed }), - workFactory.build({ status: WorkStatus.Active }), + // attach treatment types to some example works for fixtures + workFactory.build({ status: WorkStatus.Paused, treatmentTypes: treatmentTypeRepository.findAll().slice(0, 1) }), + workFactory.build({ status: WorkStatus.Failed, treatmentTypes: treatmentTypeRepository.findAll().slice(1, 2) }), + workFactory.build({ status: WorkStatus.Active, treatmentTypes: treatmentTypeRepository.findAll().slice(0, 2) }), workFactory.build({ status: WorkStatus.Active }), workFactory.build({ status: WorkStatus.Active }), workFactory.build({ status: WorkStatus.Active }), diff --git a/src/pages/Configuration.tsx b/src/pages/Configuration.tsx index cfd59cde0..93ff2ba83 100644 --- a/src/pages/Configuration.tsx +++ b/src/pages/Configuration.tsx @@ -22,11 +22,12 @@ import { alphaNumericSortDefault } from '../types/stan'; import BlueButton from '../components/buttons/BlueButton'; import LoadingSpinner from '../components/icons/LoadingSpinner'; import Success from '../components/notifications/Success'; -import { useLoaderData } from 'react-router-dom'; +import { useLoaderData, useRevalidator } from 'react-router-dom'; import ComposedEntityManager from '../components/entityManager/ComposedEntityManager'; export default function Configuration() { const configuration = useLoaderData() as GetConfigurationQuery; + const revalidator = useRevalidator(); const stanCore = useContext(StanCoreContext); const groupedComments = groupBy(configuration.comments, 'category'); const groupedEquipments = groupBy(configuration.equipments, 'category'); @@ -52,6 +53,7 @@ export default function Configuration() { 'Species', 'Solutions', 'Tissue Types', + 'Treatment Types', 'Users', 'Work Types' ]; @@ -658,6 +660,37 @@ export default function Configuration() { }} />
, + /**Treatment Types**/ +
+ Treatment Types +

+ { + const enabled = typeof value === 'boolean' ? value : false; + return stanCore + .SetTreatmentTypeEnabled({ + enabled, + name: entity.name + }) + .then((res) => { + // revalidate route loader so the configuration page sees server-side updates + try { + revalidator.revalidate(); + } catch (e) { + // noop - revalidator may not be available in all contexts + } + return res.setTreatmentTypeEnabled; + }); + }} + onCreate={(name) => stanCore.AddTreatmentType({ name }).then((res) => res.addTreatmentType)} + valueFieldComponentInfo={{ + type: 'CHECKBOX' + }} + /> +

, /**Users**/
Users @@ -706,6 +739,7 @@ export default function Configuration() { configuration, groupedEquipments, stanCore, + revalidator, handleRefreshReprojectedEntries, loading, dnapStudies, diff --git a/src/types/sdk.ts b/src/types/sdk.ts index 11ed97a2f..314c2b5ec 100644 --- a/src/types/sdk.ts +++ b/src/types/sdk.ts @@ -968,6 +968,8 @@ export type Mutation = { addSpecies: Species; /** Add a new tissue type. */ addTissueType: TissueType; + /** Add a new treatment type. */ + addTreatmentType: TreatmentType; /** Create a new user for the application. */ addUser: User; /** Create a new work type. */ @@ -1100,6 +1102,8 @@ export type Mutation = { setSolutionEnabled: Solution; /** Enable or disable a species. */ setSpeciesEnabled: Species; + /** Enable or disable a treatment type. */ + setTreatmentTypeEnabled: TreatmentType; /** Set the user role (privileges) for a user. */ setUserRole: User; /** Enable or disable a work type. */ @@ -1138,6 +1142,8 @@ export type Mutation = { updateWorkPriority: Work; /** Update the status of an existing work. */ updateWorkStatus: WorkWithComment; + /** Updates the treatment types of a work. */ + updateWorkTreatmentTypes?: Maybe; /** Update the Xenium study of a work. */ updateWorkXeniumStudy: Work; /** Record Visium Analysis. */ @@ -1338,6 +1344,15 @@ export type MutationAddTissueTypeArgs = { }; +/** + * Send information to the application. + * These typically require a user with the suitable permission for the particular request. + */ +export type MutationAddTreatmentTypeArgs = { + name: Scalars['String']['input']; +}; + + /** * Send information to the application. * These typically require a user with the suitable permission for the particular request. @@ -1407,6 +1422,7 @@ export type MutationCreateWorkArgs = { program: Scalars['String']['input']; project: Scalars['String']['input']; ssStudyId?: InputMaybe; + treatmentTypes?: InputMaybe>; workRequester: Scalars['String']['input']; workType: Scalars['String']['input']; xeniumStudyId?: InputMaybe; @@ -1959,6 +1975,16 @@ export type MutationSetSpeciesEnabledArgs = { }; +/** + * Send information to the application. + * These typically require a user with the suitable permission for the particular request. + */ +export type MutationSetTreatmentTypeEnabledArgs = { + enabled: Scalars['Boolean']['input']; + name: Scalars['String']['input']; +}; + + /** * Send information to the application. * These typically require a user with the suitable permission for the particular request. @@ -2127,6 +2153,16 @@ export type MutationUpdateWorkStatusArgs = { }; +/** + * Send information to the application. + * These typically require a user with the suitable permission for the particular request. + */ +export type MutationUpdateWorkTreatmentTypesArgs = { + treatmentTypes: Array; + workNumber: Scalars['String']['input']; +}; + + /** * Send information to the application. * These typically require a user with the suitable permission for the particular request. @@ -2699,6 +2735,8 @@ export type Query = { suggestedWorkForLabware: SuggestedWorkResponse; /** Get all the tissue types available. */ tissueTypes: Array; + /** Get treatment types that are enabled, or all including disabled. */ + treatmentTypes: Array; /** Get the current logged in user. */ user?: Maybe; /** Get all the users that are enabled, or get all including those that are disabled. */ @@ -3300,6 +3338,15 @@ export type QueryTissueTypesArgs = { }; +/** + * Get information from the application. + * These typically require no user privilege. + */ +export type QueryTreatmentTypesArgs = { + includeDisabled?: InputMaybe; +}; + + /** * Get information from the application. * These typically require no user privilege. @@ -4192,6 +4239,13 @@ export type TissueType = { spatialLocations: Array; }; +/** A treatment type. */ +export type TreatmentType = { + __typename?: 'TreatmentType'; + enabled: Scalars['Boolean']['output']; + name: Scalars['String']['output']; +}; + /** The details of a previously released item of labware to receive back into the application. */ export type UnreleaseLabware = { /** The barcode of the labware. */ @@ -4315,6 +4369,8 @@ export type Work = { project: Project; /** The current status of the work. */ status: WorkStatus; + /** The treatment types for this work, if any. */ + treatmentTypes: Array; /** The unique (generated) string identifying this work. */ workNumber: Scalars['String']['output']; /** The name of the person requesting the work.(Note sure how to deal with this as the existing work will all be null but future work we want it mandatory?) */ @@ -4510,11 +4566,13 @@ export type TissueFieldsFragment = { __typename?: 'Tissue', externalName?: strin export type TissueTypeFieldsFragment = { __typename?: 'TissueType', name: string, code: string, spatialLocations: Array<{ __typename?: 'SpatialLocation', code: number, name: string }> }; +export type TreatmentTypeFieldsFragment = { __typename?: 'TreatmentType', name: string, enabled: boolean }; + export type UserFieldsFragment = { __typename?: 'User', username: string, role: UserRole }; -export type WorkFieldsFragment = { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null }; +export type WorkFieldsFragment = { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null, treatmentTypes: Array<{ __typename?: 'TreatmentType', name: string, enabled: boolean }> }; -export type WorkProgressFieldsFragment = { __typename?: 'WorkProgress', mostRecentOperation?: string | null, workComment?: string | null, work: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null }, timestamps: Array<{ __typename?: 'WorkProgressTimestamp', type: string, timestamp: string }> }; +export type WorkProgressFieldsFragment = { __typename?: 'WorkProgress', mostRecentOperation?: string | null, workComment?: string | null, work: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null, treatmentTypes: Array<{ __typename?: 'TreatmentType', name: string, enabled: boolean }> }, timestamps: Array<{ __typename?: 'WorkProgressTimestamp', type: string, timestamp: string }> }; export type WorkProgressTimeStampFieldFragment = { __typename?: 'WorkProgressTimestamp', type: string, timestamp: string }; @@ -4522,7 +4580,7 @@ export type WorkSummaryGroupFieldsFragment = { __typename?: 'WorkSummaryGroup', export type WorkTypeFieldsFragment = { __typename?: 'WorkType', name: string, enabled: boolean }; -export type WorkWithCommentFieldsFragment = { __typename?: 'WorkWithComment', comment?: string | null, work: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null } }; +export type WorkWithCommentFieldsFragment = { __typename?: 'WorkWithComment', comment?: string | null, work: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null, treatmentTypes: Array<{ __typename?: 'TreatmentType', name: string, enabled: boolean }> } }; export type AddBioRiskMutationVariables = Exact<{ code: Scalars['String']['input']; @@ -4668,6 +4726,13 @@ export type AddTissueTypeMutationVariables = Exact<{ export type AddTissueTypeMutation = { __typename?: 'Mutation', addTissueType: { __typename?: 'TissueType', name: string, code: string, spatialLocations: Array<{ __typename?: 'SpatialLocation', code: number, name: string }> } }; +export type AddTreatmentTypeMutationVariables = Exact<{ + name: Scalars['String']['input']; +}>; + + +export type AddTreatmentTypeMutation = { __typename?: 'Mutation', addTreatmentType: { __typename?: 'TreatmentType', name: string, enabled: boolean } }; + export type AddUserMutationVariables = Exact<{ username: Scalars['String']['input']; }>; @@ -4716,11 +4781,12 @@ export type CreateWorkMutationVariables = Exact<{ omeroProject?: InputMaybe; ssStudyId?: InputMaybe; xeniumStudyId?: InputMaybe; + treatmentTypes?: InputMaybe | Scalars['String']['input']>; facultyLead: Scalars['String']['input']; }>; -export type CreateWorkMutation = { __typename?: 'Mutation', createWork: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null } }; +export type CreateWorkMutation = { __typename?: 'Mutation', createWork: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null, treatmentTypes: Array<{ __typename?: 'TreatmentType', name: string, enabled: boolean }> } }; export type DestroyMutationVariables = Exact<{ request: DestroyRequest; @@ -5149,6 +5215,14 @@ export type SetSpeciesEnabledMutationVariables = Exact<{ export type SetSpeciesEnabledMutation = { __typename?: 'Mutation', setSpeciesEnabled: { __typename?: 'Species', name: string, enabled: boolean } }; +export type SetTreatmentTypeEnabledMutationVariables = Exact<{ + name: Scalars['String']['input']; + enabled: Scalars['Boolean']['input']; +}>; + + +export type SetTreatmentTypeEnabledMutation = { __typename?: 'Mutation', setTreatmentTypeEnabled: { __typename?: 'TreatmentType', name: string, enabled: boolean } }; + export type SetUserRoleMutationVariables = Exact<{ username: Scalars['String']['input']; role: UserRole; @@ -5237,7 +5311,7 @@ export type UpdateWorkDnapStudyMutationVariables = Exact<{ }>; -export type UpdateWorkDnapStudyMutation = { __typename?: 'Mutation', updateWorkDnapStudy: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null } }; +export type UpdateWorkDnapStudyMutation = { __typename?: 'Mutation', updateWorkDnapStudy: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null, treatmentTypes: Array<{ __typename?: 'TreatmentType', name: string, enabled: boolean }> } }; export type UpdateWorkNumBlocksMutationVariables = Exact<{ workNumber: Scalars['String']['input']; @@ -5245,7 +5319,7 @@ export type UpdateWorkNumBlocksMutationVariables = Exact<{ }>; -export type UpdateWorkNumBlocksMutation = { __typename?: 'Mutation', updateWorkNumBlocks: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null } }; +export type UpdateWorkNumBlocksMutation = { __typename?: 'Mutation', updateWorkNumBlocks: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null, treatmentTypes: Array<{ __typename?: 'TreatmentType', name: string, enabled: boolean }> } }; export type UpdateWorkNumOriginalSamplesMutationVariables = Exact<{ workNumber: Scalars['String']['input']; @@ -5253,7 +5327,7 @@ export type UpdateWorkNumOriginalSamplesMutationVariables = Exact<{ }>; -export type UpdateWorkNumOriginalSamplesMutation = { __typename?: 'Mutation', updateWorkNumOriginalSamples: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null } }; +export type UpdateWorkNumOriginalSamplesMutation = { __typename?: 'Mutation', updateWorkNumOriginalSamples: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null, treatmentTypes: Array<{ __typename?: 'TreatmentType', name: string, enabled: boolean }> } }; export type UpdateWorkNumSlidesMutationVariables = Exact<{ workNumber: Scalars['String']['input']; @@ -5261,7 +5335,7 @@ export type UpdateWorkNumSlidesMutationVariables = Exact<{ }>; -export type UpdateWorkNumSlidesMutation = { __typename?: 'Mutation', updateWorkNumSlides: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null } }; +export type UpdateWorkNumSlidesMutation = { __typename?: 'Mutation', updateWorkNumSlides: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null, treatmentTypes: Array<{ __typename?: 'TreatmentType', name: string, enabled: boolean }> } }; export type UpdateWorkOmeroProjectMutationVariables = Exact<{ workNumber: Scalars['String']['input']; @@ -5269,7 +5343,7 @@ export type UpdateWorkOmeroProjectMutationVariables = Exact<{ }>; -export type UpdateWorkOmeroProjectMutation = { __typename?: 'Mutation', updateWorkOmeroProject: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null } }; +export type UpdateWorkOmeroProjectMutation = { __typename?: 'Mutation', updateWorkOmeroProject: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null, treatmentTypes: Array<{ __typename?: 'TreatmentType', name: string, enabled: boolean }> } }; export type UpdateWorkPriorityMutationVariables = Exact<{ workNumber: Scalars['String']['input']; @@ -5277,7 +5351,7 @@ export type UpdateWorkPriorityMutationVariables = Exact<{ }>; -export type UpdateWorkPriorityMutation = { __typename?: 'Mutation', updateWorkPriority: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null } }; +export type UpdateWorkPriorityMutation = { __typename?: 'Mutation', updateWorkPriority: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null, treatmentTypes: Array<{ __typename?: 'TreatmentType', name: string, enabled: boolean }> } }; export type UpdateWorkStatusMutationVariables = Exact<{ workNumber: Scalars['String']['input']; @@ -5286,7 +5360,15 @@ export type UpdateWorkStatusMutationVariables = Exact<{ }>; -export type UpdateWorkStatusMutation = { __typename?: 'Mutation', updateWorkStatus: { __typename?: 'WorkWithComment', comment?: string | null, work: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null } } }; +export type UpdateWorkStatusMutation = { __typename?: 'Mutation', updateWorkStatus: { __typename?: 'WorkWithComment', comment?: string | null, work: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null, treatmentTypes: Array<{ __typename?: 'TreatmentType', name: string, enabled: boolean }> } } }; + +export type UpdateWorkTreatmentTypesMutationVariables = Exact<{ + workNumber: Scalars['String']['input']; + treatmentTypes: Array | Scalars['String']['input']; +}>; + + +export type UpdateWorkTreatmentTypesMutation = { __typename?: 'Mutation', updateWorkTreatmentTypes?: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null, treatmentTypes: Array<{ __typename?: 'TreatmentType', name: string, enabled: boolean }> } | null }; export type UpdateWorkXeniumStudyMutationVariables = Exact<{ workNumber: Scalars['String']['input']; @@ -5294,7 +5376,7 @@ export type UpdateWorkXeniumStudyMutationVariables = Exact<{ }>; -export type UpdateWorkXeniumStudyMutation = { __typename?: 'Mutation', updateWorkXeniumStudy: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null } }; +export type UpdateWorkXeniumStudyMutation = { __typename?: 'Mutation', updateWorkXeniumStudy: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null, treatmentTypes: Array<{ __typename?: 'TreatmentType', name: string, enabled: boolean }> } }; export type VisiumAnalysisMutationVariables = Exact<{ request: VisiumAnalysisRequest; @@ -5521,14 +5603,14 @@ export type FindWorkProgressQueryVariables = Exact<{ }>; -export type FindWorkProgressQuery = { __typename?: 'Query', workProgress: Array<{ __typename?: 'WorkProgress', mostRecentOperation?: string | null, workComment?: string | null, work: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null }, timestamps: Array<{ __typename?: 'WorkProgressTimestamp', type: string, timestamp: string }> }> }; +export type FindWorkProgressQuery = { __typename?: 'Query', workProgress: Array<{ __typename?: 'WorkProgress', mostRecentOperation?: string | null, workComment?: string | null, work: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null, treatmentTypes: Array<{ __typename?: 'TreatmentType', name: string, enabled: boolean }> }, timestamps: Array<{ __typename?: 'WorkProgressTimestamp', type: string, timestamp: string }> }> }; export type FindWorksCreatedByQueryVariables = Exact<{ username: Scalars['String']['input']; }>; -export type FindWorksCreatedByQuery = { __typename?: 'Query', worksCreatedBy: Array<{ __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null }> }; +export type FindWorksCreatedByQuery = { __typename?: 'Query', worksCreatedBy: Array<{ __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null, treatmentTypes: Array<{ __typename?: 'TreatmentType', name: string, enabled: boolean }> }> }; export type GetAllWorkInfoQueryVariables = Exact<{ [key: string]: never; }>; @@ -5579,7 +5661,7 @@ export type GetCommentsQuery = { __typename?: 'Query', comments: Array<{ __typen export type GetConfigurationQueryVariables = Exact<{ [key: string]: never; }>; -export type GetConfigurationQuery = { __typename?: 'Query', destructionReasons: Array<{ __typename?: 'DestructionReason', id: number, text: string, enabled: boolean }>, comments: Array<{ __typename?: 'Comment', id: number, text: string, category: string, enabled: boolean }>, hmdmcs: Array<{ __typename?: 'Hmdmc', hmdmc: string, enabled: boolean }>, species: Array<{ __typename?: 'Species', name: string, enabled: boolean }>, fixatives: Array<{ __typename?: 'Fixative', name: string, enabled: boolean }>, releaseDestinations: Array<{ __typename?: 'ReleaseDestination', name: string, enabled: boolean }>, releaseRecipients: Array<{ __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean }>, projects: Array<{ __typename?: 'Project', name: string, enabled: boolean }>, costCodes: Array<{ __typename?: 'CostCode', code: string, enabled: boolean }>, workTypes: Array<{ __typename?: 'WorkType', name: string, enabled: boolean }>, equipments: Array<{ __typename?: 'Equipment', id: number, name: string, category: string, enabled: boolean }>, users: Array<{ __typename?: 'User', username: string, role: UserRole }>, solutions: Array<{ __typename?: 'Solution', name: string, enabled: boolean }>, xeniumProbePanels: Array<{ __typename?: 'ProbePanel', name: string, enabled: boolean, type: ProbeType }>, cytassistProbePanels: Array<{ __typename?: 'ProbePanel', name: string, enabled: boolean, type: ProbeType }>, spikeProbePanels: Array<{ __typename?: 'ProbePanel', name: string, enabled: boolean, type: ProbeType }>, programs: Array<{ __typename?: 'Program', name: string, enabled: boolean }>, omeroProjects: Array<{ __typename?: 'OmeroProject', name: string, enabled: boolean }>, dnapStudies: Array<{ __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean }>, bioRisks: Array<{ __typename?: 'BioRisk', code: string, enabled: boolean }>, tissueTypes: Array<{ __typename?: 'TissueType', name: string, code: string, spatialLocations: Array<{ __typename?: 'SpatialLocation', code: number, name: string }> }>, cellClasses: Array<{ __typename?: 'CellClass', name: string, enabled: boolean }>, proteinPanels: Array<{ __typename?: 'ProteinPanel', name: string, enabled: boolean }> }; +export type GetConfigurationQuery = { __typename?: 'Query', destructionReasons: Array<{ __typename?: 'DestructionReason', id: number, text: string, enabled: boolean }>, comments: Array<{ __typename?: 'Comment', id: number, text: string, category: string, enabled: boolean }>, hmdmcs: Array<{ __typename?: 'Hmdmc', hmdmc: string, enabled: boolean }>, species: Array<{ __typename?: 'Species', name: string, enabled: boolean }>, fixatives: Array<{ __typename?: 'Fixative', name: string, enabled: boolean }>, releaseDestinations: Array<{ __typename?: 'ReleaseDestination', name: string, enabled: boolean }>, releaseRecipients: Array<{ __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean }>, projects: Array<{ __typename?: 'Project', name: string, enabled: boolean }>, costCodes: Array<{ __typename?: 'CostCode', code: string, enabled: boolean }>, workTypes: Array<{ __typename?: 'WorkType', name: string, enabled: boolean }>, equipments: Array<{ __typename?: 'Equipment', id: number, name: string, category: string, enabled: boolean }>, users: Array<{ __typename?: 'User', username: string, role: UserRole }>, solutions: Array<{ __typename?: 'Solution', name: string, enabled: boolean }>, xeniumProbePanels: Array<{ __typename?: 'ProbePanel', name: string, enabled: boolean, type: ProbeType }>, cytassistProbePanels: Array<{ __typename?: 'ProbePanel', name: string, enabled: boolean, type: ProbeType }>, spikeProbePanels: Array<{ __typename?: 'ProbePanel', name: string, enabled: boolean, type: ProbeType }>, programs: Array<{ __typename?: 'Program', name: string, enabled: boolean }>, omeroProjects: Array<{ __typename?: 'OmeroProject', name: string, enabled: boolean }>, dnapStudies: Array<{ __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean }>, bioRisks: Array<{ __typename?: 'BioRisk', code: string, enabled: boolean }>, tissueTypes: Array<{ __typename?: 'TissueType', name: string, code: string, spatialLocations: Array<{ __typename?: 'SpatialLocation', code: number, name: string }> }>, cellClasses: Array<{ __typename?: 'CellClass', name: string, enabled: boolean }>, proteinPanels: Array<{ __typename?: 'ProteinPanel', name: string, enabled: boolean }>, treatmentTypes: Array<{ __typename?: 'TreatmentType', name: string, enabled: boolean }> }; export type GetDestroyInfoQueryVariables = Exact<{ [key: string]: never; }>; @@ -5832,7 +5914,7 @@ export type GetWorkAllocationInfoQueryVariables = Exact<{ }>; -export type GetWorkAllocationInfoQuery = { __typename?: 'Query', projects: Array<{ __typename?: 'Project', name: string, enabled: boolean }>, programs: Array<{ __typename?: 'Program', name: string, enabled: boolean }>, costCodes: Array<{ __typename?: 'CostCode', code: string, enabled: boolean }>, worksWithComments: Array<{ __typename?: 'WorkWithComment', comment?: string | null, work: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null } }>, workTypes: Array<{ __typename?: 'WorkType', name: string, enabled: boolean }>, comments: Array<{ __typename?: 'Comment', id: number, text: string, category: string, enabled: boolean }>, releaseRecipients: Array<{ __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean }>, omeroProjects: Array<{ __typename?: 'OmeroProject', name: string, enabled: boolean }>, dnapStudies: Array<{ __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean }>, releaseDestinations: Array<{ __typename?: 'ReleaseDestination', name: string, enabled: boolean }> }; +export type GetWorkAllocationInfoQuery = { __typename?: 'Query', projects: Array<{ __typename?: 'Project', name: string, enabled: boolean }>, programs: Array<{ __typename?: 'Program', name: string, enabled: boolean }>, costCodes: Array<{ __typename?: 'CostCode', code: string, enabled: boolean }>, worksWithComments: Array<{ __typename?: 'WorkWithComment', comment?: string | null, work: { __typename?: 'Work', workNumber: string, status: WorkStatus, numBlocks?: number | null, numSlides?: number | null, numOriginalSamples?: number | null, priority?: string | null, workRequester?: { __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean } | null, project: { __typename?: 'Project', name: string, enabled: boolean }, program: { __typename?: 'Program', name: string, enabled: boolean }, costCode: { __typename?: 'CostCode', code: string, enabled: boolean }, workType: { __typename?: 'WorkType', name: string, enabled: boolean }, omeroProject?: { __typename?: 'OmeroProject', name: string, enabled: boolean } | null, dnapStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, xeniumStudy?: { __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean } | null, facultyLead?: { __typename?: 'ReleaseDestination', name: string, enabled: boolean } | null, treatmentTypes: Array<{ __typename?: 'TreatmentType', name: string, enabled: boolean }> } }>, workTypes: Array<{ __typename?: 'WorkType', name: string, enabled: boolean }>, comments: Array<{ __typename?: 'Comment', id: number, text: string, category: string, enabled: boolean }>, releaseRecipients: Array<{ __typename?: 'ReleaseRecipient', username: string, fullName?: string | null, enabled: boolean }>, omeroProjects: Array<{ __typename?: 'OmeroProject', name: string, enabled: boolean }>, dnapStudies: Array<{ __typename?: 'DnapStudy', ssId: number, name: string, enabled: boolean }>, releaseDestinations: Array<{ __typename?: 'ReleaseDestination', name: string, enabled: boolean }>, treatmentTypes: Array<{ __typename?: 'TreatmentType', name: string, enabled: boolean }> }; export type GetWorkNumbersQueryVariables = Exact<{ [key: string]: never; }>; @@ -6409,6 +6491,12 @@ export const ReleaseDestinationFieldsFragmentDoc = gql` enabled } `; +export const TreatmentTypeFieldsFragmentDoc = gql` + fragment TreatmentTypeFields on TreatmentType { + name + enabled +} + `; export const WorkFieldsFragmentDoc = gql` fragment WorkFields on Work { workNumber @@ -6444,6 +6532,9 @@ export const WorkFieldsFragmentDoc = gql` numSlides numOriginalSamples priority + treatmentTypes { + ...TreatmentTypeFields + } } `; export const WorkProgressTimeStampFieldFragmentDoc = gql` @@ -6641,6 +6732,13 @@ export const AddTissueTypeDocument = gql` } } `; +export const AddTreatmentTypeDocument = gql` + mutation AddTreatmentType($name: String!) { + addTreatmentType(name: $name) { + ...TreatmentTypeFields + } +} + ${TreatmentTypeFieldsFragmentDoc}`; export const AddUserDocument = gql` mutation AddUser($username: String!) { addUser(username: $username) { @@ -6734,7 +6832,7 @@ ${SlotFieldsFragmentDoc} ${SampleFieldsFragmentDoc} ${TissueFieldsFragmentDoc}`; export const CreateWorkDocument = gql` - mutation CreateWork($prefix: String!, $workType: String!, $workRequester: String!, $project: String!, $program: String!, $costCode: String!, $numBlocks: Int, $numSlides: Int, $numOriginalSamples: Int, $omeroProject: String, $ssStudyId: Int, $xeniumStudyId: Int, $facultyLead: String!) { + mutation CreateWork($prefix: String!, $workType: String!, $workRequester: String!, $project: String!, $program: String!, $costCode: String!, $numBlocks: Int, $numSlides: Int, $numOriginalSamples: Int, $omeroProject: String, $ssStudyId: Int, $xeniumStudyId: Int, $treatmentTypes: [String!], $facultyLead: String!) { createWork( prefix: $prefix workType: $workType @@ -6748,6 +6846,7 @@ export const CreateWorkDocument = gql` omeroProject: $omeroProject ssStudyId: $ssStudyId xeniumStudyId: $xeniumStudyId + treatmentTypes: $treatmentTypes facultyLead: $facultyLead ) { ...WorkFields @@ -6761,7 +6860,8 @@ ${CostCodeFieldsFragmentDoc} ${WorkTypeFieldsFragmentDoc} ${OmeroProjectFieldsFragmentDoc} ${DnapStudyFieldsFragmentDoc} -${ReleaseDestinationFieldsFragmentDoc}`; +${ReleaseDestinationFieldsFragmentDoc} +${TreatmentTypeFieldsFragmentDoc}`; export const DestroyDocument = gql` mutation Destroy($request: DestroyRequest!) { destroy(request: $request) { @@ -7387,6 +7487,13 @@ export const SetSpeciesEnabledDocument = gql` } } ${SpeciesFieldsFragmentDoc}`; +export const SetTreatmentTypeEnabledDocument = gql` + mutation SetTreatmentTypeEnabled($name: String!, $enabled: Boolean!) { + setTreatmentTypeEnabled(name: $name, enabled: $enabled) { + ...TreatmentTypeFields + } +} + ${TreatmentTypeFieldsFragmentDoc}`; export const SetUserRoleDocument = gql` mutation SetUserRole($username: String!, $role: UserRole!) { setUserRole(username: $username, role: $role) { @@ -7500,7 +7607,8 @@ ${CostCodeFieldsFragmentDoc} ${WorkTypeFieldsFragmentDoc} ${OmeroProjectFieldsFragmentDoc} ${DnapStudyFieldsFragmentDoc} -${ReleaseDestinationFieldsFragmentDoc}`; +${ReleaseDestinationFieldsFragmentDoc} +${TreatmentTypeFieldsFragmentDoc}`; export const UpdateWorkNumBlocksDocument = gql` mutation UpdateWorkNumBlocks($workNumber: String!, $numBlocks: Int) { updateWorkNumBlocks(workNumber: $workNumber, numBlocks: $numBlocks) { @@ -7515,7 +7623,8 @@ ${CostCodeFieldsFragmentDoc} ${WorkTypeFieldsFragmentDoc} ${OmeroProjectFieldsFragmentDoc} ${DnapStudyFieldsFragmentDoc} -${ReleaseDestinationFieldsFragmentDoc}`; +${ReleaseDestinationFieldsFragmentDoc} +${TreatmentTypeFieldsFragmentDoc}`; export const UpdateWorkNumOriginalSamplesDocument = gql` mutation UpdateWorkNumOriginalSamples($workNumber: String!, $numOriginalSamples: Int) { updateWorkNumOriginalSamples( @@ -7533,7 +7642,8 @@ ${CostCodeFieldsFragmentDoc} ${WorkTypeFieldsFragmentDoc} ${OmeroProjectFieldsFragmentDoc} ${DnapStudyFieldsFragmentDoc} -${ReleaseDestinationFieldsFragmentDoc}`; +${ReleaseDestinationFieldsFragmentDoc} +${TreatmentTypeFieldsFragmentDoc}`; export const UpdateWorkNumSlidesDocument = gql` mutation UpdateWorkNumSlides($workNumber: String!, $numSlides: Int) { updateWorkNumSlides(workNumber: $workNumber, numSlides: $numSlides) { @@ -7548,7 +7658,8 @@ ${CostCodeFieldsFragmentDoc} ${WorkTypeFieldsFragmentDoc} ${OmeroProjectFieldsFragmentDoc} ${DnapStudyFieldsFragmentDoc} -${ReleaseDestinationFieldsFragmentDoc}`; +${ReleaseDestinationFieldsFragmentDoc} +${TreatmentTypeFieldsFragmentDoc}`; export const UpdateWorkOmeroProjectDocument = gql` mutation UpdateWorkOmeroProject($workNumber: String!, $omeroProject: String) { updateWorkOmeroProject(workNumber: $workNumber, omeroProject: $omeroProject) { @@ -7563,7 +7674,8 @@ ${CostCodeFieldsFragmentDoc} ${WorkTypeFieldsFragmentDoc} ${OmeroProjectFieldsFragmentDoc} ${DnapStudyFieldsFragmentDoc} -${ReleaseDestinationFieldsFragmentDoc}`; +${ReleaseDestinationFieldsFragmentDoc} +${TreatmentTypeFieldsFragmentDoc}`; export const UpdateWorkPriorityDocument = gql` mutation UpdateWorkPriority($workNumber: String!, $priority: String) { updateWorkPriority(workNumber: $workNumber, priority: $priority) { @@ -7578,7 +7690,8 @@ ${CostCodeFieldsFragmentDoc} ${WorkTypeFieldsFragmentDoc} ${OmeroProjectFieldsFragmentDoc} ${DnapStudyFieldsFragmentDoc} -${ReleaseDestinationFieldsFragmentDoc}`; +${ReleaseDestinationFieldsFragmentDoc} +${TreatmentTypeFieldsFragmentDoc}`; export const UpdateWorkStatusDocument = gql` mutation UpdateWorkStatus($workNumber: String!, $status: WorkStatus!, $commentId: Int) { updateWorkStatus( @@ -7598,7 +7711,27 @@ ${CostCodeFieldsFragmentDoc} ${WorkTypeFieldsFragmentDoc} ${OmeroProjectFieldsFragmentDoc} ${DnapStudyFieldsFragmentDoc} -${ReleaseDestinationFieldsFragmentDoc}`; +${ReleaseDestinationFieldsFragmentDoc} +${TreatmentTypeFieldsFragmentDoc}`; +export const UpdateWorkTreatmentTypesDocument = gql` + mutation UpdateWorkTreatmentTypes($workNumber: String!, $treatmentTypes: [String!]!) { + updateWorkTreatmentTypes( + workNumber: $workNumber + treatmentTypes: $treatmentTypes + ) { + ...WorkFields + } +} + ${WorkFieldsFragmentDoc} +${ReleaseRecipientFieldsFragmentDoc} +${ProjectFieldsFragmentDoc} +${ProgramFieldsFragmentDoc} +${CostCodeFieldsFragmentDoc} +${WorkTypeFieldsFragmentDoc} +${OmeroProjectFieldsFragmentDoc} +${DnapStudyFieldsFragmentDoc} +${ReleaseDestinationFieldsFragmentDoc} +${TreatmentTypeFieldsFragmentDoc}`; export const UpdateWorkXeniumStudyDocument = gql` mutation UpdateWorkXeniumStudy($workNumber: String!, $ssStudyId: Int) { updateWorkXeniumStudy(workNumber: $workNumber, ssStudyId: $ssStudyId) { @@ -7613,7 +7746,8 @@ ${CostCodeFieldsFragmentDoc} ${WorkTypeFieldsFragmentDoc} ${OmeroProjectFieldsFragmentDoc} ${DnapStudyFieldsFragmentDoc} -${ReleaseDestinationFieldsFragmentDoc}`; +${ReleaseDestinationFieldsFragmentDoc} +${TreatmentTypeFieldsFragmentDoc}`; export const VisiumAnalysisDocument = gql` mutation VisiumAnalysis($request: VisiumAnalysisRequest!) { visiumAnalysis(request: $request) { @@ -8029,6 +8163,7 @@ ${WorkTypeFieldsFragmentDoc} ${OmeroProjectFieldsFragmentDoc} ${DnapStudyFieldsFragmentDoc} ${ReleaseDestinationFieldsFragmentDoc} +${TreatmentTypeFieldsFragmentDoc} ${WorkProgressTimeStampFieldFragmentDoc}`; export const FindWorksCreatedByDocument = gql` query FindWorksCreatedBy($username: String!) { @@ -8044,7 +8179,8 @@ ${CostCodeFieldsFragmentDoc} ${WorkTypeFieldsFragmentDoc} ${OmeroProjectFieldsFragmentDoc} ${DnapStudyFieldsFragmentDoc} -${ReleaseDestinationFieldsFragmentDoc}`; +${ReleaseDestinationFieldsFragmentDoc} +${TreatmentTypeFieldsFragmentDoc}`; export const GetAllWorkInfoDocument = gql` query GetAllWorkInfo { works { @@ -8177,6 +8313,9 @@ export const GetConfigurationDocument = gql` proteinPanels(includeDisabled: true) { ...ProteinPanelFields } + treatmentTypes(includeDisabled: true) { + ...TreatmentTypeFields + } } ${DestructionReasonFieldsFragmentDoc} ${CommentFieldsFragmentDoc} @@ -8198,7 +8337,8 @@ ${DnapStudyFieldsFragmentDoc} ${BioRiskFieldsFragmentDoc} ${TissueTypeFieldsFragmentDoc} ${CellClassFieldsFragmentDoc} -${ProteinPanelFieldsFragmentDoc}`; +${ProteinPanelFieldsFragmentDoc} +${TreatmentTypeFieldsFragmentDoc}`; export const GetDestroyInfoDocument = gql` query GetDestroyInfo { destructionReasons { @@ -8583,6 +8723,9 @@ export const GetWorkAllocationInfoDocument = gql` releaseDestinations(includeDisabled: false) { ...ReleaseDestinationFields } + treatmentTypes(includeDisabled: false) { + ...TreatmentTypeFields + } } ${ProjectFieldsFragmentDoc} ${ProgramFieldsFragmentDoc} @@ -8594,6 +8737,7 @@ ${WorkTypeFieldsFragmentDoc} ${OmeroProjectFieldsFragmentDoc} ${DnapStudyFieldsFragmentDoc} ${ReleaseDestinationFieldsFragmentDoc} +${TreatmentTypeFieldsFragmentDoc} ${CommentFieldsFragmentDoc}`; export const GetWorkNumbersDocument = gql` query GetWorkNumbers { @@ -8721,6 +8865,9 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = AddTissueType(variables: AddTissueTypeMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { return withWrapper((wrappedRequestHeaders) => client.request(AddTissueTypeDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'AddTissueType', 'mutation', variables); }, + AddTreatmentType(variables: AddTreatmentTypeMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(AddTreatmentTypeDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'AddTreatmentType', 'mutation', variables); + }, AddUser(variables: AddUserMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { return withWrapper((wrappedRequestHeaders) => client.request(AddUserDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'AddUser', 'mutation', variables); }, @@ -8913,6 +9060,9 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = SetSpeciesEnabled(variables: SetSpeciesEnabledMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { return withWrapper((wrappedRequestHeaders) => client.request(SetSpeciesEnabledDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'SetSpeciesEnabled', 'mutation', variables); }, + SetTreatmentTypeEnabled(variables: SetTreatmentTypeEnabledMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(SetTreatmentTypeEnabledDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'SetTreatmentTypeEnabled', 'mutation', variables); + }, SetUserRole(variables: SetUserRoleMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { return withWrapper((wrappedRequestHeaders) => client.request(SetUserRoleDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'SetUserRole', 'mutation', variables); }, @@ -8967,6 +9117,9 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = UpdateWorkStatus(variables: UpdateWorkStatusMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { return withWrapper((wrappedRequestHeaders) => client.request(UpdateWorkStatusDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'UpdateWorkStatus', 'mutation', variables); }, + UpdateWorkTreatmentTypes(variables: UpdateWorkTreatmentTypesMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(UpdateWorkTreatmentTypesDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'UpdateWorkTreatmentTypes', 'mutation', variables); + }, UpdateWorkXeniumStudy(variables: UpdateWorkXeniumStudyMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { return withWrapper((wrappedRequestHeaders) => client.request(UpdateWorkXeniumStudyDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'UpdateWorkXeniumStudy', 'mutation', variables); }, diff --git a/tests/e2e/pages/fileManager.spec.tsx b/tests/e2e/pages/fileManager.spec.tsx index 7a88210e5..39220c3b4 100644 --- a/tests/e2e/pages/fileManager.spec.tsx +++ b/tests/e2e/pages/fileManager.spec.tsx @@ -257,8 +257,8 @@ describe('On load', () => { renderFileManagerComponent(true); }); it('should display the url with selected work number', async () => { - await waitFor(async () => { - await selectSGPNumber('SGP1008'); + await selectSGPNumber('SGP1008'); + await waitFor(() => { expect(navigateMock).toHaveBeenCalledWith('/file_manager?workNumber=SGP1008'); }); }); diff --git a/tests/unit/components/release/releaseOptions.spec.tsx b/tests/unit/components/release/releaseOptions.spec.tsx index ef0b30d17..c8f7516c5 100644 --- a/tests/unit/components/release/releaseOptions.spec.tsx +++ b/tests/unit/components/release/releaseOptions.spec.tsx @@ -15,41 +15,37 @@ jest.mock('react-router-dom', () => ({ })); describe('ReleaseOptions', () => { - it('renders page with release options', () => { + it('renders page with release options', async () => { act(() => { const router = createMemoryRouter([{ path: '/releaseOptions', element: }], { initialEntries: ['/releaseOptions?id=123'] }); render(); }); - waitFor(() => { + await waitFor(() => { expect(screen.getByText('Release File Options')).toBeInTheDocument(); expect(screen.getAllByRole('checkbox').length).toBe(3); - expect(screen.getByText('Option 1')).toBeInTheDocument(); - expect(screen.getByText('Option 2')).toBeInTheDocument(); - expect(screen.getByText('Option 3')).toBeInTheDocument(); - ['Option 1', 'Option 2', 'Option 3'].forEach((option, indx) => { + const optionLabels = ['Histology', 'Sample Processing', 'Xenium']; + optionLabels.forEach((option, indx) => { expect(screen.getByText(option)).toBeInTheDocument(); const optionCheckBox = screen.getAllByRole('checkbox')[indx]; expect(optionCheckBox).not.toBeChecked(); }); }); }); - it('On initial loading release options are selected based on query params', () => { + it('On initial loading release options are selected based on query params', async () => { act(() => { const router = createMemoryRouter([{ path: '/releaseOptions', element: }], { - initialEntries: ['/releaseOptions?id=123&groups=option_1,option_2&type=xlsx'] + initialEntries: ['/releaseOptions?id=123&groups=histology,sample_processing&type=xlsx'] }); render(); }); - waitFor(() => { - ['Option 1', 'Option 2'].forEach((option, indx) => { - const optionCheckBox = screen.getAllByRole('checkbox')[indx]; - expect(optionCheckBox).toBeChecked(); - }); - const option3 = screen.getAllByRole('checkbox')[2]; - expect(option3).not.toBeChecked(); - + await waitFor(() => { + // The first two checkboxes (Histology, Sample Processing) should be checked, the third (Xenium) should not + const checkboxes = screen.getAllByRole('checkbox'); + expect(checkboxes[0]).toBeChecked(); // Histology + expect(checkboxes[1]).toBeChecked(); // Sample Processing + expect(checkboxes[2]).not.toBeChecked(); // Xenium expect(screen.getByTestId('excel-file')).toBeChecked(); }); }); @@ -79,18 +75,15 @@ describe('ReleaseOptions', () => { }); it('calls navigate function with updated url when user deselects release options', async () => { const router = createMemoryRouter([{ path: '/releaseOptions', element: }], { - initialEntries: ['/releaseOptions?id=123&groups=option_1,option_2&type=xlsx'] + initialEntries: ['/releaseOptions?id=123&groups=histology,sample_processing&type=xlsx'] }); render(); await waitFor(() => { const option1 = screen.getAllByRole('checkbox')[0]; fireEvent.click(option1); }); - expect(navigateFunction).toHaveBeenLastCalledWith( - '/releaseOptions?id=123&groups=option_1,option_2,histology&type=xlsx', - { - replace: true - } - ); + expect(navigateFunction).toHaveBeenLastCalledWith('/releaseOptions?id=123&groups=sample_processing&type=xlsx', { + replace: true + }); }); }); diff --git a/tests/unit/components/workAllocation/downloadData.spec.tsx b/tests/unit/components/workAllocation/downloadData.spec.tsx new file mode 100644 index 000000000..ff8dcf2cb --- /dev/null +++ b/tests/unit/components/workAllocation/downloadData.spec.tsx @@ -0,0 +1,133 @@ +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import WorkAllocation from '../../../../src/components/workAllocation/WorkAllocation'; + +// Test Records for SGP management spreadsheet download data. + +// capture object passed to useDownload +let capturedDownloadData: any = null; + +afterEach(() => { + capturedDownloadData = null; + jest.clearAllMocks(); +}); + +jest.mock('@xstate/react', () => ({ + useMachine: (machine?: any) => { + // WorkRow machine expects a single `workWithComment` in context + if (machine && machine.id === 'workRowMachine') { + return [ + { + _nodes: [], + context: { + workWithComment: { + work: { + workNumber: 'SGP-1', + workType: { name: 'Work Type' }, + treatmentTypes: [ + { name: 'Type A', enabled: true }, + { name: 'Type B', enabled: false } + ], + workRequester: { username: 'req' }, + project: { name: 'proj' } + }, + comment: null + }, + editModeEnabled: false + }, + matches: (_: string): boolean => false + }, + jest.fn(), + jest.fn() + ]; + } + + // Default: WorkAllocation machine shape + return [ + { + context: { + workWithComments: [ + { + work: { + workNumber: 'SGP-1', + workType: { name: 'Work Type' }, + treatmentTypes: [ + { name: 'Type A', enabled: true }, + { name: 'Type B', enabled: false } + ], + workRequester: { username: 'req' }, + project: { name: 'proj' } + }, + comment: null + } + ], + workTypes: [], + workRequesters: [], + projects: [], + programs: [], + omeroProjects: [], + costCodes: [], + availableComments: [{ id: 1, text: 'test' }], + facultyLeads: [], + treatmentTypes: [] + }, + matches: (_: string): boolean => false + }, + jest.fn() + ]; + } +})); + +// mock react-router hooks used by the component +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: () => ({ search: '' }), + useNavigate: () => jest.fn() +})); + +// stub WorkRow so we don't render its internals in this test +jest.mock('../../../../src/components/workAllocation/WorkRow', () => () => { + const React = require('react'); + return React.createElement('tr', { 'data-testid': 'mock-work-row' }); +}); + +// mock auth to ensure Authenticated renders children +jest.mock('../../../../src/context/AuthContext', () => ({ + useAuth: () => ({ isAuthenticated: () => true, userRoleIncludes: () => true }) +})); + +// mock useDownload to capture the passed downloadData +jest.mock('../../../../src/lib/hooks/useDownload', () => ({ + useDownload: (downloadData: any) => { + capturedDownloadData = downloadData; + return { downloadURL: 'mock-url', extension: '.xlsx', requestDownload: jest.fn() }; + } +})); + +describe('WorkAllocation spreadsheet downloadData', () => { + it('builds entries with joined treatment type names', async () => { + render(); + + // wait for table to render to ensure useDownload was called + await screen.findByTestId('work-allocation-table'); + + expect(capturedDownloadData).not.toBeNull(); + const { columnData, entries } = capturedDownloadData; + const colIndex = columnData.columnNames.findIndex((n: string) => n === 'Treatment Types'); + expect(colIndex).toBeGreaterThanOrEqual(0); + expect(entries.length).toBeGreaterThanOrEqual(1); + expect(entries[0][colIndex]).toBe('Type A, Type B'); + }); + + it('renders Treatment Types header immediately after Work Type header', async () => { + render(); + const table = await screen.findByTestId('work-allocation-table'); + const headers = Array.from(table.querySelectorAll('th')).map((h) => (h.textContent || '').trim()); + const workTypeIndex = headers.findIndex((t) => t === 'Work Type'); + const treatmentIndex = headers.findIndex((t) => t === 'Treatment Types'); + + expect(workTypeIndex).toBeGreaterThanOrEqual(0); // exists + expect(treatmentIndex).toBeGreaterThanOrEqual(0); // exists + expect(treatmentIndex).toBe(workTypeIndex + 1); // next to Work Type + }); +}); diff --git a/tests/unit/components/workAllocation/workAllocation.machine.spec.ts b/tests/unit/components/workAllocation/workAllocation.machine.spec.ts new file mode 100644 index 000000000..bcfcd355f --- /dev/null +++ b/tests/unit/components/workAllocation/workAllocation.machine.spec.ts @@ -0,0 +1,86 @@ +import { interpret, waitFor } from 'xstate'; +import createWorkAllocationMachine from '../../../../src/components/workAllocation/workAllocation.machine'; +import { WorkStatus } from '../../../../src/types/sdk'; + +// Test the success message after creating work in the machine. + +// Mock stanCore used by the machine +jest.mock('../../../../src/lib/sdk', () => { + return { + stanCore: { + GetWorkAllocationInfo: jest.fn().mockResolvedValue({ + comments: [], + projects: [{ name: 'Project A' }], + programs: [{ name: 'Program A' }], + omeroProjects: [], + worksWithComments: [], + workTypes: [{ name: 'Type A' }], + costCodes: [{ code: 'CC1' }], + releaseRecipients: [{ username: 'user1' }], + releaseDestinations: [{ name: 'Lead A' }], + treatmentTypes: [{ name: 'Fresh frozen', enabled: true }] + }), + CurrentUser: jest.fn().mockResolvedValue({ user: null }), + CreateWork: jest.fn().mockResolvedValue({ + createWork: { + workNumber: 'SGP999', + workType: { name: 'Type A' }, + project: { name: 'Project A' }, + program: { name: 'Program A' }, + costCode: { code: 'CC1' }, + workRequester: { username: 'user1' }, + numBlocks: 1, + numSlides: 2, + numOriginalSamples: 0, + treatmentTypes: [ + { name: 'Fresh frozen', enabled: true }, + { name: 'FFPE', enabled: true } + ] + } + }) + } + }; +}); + +describe('workAllocation machine - assignSuccessMessage', () => { + it('composes a successMessage on CreateWork done', async () => { + const machine = createWorkAllocationMachine({ urlParams: { status: [WorkStatus.Active] } }); + const service = interpret(machine).start(); + + // wait for loading -> ready + await waitFor(service, (s: any) => s.matches('ready'), { timeout: 3000 }); + + // send allocate event with values + service.send({ + type: 'ALLOCATE_WORK', + values: { + workType: 'Type A', + workRequester: 'user1', + project: 'Project A', + program: 'Program A', + costCode: 'CC1', + isRnD: false, + numBlocks: 1, + numSlides: 2, + numOriginalSamples: undefined, + ssStudyId: '', + facultyLead: 'Lead A', + treatmentTypes: ['FFPE', 'Mixed'] + } + }); + + // wait for successMessage to appear in context + const stateWithSuccess = await waitFor(service, (s: any) => Boolean(s.context && s.context.successMessage), { + timeout: 3000 + }); + const success = stateWithSuccess.context.successMessage as string; + + // success message starts with assigned work number + expect(success).toMatch(/Assigned SGP999/); + + // treatment types appear immediately after the work type + expect(success).toMatch(/Type A, treatment types Fresh frozen, FFPE/); + + service.stop(); + }); +}); diff --git a/tests/unit/components/workAllocation/workAllocation.spec.tsx b/tests/unit/components/workAllocation/workAllocation.spec.tsx new file mode 100644 index 000000000..37fad3583 --- /dev/null +++ b/tests/unit/components/workAllocation/workAllocation.spec.tsx @@ -0,0 +1,164 @@ +import { render, screen, fireEvent, waitFor, within } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import React from 'react'; +import WorkAllocation from '../../../../src/components/workAllocation/WorkAllocation'; +import { mockCreateObjectURL } from '../../testUtils/mockCreateObjectURL'; + +// Test the Treatment Types field in the WorkAllocation form, including +// enabled/disabled options, selecting/unselecting options, displaying +// selected options as chips in the UI, and submitting the form with the +// selected treatment types to see the success message. + +// Use shared URL.createObjectURL mock util for jsdom +mockCreateObjectURL(); + +// Mock dependencies as needed (e.g., xstate, react-router, etc.) +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: () => ({ search: '' }), + useNavigate: () => jest.fn() +})); + +// Mock t +jest.mock('../../../../src/lib/sdk', () => ({ + stanCore: { + AddProject: jest.fn(), + AddCostCode: jest.fn(), + AddOmeroProject: jest.fn(), + GetDnapStudy: jest.fn() + } +})); + +// Mock context data for the machine +const mockContext = { + projects: [{ name: 'Project A' }], + programs: [{ name: 'Program A' }], + omeroProjects: [{ name: 'Omero 1' }], + costCodes: [{ code: 'CC1' }], + workWithComments: [], + workTypes: [{ name: 'Type A' }], + workRequesters: [{ username: 'user1' }], + availableComments: [], + facultyLeads: [{ name: 'Lead A' }], + requestError: undefined, + // Success message is tested in workAllocation.machine.spec.ts + successMessage: + "Assigned SGP345 (Xenium v1, treatment types Fresh frozen, FFPE - 1 blocks and 2 slides and 1 original samples) to project (cost code description) Adenoids in Otitis Media, Omero project AA_ASD, DNAP study name 'NanoSeq on 5FU-treated oesphageal epithelioids', program CellGen and faculty lead Adams Group using cost code S2648 with the work requester ab70", + allocatedWorkNumber: 'SGP345', + treatmentTypes: [ + { name: 'Fresh frozen', enabled: true }, + { name: 'FFPE', enabled: true }, + { name: 'Fixed frozen', enabled: true }, + { name: 'Paxgene', enabled: true }, + { name: 'Mixed', enabled: true }, + { name: 'Enabled treatment', enabled: true }, + { name: 'Disabled treatment', enabled: false } + ] +}; + +jest.mock('@xstate/react', () => ({ + useMachine: () => [{ context: mockContext, matches: () => false }, jest.fn()] +})); + +// Helper to select option in react-select +async function selectTreatmentType(label: string) { + const selectDiv = screen.getByTestId('treatmentTypes'); + const input = selectDiv.querySelector('input'); + if (!input) throw new Error('Treatment Types input not found'); + fireEvent.keyDown(input, { key: 'ArrowDown' }); + await waitFor(() => within(selectDiv).getByText(label)); + fireEvent.click(within(selectDiv).getByText(label)); +} + +// Helper to fill the required form fields shared across tests +async function fillRequiredFields() { + fireEvent.change(screen.getByLabelText('Work Type'), { target: { value: 'Type A' } }); + fireEvent.change(screen.getByLabelText('Work Requester'), { target: { value: 'user1' } }); + fireEvent.change(screen.getByLabelText('Project (cost code description)'), { target: { value: 'Project A' } }); + fireEvent.change(screen.getByLabelText('Program'), { target: { value: 'Program A' } }); + fireEvent.change(screen.getByLabelText('Cost Code'), { target: { value: 'CC1' } }); + fireEvent.change(screen.getByLabelText('Faculty lead'), { target: { value: 'Lead A' } }); +} + +describe('WorkAllocation - Treatment Types', () => { + it('shows enabled treatment options and hides disabled options in dropdown', async () => { + render(); + + const selectDiv = screen.getByTestId('treatmentTypes'); + const input = selectDiv.querySelector('input'); + if (!input) throw new Error('Treatment Types input not found'); + fireEvent.keyDown(input, { key: 'ArrowDown' }); + + await waitFor(() => { + expect(within(selectDiv).getByText('Enabled treatment')).toBeInTheDocument(); + expect(within(selectDiv).queryByText('Disabled treatment')).not.toBeInTheDocument(); + }); + }); + + it('allows selecting treatment types and displays selected chips', async () => { + render(); + + // Select required fields + await fillRequiredFields(); + + // Select treatment types + await selectTreatmentType('FFPE'); + await selectTreatmentType('Fresh frozen'); + + // Assert both selected treatment types have a remove button because they are selected + await waitFor(() => { + expect(screen.getByLabelText('Remove FFPE')).toBeInTheDocument(); + expect(screen.getByLabelText('Remove Fresh frozen')).toBeInTheDocument(); + }); + }); + + it('shows success UI after submitting the form', async () => { + render(); + + // Select required fields + await fillRequiredFields(); + + // Select treatment types + await selectTreatmentType('FFPE'); + await selectTreatmentType('Fresh frozen'); + + // Submit the form + fireEvent.click(screen.getByRole('button', { name: /submit/i })); + + await waitFor(() => { + expect(screen.queryByTestId('success')).toBeInTheDocument(); + + // Assert that success message is displayed + const successSection = screen.getByTestId('success'); + expect(successSection).toHaveTextContent(/Assigned/i); + expect(successSection).toHaveTextContent(/treatment types Fresh frozen, FFPE/i); + + // Assert that reminder message is displayed + expect(screen.queryByTestId('reminder-div')).toBeInTheDocument(); + }); + }); + + it('allows unselecting a treatment type', async () => { + render(); + + // fill required fields and select two treatment types + await fillRequiredFields(); + await selectTreatmentType('FFPE'); + await selectTreatmentType('Fresh frozen'); + + // ensure both selected + await waitFor(() => { + expect(screen.getByLabelText('Remove FFPE')).toBeInTheDocument(); + expect(screen.getByLabelText('Remove Fresh frozen')).toBeInTheDocument(); + }); + + // unselect FFPE (click the remove button (X) on the FFPE) + fireEvent.click(screen.getByLabelText('Remove FFPE')); + + // FFPE should be removed, Fresh frozen should remain + await waitFor(() => { + expect(screen.queryByLabelText('Remove FFPE')).not.toBeInTheDocument(); // not selected + expect(screen.getByLabelText('Remove Fresh frozen')).toBeInTheDocument(); // still selected + }); + }); +}); diff --git a/tests/unit/components/workAllocation/workAllocationTable.spec.tsx b/tests/unit/components/workAllocation/workAllocationTable.spec.tsx new file mode 100644 index 000000000..258fa5ef4 --- /dev/null +++ b/tests/unit/components/workAllocation/workAllocationTable.spec.tsx @@ -0,0 +1,162 @@ +import { render, screen, within } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import React from 'react'; +import WorkAllocation from '../../../../src/components/workAllocation/WorkAllocation'; +import { mockCreateObjectURL } from '../../testUtils/mockCreateObjectURL'; +import * as AuthContext from '../../../../src/context/AuthContext'; + +// Test the presence and order of the Treatment Types header in the +// WorkAllocation table, and the rendering of treatment type pills with +// correct colours in the table rows. + +// Use shared URL.createObjectURL mock util for jsdom +mockCreateObjectURL(); + +// Mock xstate machine: return different shapes depending on machine id so +// WorkAllocation and WorkRow each get the context they expect. +jest.mock('@xstate/react', () => ({ + useMachine: (machine?: any) => { + // Work row machine expects a single `workWithComment` in context + if (machine && machine.id === 'workRowMachine') { + return [ + { + _nodes: [], + context: { + workWithComment: { + work: { + workNumber: 'SGP-1', + workType: { name: 'Work Type' }, + treatmentTypes: [ + { name: 'Fixed frozen', enabled: true }, + { name: 'Paxgene ', enabled: false } + ], + workRequester: { username: 'req' }, + project: { name: 'proj' }, + omeroProject: { name: 'omero' }, + dnapStudy: undefined, + costCode: { code: 'C1' }, + numBlocks: 1, + numSlides: 2, + numOriginalSamples: 3, + status: 'active', + priority: '', + program: { name: 'prog' }, + facultyLead: { name: 'lead' } + }, + comment: null + }, + editModeEnabled: false + }, + matches: (s: string): boolean => false + }, + jest.fn() + ]; + } + + // Default: WorkAllocation machine shape (provide at least one workWithComments entry + // and some availableComments so WorkRow doesn't crash when it reads them) + return [ + { + context: { + workWithComments: [ + { + work: { + workNumber: 'SGP-1', + workType: { name: 'Work Type' }, + treatmentTypes: [ + { name: 'Type A', enabled: true }, + { name: 'Type B', enabled: false } + ], + workRequester: { username: 'req' }, + project: { name: 'proj' }, + omeroProject: { name: 'omero' }, + dnapStudy: undefined, + costCode: { code: 'C1' }, + numBlocks: 1, + numSlides: 2, + numOriginalSamples: 3, + status: 'active', + priority: '', + program: { name: 'prog' }, + facultyLead: { name: 'lead' } + }, + comment: null + } + ], + workTypes: [], + workRequesters: [], + projects: [], + programs: [{ name: 'prog' }], + omeroProjects: [], + costCodes: [], + availableComments: [{ id: 1, text: 'test' }], + facultyLeads: [{ name: 'lead' }], + treatmentTypes: [] + }, + matches: (s: string): boolean => false + }, + jest.fn() + ]; + } +})); + +// Mock react-router hooks used by the component +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: () => ({ search: '' }), + useNavigate: () => jest.fn() +})); + +// Mock auth to ensure Authenticated renders children +jest.spyOn(AuthContext, 'useAuth').mockReturnValue({ + isAuthenticated: () => true, + userRoleIncludes: () => true, + authState: null, + setAuthState: () => {}, + clearAuthState: () => {}, + logout: () => {} +} as any); + +describe('WorkAllocation table headers', () => { + it('renders a Treatment Types header', async () => { + render(); + const table = await screen.findByTestId('work-allocation-table'); + const scoped = within(table); + + // Ensure the header cell (columnheader; th) for Treatment Types is present + expect(scoped.getByRole('columnheader', { name: 'Treatment Types' })).toBeInTheDocument(); + }); + + it('renders Treatment Types header immediately after Work Type header', async () => { + render(); + const table = await screen.findByTestId('work-allocation-table'); + const scoped = within(table); + const headers = scoped.getAllByRole('columnheader').map((h) => (h.textContent || '').trim()); + const workTypeIndex = headers.findIndex((t) => t === 'Work Type'); + const treatmentIndex = headers.findIndex((t) => t === 'Treatment Types'); + + expect(workTypeIndex).toBeGreaterThanOrEqual(0); // exists + expect(treatmentIndex).toBeGreaterThanOrEqual(0); // exists + expect(treatmentIndex).toBe(workTypeIndex + 1); // next to Work Type + }); + + it('renders treatment type pills for the row with correct colours', async () => { + render(); + const table = await screen.findByTestId('work-allocation-table'); + // find the row for our SGP + const sgpCell = within(table).getByText('SGP-1'); + const row = sgpCell.closest('tr') as HTMLTableRowElement | null; + expect(row).not.toBeNull(); + + // treatment type pills rendered in the treatment cell + // Use data-testid for reliable selection + const pillSpans = within(row!).getAllByTestId('treatment-type-pill'); + expect(pillSpans.length).toBe(2); + + // ensure one pill has the blue class and one has the pink class + const hasBlue = pillSpans.some((p) => (p.className || '').includes('bg-sdb-300')); + const hasPink = pillSpans.some((p) => (p.className || '').includes('bg-sp')); + expect(hasBlue).toBe(true); + expect(hasPink).toBe(true); + }); +}); diff --git a/tests/unit/pages/configuration.spec.tsx b/tests/unit/pages/configuration.spec.tsx new file mode 100644 index 000000000..6d04b38b5 --- /dev/null +++ b/tests/unit/pages/configuration.spec.tsx @@ -0,0 +1,100 @@ +import React from 'react'; +import { render, screen, fireEvent, within, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { StanCoreContext } from '../../../src/lib/sdk'; + +// Test that Configuration page shows Treatment Types tab and loads types with +// NAME/ENABLED columns and checkbox works. + +// Minimal configuration object for the page (only keys used by page construction must exist) +const mockConfiguration = { + comments: [], + equipments: [], + bioRisks: [], + cellClasses: [], + destructionReasons: [], + dnapStudies: [], + fixatives: [], + hmdmcs: [], + species: [], + releaseDestinations: [], + releaseRecipients: [], + projects: [], + programs: [], + proteinPanels: [], + cytassistProbePanels: [], + spikeProbePanels: [], + xeniumProbePanels: [], + users: [], + workTypes: [], + costCodes: [], + solutions: [], + tissueTypes: [], + treatmentTypes: [ + { name: 'Type A', enabled: true }, + { name: 'Type B', enabled: false } + ] +}; + +// Mock useLoaderData to return our mock configuration +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLoaderData: () => mockConfiguration, + useRevalidator: () => ({ revalidate: jest.fn() }) +})); + +describe('Configuration page - Treatment Types', () => { + afterEach(() => { + jest.resetAllMocks(); + jest.clearAllMocks(); + }); + + it('shows Treatment Types tab and loads types with NAME/ENABLED columns and checkbox works', async () => { + const mockStanCore = { + SetTreatmentTypeEnabled: jest.fn().mockResolvedValue({ setTreatmentTypeEnabled: {} }) + } as any; + + // require Configuration after we set up mocks so the module sees them + const Configuration = require('../../../src/pages/Configuration').default; + const { MemoryRouter } = require('react-router-dom'); + render( + React.createElement( + MemoryRouter, + null, + React.createElement(StanCoreContext.Provider, { value: mockStanCore }, React.createElement(Configuration)) + ) + ); + + // Heading present + expect(screen.getByText('Treatment Types')).toBeInTheDocument(); + + // switch to the Treatment Types tab + const tab = screen.getByText('Treatment Types'); + fireEvent.click(tab); + + // Scope assertions to the panel content to avoid matching duplicate text elsewhere + const panel = await screen.findByRole('tabpanel'); + + // EntityManager rendered and headers exist (check table header cells) + const headerEls = await within(panel).findAllByRole('columnheader'); + const headers = headerEls.map((h: any) => (h.textContent || '').trim()); + expect(headers.some((h: string) => /name/i.test(h))).toBeTruthy(); + expect(headers.some((h: string) => /enabled/i.test(h))).toBeTruthy(); + + // Rows loaded + expect(await within(panel).findByText('Type A')).toBeInTheDocument(); + expect(await within(panel).findByText('Type B')).toBeInTheDocument(); + + // Checkbox toggles call stanCore - find checkboxes within the panel (in row order) + const checkboxes = await within(panel).findAllByRole('checkbox'); + const chk = checkboxes[0] as HTMLInputElement; + expect(chk.checked).toBe(true); + + // click to disable + fireEvent.click(chk); + // actor is async - wait for the stanCore call + return waitFor(() => + expect(mockStanCore.SetTreatmentTypeEnabled).toHaveBeenCalledWith({ name: 'Type A', enabled: false }) + ); + }); +}); diff --git a/tests/unit/testUtils/mockCreateObjectURL.ts b/tests/unit/testUtils/mockCreateObjectURL.ts new file mode 100644 index 000000000..7c8f50933 --- /dev/null +++ b/tests/unit/testUtils/mockCreateObjectURL.ts @@ -0,0 +1,28 @@ +// Utility to mock and restore URL.createObjectURL and URL.revokeObjectURL for jsdom environment + +let originalCreateObjectURL: typeof global.URL.createObjectURL | undefined; +let originalRevokeObjectURL: typeof global.URL.revokeObjectURL | undefined; + +export function mockCreateObjectURL() { + beforeAll(() => { + originalCreateObjectURL = global.URL.createObjectURL; + global.URL.createObjectURL = jest.fn(() => 'mock-url'); + originalRevokeObjectURL = global.URL.revokeObjectURL; + global.URL.revokeObjectURL = jest.fn(); + }); + + afterAll(() => { + if (originalCreateObjectURL) { + global.URL.createObjectURL = originalCreateObjectURL; + } else { + // @ts-expect-error: createObjectURL may not exist + delete global.URL.createObjectURL; + } + if (originalRevokeObjectURL) { + global.URL.revokeObjectURL = originalRevokeObjectURL; + } else { + // @ts-expect-error: revokeObjectURL may not exist + delete global.URL.revokeObjectURL; + } + }); +}