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

Jl - ui for protocol merge #1721

Merged
merged 12 commits into from
Feb 20, 2019
18 changes: 18 additions & 0 deletions app/assets/javascripts/dashboard/protocol_merges.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
$ ->

$(document).on 'click', '#merge-button', ->
master_protocol_id = $('#master_protocol').val()
sub_protocol_id = $('#sub_protocol').val()

data =
'master_protocol_id' : master_protocol_id,
'sub_protocol_id' : sub_protocol_id

if confirm("Preparing to merge protocol #{master_protocol_id} and protocol #{sub_protocol_id}. Do you wish to continue?")
jleonardw9 marked this conversation as resolved.
Show resolved Hide resolved
$.ajax
type: 'GET'
url: "/dashboard/protocol_merge/perform_protocol_merge"
data: data
success: ->
$('#master_protocol').val('')
$('#sub_protocol').val('')
119 changes: 119 additions & 0 deletions app/controllers/dashboard/protocol_merges_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
class Dashboard::ProtocolMergesController < Dashboard::BaseController
before_action :authorize_overlord
respond_to :json, :html

def show
@user = current_identity
end

def perform_protocol_merge
master_protocol = Protocol.where(id: params[:master_protocol_id].to_i).first
sub_protocol = Protocol.where(id: params[:sub_protocol_id].to_i).first

merge_srs = Dashboard::MergeSrs.new()
fix_ssr_ids = Dashboard::FixSsrIds.new(master_protocol)

if (master_protocol == nil) || (sub_protocol == nil)
flash[:alert] = 'Protocol(s) not found. Check IDs and try again.'
elsif (master_protocol.has_clinical_services? && sub_protocol.has_clinical_services?)
flash[:alert] = 'Both protocols have calendars. Only one protocol may have a calendar for merge to work.'
else
ActiveRecord::Base.transaction do
jleonardw9 marked this conversation as resolved.
Show resolved Hide resolved

#transfer the project roles as needed
sub_protocol.project_roles.each do |role|
if role.role != 'primary-pi' && role_should_be_assigned?(role, master_protocol)
role.update_attributes(protocol_id: master_protocol.id)
end
end

# checking for and assigning research types, impact areas, and affiliations...
if has_research?(sub_protocol, 'human_subjects') && !has_research?(master_protocol, 'human_subjects')
sub_protocol.human_subjects_info.update_attributes(protocol_id: master_protocol.id)
elsif has_research?(sub_protocol, 'vertebrate_animals') && !has_research?(master_protocol, 'vertebrate_animals')
sub_protocol.vertebrate_animals_info.update_attributes(protocol_id: master_protocol.id)
elsif has_research?(sub_protocol, 'investigational_products') && !has_research?(master_protocol, 'investigational_products')
sub_protocol.investigational_products_info.update_attributes(protocol_id: master_protocol.id)
elsif has_research?(sub_protocol, 'ip_patents') && !has_research?(master_protocol, 'ip_patents')
sub_protocol.ip_patents_info.update_attributes(protocol_id: master_protocol.id)
end

sub_protocol.impact_areas.each do |area|
area.protocol_id = master_protocol.id
area.save(validate: false)
end

sub_protocol.affiliations.each do |affiliation|
affiliation.protocol_id = master_protocol.id
affiliation.save(validate: false)
end

# assigning service requests...
fulfillment_ssrs = []
sub_protocol.service_requests.each do |request|
request.protocol_id = master_protocol.id
request.save(validate: false)
request.sub_service_requests.each do |ssr|
ssr.update_attributes(protocol_id: master_protocol.id)
master_protocol.next_ssr_id = (master_protocol.next_ssr_id + 1)
master_protocol.save(validate: false)
if ssr.in_work_fulfillment
fulfillment_ssrs << ssr
end
end
end

#assigning arms..."
sub_protocol.arms.each do |arm|
check_arm_names(arm, master_protocol)
arm.protocol_id = master_protocol.id
arm.save(validate: false)
end

#assigning documents..."
sub_protocol.documents.each do |document|
document.protocol_id = master_protocol.id
document.save(validate: false)
end

#assigning_notes
sub_protocol.notes.each do |note|
note.notable_id = master_protocol.id
note.save(validate: false)
end

#delete sub protocol
sub_protocol.delete

#cleanup
merge_srs.perform_sr_merge
fix_ssr_ids.perform_id_fix
end
flash[:success] = 'Protocol merge succesful'
end
end

private

def role_should_be_assigned?(role_to_be_assigned, protocol)
protocol.project_roles.each do |role|
if (role.role == role_to_be_assigned.role) && (role.identity_id == role_to_be_assigned.identity_id)
return false
end
end
return true
end

def authorize_overlord
unless @user.catalog_overlord?
render partial: 'service_requests/authorization_error',
locals: { error: 'You do not have access to perform a Protocol Merge',
in_dashboard: false
}
end
end

def has_research?(protocol, research_type)
protocol.research_types_info.try(research_type) || false
end
end
42 changes: 42 additions & 0 deletions app/lib/dashboard/fix_ssr_ids.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module Dashboard

class FixSsrIds

def initialize(protocol)
@protocol = protocol
end

def perform_id_fix
requests = @protocol.sub_service_requests.group_by(&:ssr_id)
if @protocol.sub_service_requests.present?
last_ssr_id = @protocol.sub_service_requests.sort_by(&:ssr_id).last.ssr_id.to_i
dup_requests_to_be_incremented = []

requests.each do |ssr_id, ssr_array|
if ssr_array.size > 1 # we have duplicate ssr_ids
ssr_array.each_with_index do |ssr, index|
if index > 0
dup_requests_to_be_incremented << ssr # place all requests with dup ids in an array to deal with later
end
end
end
end

if dup_requests_to_be_incremented.size > 0
dup_requests_to_be_incremented.each do |ssr|
ssr.ssr_id = "%04d" % (last_ssr_id + 1)
ssr.save(validate: false)
last_ssr_id += 1
end
end

# we need to increment the protocol's next_ssr_id if we had some duplicates
new_last_ssr_id = @protocol.sub_service_requests.sort_by(&:ssr_id).last.ssr_id.to_i
if @protocol.next_ssr_id? && (@protocol.next_ssr_id <= new_last_ssr_id)
@protocol.next_ssr_id = new_last_ssr_id + 1
@protocol.save(validate: false)
end
end
end
end
end
100 changes: 100 additions & 0 deletions app/lib/dashboard/merge_srs.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
module Dashboard

class MergeSrs

def perform_sr_merge
ServiceRequest.skip_callback(:save, :after, :set_original_submitted_date)

protocols = Protocol.joins(:service_requests).group('protocols.id').having('count(protocol_id) >= 2').to_a
protocols.each do |protocol|

# Merge remaining service requests into service request with most recently
# updated status
recent_service_request = protocol.service_requests.preload(:audits).distinct.map do |sr|
last_status_change = sr.audits.reverse.find { |a| a.audited_changes["status"] }
if last_status_change
[sr, last_status_change.created_at]
end
end.compact.sort do |l, r|
l[1] <=> r[1]
end

recent_service_request = if recent_service_request.empty?
# If audit trail runs out, use most recently updated ServiceRequest
protocol.service_requests.order(:updated_at).last
else
recent_service_request.last[0]
end

# And the latest submitted_at date
latest_submitted_at = protocol.service_requests.
order(:submitted_at).
where.not(submitted_at: nil).
pluck(:submitted_at).
last

if latest_submitted_at
recent_service_request.submitted_at = latest_submitted_at
recent_service_request.audit_comment = 'merge_srs'
recent_service_request.save(validate: false)
end

# Keep the earliest original_submitted_date among service requests
earliest_original_submitted_date = protocol.service_requests.
order(:original_submitted_date).
where.not(original_submitted_date: nil).
pluck(:original_submitted_date).
first

if earliest_original_submitted_date
recent_service_request.original_submitted_date = earliest_original_submitted_date
recent_service_request.audit_comment = 'merge_srs'
recent_service_request.save(validate: false)
end

# Move all SSR's, LineItems and ServiceRequest Notes under this recent_service_request
protocol.sub_service_requests.where.not(service_request_id: recent_service_request.id).each do |ssr|
ssr.service_request_id = recent_service_request.id
ssr.audit_comment = 'merge_srs'
ssr.save(validate: false)
end
line_items = LineItem.joins(:service_request).where(service_requests: { protocol_id: protocol.id }).where.not(service_request_id: recent_service_request.id)
line_items.each do |li|
li.service_request_id = recent_service_request.id
li.audit_comment = 'merge_srs'
li.save(validate: false)
end

protocol.service_requests.where.not(id: recent_service_request.id).each do |sr|
sr.notes.each do |note|
note.notable_id = recent_service_request.id
note.audit_comment = 'merge_srs'
note.save(validate: false)
end
end

delete_empty_srs(protocol)
end
end

private

def delete_empty_srs(protocol)
protocol.service_requests.includes(:sub_service_requests).where(sub_service_requests: { id: nil }).each do |sr|
begin
sr.destroy
sr.audits.last.update(comment: 'merge_srs')
rescue
# Probably a funky Note body...
sr.notes.each do |note|
note.notable_id = nil
note.audit_comment = 'merge_srs'
note.save(validate: false)
end
sr.reload.destroy
sr.audits.last.update(comment: 'merge_srs')
end
end
end
end
end
19 changes: 19 additions & 0 deletions app/views/dashboard/protocol_merges/_merge_tool.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#protocol-merges-container
%h1{style: 'text-align:center;'}
Copy link
Contributor

Choose a reason for hiding this comment

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

Replace inline style with the text-center class

Protocol Merge Tool
jleonardw9 marked this conversation as resolved.
Show resolved Hide resolved
%p &nbsp
Copy link
Contributor

Choose a reason for hiding this comment

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

Standard tags shouldn't really be used for spacing like this. You could use breaks or add margin styles

%p &nbsp
%p &nbsp
%p
Copy link
Contributor

Choose a reason for hiding this comment

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

All of this content should be wrapped in a form tag with the URL being the perform_protocol_merge route that you added. Also can you add the bootstrap form style classes for consistency? https://bootstrapdocs.com/v3.3.5/docs/css/#forms

= label_tag 'master_protocol', 'Enter the id of the Master Protocol:'
= text_field_tag 'master_protocol', nil, style: 'margin-left:207px'
%p &nbsp
%p &nbsp
%p
= label_tag 'sub_protocol', 'Enter the id of the Protocol to be merged into the Master Protocol:'
= text_field_tag 'sub_protocol'
%p &nbsp
%p &nbsp
%p{style: 'text-align:center;'}
%button.btn.btn-info.navbar-btn#merge-button{ type: 'button', title: "Perform Protocol Merge"}
Copy link
Contributor

Choose a reason for hiding this comment

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

This button should be a submit_tag for the form. To force the user to confirm before submitting, you can use the onsubmit attribute. https://stackoverflow.com/a/6515632

= "Perform Protocol Merge"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
$("#flashes_container").html("<%= j render 'shared/flash' %>")
jleonardw9 marked this conversation as resolved.
Show resolved Hide resolved
26 changes: 26 additions & 0 deletions app/views/dashboard/protocol_merges/show.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
-# Copyright © 2011-2018 MUSC Foundation for Research Development
-# All rights reserved.

-# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

-# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

-# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
-# disclaimer in the documentation and/or other materials provided with the distribution.

-# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
-# derived from this software without specific prior written permission.

-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
-# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
-# SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
-# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

= render 'dashboard/protocol_merges/merge_tool.html.haml'

:javascript
$(document).ready( function() {
Sparc.protocol.ready();
})
2 changes: 2 additions & 0 deletions app/views/layouts/dashboard/_dashboard_header.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
%li
%button.btn.btn-info.navbar-btn#epic-queue-btn{ type: 'button', title: t(:dashboard)[:navbar][:tooltips][:epic_queue], data: { toggle: 'tooltip', placement: 'bottom', delay: '{"show":"500"}'} }
= t(:dashboard)[:navbar][:epic_queue]
- if current_user.catalog_overlord?
= link_to "Protocol Merge", dashboard_protocol_merge_path, :class => ' header_button btn btn-primary btn-md', style: 'margin-top:7px;'
Copy link
Contributor

Choose a reason for hiding this comment

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

This text needs to be YAMLized and styles moved to a stylesheet

- if @show_messages
%li
%button.btn.btn-danger.navbar-btn#messages-btn{ type: 'button', title: t(:dashboard)[:navbar][:tooltips][:messages], data: { toggle: 'tooltip', placement: 'bottom', delay: '{"show":"500"}'} }
Expand Down
4 changes: 4 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,10 @@
resources :epic_queues, only: [:index, :destroy]
resources :epic_queue_records, only: [:index]

resource :protocol_merge do
get :perform_protocol_merge
jleonardw9 marked this conversation as resolved.
Show resolved Hide resolved
end

resources :fulfillments

resources :line_items do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
@user = instance_double(Identity,
ldap_uid: 'jug2',
email: 'user@email.com',
catalog_overlord?: true,
unread_notification_count: 2,
)

Expand All @@ -39,27 +40,27 @@
stub_config("epic_queue_access", ['jug2'])

it 'should display view epic queue button' do
render 'layouts/dashboard/dashboard_header', user: @user
render 'layouts/dashboard/dashboard_header', user: @user, current_user: @user

expect(response).to have_selector('button#epic-queue-btn', text: 'Epic Queue')
end
end

it 'should display number of unread notifications (for user)' do
@show_messages = true
render 'layouts/dashboard/dashboard_header', user: @user
render 'layouts/dashboard/dashboard_header', user: @user, current_user: @user

expect(response).to have_selector('button#messages-btn span.badge', text: '2')
end

it 'should display breadcrumbs by sending :breadcrumbs to session[:breadcrumbs]' do
render 'layouts/dashboard/dashboard_header', user: @user
render 'layouts/dashboard/dashboard_header', user: @user, current_user: @user

expect(response).to have_content('All those other pages.')
end

it 'should display welcome message to user' do
render 'layouts/dashboard/dashboard_header', user: @user
render 'layouts/dashboard/dashboard_header', user: @user, current_user: @user

expect(response).to have_content(t(:dashboard)[:navbar][:logged_in_as] + "user@email.com")
expect(response).to have_tag('a', text: "Logout")
Expand Down