Merge branch 'master' into features/templates

This commit is contained in:
Miha Mencin 2019-02-28 09:41:20 +01:00 committed by GitHub
commit d03ef2cd2a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 428 additions and 96 deletions

View file

@ -36,6 +36,8 @@
//= require i18n.js
//= require i18n/translations
//= require users/settings/teams/invite_users_modal
//= require select2.min
//= require select2_customization
//= require turbolinks

View file

@ -0,0 +1,92 @@
$.fn.extend({
select2Multiple: function(config = {}) {
// Adding ID to each block
var templateSelection = (state) => {
return $('<span class="select2-block-body" data-select-id="' + state.id + '">'
+ (config.customSelection !== undefined ? config.customSelection(state) : state.text)
+ '</span>');
};
var select2 = this.select2({
closeOnSelect: false,
multiple: true,
ajax: config.ajax,
templateSelection: templateSelection
});
// select all check
this[0].dataset.singleDisplay = config.singleDisplay || false;
if (this[0].dataset.selectAll === 'true') {
$.each($(this).find('option'), (index, e) => { e.selected = true; });
this.trigger('change');
}
if (config.singleDisplay) {
$(this).updateSingleName();
}
return select2
// Adding select all button
.on('select2:open', function() {
var selectElement = this;
$('.select2-selection').scrollTo(0);
$('.select2_select_all').remove();
if (selectElement.dataset.selectAllButton !== undefined) {
$('<div class="select2_select_all btn btn-default"><strong>' + selectElement.dataset.selectAllButton + '</strong></div>').prependTo('.select2-dropdown').on('click', function() {
var elementsToSelect = $.map($(selectElement).find('option'), e => e.value);
if ($(selectElement).find('option:selected').length === elementsToSelect.length) elementsToSelect = [];
$(selectElement).val(elementsToSelect).trigger('change');
$(selectElement).select2('close');
$(selectElement).select2('open');
});
}
})
// Prevent shake bug with multiple select
.on('select2:open select2:close', function() {
if ($(this).val() != null && $(this).val().length > 3) {
$(this).next().find('.select2-search__field')[0].disabled = true;
} else {
$(this).next().find('.select2-search__field')[0].disabled = false;
}
$('.select2-selection').scrollTo(0);
})
// Prevent opening window when deleteing selection
.on('select2:unselect', function() {
var resultWindow = $('.select2-container--open');
if (resultWindow.length === 0) {
$(this).select2('open');
}
})
// Fxied scroll bug
.on('select2:selecting select2:unselecting', function(e) {
$(e.currentTarget).data('scrolltop', $('.select2-results__options').scrollTop());
$('.select2-selection').scrollTo(0);
})
// Fxied scroll bug
.on('select2:select select2:unselect change', function(e) {
$('.select2-selection').scrollTo(0);
$('.select2-results__options').scrollTop($(e.currentTarget).data('scrolltop'));
if (this.dataset.singleDisplay === 'true') {
$(this).updateSingleName();
}
});
},
select2MultipleClearAll: function() {
$(this).val([]).trigger('change');
},
// Create from multiple blocks single one with counter
updateSingleName: function() {
var template = '';
var selectElement = this;
var selectedOptions = selectElement.next().find('.select2-selection__choice');
var optionsCounter = selectedOptions.length;
var allOptionsSelected = this.find('option').length === optionsCounter;
var optionText = allOptionsSelected ? this[0].dataset.selectMultipleAllSelected : optionsCounter + ' ' + this[0].dataset.selectMultipleName;
if (optionsCounter > 1) {
selectedOptions.remove();
template = '<li class="select2-selection__choice">'
+ '<span class="select2-selection__choice__remove" role="presentation">×</span>'
+ optionText
+ '</li>';
$(template).prependTo(selectElement.next().find('.select2-selection__rendered')).find('.select2-selection__choice__remove')
.click(function() { selectElement.select2MultipleClearAll(); });
}
}
});

View file

@ -0,0 +1,89 @@
@import "constants";
@import "mixins";
.select2-container {
.select2-selection {
max-height: 23px;
overflow: hidden;
position: relative;
&.select2-selection--multiple {
border: 1px solid $color-alto;
}
.select2-selection__rendered {
width: 200%;
}
.select2-selection__choice {
background: $color-concrete;
border: 1px solid $color-alto;
.select2-selection__choice__remove {
float: right;
margin: 0;
margin-left: 5px;
}
}
}
.select2-dropdown {
border: 1px solid $color-alto;
.select2_select_all {
border: 0;
border-bottom: 1px solid $color-alto;
border-radius: 0;
cursor: pointer;
line-height: 20px;
padding: 5px;
text-align: center;
width: 100%;
}
.select2-results__option[role="group"] {
strong {
font-size: $font-size-base;
}
}
.select2-results__option[role="treeitem"] {
background: $color-white;
line-height: 18px;
padding-left: 30px;
position: relative;
&::before {
background: $color-white;
border: 1px solid $color-emperor;
border-radius: 6px;
content: "";
height: 12px;
left: 0;
margin: 9px;
position: absolute;
top: 0;
transition: .3s;
width: 12px;
}
&.select2-results__option--highlighted {
background: inherit;
color: inherit;
}
&[aria-selected="true"] {
background: $color-concrete;
&::before {
background: $color-emperor;
}
}
&:hover {
background: $color-alto;
}
}
}
}

View file

@ -25,6 +25,7 @@
&:hover {
background: $color-concrete;
color: $color-emperor;
opacity: .7;
}
@ -101,8 +102,13 @@
}
}
.no-notification-meessage {
padding: 20px 0;
.system-notifications-container {
display: inline-block;
width: 100%;
}
.no-notification-message {
margin: 20px 0;
}
.title-container {
@ -124,10 +130,13 @@
margin-top: 10px;
}
}
#manage-module-system-notification-modal {
.modal-title {
margin-left: 20px;
}
.modal-body {
margin: 20px;
}

View file

@ -395,7 +395,8 @@
word-wrap: break-word;
.system-notifications-no-recent {
padding: 0 0 10px 10px;
padding: 10px;
text-align: center;
}
.system-notifications-dropdown-header {

View file

@ -248,14 +248,14 @@ class MyModule < ApplicationRecord
end
def is_overdue?(datetime = DateTime.current)
due_date.present? and datetime.utc > due_date.utc
due_date.present? && datetime.utc > due_date.end_of_day.utc
end
def overdue_for_days(datetime = DateTime.current)
if due_date.blank? or due_date.utc > datetime.utc
return 0
if due_date.blank? || due_date.end_of_day.utc > datetime.utc
0
else
return ((datetime.utc.to_i - due_date.utc.to_i) / (60*60*24).to_f).ceil
((datetime.utc.to_i - due_date.end_of_day.utc.to_i) / 1.day.to_f).ceil
end
end
@ -264,7 +264,9 @@ class MyModule < ApplicationRecord
end
def is_due_in?(datetime, diff)
due_date.present? and datetime.utc < due_date.utc and datetime.utc > (due_date.utc - diff)
due_date.present? &&
datetime.utc < due_date.end_of_day.utc &&
datetime.utc > (due_date.end_of_day.utc - diff)
end
def space_taken

View file

@ -488,7 +488,8 @@ class User < ApplicationRecord
# This method must be overwriten for addons that will be installed
def show_login_system_notification?
user_system_notifications.show_on_login.present?
user_system_notifications.show_on_login.present? &&
(ENV['ENABLE_TUTORIAL'] != 'true' || settings['tutorial_completed'])
end
# json friendly attributes

View file

@ -13,6 +13,7 @@ class UserProject < ApplicationRecord
belongs_to :project, inverse_of: :user_projects, touch: true, optional: true
before_destroy :destroy_associations
validates_uniqueness_of :user_id, scope: :project_id
def role_str
I18n.t("user_projects.enums.role.#{role.to_s}")

View file

@ -14,6 +14,7 @@ class UserTeam < ApplicationRecord
before_destroy :destroy_associations
after_create :create_samples_table_state
validates_uniqueness_of :user_id, scope: :team_id
def role_str
I18n.t("user_teams.enums.role.#{role}")

View file

@ -93,7 +93,7 @@ module Experiments
user: @user,
message: I18n.t(
'activities.clone_experiment',
user: @user,
user: @user.full_name,
experiment_new: @c_exp.name,
experiment_original: @exp.name
)

View file

@ -1,6 +1,9 @@
module FirstTimeDataGenerator
# Default inventory repository
REPO_SAMPLES_NAME = 'Samples'.freeze
# Create data for demo for new users
def seed_demo_data(user, team)
def seed_demo_data(user, team, asset_queue = :demo)
@user = user
# If private private team does not exist,
@ -8,28 +11,49 @@ module FirstTimeDataGenerator
# Do nothing
return unless team
# create custom repository samples
repository = Repository.create(
name: 'Samples',
team: team,
created_by: user
)
# check if samples repo already exist, then create custom repository samples
repository = Repository.where(team: team).where(name: REPO_SAMPLES_NAME)
repository =
if repository.blank?
if team.repositories.count < Rails.configuration.x.repositories_limit
Repository.create(
name: REPO_SAMPLES_NAME,
team: team,
created_by: user
)
else
# User first repo just as a placeholder, this call will fail anyhow
Repository.create(
name: team.repositories.first.name,
team: team,
created_by: user
)
end
else
repository.first
end
# create list value column for sample types
repository_column_sample_types = RepositoryColumn.create(
repository: repository,
created_by: user,
data_type: :RepositoryListValue,
name: 'Sample Types'
)
repo_columns = []
['Sample Types', 'Sample Groups'].each do |repo_name|
repo_column = repository.repository_columns.where(name: repo_name)
# create list value column for sample groups
repository_column_sample_groups = RepositoryColumn.create(
repository: repository,
created_by: user,
data_type: :RepositoryListValue,
name: 'Sample Groups'
)
repo_columns <<
if repo_column.blank?
RepositoryColumn.create(
repository: repository,
created_by: user,
data_type: :RepositoryListValue,
name: repo_name
)
else
repo_column.first
end
end
# Maintain old names
repository_column_sample_types, repository_column_sample_groups =
repo_columns
# create few list items for sample types
repository_items_sample_types = []
@ -41,7 +65,15 @@ module FirstTimeDataGenerator
repository_column: repository_column_sample_types,
repository: repository
)
repository_items_sample_types << item
# Check if it already exists
if item.persisted?
repository_items_sample_types << item
else
repository_items_sample_types << repository_column_sample_types
.repository_list_items
.where(data: name).first
end
end
# create few list items for sample groups
@ -54,7 +86,15 @@ module FirstTimeDataGenerator
repository_column: repository_column_sample_groups,
repository: repository
)
repository_items_sample_groups << item
# Check if it already exists
if item.persisted?
repository_items_sample_groups << item
else
repository_items_sample_groups << repository_column_sample_groups
.repository_list_items
.where(data: name).first
end
end
repository_rows_to_assign = []
@ -153,6 +193,7 @@ module FirstTimeDataGenerator
end
name = 'Demo project'
name = '[NEW] Demo project by SciNote'
exp_name = 'Polymerase chain reaction'
# If there is an existing demo project, archive and rename it
if team.projects.where(name: name).present?
@ -516,19 +557,22 @@ module FirstTimeDataGenerator
'Collection of potatoes'
]
second_rep_item = smart_annotate_rep_item(repository_rows_to_assign.second)
third_rep_item = smart_annotate_rep_item(repository_rows_to_assign.third)
fifth_rep_item = smart_annotate_rep_item(repository_rows_to_assign.fifth)
module_step_descriptions = [
'<html>
<body>
<p>50% of samples should be mock inoculated
<span class=\"atwho-inserted\"contenteditable=\"false\"
data-atwho-at-query=\"#\">[#' + sample_name + '3~rep_item~3]</span>
data-atwho-at-query=\"#\">[#' + third_rep_item + ']</span>
<span class=\"atwho-inserted\" contenteditable=\"false\"
data-atwho-at-query=\"#\">[#' + sample_name + '5~rep_item~5]</span>
data-atwho-at-query=\"#\">[#' + fifth_rep_item + ']</span>
while other 50% with PVY NTN virus
<span class=\"atwho-inserted\" contenteditable=\"false\"
data-atwho-at-query=\"#\">[#' + sample_name + '3~rep_item~3]</span>
data-atwho-at-query=\"#\">[#' + third_rep_item + ']</span>
<span class=\"atwho-inserted\" contenteditable=\"false\"
data-atwho-at-query=\"#\">[#' + sample_name + '5~rep_item~5]</span>.
data-atwho-at-query=\"#\">[#' + fifth_rep_item + ']</span>.
</p>
</body>
</html>',
@ -542,6 +586,9 @@ module FirstTimeDataGenerator
module_step_names,
module_step_descriptions)
# Delete repository items, if we went over the limit
repository_rows_to_assign.map(&:destroy) unless repository.id
# Add table to existig step
step = my_modules[1].protocol.steps.where('position = 0').take
Table.create(
@ -551,19 +598,18 @@ module FirstTimeDataGenerator
contents: tab_content['module2']['samples_table']
)
# Add file to existig step
DelayedUploaderDemo.delay(queue: :demo).add_step_asset(
DelayedUploaderDemo.delay(queue: asset_queue).add_step_asset(
step: my_modules[1].protocol.steps.where('position = 0').take,
current_user: user,
current_team: team,
file_name: 'PVY-inoculated_plant_symptoms.JPG'
)
# Add comment to step 1
user_annotation = '[@' + user.name + '~' + user.id.to_s + ']'
user_annotation = user.name
generate_step_comment(
step,
user,
user_annotation + ' I have used different sample [#' + sample_name +
'2~rep_item~2]'
"#{user_annotation} I have used different sample [##{second_rep_item}]"
)
# Add comment to step 3
step = my_modules[1].protocol.steps.where('position = 2').take
@ -573,7 +619,7 @@ module FirstTimeDataGenerator
user_annotation + ' Please complete this by Monday.'
)
# Results
DelayedUploaderDemo.delay(queue: :demo).generate_result_asset(
DelayedUploaderDemo.delay(queue: asset_queue).generate_result_asset(
my_module: my_modules[1],
current_user: user,
current_team: team,
@ -582,7 +628,7 @@ module FirstTimeDataGenerator
file_name: 'mock-inoculated-plant.JPG'
)
DelayedUploaderDemo.delay(queue: :demo).generate_result_asset(
DelayedUploaderDemo.delay(queue: asset_queue).generate_result_asset(
my_module: my_modules[1],
current_user: user,
current_team: team,
@ -713,7 +759,7 @@ module FirstTimeDataGenerator
).sneaky_save
# Second result
DelayedUploaderDemo.delay(queue: :demo).generate_result_asset(
DelayedUploaderDemo.delay(queue: asset_queue).generate_result_asset(
my_module: my_modules[2],
current_user: user,
current_team: team,
@ -810,7 +856,7 @@ module FirstTimeDataGenerator
generate_module_steps(my_modules[3], module_step_names, module_step_descriptions)
# Add file to existig step 1
DelayedUploaderDemo.delay(queue: :demo).add_step_asset(
DelayedUploaderDemo.delay(queue: asset_queue).add_step_asset(
step: my_modules[3].protocol.steps.where('position = 0').take,
current_user: user,
current_team: team,
@ -852,8 +898,8 @@ module FirstTimeDataGenerator
'Perform all centrifugation steps at 2025°C in a standard ' \
'microcentrifuge. Ensure that the centrifuge does not cool below 20°C.'
]
module_checklist_items.each do |item|
checklist.checklist_items << ChecklistItem.new(text: item)
module_checklist_items.each_with_index do |item, ind|
checklist.checklist_items << ChecklistItem.new(text: item, position: ind)
end
checklist.save
@ -877,8 +923,8 @@ module FirstTimeDataGenerator
'If performing optional on-column DNase digestion, prepare DNase I ' \
'stock solution as described in Appendix D (page 67).'
]
module_checklist_items.each do |item|
checklist.checklist_items << ChecklistItem.new(text: item)
module_checklist_items.each_with_index do |item, ind|
checklist.checklist_items << ChecklistItem.new(text: item, position: ind)
end
checklist.save
@ -903,13 +949,13 @@ module FirstTimeDataGenerator
'genomic DNA contamination”, page 21), follow steps D1D4 (page 67) ' \
'after performing this step.'
]
module_checklist_items.each do |item|
checklist.checklist_items << ChecklistItem.new(text: item)
module_checklist_items.each_with_index do |item, ind|
checklist.checklist_items << ChecklistItem.new(text: item, position: ind)
end
checklist.save
# Results
DelayedUploaderDemo.delay(queue: :demo).generate_result_asset(
DelayedUploaderDemo.delay(queue: asset_queue).generate_result_asset(
my_module: my_modules[3],
current_user: user,
current_team: team,
@ -962,8 +1008,8 @@ module FirstTimeDataGenerator
step: step
)
module_checklist_items.each do |item|
checklist.checklist_items << ChecklistItem.new(text: item)
module_checklist_items.each_with_index do |item, ind|
checklist.checklist_items << ChecklistItem.new(text: item, position: ind)
end
checklist.save
@ -1025,41 +1071,41 @@ module FirstTimeDataGenerator
'Clean surfaces with 70% ethanol or RNA remover',
'Turn on the UV light'
]
module_checklist_items.each do |item|
checklist.checklist_items << ChecklistItem.new(text: item)
module_checklist_items.each_with_index do |item, ind|
checklist.checklist_items << ChecklistItem.new(text: item, position: ind)
end
checklist.save
# Add file to existig steps
DelayedUploaderDemo.delay(queue: :demo).add_step_asset(
DelayedUploaderDemo.delay(queue: asset_queue).add_step_asset(
step: my_modules[5].protocol.steps.where('position = 0').take,
current_user: user,
current_team: team,
file_name: 'Mixes_Templats.xlsx'
)
DelayedUploaderDemo.delay(queue: :demo).add_step_asset(
DelayedUploaderDemo.delay(queue: asset_queue).add_step_asset(
step: my_modules[5].protocol.steps.where('position = 1').take,
current_user: user,
current_team: team,
file_name: 'qPCR_template.jpg'
)
DelayedUploaderDemo.delay(queue: :demo).add_step_asset(
DelayedUploaderDemo.delay(queue: asset_queue).add_step_asset(
step: my_modules[5].protocol.steps.where('position = 1').take,
current_user: user,
current_team: team,
file_name: '96plate.docx'
)
DelayedUploaderDemo.delay(queue: :demo).add_step_asset(
DelayedUploaderDemo.delay(queue: asset_queue).add_step_asset(
step: my_modules[5].protocol.steps.where('position = 2').take,
current_user: user,
current_team: team,
file_name: 'cycling_conditions.JPG'
)
DelayedUploaderDemo.delay(queue: :demo).add_step_asset(
DelayedUploaderDemo.delay(queue: asset_queue).add_step_asset(
step: my_modules[5].protocol.steps.where('position = 2').take,
current_user: user,
current_team: team,
@ -1102,7 +1148,7 @@ module FirstTimeDataGenerator
).sneaky_save
# Results
DelayedUploaderDemo.delay(queue: :demo).generate_result_asset(
DelayedUploaderDemo.delay(queue: asset_queue).generate_result_asset(
my_module: my_modules[5],
current_user: user,
current_team: team,
@ -1111,7 +1157,7 @@ module FirstTimeDataGenerator
file_name: '1505745387970-1058053257.jpg'
)
DelayedUploaderDemo.delay(queue: :demo).generate_result_asset(
DelayedUploaderDemo.delay(queue: asset_queue).generate_result_asset(
my_module: my_modules[5],
current_user: user,
current_team: team,
@ -1120,7 +1166,7 @@ module FirstTimeDataGenerator
file_name: 'chromatogram.png'
)
DelayedUploaderDemo.delay(queue: :demo).generate_result_asset(
DelayedUploaderDemo.delay(queue: asset_queue).generate_result_asset(
my_module: my_modules[5],
current_user: user,
current_team: team,
@ -1129,7 +1175,7 @@ module FirstTimeDataGenerator
file_name: 'curves.JPG'
)
DelayedUploaderDemo.delay(queue: :demo).generate_result_asset(
DelayedUploaderDemo.delay(queue: asset_queue).generate_result_asset(
my_module: my_modules[5],
current_user: user,
current_team: team,
@ -1138,7 +1184,7 @@ module FirstTimeDataGenerator
file_name: 'Bacterial_colonies.jpg'
)
DelayedUploaderDemo.delay(queue: :demo).generate_result_asset(
DelayedUploaderDemo.delay(queue: asset_queue).generate_result_asset(
my_module: my_modules[5],
current_user: user,
current_team: team,
@ -1289,21 +1335,21 @@ module FirstTimeDataGenerator
module_step_descriptions)
# Add file to existig steps
DelayedUploaderDemo.delay(queue: :demo).add_step_asset(
DelayedUploaderDemo.delay(queue: asset_queue).add_step_asset(
step: my_modules[6].protocol.steps.where('position = 0').take,
current_user: user,
current_team: team,
file_name: 'Native_SDS-PAGE_for_complex_analysis.jpg'
)
DelayedUploaderDemo.delay(queue: :demo).add_step_asset(
DelayedUploaderDemo.delay(queue: asset_queue).add_step_asset(
step: my_modules[6].protocol.steps.where('position = 4').take,
current_user: user,
current_team: team,
file_name: 'Native-PAGE-Nature_protocols.pdf'
)
DelayedUploaderDemo.delay(queue: :demo).add_step_asset(
DelayedUploaderDemo.delay(queue: asset_queue).add_step_asset(
step: my_modules[6].protocol.steps.where('position = 5').take,
current_user: user,
current_team: team,
@ -1321,8 +1367,8 @@ module FirstTimeDataGenerator
'Check stock of reagents & order new stock if needed',
'Use gloves at all times'
]
module_checklist_items.each do |item|
checklist.checklist_items << ChecklistItem.new(text: item)
module_checklist_items.each_with_index do |item, ind|
checklist.checklist_items << ChecklistItem.new(text: item, position: ind)
end
checklist.save
@ -1354,14 +1400,14 @@ module FirstTimeDataGenerator
module_step_descriptions)
# Add file to existig step
DelayedUploaderDemo.delay(queue: :demo).add_step_asset(
DelayedUploaderDemo.delay(queue: asset_queue).add_step_asset(
step: my_modules[7].protocol.steps.where('position = 0').take,
current_user: user,
current_team: team,
file_name: 'ddCq-quantification_diagnostics-template.xls'
)
DelayedUploaderDemo.delay(queue: :demo).add_step_asset(
DelayedUploaderDemo.delay(queue: asset_queue).add_step_asset(
step: my_modules[7].protocol.steps.where('position = 0').take,
current_user: user,
current_team: team,
@ -1377,7 +1423,7 @@ module FirstTimeDataGenerator
)
# Add result
DelayedUploaderDemo.delay(queue: :demo).generate_result_asset(
DelayedUploaderDemo.delay(queue: asset_queue).generate_result_asset(
my_module: my_modules[7],
current_user: user,
current_team: team,
@ -1386,7 +1432,7 @@ module FirstTimeDataGenerator
file_name: 'ddCq-quantification_diagnostics-results.xls'
)
DelayedUploaderDemo.delay(queue: :demo).generate_result_asset(
DelayedUploaderDemo.delay(queue: asset_queue).generate_result_asset(
my_module: my_modules[7],
current_user: user,
current_team: team,
@ -1395,7 +1441,7 @@ module FirstTimeDataGenerator
file_name: 'dilution_curve-efficiency.JPG'
)
DelayedUploaderDemo.delay(queue: :demo).generate_result_asset(
DelayedUploaderDemo.delay(queue: asset_queue).generate_result_asset(
my_module: my_modules[7],
current_user: user,
current_team: team,
@ -1408,6 +1454,23 @@ module FirstTimeDataGenerator
experiment.delay.generate_workflow_img
end
# Used for delayed jobs
def self.seed_demo_data_with_id(user_id, team_id)
extend self
user = User.find(user_id)
team = Team.find(team_id)
unless user || team
Rails.logger.warning("Could not retrieve user or team in " \
"seed_demo_data_with_id. " \
"User #{user_id} was mapped to #{user.inspect}." \
"Team #{team_id} was mapped to #{team.inspect}.")
return
end
seed_demo_data(user, team, :new_demo_project)
end
# WARNING: This only works on PostgreSQL
def pluck_random(scope)
scope.order('RANDOM()').first
@ -1580,4 +1643,8 @@ module FirstTimeDataGenerator
step_name: step.name)
).sneaky_save
end
def smart_annotate_rep_item(item)
"#{item.name}~rep_item~#{Base62.encode(item.id)}"
end
end

View file

@ -22,7 +22,7 @@
<a href="#"
id="system-notifications-dropdown"
class="dropdown-toggle"
title="System notifications"
title="<%= t('system_notifications.navbar.tooltip') %>"
data-toggle="dropdown"
role="button"
aria-haspopup="true"

View file

@ -20,16 +20,16 @@
<div class="system-notifications-container">
<%= render partial: "list", locals: { notifications: @system_notifications[:notifications] } %>
</div>
<div class="text-center">
<% if @system_notifications[:more_notifications_url] && @system_notifications[:notifications].present? %>
<a class="btn btn-default btn-more-notifications"
<div class="text-center">
<% if @system_notifications[:more_notifications_url] && @system_notifications[:notifications].present? %>
<a class="btn btn-default btn-more-notifications"
href="<%= @system_notifications[:more_notifications_url] %>"
data-remote="true">
<%= t("system_notifications.index.more_notifications") %></a>
<% else %>
<span class="no_notification_meessage"><%= t("system_notifications.index.no_notifications") %></span>
<% end %>
</div>
<% else %>
<span class="no-notification-message"><%= t("system_notifications.index.no_notifications") %></span>
<% end %>
</div>
</div>
<%= javascript_include_tag("system_notifications/index") %>

View file

@ -152,7 +152,7 @@
</div>
<div class="row">
<div class="col-sm-2">
<span class="system-message hidden-sm"><i class="fas fa-chess-rook" aria-hidden="true"></i></span>
<span class="system-message hidden-sm"><i class="fas fa-gift" aria-hidden="true"></i></span>
</div>
<div class="col-sm-10">
<strong><%=t 'notifications.form.system_message' %></strong>

View file

@ -1292,6 +1292,8 @@ en:
create:
success_flash: "Successfully added sample group <strong>%{sample_group}</strong> to team <strong>%{team}</strong>."
system_notifications:
navbar:
tooltip: 'Whats new notifications'
emails:
subject: "You've received a What's new notification"
intro_paragraph: "Hi %{user_name}, you've received What's new notification in SciNote:"
@ -1872,8 +1874,8 @@ en:
assignments_description: 'Assignment notifications appear whenever you get assigned to a team, project, task.'
recent_notification: 'Recent changes'
recent_notification_description: 'Recent changes notifications appear whenever there is a change on a task you are assigned to.'
system_message: 'System message'
system_message_description: 'System message notifications are specifically sent by site maintainers to notify all users about a system update.'
system_message: "What's New in SciNote"
system_message_description: 'You will be notified about new SciNote features, releases and improvements you can benefit from.'
deliver:
download_link: "Download link:"
download_text: "Click the link to download the file."

View file

@ -0,0 +1,16 @@
# frozen_string_literal: true
class AddUniqueIndexOnUserTeams < ActiveRecord::Migration[5.1]
def up
# firstly delete the duplicates
execute 'WITH uniq AS
(SELECT DISTINCT ON (user_id, team_id) * FROM user_teams)
DELETE FROM user_teams WHERE user_teams.id NOT IN
(SELECT id FROM uniq)'
add_index :user_teams, %i(user_id team_id), unique: true
end
def down
remove_index :user_teams, column: %i(user_id team_id)
end
end

View file

@ -0,0 +1,16 @@
# frozen_string_literal: true
class AddUniqueIndexOnUserProjects < ActiveRecord::Migration[5.1]
def up
# firstly delete the duplicates
execute 'WITH uniq AS
(SELECT DISTINCT ON (user_id, project_id) * FROM user_projects)
DELETE FROM user_projects WHERE user_projects.id NOT IN
(SELECT id FROM uniq)'
add_index :user_projects, %i(user_id project_id), unique: true
end
def down
remove_index :user_projects, columns: %i(user_id project_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: 20190125123107) do
ActiveRecord::Schema.define(version: 20190227125352) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -796,6 +796,7 @@ ActiveRecord::Schema.define(version: 20190125123107) do
t.bigint "assigned_by_id"
t.index ["assigned_by_id"], name: "index_user_projects_on_assigned_by_id"
t.index ["project_id"], name: "index_user_projects_on_project_id"
t.index ["user_id", "project_id"], name: "index_user_projects_on_user_id_and_project_id", unique: true
t.index ["user_id"], name: "index_user_projects_on_user_id"
end
@ -821,6 +822,7 @@ ActiveRecord::Schema.define(version: 20190125123107) do
t.bigint "assigned_by_id"
t.index ["assigned_by_id"], name: "index_user_teams_on_assigned_by_id"
t.index ["team_id"], name: "index_user_teams_on_team_id"
t.index ["user_id", "team_id"], name: "index_user_teams_on_user_id_and_team_id", unique: true
t.index ["user_id"], name: "index_user_teams_on_user_id"
end

View file

@ -131,23 +131,53 @@ namespace :data do
end
desc 'Update all templates projects'
task update_all_templates: :environment do
task :update_all_templates,
%i(slice_size) => [:environment] do |_, args|
args.with_defaults(slice_size: 800)
Rails.logger.info('Templates, syncing all templates projects')
updated, total = TemplatesService.new.update_all_templates
Rails.logger.info(
"Templates, total number of updated projects: #{updated} out of #{total}}"
)
Team.all.order(updated_at: :desc)
.each_slice(args[:slice_size].to_i).with_index do |teams, i|
Rails.logger.info("Processing slice with index #{i}. " \
"First team: #{teams.first.id}, " \
"Last team: #{teams.last.id}.")
teams.each do |team|
TemplatesService.new.delay(
run_at: i.hours.from_now,
queue: :templates,
priority: 5
).update_team(team)
end
end
end
desc 'Create demo project on existing users'
task create_demo_project_on_existing_users: :environment do
task :create_demo_project_on_existing_users,
%i(slice_size) => [:environment] do |_, args|
args.with_defaults(slice_size: 800)
require "#{Rails.root}/app/utilities/first_time_data_generator"
include FirstTimeDataGenerator
Rails.logger.info('Creating demo project on existing users')
User.find_each do |user|
seed_demo_data(user, user.teams.first)
Team.all.order(updated_at: :desc)
.each_slice(args[:slice_size]).with_index do |teams, i|
Rails.logger.info("Processing slice with index #{i}. " \
"First team: #{teams.first.id}, " \
"Last team: #{teams.last.id}.")
teams.each do |team|
owner_ut = team.user_teams.where(role: 2).first
next unless owner_ut
FirstTimeDataGenerator.delay(
run_at: i.hours.from_now,
queue: :new_demo_project,
priority: 10
).seed_demo_data_with_id(owner_ut.user.id, team.id)
end
end
end
end

View file

@ -68,7 +68,6 @@ describe ClientApi::UserTeamService do
describe '#update_role!' do
it 'should raise ClientApi::CustomUserTeamError if no role is set' do
create :user_team, team: team_one, user: user_one
ut_service = ClientApi::UserTeamService.new(
user: user_one,
team_id: team_one.id,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long