Skip to content

Commit

Permalink
35254 Add Metagenomic workflow
Browse files Browse the repository at this point in the history
- Added Index Assignment tab
- Implemented Assign by table to fit usage of MetagenimicsBatchItem and PcrBatchItem
- Assign by table correctly loads Well Coordinates and Material Sample Name
- Can assign, save, and load Index i5 and Index i7
  • Loading branch information
johnphan96 committed Nov 28, 2024
1 parent e177a35 commit dad62c3
Show file tree
Hide file tree
Showing 6 changed files with 911 additions and 15 deletions.
4 changes: 2 additions & 2 deletions packages/common-ui/lib/api-client/useQuery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,6 @@ export function useQuery<TData extends KitsuResponseData, TMeta = undefined>(
);
}

await onSuccess?.(response);

if (joinSpecs) {
const { data } = response;
const resources = isArray(data) ? data : [data];
Expand All @@ -104,6 +102,8 @@ export function useQuery<TData extends KitsuResponseData, TMeta = undefined>(
}
}

await onSuccess?.(response);

return response;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,100 @@
export function MetagenomicsIndexAssignmentStep() {
return <></>;
import { PersistedResource } from "kitsu";
import { PcrBatch } from "packages/dina-ui/types/seqdb-api";
import { Dispatch, SetStateAction } from "react";
import Col from "react-bootstrap/Col";
import Nav from "react-bootstrap/Nav";
import Row from "react-bootstrap/Row";
import Tab from "react-bootstrap/Tab";
import { useSeqdbIntl } from "packages/dina-ui/intl/seqdb-intl";
import { useLocalStorage } from "@rehooks/local-storage";
import { MetagenomicsBatch } from "packages/dina-ui/types/seqdb-api/resources/metagenomics/MetagenomicsBatch";
import { useMetagenomicsIndexAssignmentAPI } from "../ngs-workflow/useMetagenomicsIndexAssignmentAPI";
import { MetagenomicsIndexGrid } from "../ngs-workflow/index-grid/MetagenomicsIndexGrid";
import { MetagenomicsIndexAssignmentTable } from "../ngs-workflow/MetagenomicsIndexAssignmentTable";

export interface MetagenomicsIndexAssignmentStepProps {
pcrBatchId: string;
pcrBatch: PcrBatch;
metagenomicsBatchId: string;
metagenomicsBatch: MetagenomicsBatch;
onSaved: (
nextStep: number,
batchSaved?: PersistedResource<MetagenomicsBatch>
) => Promise<void>;
editMode: boolean;
setEditMode: Dispatch<SetStateAction<boolean>>;
performSave: boolean;
setPerformSave: (newValue: boolean) => void;
}

export function MetagenomicsIndexAssignmentStep(
props: MetagenomicsIndexAssignmentStepProps
) {
const { formatMessage } = useSeqdbIntl();
// Get the last active tab from local storage (defaults to "assignByGrid")
const [activeKey, setActiveKey] = useLocalStorage(
"metagenomicsIndexAssignmentStep_activeTab",
"assignByGrid"
);

// Data required for both options is pretty much the same so share the data between both.
const metagenomicsIndexAssignmentApiProps =
useMetagenomicsIndexAssignmentAPI(props);

const handleSelect = (eventKey) => {
// Do not switch modes if in edit mode. This is used to prevent data from being mixed up.
if (props.editMode) {
return;
}

setActiveKey(eventKey);
};
return (
<Tab.Container
id="left-tabs-example"
activeKey={activeKey}
onSelect={handleSelect}
>
<Row>
<Col sm={2}>
<Nav variant="pills" className="flex-column">
<Nav.Item>
<Nav.Link
eventKey="assignByGrid"
style={{ cursor: props.editMode ? "default" : "pointer" }}
disabled={props.editMode}
>
{formatMessage("assignByGrid")}
</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link
eventKey="assignByTable"
style={{ cursor: props.editMode ? "default" : "pointer" }}
disabled={props.editMode}
>
{formatMessage("assignByTable")}
</Nav.Link>
</Nav.Item>
</Nav>
</Col>
<Col sm={10}>
<Tab.Content>
<Tab.Pane eventKey="assignByGrid">
<MetagenomicsIndexGrid
{...props}
{...metagenomicsIndexAssignmentApiProps}
/>
</Tab.Pane>
<Tab.Pane eventKey="assignByTable">
<MetagenomicsIndexAssignmentTable
{...props}
{...metagenomicsIndexAssignmentApiProps}
/>
</Tab.Pane>
</Tab.Content>
</Col>
</Row>
</Tab.Container>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import {
DinaForm,
LoadingSpinner,
ReactTable,
SelectField,
SelectOption,
SubmitButton,
useStringComparator
} from "packages/common-ui/lib";
import { IndexAssignmentStepProps } from "./IndexAssignmentStep";
import { useMemo } from "react";
import { PcrBatchItem } from "packages/dina-ui/types/seqdb-api";
import { ColumnDef } from "@tanstack/react-table";
import { MaterialSampleSummary } from "packages/dina-ui/types/collection-api";
import { UseIndexAssignmentReturn } from "./useIndexAssignmentAPI";
import { useSeqdbIntl } from "packages/dina-ui/intl/seqdb-intl";
import { MetagenomicsBatchItem } from "packages/dina-ui/types/seqdb-api/resources/metagenomics/MetagenomicsBatchItem";
import { MetagenomicsIndexAssignmentStepProps } from "../metagenomics-workflow/MetagenomicsIndexAssignmentStep";
import {
MetagenomicsIndexAssignmentResource,
UseMetagenomicsIndexAssignmentReturn
} from "./useMetagenomicsIndexAssignmentAPI";

interface MetagenomicsIndexAssignmentRow {
materialSample?: MaterialSampleSummary;
metagenomicsIndexAssignmentResource?: MetagenomicsIndexAssignmentResource;
pcrBatchItem?: PcrBatchItem;
}

interface MetagenomicsIndexAssignmentTableProps
extends MetagenomicsIndexAssignmentStepProps,
UseMetagenomicsIndexAssignmentReturn {}

export function MetagenomicsIndexAssignmentTable(
props: MetagenomicsIndexAssignmentTableProps
) {
const {
editMode,
metagenomicsBatch,
performSave,
setPerformSave,
loading,
metagenomicsIndexAssignmentResources,
materialSamples,
ngsIndexes,
onSubmitTable
} = props;

const { compareByStringAndNumber } = useStringComparator();
const { formatMessage } = useSeqdbIntl();

// Hidden button bar is used to submit the page from the button bar in a parent component.
const hiddenButtonBar = (
<SubmitButton
className="hidden"
performSave={performSave}
setPerformSave={setPerformSave}
/>
);

const COLUMNS: ColumnDef<MetagenomicsIndexAssignmentRow>[] = [
{
cell: ({ row: { original: sr } }) => {
const { metagenomicsIndexAssignmentResource } =
sr as MetagenomicsIndexAssignmentRow;
if (
!metagenomicsIndexAssignmentResource ||
!metagenomicsIndexAssignmentResource.storageUnitUsage
) {
return "";
}

const { wellRow, wellColumn } =
metagenomicsIndexAssignmentResource.storageUnitUsage;
const wellCoordinates =
wellColumn === null || !wellRow ? "" : `${wellRow}${wellColumn}`;

return wellCoordinates;
},
header: "Well Coordinates",
accessorKey: "wellCoordinates",
sortingFn: (a: any, b: any): number => {
const aStorageUnit =
a.original?.metagenomicsIndexAssignmentResource?.storageUnitUsage;
const bStorageUnit =
b.original?.metagenomicsIndexAssignmentResource?.storageUnitUsage;

const aString =
!aStorageUnit ||
aStorageUnit?.wellRow === null ||
aStorageUnit?.wellColumn === null
? ""
: `${aStorageUnit?.wellRow}${aStorageUnit?.wellColumn}`;
const bString =
!bStorageUnit ||
bStorageUnit?.wellRow === null ||
bStorageUnit?.wellColumn === null
? ""
: `${bStorageUnit?.wellRow}${bStorageUnit?.wellColumn}`;
return compareByStringAndNumber(aString, bString);
},
enableSorting: true,
size: 150
},
{
header: "Material Sample Name",
accessorKey: "materialSample.materialSampleName"
},
{
header: "Index i5",
cell: ({ row: { index } }) =>
metagenomicsBatch.indexSet && (
<SelectField
hideLabel={true}
name={`indexAssignment[${index}].indexI5`}
options={ngsIndexes
?.filter((ngsIndex) => ngsIndex.direction === "I5")
?.map<SelectOption<string>>((ngsIndex) => ({
label: ngsIndex.name,
value: ngsIndex.id
}))}
selectProps={{
isClearable: true
}}
/>
)
},
{
header: "Index i7",
cell: ({ row: { index } }) =>
metagenomicsBatch.indexSet && (
<SelectField
hideLabel={true}
name={`indexAssignment[${index}].indexI7`}
options={ngsIndexes
?.filter((ngsIndex) => ngsIndex.direction === "I7")
?.map<SelectOption<string>>((ngsIndex) => ({
label: ngsIndex.name,
value: ngsIndex.id
}))}
selectProps={{
isClearable: true
}}
/>
)
}
];

const tableData = useMemo(
() =>
metagenomicsIndexAssignmentResources &&
metagenomicsIndexAssignmentResources.length !== 0
? metagenomicsIndexAssignmentResources.map<MetagenomicsIndexAssignmentRow>(
(prep) => ({
metagenomicsIndexAssignmentResource: prep,
materialSample: materialSamples?.find(
(samp) => samp.id === prep?.materialSampleSummary?.id
)
})
)
: [],
[metagenomicsIndexAssignmentResources, materialSamples]
);

const initialValues = useMemo(() => {
if (
!metagenomicsIndexAssignmentResources ||
metagenomicsIndexAssignmentResources.length === 0
) {
return {};
}

return {
indexAssignment: metagenomicsIndexAssignmentResources.map((resource) => ({
...(resource.indexI5 ? { indexI5: resource?.indexI5?.id } : {}),
...(resource.indexI7 ? { indexI7: resource?.indexI7?.id } : {})
}))
};
}, [metagenomicsIndexAssignmentResources]);

if (loading) {
return <LoadingSpinner loading={true} />;
}

if (!metagenomicsBatch?.indexSet?.id) {
return (
<div className="alert alert-warning mt-2">
{formatMessage("missingIndexForAssignment")}
</div>
);
}

return (
<DinaForm
initialValues={initialValues}
onSubmit={onSubmitTable}
readOnly={editMode === false}
enableReinitialize={true}
>
{hiddenButtonBar}
<ReactTable<MetagenomicsIndexAssignmentRow>
className="-striped react-table-overflow"
columns={COLUMNS}
data={tableData}
loading={loading}
manualPagination={true}
showPagination={false}
pageSize={tableData.length}
sort={[{ id: "wellCoordinates", desc: false }]}
/>
</DinaForm>
);
}
Loading

0 comments on commit dad62c3

Please sign in to comment.