adds new reports index page [fixes SCI-2124]

This commit is contained in:
zmagod 2018-04-18 16:47:52 +02:00
parent 0815111cae
commit 9d282d1800
24 changed files with 614 additions and 268 deletions

View file

@ -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);
}());

View 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);

View file

@ -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

View 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

View file

@ -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,

View file

@ -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,

View file

@ -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}%"

View file

@ -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

View file

@ -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

View 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

View file

@ -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

View file

@ -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

View file

@ -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">&times;</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") %>

View file

@ -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 %>

View file

@ -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)

View file

@ -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?"

View file

@ -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,

View 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

View 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

View file

@ -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

View 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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe DatatablesReport, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end