diff --git a/app/assets/javascripts/protocols/external_protocols_tab.js b/app/assets/javascripts/protocols/external_protocols_tab.js
index eebabc86c..5f42bd52c 100644
--- a/app/assets/javascripts/protocols/external_protocols_tab.js
+++ b/app/assets/javascripts/protocols/external_protocols_tab.js
@@ -1,25 +1,214 @@
function applyClickCallbackOnProtocolCards() {
$('.protocol-card').off('click').on('click', function(e) {
+ var currProtocolCard = $(this);
+
+ // Check whether this card is already active and deactivate it
+ if ($(currProtocolCard).hasClass('active')) {
+ resetPreviewPanel();
+ $(currProtocolCard).removeClass('active');
+ } else {
+ $('.protocol-card').removeClass('active');
+ currProtocolCard.addClass('active');
+
+ $.ajax({
+ url: $(currProtocolCard).data('show-url'),
+ type: 'GET',
+ dataType: 'json',
+ data: {
+ protocol_source: $(currProtocolCard).data('protocol-source'),
+ protocol_id: $(currProtocolCard).data('show-protocol-id')
+ },
+ beforeSend: animateSpinner($('.protocol-preview-panel'), true),
+ success: function(data) {
+ $('.empty-preview-panel').hide();
+ $('.full-preview-panel').show();
+ $('.btn-holder').html($(currProtocolCard).find('.external-import-btn').clone());
+ $('.preview-iframe').contents().find('body').html(data.html);
+
+ initLoadProtocolModalPreview();
+ animateSpinner($('.protocol-preview-panel'), false);
+ },
+ error: function(_error) {
+ // TODO: we should probably show some alert bubble
+ resetPreviewPanel();
+ animateSpinner($('.protocol-preview-panel'), false);
+ }
+ });
+ }
+ e.preventDefault();
+ return false;
+ });
+}
+
+// Resets preview to the default state
+function resetPreviewPanel() {
+ $('.empty-preview-panel').show();
+ $('.full-preview-panel').hide();
+}
+
+// Reset whole view to the default state
+function setDefaultViewState() {
+ resetPreviewPanel();
+ $('.empty-text').show();
+ $('.list-wrapper').hide();
+}
+
+// Apply AJAX callbacks onto the search box
+function applySearchCallback() {
+ var timeout;
+
+ // Submit form on every input in the search box
+ $('input[name="key"]').off('input').on('input', function() {
+ if (timeout) {
+ clearTimeout(timeout);
+ }
+
+ timeout = setTimeout(function() {
+ $('form.protocols-search-bar').submit();
+ }, 500);
+ });
+
+ // Submit form when clicking on sort buttons
+ $('input[name="sort_by"]').off('change').on('change', function() {
+ $('form.protocols-search-bar').submit();
+ });
+
+ // Bind ajax calls on the form
+ $('form.protocols-search-bar').off('ajax:success').off('ajax:error')
+ .bind('ajax:success', function(evt, data, status, xhr) {
+ if (data.html) {
+ resetPreviewPanel();
+ $('.empty-text').hide();
+ $('.list-wrapper').show();
+
+ $('.list-wrapper').html(data.html);
+ applyClickCallbackOnProtocolCards();
+ initLoadProtocolModalPreview();
+ } else {
+ setDefaultViewState();
+ }
+ })
+ .bind("ajax:error", function(evt, xhr, status, error) {
+ setDefaultViewState();
+
+ console.log(xhr.responseText);
+ });
+}
+
+function resetFormErrors(modal) {
+ // Reset all errors
+ modal.find('form > .form-group.has-error').removeClass('has-error');
+ modal.find('form > .form-group>span.help-block').html('');
+ modal.find('.general-error > span').html('');
+}
+
+function showFormErrors(modal, errors) {
+ resetFormErrors(modal);
+
+ // AR valdiation errors
+ Object.keys(errors.protocol).forEach(function(key) {
+ var input = modal.find('#protocol_' + key);
+ var msg;
+ msg = key.charAt(0).toUpperCase() + key.slice(1) + ': ' + errors.protocol[key].join(', ');
+ if ((input.length > 0) && (errors.protocol[key].length > 0)) {
+ input.parent().next('span.help-block').html(msg);
+ input.parent().parent().addClass('has-error');
+ } else if (errors.protocol[key].length > 0) {
+ modal.find('.general-error > span').append(msg + '
');
+ }
+ });
+}
+
+function renderTable(table) {
+ $(table).handsontable('render');
+ // Yet another dirty hack to solve HandsOnTable problems
+ if (parseInt($(table).css('height'), 10) < parseInt($(table).css('max-height'), 10) - 30) {
+ $(table).find('.ht_master .wtHolder').css({ height: '100%', width: '100%' });
+ }
+}
+
+// Expand all steps
+function expandAllSteps() {
+ $('.step .panel-collapse').collapse('show');
+ $(document).find("[data-role='step-hot-table']").each(function() {
+ renderTable($(this));
+ });
+ $(document).find('span.collapse-step-icon').each(function() {
+ $(this).addClass('fa-caret-square-up');
+ $(this).removeClass('fa-caret-square-down');
+ });
+}
+
+function handleFormSubmit(modal) {
+ var form = modal.find('form');
+ form.on('submit', function(e) {
+ var url = form.attr('action');
+ e.preventDefault(); // avoid to execute the actual submit of the form.
+ e.stopPropagation();
+ animateSpinner(modal, true);
$.ajax({
- url: $(this).data('show-url'),
- type: 'GET',
- dataType: 'json',
- data: {
- protocol_source: $(this).data('protocol-source'),
- protocol_id: $(this).data('show-protocol-id')
- },
- beforeSend: animateSpinner($('.protocol-preview-panel'), true),
+ type: 'POST',
+ url: url,
+ data: form.serialize(), // serializes the form's elements.
success: function(data) {
- $('.empty-preview-panel').hide();
- $('.full-preview-panel').show();
- $('.preview-iframe').contents().find('body').html(data.html);
- animateSpinner($('.protocol-preview-panel'), false);
+ animateSpinner(modal, false);
+ window.location.replace(data.redirect_url);
},
- error: function(_error) {
- // TODO: we should probably show some alert bubble
- $('.empty-preview-panel').show();
- $('.full-preview-panel').hide();
- animateSpinner($('.protocol-preview-panel'), false);
+ error: function(data) {
+ showFormErrors(modal, data.responseJSON.errors);
+ },
+ complete: function() {
+ animateSpinner(modal, false);
+ }
+ });
+ });
+}
+
+function initLoadProtocolModalPreview() {
+ $('.external-import-btn').off('click').on('click', function(e) {
+ var link = $(this).parents('.protocol-card');
+
+ // When clicking on the banner button, we have no protocol-card parent
+ if (link.length === 0) {
+ link = $('.protocol-card.active');
+ }
+
+ animateSpinner(null, true);
+ $.ajax({
+ url: link.data('url'),
+ type: 'GET',
+ data: {
+ protocol_source: link.data('protocol-source'),
+ protocol_client_id: link.data('id')
+ },
+ dataType: 'json',
+ success: function(data) {
+ var modal = $('#protocol-preview-modal');
+ var modalTitle = modal.find('.modal-title');
+ var modalBody = modal.find('.modal-body');
+ var modalFooter = modal.find('.modal-footer');
+
+ modalTitle.html(data.title);
+ modalBody.html(data.html);
+ modalFooter.html(data.footer);
+ initHandsOnTable(modalBody);
+ modal.modal('show');
+ expandAllSteps();
+ initHandsOnTable(modalBody);
+
+ if (data.validation_errors) {
+ showFormErrors(modal, data.validation_errors);
+ }
+
+ initFormSubmits();
+ handleFormSubmit(modal);
+ },
+ error: function(error) {
+ console.log(error.responseJSON.errors);
+ alert('Server error');
+ },
+ complete: function() {
+ animateSpinner(null, false);
}
});
e.preventDefault();
@@ -27,4 +216,14 @@ function applyClickCallbackOnProtocolCards() {
});
}
-applyClickCallbackOnProtocolCards();
+function initFormSubmits() {
+ var modal = $('#protocol-preview-modal');
+ modal.find('button[data-action=import_protocol]').off('click').on('click', function() {
+ var form = modal.find('form');
+ var hiddenField = form.find('#protocol_protocol_type');
+ hiddenField.attr('value', $(this).data('import_type'));
+ form.submit();
+ });
+}
+
+applySearchCallback();
diff --git a/app/assets/javascripts/sitewide/external_protocols.js b/app/assets/javascripts/sitewide/external_protocols.js
deleted file mode 100644
index a93395187..000000000
--- a/app/assets/javascripts/sitewide/external_protocols.js
+++ /dev/null
@@ -1,142 +0,0 @@
-/* global animateSpinner initHandsOnTable */
-/* eslint-disable no-restricted-globals, no-alert */
-
-var ExternalProtocols = (function() {
- function resetFormErrors(modal) {
- // Reset all errors
- modal.find('form > .form-group.has-error').removeClass('has-error');
- modal.find('form > .form-group>span.help-block').html('');
- modal.find('.general-error > span').html('');
- }
-
- function showFormErrors(modal, errors) {
- resetFormErrors(modal);
-
- // AR valdiation errors
- Object.keys(errors.protocol).forEach(function(key) {
- var input = modal.find('#protocol_' + key);
- var msg;
- msg = key.charAt(0).toUpperCase() + key.slice(1) + ': ' + errors.protocol[key].join(', ');
- if ((input.length > 0) && (errors.protocol[key].length > 0)) {
- input.parent().next('span.help-block').html(msg);
- input.parent().parent().addClass('has-error');
- } else if (errors.protocol[key].length > 0) {
- modal.find('.general-error > span').append(msg + '
');
- }
- });
- }
-
- function renderTable(table) {
- $(table).handsontable('render');
- // Yet another dirty hack to solve HandsOnTable problems
- if (parseInt($(table).css('height'), 10) < parseInt($(table).css('max-height'), 10) - 30) {
- $(table).find('.ht_master .wtHolder').css({ height: '100%', width: '100%' });
- }
- }
-
- // Expand all steps
- function expandAllSteps() {
- $('.step .panel-collapse').collapse('show');
- $(document).find("[data-role='step-hot-table']").each(function() {
- renderTable($(this));
- });
- $(document).find('span.collapse-step-icon').each(function() {
- $(this).addClass('fa-caret-square-up');
- $(this).removeClass('fa-caret-square-down');
- });
- }
-
- function handleFormSubmit(modal) {
- var form = modal.find('form');
- form.on('submit', function(e) {
- var url = form.attr('action');
- e.preventDefault(); // avoid to execute the actual submit of the form.
- e.stopPropagation();
- animateSpinner(modal, true);
- $.ajax({
- type: 'POST',
- url: url,
- data: form.serialize(), // serializes the form's elements.
- success: function(data) {
- animateSpinner(modal, false);
- window.location.replace(data.redirect_url);
- },
- error: function(data) {
- showFormErrors(modal, data.responseJSON.errors);
- },
- complete: function() {
- animateSpinner(modal, false);
- }
- });
- });
- }
-
- function initLoadProtocolModalPreview() {
- var externalProtocols = $('.external-protocol-result');
- externalProtocols.on('click', 'a[data-action="external-import"]', function(e) {
- var link = $(this);
- animateSpinner(null, true);
- $.ajax({
- url: link.attr('data-url'),
- type: 'GET',
- data: {
- protocol_source: link.attr('data-source'),
- protocol_client_id: link.attr('data-id')
- },
- dataType: 'json',
- success: function(data) {
- var modal = $('#protocol-preview-modal');
- var modalTitle = modal.find('.modal-title');
- var modalBody = modal.find('.modal-body');
- var modalFooter = modal.find('.modal-footer');
-
- modalTitle.html(data.title);
- modalBody.html(data.html);
- modalFooter.html(data.footer);
- initHandsOnTable(modalBody);
- modal.modal('show');
- expandAllSteps();
- initHandsOnTable(modalBody);
-
- if (data.validation_errors) {
- showFormErrors(modal, data.validation_errors);
- }
-
- handleFormSubmit(modal);
- },
- error: function(error) {
- console.log(error.responseJSON.errors);
- alert('Server error');
- },
- complete: function() {
- animateSpinner(null, false);
- }
- });
- e.preventDefault();
- return false;
- });
- }
-
- function initFormSubmits() {
- var modal = $('#protocol-preview-modal');
- modal.on('click', 'button[data-action=import_protocol]', function() {
- var form = modal.find('form');
- var hiddenField = form.find('#protocol_protocol_type');
- hiddenField.attr('value', $(this).data('import_type'));
- form.submit();
- });
- }
-
- return {
- init: () => {
- if ($('.external-protocols-tab').length > 0) {
- initLoadProtocolModalPreview();
- initFormSubmits();
- }
- }
- };
-}());
-
-$(document).on('turbolinks:load', function() {
- ExternalProtocols.init();
-});
diff --git a/app/assets/stylesheets/protocol_management.scss b/app/assets/stylesheets/protocol_management.scss
index 0ea1094d3..fbe09bd44 100644
--- a/app/assets/stylesheets/protocol_management.scss
+++ b/app/assets/stylesheets/protocol_management.scss
@@ -74,6 +74,12 @@
margin-bottom: 15px;
padding-right: 0;
+ .service-provider {
+ display: flex;
+ align-items: center;
+ width: 50%;
+ }
+
.protocolsio-logo {
height: 30px;
width: 30px;
@@ -82,25 +88,20 @@
.protocolsio-title {
color: $brand-primary;
font-size: 14px;
+ margin-left: 3px;
vertical-align: middle;
}
- .protocols-search-bar {
- padding-right: 0;
- width: 70%;
+ .input-group {
+ margin-bottom: 0;
- .input-group {
- margin-bottom: 0;
- width: 100%;
+ .form-control {
+ border-right: 0;
+ }
- .form-control {
- border-right: 0;
- }
-
- .input-group-addon {
- background: $color-white;
- color: $color-silver-chalice;
- }
+ .input-group-addon {
+ background: $color-white;
+ color: $color-silver-chalice;
}
}
}
@@ -149,6 +150,40 @@
font-size: 13px;
margin-top: 20px;
}
+
+ .list-wrapper {
+ height: 100%;
+ overflow: auto;
+ }
+
+ .protocol-card {
+ border-bottom: 1px solid $color-gainsboro;
+ margin-right: 20px;
+ padding: 12px 11px 7px 11px;
+
+ &.active {
+ border: 2px solid $brand-primary;
+ border-radius: 2px;
+ box-shadow: 0 1px 4px 0 $color-black;
+ }
+
+ &:hover {
+ background-color: rgba(64,161,215,0.1);
+ }
+ }
+
+ .protocol-title {
+ color: $brand-primary;
+ font-size: 16px;
+ }
+
+ .info-line {
+ color: $color-dove-gray;
+ font-size: 13px;
+ padding-left: 0;
+ padding-right: 0;
+ }
+
}
.protocol-preview-panel {
@@ -188,10 +223,35 @@
}
.full-preview-panel {
+ height: 100%;
+ }
+
+ .preview-banner {
+ align-items: center;
+ background-color: $color-white;
+ border-bottom: 1px solid $color-alto;
+ color: $color-dove-gray;
+ display: flex;
+ font-size: 16px;
+ height: 40px;
+ padding-left: 21px;
+
+ .txt-holder {
+ padding-left: 0;
+ }
+
+ .btn-holder {
+ padding-right: 21px;
+ }
+ }
+
+
+ .preview-holder {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
+ padding-bottom: 40px;
padding-left: 21px;
padding-right: 21px;
width: 100%;
@@ -204,4 +264,13 @@
}
}
}
+
+ .external-import-btn {
+ background-color: $brand-primary;
+ border: none;
+ color: $color-white;
+ font-size: 12px;
+ padding-bottom: 3px;
+ padding-top: 3px;
+ }
}
diff --git a/app/controllers/external_protocols_controller.rb b/app/controllers/external_protocols_controller.rb
index 619d7f2f4..1384646b2 100644
--- a/app/controllers/external_protocols_controller.rb
+++ b/app/controllers/external_protocols_controller.rb
@@ -7,7 +7,8 @@ class ExternalProtocolsController < ApplicationController
# GET list_external_protocols
def index
service_call = ProtocolImporters::SearchProtocolsService
- .call(protocol_source: 'protocolsio/v3', query_params: index_params)
+ .call(protocol_source: index_params[:protocol_source],
+ query_params: index_params)
if service_call.succeed?
render json: {
@@ -101,7 +102,7 @@ class ExternalProtocolsController < ApplicationController
end
def index_params
- params.permit(:protocol_source, :key, :page_id, :page_size, :order_field, :order_dir)
+ params.permit(:protocol_source, :key, :page_id, :page_size, :sort_by)
end
def show_params
diff --git a/app/services/protocol_importers/search_protocols_service.rb b/app/services/protocol_importers/search_protocols_service.rb
index f3fd73034..fdd61e5c5 100644
--- a/app/services/protocol_importers/search_protocols_service.rb
+++ b/app/services/protocol_importers/search_protocols_service.rb
@@ -11,7 +11,7 @@ module ProtocolImporters
def initialize(protocol_source:, query_params: {})
@protocol_source = protocol_source
- @query_params = query_params
+ @query_params = query_params.except(:protocol_source)
@errors = Hash.new { |h, k| h[k] = {} }
end
@@ -51,16 +51,6 @@ module ProtocolImporters
@errors[:invalid_params][:page_id] = 'Page needs to be positive'
end
- # try if order_field is ok
- if @query_params[:order_field] && CONSTANTS[:available_order_fields].exclude?(@query_params[:order_field]&.to_sym)
- @errors[:invalid_params][:order_field] = 'Order field is not ok'
- end
-
- # try if order dir is ok
- if @query_params[:order_field] && CONSTANTS[:available_order_dirs].exclude?(@query_params[:order_dir]&.to_sym)
- @errors[:invalid_params][:order_dir] = 'Order dir is not ok'
- end
-
# try if endpints exists
@errors[:invalid_params][:source_endpoint] = 'Wrong source endpoint' unless endpoint_name&.is_a?(String)
diff --git a/app/utilities/protocol_importers/protocols_io/v3/api_client.rb b/app/utilities/protocol_importers/protocols_io/v3/api_client.rb
index 441dbd200..c8986e3a0 100644
--- a/app/utilities/protocol_importers/protocols_io/v3/api_client.rb
+++ b/app/utilities/protocol_importers/protocols_io/v3/api_client.rb
@@ -44,8 +44,13 @@ module ProtocolImporters
# Default is 1.
def protocol_list(query_params = {})
response = with_handle_network_errors do
+ sort_mappings = CONSTANTS[:sort_mappings]
query = CONSTANTS.dig(:endpoints, :protocols, :default_query_params)
- .merge(query_params)
+ .merge(query_params.except(:sort_by))
+
+ if sort_mappings[query_params[:sort_by]&.to_sym]
+ query = query.merge(sort_mappings[query_params[:sort_by].to_sym])
+ end
self.class.get('/protocols', query: query)
end
diff --git a/app/utilities/protocol_importers/protocols_io/v3/protocol_normalizer.rb b/app/utilities/protocol_importers/protocols_io/v3/protocol_normalizer.rb
index cac909a40..decb3a068 100644
--- a/app/utilities/protocol_importers/protocols_io/v3/protocol_normalizer.rb
+++ b/app/utilities/protocol_importers/protocols_io/v3/protocol_normalizer.rb
@@ -50,11 +50,17 @@ module ProtocolImporters
original_order = protocol_hash[:steps].map { |m| [m[:previous_id], m[:id]] }.to_h
current_position = 0
while next_step_id
+
current_position += 1
steps[next_step_id][:position] = current_position
next_step_id = original_order[next_step_id]
end
+ # Check if step name are valid
+ steps.each do |step|
+ step[1][:name] = "Step #{(step[1][:position] + 1)}" if step[1][:name].blank?
+ end
+
{ protocol: normalized_data }
rescue StandardError => e
raise ProtocolImporters::ProtocolsIO::V3::NormalizerError.new(e.class.to_s.downcase.to_sym), e.message
@@ -69,6 +75,7 @@ module ProtocolImporters
{
id: e[:id],
title: e[:title],
+ source: Constants::PROTOCOLS_IO_V3_API[:source_id],
created_on: e[:created_on],
authors: e[:authors].map { |a| a[:name] }.join(', '),
nr_of_steps: e[:stats][:number_of_steps],
diff --git a/app/views/protocol_importers/_list_of_protocol_cards.html.erb b/app/views/protocol_importers/_list_of_protocol_cards.html.erb
index 4164c5c72..45cdd4e94 100644
--- a/app/views/protocol_importers/_list_of_protocol_cards.html.erb
+++ b/app/views/protocol_importers/_list_of_protocol_cards.html.erb
@@ -1,5 +1,4 @@
-<% protocols.each do |protocol| %>
+<% protocols[:protocols].each do |protocol| %>
<%= render partial: 'protocol_importers/protocol_card',
locals: { protocol: protocol } %>
-
<%= protocol[:title] %>
+ +