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

Metadata uploader - PARSE FILE #1362

Merged
merged 43 commits into from
Oct 24, 2022
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
9331f47
reading file with sheetjs
ksierks Aug 31, 2022
74c1369
persisting file contents
ksierks Sep 1, 2022
cee5320
Merge branch 'metadata-uploader-base' into metadata-uploader-parse-file
ksierks Sep 8, 2022
ece38b9
setting sample name column
ksierks Sep 9, 2022
b60996b
starting to show metadata in table on review step
ksierks Sep 9, 2022
becabf5
starting to show metadata in table on review step
ksierks Sep 9, 2022
9789f43
supplementing metadata after sample name column is selected
ksierks Sep 12, 2022
5876171
fixing onCell for sample name collumn
ksierks Sep 13, 2022
f0aebf6
working on validating sample names
ksierks Sep 14, 2022
86daff7
navigating to review page once setting sample name is complete
ksierks Sep 15, 2022
1f2f2b0
starting to create/update samples with metadata
ksierks Sep 16, 2022
f141727
displaying stats on complete page
ksierks Sep 16, 2022
b70cf42
fixing stats
ksierks Sep 20, 2022
5ab8c35
showing errors
ksierks Sep 20, 2022
9201e48
keeping formatted numeric cells instead of raw
ksierks Sep 21, 2022
77702a9
fixing tests
ksierks Sep 21, 2022
cfeafaa
fixing typescript errors
ksierks Sep 21, 2022
2fcfe85
adding table pagination to review table
ksierks Sep 21, 2022
11c6ffa
adding progress bar for saving samples
ksierks Sep 22, 2022
22d4aea
trying chunk uploading
ksierks Sep 23, 2022
a87f615
fixing test
ksierks Sep 23, 2022
e20bc82
adding some spinners to pages to show loading
ksierks Sep 27, 2022
9dd9e60
navigating back to beginning after upload complete
ksierks Sep 27, 2022
facf54f
storing saving and validation details in seperate hashes instead of m…
ksierks Sep 28, 2022
6cb252f
Merge branch 'metadata-uploader-base' into metadata-uploader-parse-file
ksierks Sep 28, 2022
4b8c1d6
skipping saving samples that have already been saved
ksierks Sep 28, 2022
d47b7ff
converting SampleMetadataImportSteps.jsx to typescript
ksierks Sep 28, 2022
29612d2
converting SampleMetadataImportUploadFile.jsx to typescript
ksierks Sep 29, 2022
ef985c4
converting SampleMetadataImportMapHeaders.jsx to typescript
ksierks Oct 4, 2022
ce16ddf
converting SampleMetadataImportReview.tsx to typescript
ksierks Oct 5, 2022
997c1d5
converting SampleMetadataImportComplete.jsx to typescript
ksierks Oct 5, 2022
02987a9
more typescript
ksierks Oct 5, 2022
b907052
getting around deadlock
ksierks Oct 5, 2022
3a17030
fix test
ksierks Oct 5, 2022
a50d6d7
eslinting
ksierks Oct 7, 2022
d5b971a
removing todo
ksierks Oct 11, 2022
981db32
saving state on page refresh
ksierks Oct 18, 2022
e208e2a
changing for loop syntax
ksierks Oct 18, 2022
aeeb549
making children not optional for SampleMetadataImportWizardProps
ksierks Oct 18, 2022
0e459f6
fixing wizard layout
ksierks Oct 18, 2022
4da7a74
removing file extension on DragUpload import
ksierks Oct 18, 2022
43b438e
fixing DraagUpload type
ksierks Oct 18, 2022
e022f0d
updating colors to use css variables
ksierks Oct 18, 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
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ public ResponseEntity<AjaxResponse> createSample(CreateSampleRequest request, Lo
if (!Strings.isNullOrEmpty(request.getDescription())) {
sample.setDescription(request.getDescription());
}
Join<Project, Sample> join = projectService.addSampleToProject(project, sample, true);
Join<Project, Sample> join = projectService.addSampleToProjectWithoutEvent(project, sample, true);
if (request.getMetadata() != null) {
Set<MetadataEntry> metadataEntrySet = request.getMetadata().stream().map(entry -> {
MetadataTemplateField field = metadataTemplateService.saveMetadataField(
Expand Down Expand Up @@ -169,8 +169,12 @@ public ResponseEntity<AjaxResponse> updateSample(UpdateSampleRequest request, Lo
try {
Sample sample = sampleService.read(sampleId);
sample.setSampleName(request.getName());
sample.setOrganism(request.getOrganism());
sample.setDescription(request.getDescription());
if (!Strings.isNullOrEmpty(request.getOrganism())) {
sample.setOrganism(request.getOrganism());
}
if (request.getDescription() != null) {
sample.setDescription(request.getDescription());
}
if (request.getMetadata() != null) {
Set<MetadataEntry> metadataEntrySet = request.getMetadata().stream().map(entry -> {
MetadataTemplateField field = metadataTemplateService.saveMetadataField(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,17 @@ public Join<Project, UserGroup> updateUserGroupProjectMetadataRole(Project proje
*/
public Join<Project, Sample> addSampleToProject(Project project, Sample sample, boolean owner);

/**
* Add the specified {@link Sample} to the {@link Project} without creating an event.
*
* @param project the {@link Project} to add the {@link Sample} to.
* @param sample the {@link Sample} to add to the {@link Project}. If the {@link Sample} has not previously been
* persisted, the service will persist the {@link Sample}.
* @param owner Whether the project will have modification access for this sample
* @return a reference to the relationship resource created between the two entities.
*/
public Join<Project, Sample> addSampleToProjectWithoutEvent(Project project, Sample sample, boolean owner);

/**
* Move a {@link Sample} from one {@link Project} to another
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
Expand Down Expand Up @@ -411,8 +410,9 @@ public ProjectSampleJoin addSampleToProject(Project project, Sample sample, bool
// Check to ensure a sample with this sample name doesn't exist in this
// project already
if (sampleRepository.getSampleBySampleName(project, sample.getSampleName()) != null) {
throw new ExistingSampleNameException("Sample with the name '" + sample.getSampleName()
+ "' already exists in project " + project.getId(), sample);
throw new ExistingSampleNameException(
"Sample with the name '" + sample.getSampleName() + "' already exists in project "
+ project.getId(), sample);
}

// the sample hasn't been persisted before, persist it before calling
Expand All @@ -438,13 +438,24 @@ public ProjectSampleJoin addSampleToProject(Project project, Sample sample, bool
}
}

/**
* {@inheritDoc}
*/
@Override
@Transactional
@PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_SEQUENCER') or (hasPermission(#project, 'isProjectOwner'))")
public ProjectSampleJoin addSampleToProjectWithoutEvent(Project project, Sample sample, boolean owner) {
return addSampleToProject(project, sample, owner);
}

/**
* {@inheritDoc}
*/
@Override
@Transactional
@LaunchesProjectEvent(SampleAddedProjectEvent.class)
@PreAuthorize("hasRole('ROLE_ADMIN') or ( hasPermission(#source, 'isProjectOwner') and hasPermission(#destination, 'isProjectOwner'))")
@PreAuthorize(
"hasRole('ROLE_ADMIN') or ( hasPermission(#source, 'isProjectOwner') and hasPermission(#destination, 'isProjectOwner'))")
public ProjectSampleJoin moveSampleBetweenProjects(Project source, Project destination, Sample sample) {
//read the existing ProjectSampleJoin to see if we're the owner
ProjectSampleJoin projectSampleJoin = psjRepository.readSampleForProject(source, sample);
Expand Down Expand Up @@ -608,15 +619,16 @@ public Collection<UserGroupProjectJoin> getUserGroupProjectJoins(User user, Proj
* {@inheritDoc}
*/
@Override
@PreAuthorize("hasRole('ROLE_ADMIN') or hasPermission(#subject,'isProjectOwner') and hasPermission(#relatedProject,'canReadProject')")
@PreAuthorize(
"hasRole('ROLE_ADMIN') or hasPermission(#subject,'isProjectOwner') and hasPermission(#relatedProject,'canReadProject')")
public RelatedProjectJoin addRelatedProject(Project subject, Project relatedProject) {
if (subject.equals(relatedProject)) {
throw new IllegalArgumentException("Project cannot be related to itself");
}

try {
RelatedProjectJoin relation = relatedProjectRepository
.save(new RelatedProjectJoin(subject, relatedProject));
RelatedProjectJoin relation = relatedProjectRepository.save(
new RelatedProjectJoin(subject, relatedProject));
return relation;
} catch (DataIntegrityViolationException e) {
throw new EntityExistsException(
Expand Down Expand Up @@ -692,8 +704,8 @@ public Join<Project, ReferenceFile> addReferenceFileToProject(Project project, R
@Override
@PreAuthorize("hasRole('ROLE_ADMIN') or hasPermission(#project, 'isProjectOwner')")
public void removeReferenceFileFromProject(Project project, ReferenceFile file) {
List<Join<Project, ReferenceFile>> referenceFilesForProject = prfjRepository
.findReferenceFilesForProject(project);
List<Join<Project, ReferenceFile>> referenceFilesForProject = prfjRepository.findReferenceFilesForProject(
project);
Join<Project, ReferenceFile> specificJoin = null;
for (Join<Project, ReferenceFile> join : referenceFilesForProject) {
if (join.getObject().equals(file)) {
Expand All @@ -704,8 +716,9 @@ public void removeReferenceFileFromProject(Project project, ReferenceFile file)
if (specificJoin != null) {
prfjRepository.delete((ProjectReferenceFileJoin) specificJoin);
} else {
throw new EntityNotFoundException("Cannot find a join for project [" + project.getName()
+ "] and reference file [" + file.getLabel() + "].");
throw new EntityNotFoundException(
"Cannot find a join for project [" + project.getName() + "] and reference file [" + file.getLabel()
+ "].");
}
}

Expand Down Expand Up @@ -849,8 +862,8 @@ public Project createProjectWithSamples(Project project, List<Long> sampleIds, b
@PostFilter("hasPermission(filterObject, 'canReadProject')")
@Override
public List<Project> getProjectsUsedInAnalysisSubmission(AnalysisSubmission submission) {
Set<SequencingObject> findSequencingObjectsForAnalysisSubmission = sequencingObjectRepository
.findSequencingObjectsForAnalysisSubmission(submission);
Set<SequencingObject> findSequencingObjectsForAnalysisSubmission = sequencingObjectRepository.findSequencingObjectsForAnalysisSubmission(
submission);

// get available projects
Set<Project> projectsInAnalysis = getProjectsForSequencingObjects(findSequencingObjectsForAnalysisSubmission);
Expand Down Expand Up @@ -891,7 +904,7 @@ private static final Sort getOrDefaultSort(Sort sort) {
* @param projectRole The {@link ProjectRole} to search for.
* @param user The user to search
* @return a {@link Specification} to search for {@link Project} where the specified {@link User} has a certain
* {@link ProjectRole}.
* {@link ProjectRole}.
*/
private static final Specification<ProjectUserJoin> getProjectJoinsWithRole(User user, ProjectRole projectRole) {
return new Specification<ProjectUserJoin>() {
Expand Down
80 changes: 0 additions & 80 deletions src/main/webapp/resources/js/apis/metadata/metadata-import.js

This file was deleted.

14 changes: 0 additions & 14 deletions src/main/webapp/resources/js/apis/metadata/sample-utils.js

This file was deleted.

18 changes: 18 additions & 0 deletions src/main/webapp/resources/js/apis/metadata/sample-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Utility file for samples
*/

export const sampleNameRegex = new RegExp("^[A-Za-z0-9-_]{3,}$");
ericenns marked this conversation as resolved.
Show resolved Hide resolved

/**
* Checks is the sample name is valid
* @param sampleName the name of the sample
* @returns if the sample name is valid
*/
export function validateSampleName(sampleName: string): boolean {
if (sampleName) {
return sampleNameRegex.test(sampleName);
} else {
return false;
}
}
71 changes: 71 additions & 0 deletions src/main/webapp/resources/js/apis/projects/samples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,44 @@ import {
} from "../../types/irida";
import { getProjectIdFromUrl, setBaseUrl } from "../../utilities/url-utilities";
import { get, post } from "../requests";
import axios from "axios";

export interface SequencingFiles {
singles: SingleEndSequenceFile[];
pairs: PairedEndSequenceFile[];
}

export interface ValidateSampleNameModel {
ids?: number[];
name: string;
}

export interface ValidateSamplesResponse {
samples: ValidateSampleNameModel[];
}

export interface MetadataItem {
[field: string]: string;
rowKey: string;
}

export interface FieldUpdate {
field: string;
value: string;
}

export interface SampleRequest {
name: string;
organism?: string;
description?: string;
metadata: FieldUpdate[];
}

export interface ValidateSampleNamesRequest {
samples: ValidateSampleNameModel[];
associatedProjectIds?: number[];
}

const PROJECT_ID = getProjectIdFromUrl();
const URL = setBaseUrl(`/ajax/projects`);

Expand Down Expand Up @@ -70,6 +102,45 @@ export const {
useShareSamplesWithProjectMutation,
} = samplesApi;

export async function validateSamples({
projectId,
body,
}: {
projectId: string;
body: ValidateSampleNamesRequest;
}): Promise<ValidateSamplesResponse> {
const response = await axios.post(
`${URL}/${projectId}/samples/validate`,
body
);
return response.data;
}

export async function createSample({
projectId,
body,
}: {
projectId: string;
body: SampleRequest;
}) {
return await axios.post(`${URL}/${projectId}/samples/add-sample`, body);
}

export async function updateSample({
projectId,
sampleId,
body,
}: {
projectId: string;
sampleId: number;
body: SampleRequest;
}) {
return await axios.patch(
`${URL}/${projectId}/samples/add-sample/${sampleId}`,
body
);
}

/**
* Server side validation of a new sample name.
* @param name - sample name to validate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ const RadioItem = styled.button`
}
`;

interface BlockRadioInputProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
children?: React.ReactNode;
}

/**
* React component to Render a Ant Design Radio button in block.
*
Expand All @@ -27,6 +32,9 @@ const RadioItem = styled.button`
* @returns {JSX.Element}
* @constructor
*/
export function BlockRadioInput({ children, ...props }) {
export function BlockRadioInput({
children,
...props
}: BlockRadioInputProps): JSX.Element {
return <RadioItem {...props}>{children}</RadioItem>;
}
Loading