Commit 8a74d740 authored by Thomas Matthews's avatar Thomas Matthews

Merge branch 'development' into lock-after-failed-logins

parents af7b9780 16d3c23a
Pipeline #6468 failed with stage
in 25 minutes and 8 seconds
...@@ -18,6 +18,7 @@ Changes ...@@ -18,6 +18,7 @@ Changes
* [UI]: Clean up of the main navigation bar code, and removed its dependency on angular-ui. * [UI]: Clean up of the main navigation bar code, and removed its dependency on angular-ui.
* [UI]: Fixed reflow layout of pipeline launch page. * [UI]: Fixed reflow layout of pipeline launch page.
* [UI]: Changed the wording of 'copying' samples to 'sharing' samples. * [UI]: Changed the wording of 'copying' samples to 'sharing' samples.
* [UI]: Allow users to share (copy) samples from a remote project. Disabled menu items for move and merge.
* [Developer]: Ran `prettier` on all javascript files within `resources/js`. * [Developer]: Ran `prettier` on all javascript files within `resources/js`.
* [Developer]: Ran `prettier` on all scss files within `resources/sass`. * [Developer]: Ran `prettier` on all scss files within `resources/sass`.
* [Developer]: Add a git pre-commit hook to ensure `prettier` formatting. * [Developer]: Add a git pre-commit hook to ensure `prettier` formatting.
......
...@@ -257,7 +257,7 @@ Alternatively, there is a dropdown next to the select all checkbox that allows y ...@@ -257,7 +257,7 @@ Alternatively, there is a dropdown next to the select all checkbox that allows y
An alternative to [sharing samples between projects](#sharing-samples-between-projects) is to **move** a sample between projects. Unlike sharing, when a sample is moved, the original sample is removed. An alternative to [sharing samples between projects](#sharing-samples-between-projects) is to **move** a sample between projects. Unlike sharing, when a sample is moved, the original sample is removed.
Like sharing samples, you must be a project <img src="images/manager-icon.png" class="inline" alt="Manager role icon."> **Manager** on **both** the project that you are moving the sample *from*, and the project that you are moving the sample *to*. Like sharing samples, you must be a project <img src="images/manager-icon.png" class="inline" alt="Manager role icon."> **Manager** on **both** the project that you are moving the sample *from*, and the project that you are moving the sample *to*. In addition, the source project you are sharing *from* must **not** be a remote project.
Start by [selecting the samples](#selecting-samples) that you want to move to the other project. When you've selected the samples that you want to move, click on the "Samples" button just above the samples list and select "Move Samples": Start by [selecting the samples](#selecting-samples) that you want to move to the other project. When you've selected the samples that you want to move, click on the "Samples" button just above the samples list and select "Move Samples":
...@@ -275,7 +275,7 @@ Once you've selected the project that you want to move the samples to, click on ...@@ -275,7 +275,7 @@ Once you've selected the project that you want to move the samples to, click on
### Merging samples within a project ### Merging samples within a project
If a sample was created when sequencing data was uploaded with an incorrect name, you may want to merge two samples together. When you merge two samples, you will move all of the **sequencing files** and **assembled genomes** from one sample to another, then **delete the original sample**. **None** of the sample metadata will be copied between the merged samples, instead you will select one sample as the target for the sample merge. Only users with the project <img src="images/manager-icon.png" class="inline" alt="Manager role icon."> **Manager** role can merge samples in a project. If a sample was created when sequencing data was uploaded with an incorrect name, you may want to merge two samples together. When you merge two samples, you will move all of the **sequencing files** and **assembled genomes** from one sample to another, then **delete the original sample**. **None** of the sample metadata will be copied between the merged samples, instead you will select one sample as the target for the sample merge. Only users with the project <img src="images/manager-icon.png" class="inline" alt="Manager role icon."> **Manager** role can merge samples in a project and samples cannot be merged within **remote** projects.
Start by [selecting the samples](#selecting-samples) that you want to merge. You **must** select more than one sample to enable the merge samples button. Once you've selected the two or more samples that you would like to merge, click on the "Samples" button just above the samples list and select "Merge Samples": Start by [selecting the samples](#selecting-samples) that you want to merge. You **must** select more than one sample to enable the merge samples button. Once you've selected the two or more samples that you would like to merge, click on the "Samples" button just above the samples list and select "Merge Samples":
......
...@@ -2,7 +2,6 @@ package ca.corefacility.bioinformatics.irida.ria.web.models.datatables; ...@@ -2,7 +2,6 @@ package ca.corefacility.bioinformatics.irida.ria.web.models.datatables;
import java.util.Date; import java.util.Date;
import ca.corefacility.bioinformatics.irida.model.joins.Join;
import ca.corefacility.bioinformatics.irida.model.joins.impl.ProjectUserJoin; import ca.corefacility.bioinformatics.irida.model.joins.impl.ProjectUserJoin;
import ca.corefacility.bioinformatics.irida.model.project.Project; import ca.corefacility.bioinformatics.irida.model.project.Project;
import ca.corefacility.bioinformatics.irida.model.user.User; import ca.corefacility.bioinformatics.irida.model.user.User;
......
...@@ -70,6 +70,9 @@ public class ProjectControllerUtils { ...@@ -70,6 +70,9 @@ public class ProjectControllerUtils {
boolean isOwner = projectOwnerPermission.isAllowed(authentication, project); boolean isOwner = projectOwnerPermission.isAllowed(authentication, project);
model.addAttribute("isOwner", isOwner); model.addAttribute("isOwner", isOwner);
boolean isOwnerAllowRemote = projectMembersPermission.isAllowed(authentication, project);
model.addAttribute("isOwnerAllowRemote", isOwnerAllowRemote);
boolean manageMembers = projectMembersPermission.isAllowed(authentication, project); boolean manageMembers = projectMembersPermission.isAllowed(authentication, project);
model.addAttribute("manageMembers", manageMembers); model.addAttribute("manageMembers", manageMembers);
......
...@@ -26,6 +26,7 @@ import org.springframework.data.domain.Page; ...@@ -26,6 +26,7 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Direction;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
...@@ -262,20 +263,23 @@ public class ProjectSamplesController { ...@@ -262,20 +263,23 @@ public class ProjectSamplesController {
} }
/** /**
* Create a modal dialogue for moving or copying {@link Sample} to another {@link Project} * Create a modal dialogue for moving or sharing {@link Sample} to another {@link Project}
* *
* @param ids {@link List} of identifiers for {@link Sample}s to copy or move. * @param ids {@link List} of identifiers for {@link Sample}s to copy or move.
* @param projectId Identifier for the current {@link Project} * @param projectId Identifier for the current {@link Project}
* @param model UI Model * @param model UI Model
* @param move Whether or not to display copy or move wording. * @param move Whether or not to display share or move wording.
* @return Path to copy or move modal template. * @return Path to share or move modal template.
*/ */
@RequestMapping(value = "/projects/{projectId}/templates/copy-move-modal", produces = MediaType.TEXT_HTML_VALUE) @RequestMapping(value = "/projects/{projectId}/templates/copy-move-modal", produces = MediaType.TEXT_HTML_VALUE)
public String getCopySamplesModal(@RequestParam(name = "sampleIds[]") List<Long> ids, @PathVariable Long projectId, public String getShareSamplesModal(@RequestParam(name = "sampleIds[]") List<Long> ids, @PathVariable Long projectId,
Model model, @RequestParam(required = false) boolean move) { Model model, @RequestParam(required = false) boolean move) {
model.addAllAttributes(generateCopyMoveSamplesContent(projectId, ids)); Project project = projectService.read(projectId);
model.addAllAttributes(generateShareMoveSamplesContent(project, ids));
model.addAttribute("projectId", projectId); model.addAttribute("projectId", projectId);
model.addAttribute("type", move ? "move" : "copy"); model.addAttribute("type", move ? "move" : "copy");
model.addAttribute("isRemoteProject", project.isRemote());
return PROJECT_TEMPLATE_DIR + "copy-move-modal.tmpl"; return PROJECT_TEMPLATE_DIR + "copy-move-modal.tmpl";
} }
...@@ -324,16 +328,16 @@ public class ProjectSamplesController { ...@@ -324,16 +328,16 @@ public class ProjectSamplesController {
} }
/** /**
* Generate a {@link Map} of {@link Sample} to move or copy. * Generate a {@link Map} of {@link Sample} to move or share.
* *
* @param project The {@link Project}.
* @param ids * @param ids
* {@link Long} of ids for {@link Sample} * {@link Long} of ids for {@link Sample}
* *
* @return {@link Map} of samples to be moved or copied * @return {@link Map} of samples to be moved or shared.
*/ */
private Map<String, List<Sample>> generateCopyMoveSamplesContent(Long projectId, List<Long> ids) { private Map<String, List<Sample>> generateShareMoveSamplesContent(Project project, List<Long> ids) {
Map<String, List<Sample>> model = new HashMap<>(); Map<String, List<Sample>> model = new HashMap<>();
Project project = projectService.read(projectId);
List<Sample> samples = new ArrayList<>(); List<Sample> samples = new ArrayList<>();
List<Sample> extraSamples = new ArrayList<>(); List<Sample> extraSamples = new ArrayList<>();
List<Sample> lockedSamples = new ArrayList<>(); List<Sample> lockedSamples = new ArrayList<>();
...@@ -549,10 +553,10 @@ public class ProjectSamplesController { ...@@ -549,10 +553,10 @@ public class ProjectSamplesController {
} }
/** /**
* Copy or move samples from one project to another * Share or move samples from one project to another
* *
* @param projectId The original project id * @param projectId The original project id
* @param sampleIds the sample identifiers to copy * @param sampleIds the sample identifiers to share
* @param newProjectId The new project id * @param newProjectId The new project id
* @param remove true/false whether to remove the samples from the original project * @param remove true/false whether to remove the samples from the original project
* @param giveOwner whether to give ownership of the sample to the new project * @param giveOwner whether to give ownership of the sample to the new project
...@@ -561,7 +565,7 @@ public class ProjectSamplesController { ...@@ -561,7 +565,7 @@ public class ProjectSamplesController {
*/ */
@RequestMapping(value = "/projects/{projectId}/ajax/samples/copy", method = RequestMethod.POST) @RequestMapping(value = "/projects/{projectId}/ajax/samples/copy", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Map<String, Object> copySampleToProject(@PathVariable Long projectId, public Map<String, Object> shareSampleToProject(@PathVariable Long projectId,
@RequestParam(value = "sampleIds[]") List<Long> sampleIds, @RequestParam Long newProjectId, @RequestParam(value = "sampleIds[]") List<Long> sampleIds, @RequestParam Long newProjectId,
@RequestParam(required = false) boolean remove, @RequestParam(required = false) boolean remove,
@RequestParam(required = false, defaultValue = "false") boolean giveOwner, Locale locale) { @RequestParam(required = false, defaultValue = "false") boolean giveOwner, Locale locale) {
...@@ -576,12 +580,20 @@ public class ProjectSamplesController { ...@@ -576,12 +580,20 @@ public class ProjectSamplesController {
List<ProjectSampleJoin> successful = new ArrayList<>(); List<ProjectSampleJoin> successful = new ArrayList<>();
try { try {
successful = projectService.copyOrMoveSamples(originalProject, newProject, Lists.newArrayList(samples), if (remove) {
remove, giveOwner); successful = projectService.moveSamples(originalProject, newProject, Lists.newArrayList(samples), giveOwner);
} else {
successful = projectService.shareSamples(originalProject, newProject, Lists.newArrayList(samples), giveOwner);
}
} catch (EntityExistsException ex) { } catch (EntityExistsException ex) {
logger.warn("Attempt to add project to sample failed", ex); logger.warn("Attempt to add project to sample failed", ex);
warnings.add(ex.getLocalizedMessage()); warnings.add(ex.getLocalizedMessage());
} catch (AccessDeniedException ex) {
logger.warn("Access denied adding samples to project " + newProjectId, ex);
String msg = remove ? "project.samples.move.sample-denied" : "project.samples.copy.sample-denied";
warnings.add(
messageSource.getMessage(msg, new Object[] { newProject.getName() }, locale));
} }
if (!warnings.isEmpty() || successful.size() == 0) { if (!warnings.isEmpty() || successful.size() == 0) {
......
...@@ -17,7 +17,7 @@ import ca.corefacility.bioinformatics.irida.security.ProjectSynchronizationAuthe ...@@ -17,7 +17,7 @@ import ca.corefacility.bioinformatics.irida.security.ProjectSynchronizationAuthe
/** /**
* Confirms that a given user is the owner of a project * Confirms that a given user is the owner of a project
* *
* *
*/ */
@Component @Component
...@@ -26,7 +26,7 @@ public class ProjectOwnerPermission extends ModifyProjectPermission { ...@@ -26,7 +26,7 @@ public class ProjectOwnerPermission extends ModifyProjectPermission {
private static final String PERMISSION_PROVIDED = "isProjectOwner"; private static final String PERMISSION_PROVIDED = "isProjectOwner";
/** /**
* Construct an instance of {@link ReadProjectPermission}. * Construct an instance of {@link ProjectOwnerPermission}.
* *
* @param projectRepository the project repository. * @param projectRepository the project repository.
* @param userRepository the user repository. * @param userRepository the user repository.
......
...@@ -153,23 +153,38 @@ public interface ProjectService extends CRUDService<Long, Project> { ...@@ -153,23 +153,38 @@ public interface ProjectService extends CRUDService<Long, Project> {
public ProjectSampleJoin moveSampleBetweenProjects(Project source, Project destination, Sample sample, boolean owner); public ProjectSampleJoin moveSampleBetweenProjects(Project source, Project destination, Sample sample, boolean owner);
/** /**
* Copy or move a list of {@link Sample} between 2 {@link Project} * Share a list of {@link Sample}s between two {@link Project}s.
* *
* @param source * @param source
* the source {@link Project} * the source {@link Project}
* @param destination * @param destination
* the {@link Project} being copied to * the {@link Project} being shared into
* @param samples * @param samples
* a collection of {@link Sample} * a collection of {@link Sample}
* @param move
* boolean whether to move or copy. true for move
* @param giveOwner * @param giveOwner
* whether to give ownership rights to the destination * whether to give ownership rights to the destination
* {@link Project} * {@link Project}
* @return a list of new {@link ProjectSampleJoin} * @return a list of new {@link ProjectSampleJoin}
*/ */
public List<ProjectSampleJoin> copyOrMoveSamples(Project source, Project destination, Collection<Sample> samples, public List<ProjectSampleJoin> shareSamples(Project source, Project destination, Collection<Sample> samples,
boolean move, boolean giveOwner); boolean giveOwner);
/**
* Move a list of {@link Sample}s between 2 {@link Project}
*
* @param source
* the source {@link Project}
* @param destination
* the {@link Project} being moved to
* @param samples
* a collection of {@link Sample}s
* @param giveOwner
* whether to give ownership rights to the destination
* {@link Project}
* @return a list of new {@link ProjectSampleJoin}
*/
public List<ProjectSampleJoin> moveSamples(Project source, Project destination, Collection<Sample> samples,
boolean giveOwner);
/** /**
* Remove the specified {@link Sample} from the {@link Project}. The {@link Sample} will also be deleted from the * Remove the specified {@link Sample} from the {@link Project}. The {@link Sample} will also be deleted from the
......
...@@ -415,27 +415,46 @@ public class ProjectServiceImpl extends CRUDServiceImpl<Long, Project> implement ...@@ -415,27 +415,46 @@ public class ProjectServiceImpl extends CRUDServiceImpl<Long, Project> implement
@Override @Override
@Transactional @Transactional
@LaunchesProjectEvent(SampleAddedProjectEvent.class) @LaunchesProjectEvent(SampleAddedProjectEvent.class)
@PreAuthorize("hasPermission(#source, 'isProjectOwner') " + "and hasPermission(#destination, 'isProjectOwner') " @PreAuthorize("hasPermission(#source, 'canManageLocalProjectSettings')"
+ "and hasPermission(#samples, 'canReadSample') " + " and hasPermission(#destination, 'isProjectOwner')"
+ "and ((not #giveOwner) or hasPermission(#samples, 'canUpdateSample') )") + " and hasPermission(#samples, 'canReadSample')"
public List<ProjectSampleJoin> copyOrMoveSamples(Project source, Project destination, Collection<Sample> samples, + " and ((not #giveOwner) or hasPermission(#samples, 'canUpdateSample'))")
boolean move, boolean giveOwner) { public List<ProjectSampleJoin> shareSamples(Project source, Project destination, Collection<Sample> samples,
boolean giveOwner) {
List<ProjectSampleJoin> newJoins = new ArrayList<>(); List<ProjectSampleJoin> newJoins = new ArrayList<>();
for (Sample sample : samples) { for (Sample sample : samples) {
ProjectSampleJoin newJoin = addSampleToProject(destination, sample, giveOwner);
logger.trace("Shared sample " + sample.getId() + " to project " + destination.getId());
ProjectSampleJoin newJoin; newJoins.add(newJoin);
if (move) { }
newJoin = moveSampleBetweenProjects(source, destination, sample, giveOwner);
} else { return newJoins;
newJoin = addSampleToProject(destination, sample, giveOwner); }
}
/**
* {@inheritDoc}
*/
@Override
@Transactional
@LaunchesProjectEvent(SampleAddedProjectEvent.class)
@PreAuthorize("hasPermission(#source, 'isProjectOwner') and hasPermission(#destination, 'isProjectOwner') "
+ "and hasPermission(#samples, 'canReadSample') "
+ "and ((not #giveOwner) or hasPermission(#samples, 'canUpdateSample') )")
public List<ProjectSampleJoin> moveSamples(Project source, Project destination, Collection<Sample> samples,
boolean giveOwner) {
logger.trace("Copied sample " + sample.getId() + " to project " + destination.getId()); List<ProjectSampleJoin> newJoins = new ArrayList<>();
newJoins.add(newJoin); for (Sample sample : samples) {
ProjectSampleJoin newJoin = moveSampleBetweenProjects(source, destination, sample, giveOwner);
logger.trace("Moved sample " + sample.getId() + " to project " + destination.getId());
newJoins.add(newJoin);
} }
return newJoins; return newJoins;
...@@ -446,7 +465,7 @@ public class ProjectServiceImpl extends CRUDServiceImpl<Long, Project> implement ...@@ -446,7 +465,7 @@ public class ProjectServiceImpl extends CRUDServiceImpl<Long, Project> implement
*/ */
@Override @Override
@Transactional @Transactional
@PreAuthorize("hasRole('ROLE_ADMIN') or hasPermission(#project, 'isProjectOwner')") @PreAuthorize("hasRole('ROLE_ADMIN') or hasPermission(#project, 'canManageLocalProjectSettings')")
@LaunchesProjectEvent(SampleRemovedProjectEvent.class) @LaunchesProjectEvent(SampleRemovedProjectEvent.class)
public void removeSampleFromProject(Project project, Sample sample) { public void removeSampleFromProject(Project project, Sample sample) {
ProjectSampleJoin readSampleForProject = psjRepository.readSampleForProject(project, sample); ProjectSampleJoin readSampleForProject = psjRepository.readSampleForProject(project, sample);
...@@ -463,7 +482,7 @@ public class ProjectServiceImpl extends CRUDServiceImpl<Long, Project> implement ...@@ -463,7 +482,7 @@ public class ProjectServiceImpl extends CRUDServiceImpl<Long, Project> implement
*/ */
@Override @Override
@Transactional @Transactional
@PreAuthorize("hasRole('ROLE_ADMIN') or hasPermission(#project, 'isProjectOwner')") @PreAuthorize("hasRole('ROLE_ADMIN') or hasPermission(#project, 'canManageLocalProjectSettings')")
@LaunchesProjectEvent(SampleRemovedProjectEvent.class) @LaunchesProjectEvent(SampleRemovedProjectEvent.class)
public void removeSamplesFromProject(Project project, Iterable<Sample> samples) { public void removeSamplesFromProject(Project project, Iterable<Sample> samples) {
for (Sample s : samples) { for (Sample s : samples) {
...@@ -605,7 +624,7 @@ public class ProjectServiceImpl extends CRUDServiceImpl<Long, Project> implement ...@@ -605,7 +624,7 @@ public class ProjectServiceImpl extends CRUDServiceImpl<Long, Project> implement
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
@PreAuthorize("hasRole('ROLE_ADMIN') or hasPermission(#p, 'isProjectOwner')") @PreAuthorize("hasRole('ROLE_ADMIN') or hasPermission(#p, 'canManageLocalProjectSettings')")
public Page<Project> getUnassociatedProjects(final Project p, final String searchName, final Integer page, final Integer count, public Page<Project> getUnassociatedProjects(final Project p, final String searchName, final Integer page, final Integer count,
final Direction sortDirection, final String... sortedBy) { final Direction sortDirection, final String... sortedBy) {
......
...@@ -581,6 +581,8 @@ project.samples.move-success=Successfully moved {0} sample(s). ...@@ -581,6 +581,8 @@ project.samples.move-success=Successfully moved {0} sample(s).
project.samples.copy.project=New Project project.samples.copy.project=New Project
project.samples.copy.sample-exists=Sample {0} was not shared as it already exists in {1}. project.samples.copy.sample-exists=Sample {0} was not shared as it already exists in {1}.
project.samples.move.sample-exists=Sample {0} was not moved as it already exists in {1}. project.samples.move.sample-exists=Sample {0} was not moved as it already exists in {1}.
project.samples.copy.sample-denied=Permission denied when sharing samples to {0}.
project.samples.move.sample-denied=Permission denied when moving samples to {0}.
project.samples.copy.no-project-selected=Project must be selected. project.samples.copy.no-project-selected=Project must be selected.
project.samples.run-pipeline=Run Pipeline project.samples.run-pipeline=Run Pipeline
project.samples.run-modal.title=Select a Pipeline to Run project.samples.run-modal.title=Select a Pipeline to Run
...@@ -597,6 +599,10 @@ project.samples.tooltip.share.requires-more-than-one=Select at least one sample ...@@ -597,6 +599,10 @@ project.samples.tooltip.share.requires-more-than-one=Select at least one sample
project.samples.tooltip.move.requires-more-than-one=Select at least one sample from this project to move to another project. project.samples.tooltip.move.requires-more-than-one=Select at least one sample from this project to move to another project.
project.samples.tooltip.remove.requires-more-than-one=Select one or more samples to remove from this project. project.samples.tooltip.remove.requires-more-than-one=Select one or more samples to remove from this project.
project.samples.tooltip.download.requires-more-than-one=Select one or more samples to download from this project. project.samples.tooltip.download.requires-more-than-one=Select one or more samples to download from this project.
project.samples.tooltip.merge.remote-project=Merging samples cannot be performed in a remote project.
project.samples.tooltip.move.remote-project=Moving a remote sample cannot be performed.
project.samples.tooltip.import-metadata.remote-project=Cannot import metadata for a remote project.
project.samples.tooltip.new-sample.remote-project=Cannot add new sample to a remote project.
project.samples.tooltip.ncbi.requires-more-than-one=Select one or more samples from this project to upload to the NCBI SRA. project.samples.tooltip.ncbi.requires-more-than-one=Select one or more samples from this project to upload to the NCBI SRA.
project.samples.only-for-project-tooltip=This cannot be used when associated projects are displayed. project.samples.only-for-project-tooltip=This cannot be used when associated projects are displayed.
project.samples.locked-title=Sample is locked from modification project.samples.locked-title=Sample is locked from modification
...@@ -653,6 +659,7 @@ project.samples.modal.copy.select-label-plural=Select a project to share the sam ...@@ -653,6 +659,7 @@ project.samples.modal.copy.select-label-plural=Select a project to share the sam
project.samples.modal.copy.complete=Share Samples project.samples.modal.copy.complete=Share Samples
project.samples.modal.copy.locked=Shared samples will be non-modifiable as one or more locked sample are selected. project.samples.modal.copy.locked=Shared samples will be non-modifiable as one or more locked sample are selected.
project.samples.modal.copy.lock-copied=Allow modification access to samples in the new project. project.samples.modal.copy.lock-copied=Allow modification access to samples in the new project.
project.samples.modal.copy.remote=All samples will be read only as this project is a remote project.
project.samples.modal.move.title-plural=Move Sample project.samples.modal.move.title-plural=Move Sample
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
<link rel="stylesheet" th:href="@{/resources/dist/css/project-samples-filter.bundle.css}"> <link rel="stylesheet" th:href="@{/resources/dist/css/project-samples-filter.bundle.css}">
<script th:inline="javascript"> <script th:inline="javascript">
var PAGE = { var PAGE = {
isRemoteProject: /*[[${project.isRemote()}]]*/ false,
linker: /*[[${linker}]]*/'', linker: /*[[${linker}]]*/'',
urls: { urls: {
projects: { projects: {
...@@ -57,8 +58,8 @@ ...@@ -57,8 +58,8 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="btn-toolbar dt-btn-toolbar" role="toolbar"> <div class="btn-toolbar dt-btn-toolbar" role="toolbar">
<div class="btn-group" th:if="${isOwner || isAdmin}"> <div class="btn-group" th:if="${isOwnerAllowRemote}">
<div th:if="${isOwner || isAdmin}" class="dropdown"> <div class="dropdown">
<button id="sample-tools" class="btn btn-default btn-sm dropdown-toggle t-sample-tools" <button id="sample-tools" class="btn btn-default btn-sm dropdown-toggle t-sample-tools"
data-toggle="dropdown"> data-toggle="dropdown">
<th:block th:text="#{project.samples.nav.sample-tools}"/> <th:block th:text="#{project.samples.nav.sample-tools}"/>
...@@ -69,6 +70,7 @@ ...@@ -69,6 +70,7 @@
<a href="#" class="js-sample-tool-btn t-merge-btn" <a href="#" class="js-sample-tool-btn t-merge-btn"
data-enabled-at="2" data-toggle="tooltip" data-enabled-at="2" data-toggle="tooltip"
data:enabled-msg="#{project.samples.tooltip.merge.requires-more-than-one}" data:enabled-msg="#{project.samples.tooltip.merge.requires-more-than-one}"
data:remote-msg="#{project.samples.tooltip.merge.remote-project}"
data:associated-msg="#{project.samples.only-for-project-tooltip}" data:associated-msg="#{project.samples.only-for-project-tooltip}"
data:url="@{/projects/{projectId}/templates/merge-modal(projectId=${project.getId()})}" data:url="@{/projects/{projectId}/templates/merge-modal(projectId=${project.getId()})}"
data:script="@{/resources/dist/js/project-samples-merge.bundle.js}"> data:script="@{/resources/dist/js/project-samples-merge.bundle.js}">
...@@ -93,6 +95,7 @@ ...@@ -93,6 +95,7 @@
data-enabled-at="1" data-params="{&quot;move&quot;: true}" data-enabled-at="1" data-params="{&quot;move&quot;: true}"
data-toggle="tooltip" data-toggle="tooltip"
data:enabled-msg="#{project.samples.tooltip.move.requires-more-than-one}" data:enabled-msg="#{project.samples.tooltip.move.requires-more-than-one}"
data:remote-msg="#{project.samples.tooltip.move.remote-project}"
data:associated-msg="#{project.samples.only-for-project-tooltip}" data:associated-msg="#{project.samples.only-for-project-tooltip}"
data:url="@{/projects/{projectId}/templates/copy-move-modal(projectId=${project.getId()})}" data:url="@{/projects/{projectId}/templates/copy-move-modal(projectId=${project.getId()})}"
data:script="@{/resources/dist/js/project-samples-copy.bundle.js}"> data:script="@{/resources/dist/js/project-samples-copy.bundle.js}">
...@@ -116,7 +119,11 @@ ...@@ -116,7 +119,11 @@
<!-- IMPORT METADATA LINK--> <!-- IMPORT METADATA LINK-->
<li role="menuitem"> <li role="menuitem">
<a th:href="@{/projects/{id}/sample-metadata/upload(id=${project.getId()})}"> <a href="#"
class="js-sample-project-tool-btn t-import-metadata-btn"
data:url="@{/projects/{id}/sample-metadata/upload(id=${project.getId()})}"
data-enabled-at="0" data-toggle="tooltip"
data:remote-msg="#{project.samples.tooltip.import-metadata.remote-project}">
<i class="fa fa-upload fa-fw spaced-right__sm" aria-hidden="true"></i> <i class="fa fa-upload fa-fw spaced-right__sm" aria-hidden="true"></i>
<th:block th:text="#{project.nav.samples.import-metadata}"/> <th:block th:text="#{project.nav.samples.import-metadata}"/>
</a> </a>
...@@ -125,7 +132,11 @@ ...@@ -125,7 +132,11 @@
<!-- NEW SAMPLE LINK --> <!-- NEW SAMPLE LINK -->
<li role="menuitem"> <li role="menuitem">
<a th:href="@{/projects/{id}/samples/new(id=${project.getId()})}"> <a href="#"
class="js-sample-project-tool-btn t-new-sample-btn"
data:url="@{/projects/{id}/samples/new(id=${project.getId()})}"
data-enabled-at="0" data-toggle="tooltip"
data:remote-msg="#{project.samples.tooltip.new-sample.remote-project}">
<i class="fa fa-plus fa-fw spaced-right__sm" aria-hidden="true"></i> <i class="fa fa-plus fa-fw spaced-right__sm" aria-hidden="true"></i>
<th:block th:text="#{project.samples.nav.new}"/> <th:block th:text="#{project.samples.nav.new}"/>
</a> </a>
......
...@@ -54,13 +54,18 @@ ...@@ -54,13 +54,18 @@
<input type="hidden" value="false" name="giveOwner" id="giveOwner" /> <input type="hidden" value="false" name="giveOwner" id="giveOwner" />
</div> </div>
<div class="alert alert-warning" th:if="${isRemoteProject}">
<span th:text="#{project.samples.modal.copy.remote}">_All samples will be read only as this project is a remote project_</span>
</div>
<div th:if="${not isRemoteProject}">
<!-- Only the the allow modification checkbox for the copy not the move --> <!-- Only the the allow modification checkbox for the copy not the move -->
<div class="form-group" th:if="${#lists.size(lockedSamples) == 0 and #strings.equals(type, 'copy')}"> <div class="form-group" th:if="${#lists.size(lockedSamples) == 0 and #strings.equals(type, 'copy')}">
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" value="true" id="giveOwner" name="giveOwner"/> <input type="checkbox" value="true" id="giveOwner" name="giveOwner"/>
<span th:text="#{project.samples.modal.copy.lock-copied}">_Allow modification in new project_</span> <span th:text="#{project.samples.modal.copy.lock-copied}">_Allow modification in new project_</span>
</label> </label>
</div>
</div> </div>
</div> </div>
......
...@@ -10,11 +10,9 @@ import $ from "jquery"; ...@@ -10,11 +10,9 @@ import $ from "jquery";
* Check the state of the calling button to see if it should be enabled or not. * Check the state of the calling button to see if it should be enabled or not.
* @param count the number if samples currently selected. * @param count the number if samples currently selected.
* @param hasAssociated if there are associated project currently displayed. * @param hasAssociated if there are associated project currently displayed.
* @param isRemote If the project is a remote project.
*/ */
function checkState(count, hasAssociated) { function checkState(count, hasAssociated, isRemote) {
// Remove the tooltip. A new one will be created based on the
// information provided.
this.$node.tooltip("destroy");
if (hasAssociated && this.$node.data("associatedMsg")) { if (hasAssociated && this.$node.data("associatedMsg")) {
this.$node.parent().addClass("disabled"); this.$node.parent().addClass("disabled");
// Activate the tooltip // Activate the tooltip
...@@ -23,18 +21,26 @@ function checkState(count, hasAssociated) { ...@@ -23,18 +21,26 @@ function checkState(count, hasAssociated) {
placement: "right", placement: "right",
title: this.$node.data("associatedMsg") title: this.$node.data("associatedMsg")
}); });
} else if (isRemote && this.$node.data("remoteMsg")) {
this.$node.parent().addClass("disabled");
// Activate the tooltip
this.$node.tooltip({
container: "body",
placement: "right",
title: this.$node.data("remoteMsg")
});
} else if (count < this.$node.data("enabledAt")) {
this.$node.parent().addClass("disabled");
// Activate the tooltip
this.$node.tooltip({
container: "body",
placement: "right",
title: this.$node.data("enabledMsg")
});
} else { } else {
if (count < this.$node.data("enabledAt")) { // Remove the tooltip.
this.$node.parent().addClass("disabled"); this.$node.tooltip("destroy");
// Activate the tooltip this.$node.parent().removeClass("disabled");
this.$node.tooltip({
container: "body",
placement: "right",
title: this.$node.data("enabledMsg")
});
} else {
this.$node.parent().removeClass("disabled");
}
} }
} }
...@@ -94,15 +100,16 @@ export class SampleExportButton { ...@@ -94,15 +100,16 @@ export class SampleExportButton {
* Check the state the button should be in. * Check the state the button should be in.
* @param {number} count current number of selected samples (defaults to 0) * @param {number} count current number of selected samples (defaults to 0)
* @param {boolean} hasAssociated if associated projects are displayed in the table. * @param {boolean} hasAssociated if associated projects are displayed in the table.
* @param {boolean} isRemote Whether the project is a remote project.
*/ */
checkState(count = 0, hasAssociated = false) { checkState(count = 0, hasAssociated = false, isRemote = false) {
checkState.call(this, count, hasAssociated); checkState.call(this, count, hasAssociated, isRemote);
} }
} }
/** /**
* This class represents the state and function of buttons within the * This class represents the state and function of buttons within the
* project > sample > Sample Tools dropdown menu. * project > sample > Sample Tools dropdown menu which apply to individual samples.
*/ */
export class SampleDropdownButton { export class SampleDropdownButton {
/** /**
...@@ -130,8 +137,45 @@ export class SampleDropdownButton { ...@@ -130,8 +137,45 @@ export class SampleDropdownButton {
* number of currently selected samples * number of currently selected samples
* @param {number} count of selected samples * @param {number} count of selected samples
* @param {boolean} hasAssociated whether associated projects are being displayed. * @param {boolean} hasAssociated whether associated projects are being displayed.
* @param {boolean} isRemote Whether the project is a remote project.