mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-04 04:04:36 +08:00
adds new reports index page [fixes SCI-2124]
This commit is contained in:
parent
0815111cae
commit
9d282d1800
24 changed files with 614 additions and 268 deletions
|
@ -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);
|
||||
}());
|
200
app/assets/javascripts/reports/reports_datatable.js.erb
Normal file
200
app/assets/javascripts/reports/reports_datatable.js.erb
Normal file
|
@ -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 = "<input data-action='toggle' data-report-id='";
|
||||
html += data + "' type='checkbox'>";
|
||||
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);
|
|
@ -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
|
||||
|
|
50
app/datatables/report_datatable.rb
Normal file
50
app/datatables/report_datatable.rb
Normal file
|
@ -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
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}%"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
75
app/models/views/datatables/datatables_report.rb
Normal file
75
app/models/views/datatables/datatables_report.rb
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" %>
|
||||
|
||||
<div id="content">
|
||||
<div>
|
||||
<% 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 %>
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<span class="hidden-xs"><%=t "projects.reports.index.new" %></span>
|
||||
<% end %>
|
||||
<%= link_to "", remote: true, class: "btn btn-default", id: "edit-report-btn" do %>
|
||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
|
||||
<span class="hidden-xs"><%=t "projects.reports.index.edit" %></span>
|
||||
<% end %>
|
||||
<%= link_to "", remote: true, class: "btn btn-default", id: "delete-reports-btn" do %>
|
||||
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
|
||||
<span class="hidden-xs"><%=t "projects.reports.index.delete" %></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<table class="table report-table" data-project-id="<%= @project.id %>">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input type="checkbox" class="check-all-reports"></th>
|
||||
<th><%=t "projects.reports.index.thead_name" %></th>
|
||||
<th><%=t "projects.reports.index.thead_created_by" %></th>
|
||||
<th><%=t "projects.reports.index.thead_last_modified_by" %></th>
|
||||
<th><%=t "projects.reports.index.thead_created_at" %></th>
|
||||
<th><%=t "projects.reports.index.thead_updated_at" %></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% if @project.reports.count > 0 %>
|
||||
<% @project.reports.each do |report| %>
|
||||
<tr
|
||||
class="report-row"
|
||||
data-id="<%= report.id %>"
|
||||
data-edit-link="<%= edit_project_report_path(@project, report) %>"
|
||||
>
|
||||
<th><input type="checkbox" class="check-report"></th>
|
||||
<td><%= report.name %></td>
|
||||
<td><%= report.user.full_name %></td>
|
||||
<td><%= report.last_modified_by ? report.last_modified_by.full_name : report.user.full_name %></td>
|
||||
<td><%=l report.created_at, format: :full %></td>
|
||||
<td><%=l report.updated_at, format: :full %></td>
|
||||
</tr>
|
||||
<div id="content" class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<% if can_manage_reports?(current_team) %>
|
||||
<%= link_to '#', class: 'btn btn-primary', id: 'new-report-btn', 'data-no-turbolink' => true do %>
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<span class="hidden-xs"><%=t "projects.reports.index.new" %></span>
|
||||
<% end %>
|
||||
<%= link_to "", remote: true, class: "btn btn-default", id: "edit-report-btn" do %>
|
||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
|
||||
<span class="hidden-xs"><%=t "projects.reports.index.edit" %></span>
|
||||
<% end %>
|
||||
<%= link_to "", remote: true, class: "btn btn-default", id: "delete-reports-btn" do %>
|
||||
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
|
||||
<span class="hidden-xs"><%=t "projects.reports.index.delete" %></span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<tr><td colspan="5"><%=t "projects.reports.index.no_reports" %></td></tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="reports-datatable">
|
||||
<table id="reports-table"
|
||||
class="table"
|
||||
data-source="<%= reports_datatable_path(format: :json) %>">
|
||||
<thead>
|
||||
<tr>
|
||||
<th id="select-all"><input name="select_all" type="checkbox"></th>
|
||||
<th id="project-name"><%=t 'projects.reports.index.thead_project_name' %></th>
|
||||
<th id="report-name"><%=t 'projects.reports.index.thead_name' %></th>
|
||||
<th id="report-created-by"><%=t 'projects.reports.index.thead_created_by' %></th>
|
||||
<th id="report-last-modified-by"><%=t 'projects.reports.index.thead_last_modified_by' %></th>
|
||||
<th id="report-created-at"><%=t 'projects.reports.index.thead_created_at' %></th>
|
||||
<th id="report-updated-at"><%=t 'projects.reports.index.thead_updated_at' %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Save report modal -->
|
||||
<div class="modal" id="delete-reports-modal" tabindex="-1" role="dialog" aria-labelledby="delete-reports-modal-label">
|
||||
<%= 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| %>
|
||||
<input type="hidden" name="report_ids" id="report-ids">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
|
@ -79,4 +69,36 @@
|
|||
</div>
|
||||
|
||||
|
||||
<%= javascript_include_tag("reports/index") %>
|
||||
<div class="modal" id="new-report-modal" tabindex="-1" role="dialog" aria-labelledby="delete-reports-modal-label">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="new-report-modal-label"><%=t "projects.reports.index.modal_new.head_title" %></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<%=t "projects.reports.index.modal_new.message" %>
|
||||
<br />
|
||||
<% if @visible_projects&.length > 0 %>
|
||||
<select class="form-control selectpicker" data-abs-min-length="2" data-live-search="true">
|
||||
<% @visible_projects.each do |project| %>
|
||||
<option value="<%= project.path %>"><%= project.name %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
<% else %>
|
||||
<p><%=t 'projects.reports.index.modal_new.no_projects' %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button id="new-report-reports-btn"
|
||||
class="btn btn-primary"
|
||||
data-new-report-path="<%= @visible_projects.first.path if @visible_projects.first %>"
|
||||
<%= 'disabled' unless @visible_projects&.length > 0 %>
|
||||
><%=t 'projects.reports.index.modal_new.create' %></button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal"><%=t "general.cancel" %></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= javascript_include_tag("reports/reports_datatable") %>
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
<% end %>
|
||||
</li>
|
||||
<li class="<%= "active" if reports_are_selected? %>">
|
||||
<%= 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 %>
|
||||
<span class="glyphicon glyphicon-indent-left"></span>
|
||||
<span><%= t('left_menu_bar.reports') %></span>
|
||||
<% end %>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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?"
|
||||
|
|
|
@ -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,
|
||||
|
|
13
db/migrate/20180416114040_add_team_id_to_reports.rb
Normal file
13
db/migrate/20180416114040_add_team_id_to_reports.rb
Normal file
|
@ -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
|
7
db/migrate/20180417062042_create_datatables_reports.rb
Normal file
7
db/migrate/20180417062042_create_datatables_reports.rb
Normal file
|
@ -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
|
39
db/schema.rb
39
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
|
||||
|
|
39
db/views/datatables_reports_v01.sql
Normal file
39
db/views/datatables_reports_v01.sql
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
5
spec/models/views/datatables/datatables_report_spec.rb
Normal file
5
spec/models/views/datatables/datatables_report_spec.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe DatatablesReport, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
Loading…
Add table
Reference in a new issue