Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

WIP: Create samples from sequence run #1321

Draft
wants to merge 49 commits into
base: development
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
4029aba
creating the new create samples page
ksierks Jun 6, 2022
4ae35b0
Merge branch 'development' into create-samples-from-sequence-run
ksierks Jun 7, 2022
1fa43bf
playing with react-dnd
ksierks Jun 9, 2022
34de15b
adding a conditon to canDrop
ksierks Jun 9, 2022
47ea1e3
refactoring
ksierks Jun 9, 2022
545c79b
working on the sequencing files within a sample card
ksierks Jun 15, 2022
3889466
Merge branch 'development' into create-samples-from-sequence-run
ksierks Jun 15, 2022
0529854
adding remove sample functionality
ksierks Jun 15, 2022
2cf1017
adding drop restrictions
ksierks Jun 15, 2022
00f9906
fixing admin routing
ksierks Jun 20, 2022
63acb7f
adding avatar
ksierks Jun 20, 2022
503942d
starting to add a shadow on hover
ksierks Jun 20, 2022
acbf97d
working on shadow
ksierks Jun 21, 2022
968e1b3
updating sequence file names in required-data.sql
ksierks Jun 21, 2022
9f62c24
setting samples on page load
ksierks Jun 21, 2022
5ba415d
working on adding a list of file pairs within a sample
ksierks Jun 24, 2022
1397957
Merge branch 'development' into create-samples-from-sequence-run
ksierks Jun 24, 2022
cb49c86
removing the pair from the pair list if it's empty
ksierks Jun 24, 2022
e7f714b
starting to refactor
ksierks Jun 24, 2022
b0e3a0d
working on routing
ksierks Jun 27, 2022
5b43e5b
fixing can drop for sequencing run list
ksierks Jun 27, 2022
84c4388
Merge branch 'development' into create-samples-from-sequence-run
ksierks Jun 29, 2022
de210e3
adding scrolling
ksierks Jun 29, 2022
b9e31ab
renaming deleteSample
ksierks Jun 29, 2022
59731c5
adding a background color to the list on isover
ksierks Jun 29, 2022
692f5c0
adding remove icon to file in sample card
ksierks Jun 29, 2022
f84df5f
fixing flex for skeleton
ksierks Jun 29, 2022
9eacc6b
some css changes
ksierks Jul 4, 2022
4cb6d74
Merge branch 'development' into create-samples-from-sequence-run
ksierks Jul 4, 2022
2b5fdf5
starting modal
ksierks Jul 5, 2022
fdc2cdb
working on retrieving projects
ksierks Jul 7, 2022
717e456
working on displaying projects
ksierks Jul 8, 2022
6ee2394
working on retrieving samples
ksierks Jul 12, 2022
cc8f773
removing organism from modal
ksierks Jul 13, 2022
252f8f8
fixing urls in samplesApi
ksierks Jul 13, 2022
c5370d2
Merge branch 'development' into create-samples-from-sequence-run
ksierks Jul 14, 2022
7be3b49
fixing urls in samplesApi
ksierks Jul 14, 2022
a2f4023
working on sample name validation
ksierks Jul 14, 2022
6888caa
working on validating sample name
ksierks Jul 15, 2022
6e248bd
working on creating sample
ksierks Jul 15, 2022
cf3c162
fixing populating samples in modal
ksierks Jul 25, 2022
8bfa715
Merge branch 'development' into create-samples-from-sequence-run
ksierks Jul 25, 2022
8903c38
displaying project id in sample card
ksierks Jul 26, 2022
bd23eb1
setting up create sample functionality
ksierks Jul 27, 2022
53898eb
working on create samples functionality
ksierks Jul 28, 2022
79b6c15
fix broken test
ksierks Aug 2, 2022
d396bf9
adding missing i18n
ksierks Aug 3, 2022
7c7ac6c
adding fast5 to required-data.sql
ksierks Aug 3, 2022
8809a86
adding restrictions to disallow creating pairs for fast5
ksierks Aug 3, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/main/resources/i18n/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1578,6 +1578,17 @@ SequencingRunDetailsPage.list.description=Description
SequencingRunDetailsPage.table.fileName=Filename
SequencingRunDetailsPage.table.size=Size
SequencingRunDetailsPage.table.download=Download
SequencingRunDetailsPage.button=Create Samples

SequencingRunCreateSamplesPage.title=Sequencing Run {0}
SequencingRunCreateSamplesPage.files.title=Sequencing Files
SequencingRunCreateSamplesPage.samples.title=Samples
SequencingRunCreateSamplesPage.samples.button= Add New Sample
SequencingRunCreateSamplesPage.empty=No Sequencing Run Files.

SequencingRunSamplesList.empty=Add a new sample to get started.

SequencingRunSampleFilesList.empty=Drag & drop sequencing files.

server.sequencingruns.status.COMPLETE=Complete
server.sequencingruns.status.UPLOADING=Uploading
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export const sequencingRunsApi = createApi({
query: (runId) => ({
url: `${runId}/sequenceFiles`,
}),
transformResponse(response, meta) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Wondering if this should not be done through a utility class in the controller. Reason I am asking is that another controller might want to get the sequencing files but not need this attribute.

return response.map((file) => {
return { ...file, show: true };
});
},
}),
/*
Delete a sequencing run.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from "react";
import { PageWrapper } from "../../../components/page/PageWrapper";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { useGetSequencingRunFilesQuery } from "../../../apis/sequencing-runs/sequencing-runs";
import { useNavigate, useParams } from "react-router-dom";
import { Col, Row, Typography } from "antd";
import { AddNewButton } from "../../../components/Buttons/AddNewButton";
import { SequencingRunSamplesList } from "./SequencingRunSamplesList";
import { SequencingRunFilesList } from "./SequencingRunFilesList";
import { useDispatch, useSelector } from "react-redux";
import { addSample, setFiles } from "../services/runReducer";

/**
* React component to display page that creates samples from a sequencing run.
* @returns {*}
* @constructor
*/
export default function SequencingRunCreateSamplesPage() {
const { runId } = useParams();
const navigate = useNavigate();
const dispatch = useDispatch();
const { data = [] } = useGetSequencingRunFilesQuery(runId);

React.useEffect(() => {
dispatch(setFiles(data));
}, [data]);

const { files, samples } = useSelector((state) => state.reducer);

const addNewSample = () => {
dispatch(
addSample({
sampleName: "New Sample",
forwardSequenceFile: null,
reverseSequenceFile: null,
})
);
};

return (
<PageWrapper
title={i18n("SequencingRunCreateSamplesPage.title", runId)}
onBack={() => navigate(-1)}
headerExtras={
<AddNewButton
onClick={addNewSample}
text={i18n("SequencingRunCreateSamplesPage.samples.button")}
/>
}
>
<DndProvider backend={HTML5Backend}>
<Row gutter={32}>
<Col span={16}>
<Typography.Title level={5}>
{i18n("SequencingRunCreateSamplesPage.samples.title")}
</Typography.Title>
<SequencingRunSamplesList samples={samples} />
</Col>
<Col span={8}>
<Typography.Title level={5}>
{i18n("SequencingRunCreateSamplesPage.files.title")}
</Typography.Title>
<SequencingRunFilesList
samples={samples}
files={files.filter((item) => item.show)}
/>
</Col>
</Row>
</DndProvider>
</PageWrapper>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { NarrowPageWrapper } from "../../../components/page/NarrowPageWrapper";
import { SequencingRunStatusBadge } from "./SequencingRunStatusBadge";
import { grey1 } from "../../../styles/colors";
import { SPACE_LG } from "../../../styles/spacing";
import { AddNewButton } from "../../../components/Buttons/AddNewButton";

const { Content } = Layout;

Expand Down Expand Up @@ -123,6 +124,12 @@ export default function SequencingRunDetailsPage() {
<NarrowPageWrapper
title={i18n("SequencingRunDetailsPage.title", runId)}
onBack={showBack ? goToAdminSequenceRunListPage : undefined}
headerExtras={
<AddNewButton
href={setBaseUrl(`sequencing-runs/${runId}/samples`)}
text={i18n("SequencingRunDetailsPage.button")}
/>
}
>
<Layout>
<Content
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from "react";
import { useDrag } from "react-dnd";
import { Card } from "antd";

/**
* React component to render a drag'n'drop card.
* @param {object} file - the file that is being stored in the card
* @param {number} index - the index of the array the card is moving from
* @param {object} children - content to display in card
* @param {object} props - any other attributes to add
* @returns {JSX.Element} - Returns a card component
*/
export function SequencingRunFileCard({
file,
index = null,
children,
...props
}) {
const [{ isDragging }, drag] = useDrag({
type: "card",
item: { file, prevIndex: index },
});

return (
<Card ref={drag} {...props}>
{children}
</Card>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Empty, List } from "antd";
import { SequencingRunFileCard } from "./SequencingRunFileCard";
import React from "react";
import { useDrop } from "react-dnd";
import { addFile, updateSample } from "../services/runReducer";
import { useDispatch } from "react-redux";

/**
* React component to render the list of sequencing run files.
* @param {array} samples - list of samples
* @param {array} files - list of sequencing run files
* @returns {JSX.Element} - Returns a list component
*/
export function SequencingRunFilesList({ samples, files }) {
const dispatch = useDispatch();

const [{ canDrop, isOver }, drop] = useDrop({
accept: "card",
canDrop: (item, monitor) => {
if (item.prevIndex === null) {
return false;
} else {
return true;
}
},
drop: (item) => {
const { file, prevIndex } = item;
const prevSample = samples[prevIndex];
const newSample = {
sampleName: prevSample.sampleName,
forwardSequenceFile:
prevSample.forwardSequenceFile?.id === file.id
? prevSample.reverseSequenceFile
: prevSample.forwardSequenceFile,
reverseSequenceFile: null,
};
dispatch(updateSample(newSample, prevIndex));
dispatch(addFile(file.id));
},
});

return (
<div ref={drop}>
{files.length === 0 ? (
<Empty
description={i18n("SequencingRunCreateSamplesPage.empty")}
image={Empty.PRESENTED_IMAGE_SIMPLE}
/>
) : (
<List
grid={{ column: 1 }}
dataSource={files}
renderItem={(file) => {
return (
<List.Item>
<SequencingRunFileCard file={file}>
{file.fileName}
</SequencingRunFileCard>
</List.Item>
);
}}
/>
)}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import React from "react";
import { useDrop } from "react-dnd";
import { Button, Card, Col, Row } from "antd";
import {
IconArrowLeft,
IconArrowRight,
IconRemove,
IconSwap,
} from "../../../components/icons/Icons";
import { grey1 } from "../../../styles/colors";
import { FONT_COLOR_PRIMARY } from "../../../styles/fonts";
import { SequencingRunFileCard } from "./SequencingRunFileCard";
import { SPACE_LG } from "../../../styles/spacing";
import {
addFile,
deleteSample,
removeFile,
updateSample,
} from "../services/runReducer";
import { useDispatch } from "react-redux";

/**
* React component to render a sample.
* @param {array} samples - list of samples
* @param {object} sample - the sample
* @param {number} index - the index of the sample in the samples array
* @returns {JSX.Element} - Returns a sample component
*/
export function SequencingRunSample({ samples, sample, index }) {
const dispatch = useDispatch();

const removeSample = () => {
if (sample.forwardSequenceFile !== null)
dispatch(addFile(sample.forwardSequenceFile.id));
if (sample.reverseSequenceFile !== null)
dispatch(addFile(sample.reverseSequenceFile.id));
dispatch(deleteSample(index));
};

const switchPair = () => {
const newSample = {
sampleName: sample.sampleName,
forwardSequenceFile: sample.reverseSequenceFile,
reverseSequenceFile: sample.forwardSequenceFile,
};
dispatch(updateSample(newSample, index));
};

const [{ canDrop, isOver }, drop] = useDrop({
accept: "card",
canDrop: (item, monitor) => {
//do not drop on the same sample card
if (item.prevIndex === index) {
return false;
//do not drop on a full sample card
} else if (
sample.forwardSequenceFile !== null &&
sample.reverseSequenceFile !== null
) {
return false;
} else {
return true;
}
},
drop: (item) => {
const { file, prevIndex } = item;

//remove file from previous location
if (prevIndex === null) {
dispatch(removeFile(file.id));
} else {
const prevSample = samples[prevIndex];
const newSample = {
sampleName: prevSample.sampleName,
forwardSequenceFile:
file.id === prevSample.forwardSequenceFile?.id
? prevSample.reverseSequenceFile
: prevSample.forwardSequenceFile,
reverseSequenceFile: null,
};
dispatch(updateSample(newSample, prevIndex));
}

//add file to target location
if (sample.forwardSequenceFile === null) {
const newSample = {
sampleName: sample.sampleName,
forwardSequenceFile: file,
reverseSequenceFile: sample.reverseSequenceFile,
};
dispatch(updateSample(newSample, index));
} else if (sample.reverseSequenceFile === null) {
const newSample = {
sampleName: sample.sampleName,
forwardSequenceFile: sample.forwardSequenceFile,
reverseSequenceFile: file,
};
dispatch(updateSample(newSample, index));
} else {
//do nothing
}
},
});

return (
<Card
title={sample.sampleName}
ref={drop}
extra={
<Button
onClick={removeSample}
style={{ border: "none" }}
icon={<IconRemove />}
/>
}
>
<Row align="middle" justify="center">
{sample.forwardSequenceFile !== null && (
<>
<Col flex="75px">
<IconArrowRight
style={{
fontSize: "2em",
flex: 1,
padding: SPACE_LG,
color: grey1,
backgroundColor: FONT_COLOR_PRIMARY,
}}
/>
</Col>
<Col flex="auto">
<SequencingRunFileCard
file={sample.forwardSequenceFile}
index={index}
>
{sample.forwardSequenceFile.fileName}
</SequencingRunFileCard>
</Col>
</>
)}
{sample.reverseSequenceFile !== null && (
<>
<Col span={2} offset={1}>
<Button
onClick={switchPair}
style={{ border: "none" }}
icon={<IconSwap />}
/>
</Col>
<Col flex="75px">
<IconArrowLeft
style={{
fontSize: "2em",
flex: 1,
padding: SPACE_LG,
color: grey1,
backgroundColor: FONT_COLOR_PRIMARY,
}}
/>
</Col>
<Col flex="auto">
<SequencingRunFileCard
file={sample.reverseSequenceFile}
index={index}
>
{sample.reverseSequenceFile.fileName}
</SequencingRunFileCard>
</Col>
</>
)}
</Row>
</Card>
);
}
Loading