diff --git a/app/assets/javascripts/reports/index.js b/app/assets/javascripts/reports/index.js
deleted file mode 100644
index e6a8ee9ea..000000000
--- a/app/assets/javascripts/reports/index.js
+++ /dev/null
@@ -1,166 +0,0 @@
-(function () {
-
- var newReportModal = null;
- var newReportModalBody = null;
- var newReportCreateButton = null;
-
- var deleteReportsModal = null;
- var deleteReportsInput = null;
-
- var newReportButton = null;
- var editReportButton = null;
- var deleteReportsButton = null;
- var checkAll = null;
- var allChecks = null;
- var allRows = null;
-
- var checkedReports = [];
-
- /**
- * Initializes page
- */
- function init() {
- // Initialize selectors
- newReportModal = $('#new-report-modal');
- newReportModalBody = newReportModal.find('.modal-body');
- newReportCreateButton = $('#create-new-report-btn');
- deleteReportsModal = $('#delete-reports-modal');
- deleteReportsInput = $('#report-ids');
- newReportButton = $('#new-report-btn');
- editReportButton = $('#edit-report-btn');
- deleteReportsButton = $('#delete-reports-btn');
- checkAll = $('.check-all-reports');
- allChecks = $('.check-report');
- allRows = $('.report-row');
-
- initNewReportModal();
- initCheckboxesAndEditing();
- updateButtons();
- initEditReport();
- initDeleteReports();
- }
-
- /**
- * Initialize the new report modal.
- */
- function initNewReportModal() {
- // TEMPORARY DISABLED
- /**
- // Remove modal content when modal window is closed.
- newReportModal.on("hidden.bs.modal", function () {
- newReportModalBody.html("");
- });
-
- // Populate modal content when AJAX call is complete
- newReportButton
- .on("ajax:before", function () {
- newReportModal.modal('show');
- })
- .on("ajax:success", function (e, data) {
- newReportModalBody.html(data.html);
- });
-
- // Before redirecting, pass parameters
- newReportCreateButton.click(function(event){
- var url = $(this).closest("form").attr("action");
-
- event.preventDefault();
-
- // Copy the GET params
- var val = newReportModalBody.find(".btn-primary.active > input[type='radio']").attr("value");
- url += "/" + val;
-
- $(location).attr("href", url);
- return false;
- });
- */
- }
-
- /**
- * Initialize interaction between checkboxes, editing and deleting.
- */
- function initCheckboxesAndEditing() {
- checkAll.click(function() {
- allChecks.prop("checked", this.checked);
- checkedReports = [];
- if (this.checked) {
- _.each(allRows, function(row) {
- checkedReports.push($(row).data("id"));
- });
- }
-
- updateButtons();
- });
- allChecks.click(function() {
- checkAll.prop("checked", false);
- var id = $(this).closest(".report-row").data("id");
- if (this.checked) {
- if (_.indexOf(checkedReports, id) === -1) {
- checkedReports.push(id);
- }
- } else {
- var idx = _.indexOf(checkedReports, id);
- if (idx !== -1) {
- checkedReports.splice(idx, 1);
- }
- }
-
- updateButtons();
- });
- }
-
- /**
- * Update edit & delete buttons depending on checking of reports.
- */
- function updateButtons() {
- if (checkedReports.length === 0) {
- editReportButton.addClass("disabled");
- deleteReportsButton.addClass("disabled");
- } else if (checkedReports.length === 1) {
- editReportButton.removeClass("disabled");
- deleteReportsButton.removeClass("disabled");
- } else {
- editReportButton.addClass("disabled");
- deleteReportsButton.removeClass("disabled");
- }
- }
-
- /**
- * Initialize the edit report functionality.
- */
- function initEditReport() {
- editReportButton.click(function(e) {
- animateLoading();
- if (checkedReports.length === 1) {
- var id = checkedReports[0];
- var row = $(".report-row[data-id='" + id + "']");
- var url = row.data("edit-link");
-
- $(location).attr("href", url);
- }
-
- return false;
- });
- }
-
- /**
- * Initialize the deleting of reports.
- */
- function initDeleteReports() {
- deleteReportsButton.click(function(e) {
- if (checkedReports.length > 0) {
- // Copy the checked IDs into the hidden input
- deleteReportsInput.attr("value", "[" + checkedReports + "]");
-
- // Show modal
- deleteReportsModal.modal("show");
- }
- });
-
- $("#confirm-delete-reports-btn").click(function(e) {
- animateLoading();
- });
- }
-
- $(document).ready(init);
-}());
diff --git a/app/assets/javascripts/reports/reports_datatable.js.erb b/app/assets/javascripts/reports/reports_datatable.js.erb
new file mode 100644
index 000000000..3fdc28838
--- /dev/null
+++ b/app/assets/javascripts/reports/reports_datatable.js.erb
@@ -0,0 +1,200 @@
+(function(global) {
+ 'use strict';
+
+ var DATATABLE;
+ var CHECKED_REPORTS = [];
+
+ function tableDrowCallback() {
+ checkboxToggleCallback();
+ initToggleAllCheckboxes();
+ updateButtons();
+ }
+
+ function initSelectPicker() {
+ $('.selectpicker').selectpicker({liveSearch: true})
+ .ajaxSelectPicker({
+ ajax: {
+ url: '<%= Rails.application.routes.url_helpers.reports_visible_projects_path %>',
+ type: 'POST',
+ dataType: 'json',
+ data: function () {
+ return { q: '{{{q}}}' };
+ }
+ },
+ locale: {
+ emptyTitle: 'Nothing selected'
+ },
+ preprocessData: appendSearchResults,
+ emptyRequest: true,
+ clearOnEmpty: false,
+ preserveSelected: false
+ }).on('change.bs.select', function(el) {
+ $('#new-report-reports-btn').attr('data-new-report-path', el.target.value);
+ }).on('loaded.bs.select', function(el) {
+ $('#new-report-reports-btn').attr('data-new-report-path', el.target.value);
+ });
+ }
+
+ function appendSearchResults(data) {
+ var items = [];
+ if(data.hasOwnProperty('projects')){
+ $.each(data.projects, function(index, el) {
+ debugger;
+ items.push(
+ {
+ 'value': el.path,
+ 'text': el.name,
+ 'disabled': false
+ }
+ )
+ });
+ }
+ return items;
+ }
+
+ function initRedirectToNewReportPage() {
+ $('#new-report-reports-btn').on('click', function() {
+ animateSpinner();
+ var url = $(this).attr('data-new-report-path');
+ $(location).attr('href', url);
+ });
+ }
+
+ function initToggleAllCheckboxes() {
+ $('input[name="select_all"]').change(function() {
+ if($(this).is(':checked')) {
+ $("[data-action='toggle']").prop('checked', true);
+ addAllItems();
+ } else {
+ $("[data-action='toggle']").prop('checked', false);
+ removeAllItems();
+ }
+ updateButtons();
+ });
+ }
+
+ function addAllItems() {
+ $.each($("[data-action='toggle']"), function(i, el) {
+ CHECKED_REPORTS.push($(el).attr('data-report-id'));
+ })
+ }
+
+ function removeAllItems() {
+ CHECKED_REPORTS = [];
+ }
+
+ function renderCheckboxHTML(data) {
+ var html;
+ html = "";
+ return html;
+ }
+
+ function appendEditPathToRow(row, data) {
+ $(row).addClass('report-row')
+ .attr('data-edit-path', data['edit'])
+ .attr('data-id', data['0']);
+ }
+
+ function checkboxToggleCallback() {
+ $("[data-action='toggle']").change(function() {
+ var id = $(this).attr('data-report-id');
+ if($(this).is(':checked')) {
+ CHECKED_REPORTS.push(id);
+ } else {
+ var index = CHECKED_REPORTS.indexOf(id);
+ if(index != -1) {
+ CHECKED_REPORTS.splice(index, 1);
+ }
+ }
+ updateButtons();
+ });
+ }
+
+ function updateButtons() {
+ var editReportButton = $('#edit-report-btn');
+ var deleteReportsButton = $('#delete-reports-btn');
+ if (CHECKED_REPORTS.length === 0) {
+ editReportButton.addClass("disabled");
+ deleteReportsButton.addClass("disabled");
+ } else if (CHECKED_REPORTS.length === 1) {
+ editReportButton.removeClass("disabled");
+ deleteReportsButton.removeClass("disabled");
+ } else {
+ editReportButton.addClass("disabled");
+ deleteReportsButton.removeClass("disabled");
+ }
+ }
+
+ // INIT
+
+ function initDatatable() {
+ var $table = $('#reports-table')
+ DATATABLE = $table.dataTable({
+ 'order': [[2, 'desc']],
+ 'processing': true,
+ 'serverSide': true,
+ 'ajax': $table.data('source'),
+ 'pagingType': 'simple_numbers',
+ 'colReorder': {
+ 'fixedColumnsLeft': 1000000 // Disable reordering
+ },
+ 'columnDefs': [{
+ 'targets': 0,
+ 'searchable': false,
+ 'orderable': false,
+ 'className': 'dt-body-center',
+ 'sWidth': '1%',
+ 'render': renderCheckboxHTML
+ }],
+ 'fnDrawCallback': tableDrowCallback,
+ 'createdRow': appendEditPathToRow
+ });
+ }
+
+ function initEditReport() {
+ $('#edit-report-btn').click(function(e) {
+ e.preventDefault();
+ animateSpinner();
+ if (CHECKED_REPORTS.length === 1) {
+ var id = CHECKED_REPORTS[0];
+ var row = $(".report-row[data-id='" + id + "']");
+ var url = row.attr('data-edit-path');
+ $(location).attr('href', url);
+ }
+ });
+ }
+
+ function initDeleteReports() {
+ $('#delete-reports-btn').click(function(e) {
+ if (CHECKED_REPORTS.length > 0) {
+ $('#report-ids').attr("value", "[" + CHECKED_REPORTS + "]");
+ $('#delete-reports-modal').modal("show");
+ }
+ });
+
+ $("#confirm-delete-reports-btn").click(function(e) {
+ animateLoading();
+ });
+ }
+
+ function initNewReportModal() {
+ $('#new-report-btn').on('click', function() {
+ $('#new-report-modal').modal('show').promise().done(function() {
+ initSelectPicker();
+ initRedirectToNewReportPage();
+ });
+ });
+ }
+
+ function init() {
+ $(document).ready(function() {
+ initDatatable();
+ initEditReport();
+ initDeleteReports();
+ initNewReportModal();
+ });
+ }
+
+ init();
+})(window);
diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb
index 83cd7850e..bd1768087 100644
--- a/app/controllers/reports_controller.rb
+++ b/app/controllers/reports_controller.rb
@@ -5,37 +5,11 @@ class ReportsController < ApplicationController
# used via target='_blank')
protect_from_forgery with: :exception, except: :generate
- before_action :load_vars, only: [
- :edit,
- :update
- ]
- before_action :load_vars_nested, only: [
- :index,
- :new,
- :create,
- :edit,
- :update,
- :generate,
- :destroy,
- :save_modal,
- :project_contents_modal,
- :experiment_contents_modal,
- :module_contents_modal,
- :step_contents_modal,
- :result_contents_modal,
- :project_contents,
- :module_contents,
- :step_contents,
- :result_contents
- ]
-
- before_action :check_view_permissions, only: :index
- before_action :check_manage_permissions, only: %i(
+ BEFORE_ACTION_METHODS = %i(
new
create
edit
update
- destroy
generate
save_modal
project_contents_modal
@@ -47,12 +21,30 @@ class ReportsController < ApplicationController
module_contents
step_contents
result_contents
- )
+ ).freeze
+
+ before_action :load_vars, only: %i(edit update)
+ before_action :load_vars_nested, only: BEFORE_ACTION_METHODS
+ before_action :load_visible_projects, only: %i(index visible_projects)
+
+ # before_action :check_view_permissions, only: :index
+ before_action :check_manage_permissions, only: BEFORE_ACTION_METHODS
layout 'fluid'
# Index showing all reports of a single project
- def index
+ def index; end
+
+ def datatable
+ respond_to do |format|
+ format.json do
+ render json: ::ReportDatatable.new(
+ view_context,
+ current_user,
+ current_team.datatables_reports.visible_by(current_user, current_team)
+ )
+ end
+ end
end
# Report grouped by modules
@@ -72,6 +64,7 @@ class ReportsController < ApplicationController
@report = Report.new(report_params)
@report.project = @project
@report.user = current_user
+ @report.team = current_team
@report.last_modified_by = current_user
if continue && @report.save_with_contents(report_contents)
@@ -88,7 +81,7 @@ class ReportsController < ApplicationController
)
respond_to do |format|
format.json do
- render json: { url: project_reports_path(@project) }, status: :ok
+ render json: { url: reports_path }, status: :ok
end
end
else
@@ -155,7 +148,7 @@ class ReportsController < ApplicationController
report_ids.each do |report_id|
report = Report.find_by_id(report_id)
- next unless report.present?
+ next unless report.present? && can_manage_reports?(current_team)
# record an activity
Activity.create(
type_of: :delete_report,
@@ -170,7 +163,7 @@ class ReportsController < ApplicationController
report.destroy
end
- redirect_to project_reports_path(@project)
+ redirect_to reports_path
end
# Generation action
@@ -434,8 +427,14 @@ class ReportsController < ApplicationController
end
end
+ def visible_projects
+ render json: { projects: @visible_projects }, status: :ok
+ end
+
private
+ VisibleProject = Struct.new(:path, :name)
+
def load_vars
@report = Report.find_by_id(params[:id])
render_404 unless @report
@@ -451,11 +450,27 @@ class ReportsController < ApplicationController
end
def check_manage_permissions
- render_403 unless can_manage_reports?(@project)
+ render_403 unless can_manage_reports?(@project.team)
+ end
+
+ def load_visible_projects
+ render_404 unless current_team
+ projects = current_team.projects.visible_by(current_user)
+ .where('projects.name ILIKE ?',
+ "%#{search_params[:q]}%")
+ .limit(Constants::SEARCH_LIMIT)
+ .select(:id, :name)
+ @visible_projects = projects.collect do |project|
+ VisibleProject.new(new_project_reports_path(project), project.name)
+ end
end
def report_params
params.require(:report)
.permit(:name, :description, :grouped_by, :report_contents)
end
+
+ def search_params
+ params.permit(:q)
+ end
end
diff --git a/app/datatables/report_datatable.rb b/app/datatables/report_datatable.rb
new file mode 100644
index 000000000..3edd0e934
--- /dev/null
+++ b/app/datatables/report_datatable.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+class ReportDatatable < CustomDatatable
+ TABLE_COLUMNS = %w(
+ Views::Datatables::DatatablesReport.project_name
+ Views::Datatables::DatatablesReport.name
+ Views::Datatables::DatatablesReport.created_by
+ Views::Datatables::DatatablesReport.last_modified_by
+ Views::Datatables::DatatablesReport.created_at
+ Views::Datatables::DatatablesReport.updated_at
+ ).freeze
+
+ def_delegator :@view, :edit_project_report_path
+ def initialize(view, user, reports)
+ super(view)
+ @user = user
+ @reports = reports
+ end
+
+ def sortable_columns
+ @sortable_columns ||= TABLE_COLUMNS
+ end
+
+ def searchable_columns
+ @searchable_columns ||= TABLE_COLUMNS
+ end
+
+ private
+
+ def data
+ records.map do |record|
+ {
+ '0' => record.id,
+ '1' => record.project_name,
+ '2' => record.name,
+ '3' => record.created_by,
+ '4' => record.last_modified_by,
+ '5' => I18n.l(record.created_at, format: :full),
+ '6' => I18n.l(record.updated_at, format: :full),
+ 'edit' => edit_project_report_path(record.project_id, record.id)
+ }
+ end
+ end
+
+ def get_raw_records
+ @reports
+ end
+
+ # ==== Insert 'presenter'-like methods below if necessary
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index 3fcc8b0c3..9c68b096f 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -38,6 +38,18 @@ class Project < ApplicationRecord
has_many :reports, inverse_of: :project, dependent: :destroy
has_many :report_elements, inverse_of: :project, dependent: :destroy
+ after_commit do
+ Scenic.database.refresh_materialized_view(:datatables_reports,
+ concurrently: true,
+ cascade: false)
+ end
+
+ scope :visible_by, -> (user) {
+ joins(:user_projects).where(
+ 'user_projects.user_id = ? AND projects.archived = false', user.id
+ )
+ }
+
def self.search(
user,
include_archived,
diff --git a/app/models/report.rb b/app/models/report.rb
index aa44da7a8..777a962f2 100644
--- a/app/models/report.rb
+++ b/app/models/report.rb
@@ -12,6 +12,7 @@ class Report < ApplicationRecord
belongs_to :project, inverse_of: :reports, optional: true
belongs_to :user, inverse_of: :reports, optional: true
+ belongs_to :team, inverse_of: :reports
belongs_to :last_modified_by,
foreign_key: 'last_modified_by_id',
class_name: 'User',
@@ -21,6 +22,12 @@ class Report < ApplicationRecord
# or many module elements (if grouped by module)
has_many :report_elements, inverse_of: :report, dependent: :destroy
+ after_commit do
+ Scenic.database.refresh_materialized_view(:datatables_reports,
+ concurrently: true,
+ cascade: false)
+ end
+
def self.search(
user,
include_archived,
diff --git a/app/models/team.rb b/app/models/team.rb
index d06cc5364..56b906894 100644
--- a/app/models/team.rb
+++ b/app/models/team.rb
@@ -32,6 +32,15 @@ class Team < ApplicationRecord
has_many :protocol_keywords, inverse_of: :team, dependent: :destroy
has_many :tiny_mce_assets, inverse_of: :team, dependent: :destroy
has_many :repositories, dependent: :destroy
+ has_many :reports, inverse_of: :team, dependent: :destroy
+ has_many :datatables_reports,
+ class_name: 'Views::Datatables::DatatablesReport'
+
+ after_commit do
+ Scenic.database.refresh_materialized_view(:datatables_reports,
+ concurrently: true,
+ cascade: false)
+ end
def search_users(query = nil)
a_query = "%#{query}%"
diff --git a/app/models/user_project.rb b/app/models/user_project.rb
index 6dd2879cf..3cac87183 100644
--- a/app/models/user_project.rb
+++ b/app/models/user_project.rb
@@ -14,6 +14,12 @@ class UserProject < ApplicationRecord
before_destroy :destroy_associations
+ after_commit do
+ Scenic.database.refresh_materialized_view(:datatables_reports,
+ concurrently: true,
+ cascade: false)
+ end
+
def role_str
I18n.t("user_projects.enums.role.#{role.to_s}")
end
diff --git a/app/models/user_team.rb b/app/models/user_team.rb
index 6850ac154..2bc3fe686 100644
--- a/app/models/user_team.rb
+++ b/app/models/user_team.rb
@@ -15,6 +15,12 @@ class UserTeam < ApplicationRecord
before_destroy :destroy_associations
after_create :create_samples_table_state
+ after_commit do
+ Scenic.database.refresh_materialized_view(:datatables_reports,
+ concurrently: true,
+ cascade: false)
+ end
+
def role_str
I18n.t("user_teams.enums.role.#{role}")
end
diff --git a/app/models/views/datatables/datatables_report.rb b/app/models/views/datatables/datatables_report.rb
new file mode 100644
index 000000000..82c3120ef
--- /dev/null
+++ b/app/models/views/datatables/datatables_report.rb
@@ -0,0 +1,75 @@
+module Views
+ module Datatables
+ class DatatablesReport < ApplicationRecord
+ belongs_to :team
+
+ # this isn't strictly necessary, but it will prevent
+ # rails from calling save, which would fail anyway.
+ def readonly?
+ true
+ end
+
+ class << self
+ def visible_by(user, team)
+ permitted_by_team = get_permitted_by_team_tokenized
+ permitted_by_project = get_permitted_by_project_tokenized
+ if user.is_admin_of_team? team
+ allowed_ids = for_admin(
+ user, permitted_by_team, permitted_by_project
+ )
+ else
+ allowed_ids = for_non_admin(
+ user, permitted_by_team, permitted_by_project
+ )
+ end
+ where(id: allowed_ids).where(project_archived: false)
+ end
+
+ private
+
+ PermissionItem = Struct.new(:report_id, :users_ids)
+
+ def tokenize(items)
+ items.collect do |item|
+ PermissionItem.new(item[0], item[1])
+ end
+ end
+
+ def get_permitted_by_team_tokenized
+ tokenize(pluck(:id, :users_with_team_read_permissions))
+ end
+
+ def get_permitted_by_project_tokenized
+ tokenize(pluck(:id, :users_with_project_read_permissions))
+ end
+
+ def get_by_project_item(permitted_by_project, item)
+ permitted_by_project.select { |el| el.report_id == item.report_id }
+ .first
+ end
+
+ def for_admin(user, permitted_by_team, permitted_by_project)
+ allowed_ids = []
+ permitted_by_team.each do |item|
+ next unless user.id.in? item.users_ids
+ by_project = get_by_project_item(permitted_by_project, item)
+ next unless user.id.in? by_project.users_ids
+ allowed_ids << item.report_id
+ end
+ allowed_ids
+ end
+
+ def for_non_admin(user, permitted_by_team, permitted_by_project)
+ allowed_ids = []
+ permitted_by_team.each do |item|
+ next unless user.id.in? item.users_ids
+ by_project = get_by_project_item(permitted_by_project, item)
+ next unless user.id.in? by_project.users_ids
+ allowed_ids << item.report_id
+ end
+ allowed_ids
+ end
+ end
+ end
+ end
+end
diff --git a/app/permissions/project.rb b/app/permissions/project.rb
index e3701db6d..74ec68e18 100644
--- a/app/permissions/project.rb
+++ b/app/permissions/project.rb
@@ -5,8 +5,7 @@ Canaid::Permissions.register_for(Project) do
archive_project
create_experiments
create_comments_in_project
- manage_tags
- manage_reports)
+ manage_tags)
.each do |perm|
can perm do |_, project|
project.active?
@@ -54,11 +53,6 @@ Canaid::Permissions.register_for(Project) do
can :manage_tags do |user, project|
user.is_user_or_higher_of_project?(project)
end
-
- # reports: create, delete
- can :manage_reports do |user, project|
- user.is_technician_or_higher_of_project?(project)
- end
end
Canaid::Permissions.register_for(ProjectComment) do
diff --git a/app/permissions/team.rb b/app/permissions/team.rb
index a27c56585..c7d288cc9 100644
--- a/app/permissions/team.rb
+++ b/app/permissions/team.rb
@@ -61,6 +61,10 @@ Canaid::Permissions.register_for(Team) do
can :create_repository_columns do |user, team|
user.is_normal_user_or_admin_of_team?(team)
end
+
+ can :manage_reports do |user, team|
+ user.is_normal_user_or_admin_of_team?(team)
+ end
end
Canaid::Permissions.register_for(Protocol) do
diff --git a/app/views/reports/index.html.erb b/app/views/reports/index.html.erb
index af60f381e..80b1e7f0b 100644
--- a/app/views/reports/index.html.erb
+++ b/app/views/reports/index.html.erb
@@ -1,64 +1,54 @@
-<% provide(:head_title, t("projects.reports.index.head_title", project: h(@project.name)).html_safe) %>
+<% provide(:head_title, t('projects.reports.index.head_title').html_safe) %>
+<%= stylesheet_link_tag 'datatables' %>
<%= render partial: "shared/sidebar" %>
-<%= render partial: "shared/secondary_navigation" %>
-
-
- <% if can_manage_reports?(@project) %>
- <%= link_to new_project_reports_path(@project), class: 'btn btn-primary', id: 'new-report-btn', 'data-no-turbolink' => true do %>
-
- <%=t "projects.reports.index.new" %>
- <% end %>
- <%= link_to "", remote: true, class: "btn btn-default", id: "edit-report-btn" do %>
-
- <%=t "projects.reports.index.edit" %>
- <% end %>
- <%= link_to "", remote: true, class: "btn btn-default", id: "delete-reports-btn" do %>
-
- <%=t "projects.reports.index.delete" %>
- <% end %>
- <% end %>
-
-
-
-
-
- |
- <%=t "projects.reports.index.thead_name" %> |
- <%=t "projects.reports.index.thead_created_by" %> |
- <%=t "projects.reports.index.thead_last_modified_by" %> |
- <%=t "projects.reports.index.thead_created_at" %> |
- <%=t "projects.reports.index.thead_updated_at" %> |
- |
-
-
-
- <% if @project.reports.count > 0 %>
- <% @project.reports.each do |report| %>
-
- |
- <%= report.name %> |
- <%= report.user.full_name %> |
- <%= report.last_modified_by ? report.last_modified_by.full_name : report.user.full_name %> |
- <%=l report.created_at, format: :full %> |
- <%=l report.updated_at, format: :full %> |
-
+
+
+
+ <% if can_manage_reports?(current_team) %>
+ <%= link_to '#', class: 'btn btn-primary', id: 'new-report-btn', 'data-no-turbolink' => true do %>
+
+ <%=t "projects.reports.index.new" %>
+ <% end %>
+ <%= link_to "", remote: true, class: "btn btn-default", id: "edit-report-btn" do %>
+
+ <%=t "projects.reports.index.edit" %>
+ <% end %>
+ <%= link_to "", remote: true, class: "btn btn-default", id: "delete-reports-btn" do %>
+
+ <%=t "projects.reports.index.delete" %>
<% end %>
- <% else %>
-
<%=t "projects.reports.index.no_reports" %> |
<% end %>
-
-
-
+
+
+
+
- <%= bootstrap_form_tag url: destroy_project_reports_path, method: :post, id: "delete-reports-form" do |f| %>
+ <%= bootstrap_form_tag url: reports_destroy_path, method: :post, id: "delete-reports-form" do |f| %>
@@ -79,4 +69,36 @@
-<%= javascript_include_tag("reports/index") %>
+
+
+
+
+
+ <%=t "projects.reports.index.modal_new.message" %>
+
+ <% if @visible_projects&.length > 0 %>
+
+ <% else %>
+
<%=t 'projects.reports.index.modal_new.no_projects' %>
+ <% end %>
+
+
+
+
+
+
+<%= javascript_include_tag("reports/reports_datatable") %>
diff --git a/app/views/shared/_left_menu_bar.html.erb b/app/views/shared/_left_menu_bar.html.erb
index a8218df95..4a44b63ed 100644
--- a/app/views/shared/_left_menu_bar.html.erb
+++ b/app/views/shared/_left_menu_bar.html.erb
@@ -32,7 +32,7 @@
<% end %>
">
- <%= link_to "#", id: "reports-link", title: t('left_menu_bar.reports') do %>
+ <%= link_to reports_path, id: "reports-link", title: t('left_menu_bar.reports') do %>
<%= t('left_menu_bar.reports') %>
<% end %>
diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb
index c32a85883..d1ddf604b 100644
--- a/config/initializers/assets.rb
+++ b/config/initializers/assets.rb
@@ -48,7 +48,6 @@ Rails.application.config.assets.precompile += %w(samples/samples_importer.js)
Rails.application.config.assets.precompile += %w(projects/canvas.js)
Rails.application.config.assets.precompile +=
%w(experiments/dropdown_actions.js)
-Rails.application.config.assets.precompile += %w(reports/index.js)
Rails.application.config.assets.precompile += %w(reports/new.js)
Rails.application.config.assets.precompile += %w(protocols/index.js)
Rails.application.config.assets.precompile += %w(protocols/header.js)
@@ -87,6 +86,7 @@ Rails.application.config.assets.precompile += %w(activities/index.js)
Rails.application.config.assets.precompile += %w(repository_columns/index.js)
Rails.application.config.assets.precompile += %w(repositories/show.js)
Rails.application.config.assets.precompile += %w(sidebar_toggle.js)
+Rails.application.config.assets.precompile += %w(reports/reports_datatable.js)
# Libraries needed for Handsontable formulas
Rails.application.config.assets.precompile += %w(lodash.js)
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 2621e0a95..482fe01ad 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -288,16 +288,22 @@ en:
reports:
print_title: "%{project} | Report"
index:
- head_title: "%{project} | Reports"
+ head_title: " Reports"
new: "New report"
edit: "Edit report"
delete: "Delete report/s"
+ thead_project_name: "Project name"
thead_name: "Report name"
thead_created_by: "Created by"
thead_last_modified_by: "Last modified by"
thead_created_at: "Created at"
thead_updated_at: "Last updated at"
no_reports: "No reports!"
+ modal_new:
+ head_title: "Create new report"
+ no_projects: "No projects available."
+ message: "You are about to create a new report. Please select a project from which you would like to create a report for."
+ create: "Create"
modal_delete:
head_title: "Delete report/s"
message: "Are you sure to delete selected report/s?"
diff --git a/config/routes.rb b/config/routes.rb
index d5d486ef3..91a8f1170 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -189,6 +189,12 @@ Rails.application.routes.draw do
get 'projects/archive', to: 'projects#archive', as: 'projects_archive'
+ resources :reports, only: :index
+ get 'reports/datatable', to: 'reports#datatable'
+ post 'reports/visible_projects', to: 'reports#visible_projects',
+ defaults: { format: 'json' }
+ post 'reports/destroy', to: 'reports#destroy'
+
resources :projects, except: [:new, :destroy] do
resources :user_projects, path: '/users',
only: [:create, :index, :update, :destroy]
@@ -201,7 +207,7 @@ Rails.application.routes.draw do
resources :tags, only: [:create, :update, :destroy]
resources :reports,
path: '/reports',
- only: [:index, :new, :create, :edit, :update] do
+ only: %i(edit update create) do
collection do
# The posts following here should in theory be gets,
# but are posts because of parameters payload
@@ -240,7 +246,6 @@ Rails.application.routes.draw do
post '_save',
to: 'reports#save_modal',
as: :save_modal
- post 'destroy', as: :destroy # Destroy multiple entries at once
end
end
resources :experiments,
diff --git a/db/migrate/20180416114040_add_team_id_to_reports.rb b/db/migrate/20180416114040_add_team_id_to_reports.rb
new file mode 100644
index 000000000..ef8fcc47e
--- /dev/null
+++ b/db/migrate/20180416114040_add_team_id_to_reports.rb
@@ -0,0 +1,13 @@
+class AddTeamIdToReports < ActiveRecord::Migration[5.1]
+ def up
+ add_reference :reports, :team, index: true
+ Report.preload(:project).find_each do |report|
+ team_id = report.project.team_id
+ report.update_column(:team_id, team_id)
+ end
+ end
+
+ def down
+ remove_column :reports, :team
+ end
+end
diff --git a/db/migrate/20180417062042_create_datatables_reports.rb b/db/migrate/20180417062042_create_datatables_reports.rb
new file mode 100644
index 000000000..3edf73c65
--- /dev/null
+++ b/db/migrate/20180417062042_create_datatables_reports.rb
@@ -0,0 +1,7 @@
+class CreateDatatablesReports < ActiveRecord::Migration[5.1]
+ def change
+ create_view :datatables_reports, materialized: true
+ add_index :datatables_reports, :id, unique: true
+ add_index :datatables_reports, :team_id
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index d4082ef50..9a5ae98b2 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20180308094354) do
+ActiveRecord::Schema.define(version: 20180417062042) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -348,10 +348,12 @@ ActiveRecord::Schema.define(version: 20180308094354) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "last_modified_by_id"
+ t.bigint "team_id"
t.index "trim_html_tags((description)::text) gin_trgm_ops", name: "index_reports_on_description", using: :gin
t.index "trim_html_tags((name)::text) gin_trgm_ops", name: "index_reports_on_name", using: :gin
t.index ["last_modified_by_id"], name: "index_reports_on_last_modified_by_id"
t.index ["project_id"], name: "index_reports_on_project_id"
+ t.index ["team_id"], name: "index_reports_on_team_id"
t.index ["user_id"], name: "index_reports_on_user_id"
end
@@ -413,8 +415,8 @@ ActiveRecord::Schema.define(version: 20180308094354) do
t.bigint "last_modified_by_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
+ t.index "trim_html_tags(data) gin_trgm_ops", name: "index_repository_list_items_on_data", using: :gin
t.index ["created_by_id"], name: "index_repository_list_items_on_created_by_id"
- t.index ["data"], name: "index_repository_list_items_on_data"
t.index ["last_modified_by_id"], name: "index_repository_list_items_on_last_modified_by_id"
t.index ["repository_column_id"], name: "index_repository_list_items_on_repository_column_id"
t.index ["repository_id"], name: "index_repository_list_items_on_repository_id"
@@ -779,7 +781,6 @@ ActiveRecord::Schema.define(version: 20180308094354) do
t.string "invited_by_type"
t.integer "invited_by_id"
t.integer "invitations_count", default: 0
- t.integer "tutorial_status", default: 0, null: false
t.integer "current_team_id"
t.string "authentication_token", limit: 30
t.jsonb "settings", default: {}, null: false
@@ -977,4 +978,36 @@ ActiveRecord::Schema.define(version: 20180308094354) do
JOIN user_teams ON ((teams.id = user_teams.team_id)));
SQL
+ create_view "datatables_reports", materialized: true, sql_definition: <<-SQL
+ SELECT DISTINCT ON (reports.id) reports.id,
+ reports.name,
+ projects.name AS project_name,
+ ( SELECT users.full_name
+ FROM users
+ WHERE (users.id = reports.user_id)) AS created_by,
+ ( SELECT users.full_name
+ FROM users
+ WHERE (users.id = reports.last_modified_by_id)) AS last_modified_by,
+ reports.created_at,
+ reports.updated_at,
+ projects.archived AS project_archived,
+ projects.visibility AS project_visibility,
+ projects.id AS project_id,
+ reports.team_id,
+ ARRAY( SELECT DISTINCT user_teams_1.user_id
+ FROM user_teams user_teams_1
+ WHERE (user_teams_1.team_id = teams.id)) AS users_with_team_read_permissions,
+ ARRAY( SELECT DISTINCT user_projects_1.user_id
+ FROM user_projects user_projects_1
+ WHERE (user_projects_1.project_id = projects.id)) AS users_with_project_read_permissions
+ FROM ((((reports
+ JOIN projects ON ((projects.id = reports.project_id)))
+ JOIN user_projects ON ((user_projects.project_id = projects.id)))
+ JOIN teams ON ((teams.id = projects.team_id)))
+ JOIN user_teams ON ((user_teams.team_id = teams.id)));
+ SQL
+
+ add_index "datatables_reports", ["id"], name: "index_datatables_reports_on_id", unique: true
+ add_index "datatables_reports", ["team_id"], name: "index_datatables_reports_on_team_id"
+
end
diff --git a/db/views/datatables_reports_v01.sql b/db/views/datatables_reports_v01.sql
new file mode 100644
index 000000000..224c84403
--- /dev/null
+++ b/db/views/datatables_reports_v01.sql
@@ -0,0 +1,39 @@
+SELECT DISTINCT ON (id)
+ reports.id AS id,
+ reports.name AS name,
+ projects.name AS project_name,
+ (
+ SELECT users.full_name
+ FROM users
+ WHERE users.id = reports.user_id
+ ) AS created_by,
+ (
+ SELECT users.full_name
+ FROM users
+ WHERE users.id = reports.last_modified_by_id
+ ) AS last_modified_by,
+ reports.created_at AS created_at,
+ reports.updated_at AS updated_at,
+ projects.archived AS project_archived,
+ projects.visibility AS project_visibility,
+ projects.id AS project_id,
+ reports.team_id AS team_id,
+ ARRAY(
+ SELECT DISTINCT user_teams.user_id
+ FROM user_teams
+ WHERE user_teams.team_id = teams.id
+ ) AS users_with_team_read_permissions,
+ ARRAY(
+ SELECT DISTINCT user_projects.user_id
+ FROM user_projects
+ WHERE user_projects.project_id = projects.id
+ ) AS users_with_project_read_permissions
+FROM reports
+INNER JOIN projects
+ON projects.id = reports.project_id
+INNER JOIN user_projects
+ON user_projects.project_id = projects.id
+INNER JOIN teams
+ON teams.id = projects.team_id
+INNER JOIN user_teams
+ON user_teams.team_id = teams.id
diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb
index 1dd288081..3065ff858 100644
--- a/spec/models/report_spec.rb
+++ b/spec/models/report_spec.rb
@@ -13,11 +13,14 @@ describe Report, type: :model do
it { should have_db_column :created_at }
it { should have_db_column :updated_at }
it { should have_db_column :last_modified_by_id }
+ it { should have_db_column :team_id }
end
describe 'Relations' do
it { should belong_to :project }
it { should belong_to :user }
+ it { should belong_to :project }
+ it { should belong_to :team }
it { should belong_to(:last_modified_by).class_name('User') }
it { should have_many :report_elements }
end
diff --git a/spec/models/team_spec.rb b/spec/models/team_spec.rb
index cfdf642bf..b735a1a44 100644
--- a/spec/models/team_spec.rb
+++ b/spec/models/team_spec.rb
@@ -30,6 +30,7 @@ describe Team, type: :model do
it { should have_many :protocol_keywords }
it { should have_many :tiny_mce_assets }
it { should have_many :repositories }
+ it { should have_many :reports }
end
describe 'Should be a valid object' do
diff --git a/spec/models/views/datatables/datatables_report_spec.rb b/spec/models/views/datatables/datatables_report_spec.rb
new file mode 100644
index 000000000..534ca3770
--- /dev/null
+++ b/spec/models/views/datatables/datatables_report_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe DatatablesReport, type: :model do
+ pending "add some examples to (or delete) #{__FILE__}"
+end