Merge remote-tracking branch 'upstream/ux-release-1' into ml-sci-2118

This commit is contained in:
mlorb 2018-03-20 14:25:59 +01:00
commit 884eef9d07
24 changed files with 946 additions and 781 deletions

View file

@ -198,9 +198,6 @@ var HelperModule = (function(){
window.setTimeout(function () {
flash.fadeTo(500, 0).slideUp(500, function () {
$(this).remove();
if($('.alert').length <= 0) {
$('#content-wrapper').removeClass('alert-shown');
}
});
}, 5000);
}
@ -234,7 +231,6 @@ var HelperModule = (function(){
'</div>' +
'</div>';
$('#notifications').html(htmlSnippet);
$('#content-wrapper').addClass('alert-shown');
helpers.hideFlashMsg();
}

View file

@ -94,20 +94,9 @@
});
}
function focusSearchInput() {
var searchIco = $('#search-ico');
searchIco
.on('shown.bs.dropdown', function() {
searchIco
.find('input.form-control')
.focus();
});
}
// init
loadDropdownNotifications();
loadUnseenNotificationsNumber();
toggleNotificationBellPosition();
focusSearchInput();
initGlobalSwitchForm();
})();

View file

@ -36,7 +36,6 @@ $(document).ready(function () {
$("#hide-alert").click(function(ev) {
$(this).closest("div.alert").addClass("alert-hidden");
$("#content-wrapper").addClass("alert-hidden");
$("#content-wrapper").removeClass("alert-shown");
ev.preventDefault();
ev.stopPropagation();

View file

@ -3,11 +3,13 @@
//==============================================================================
// Theme colors
$color-theme-primary: #37a0d9;
$color-theme-primary: #41B0E0;
$color-theme-secondary: #8fd13f;
$color-theme-dark: #6d6e71;
// Grayscale colors
$color-border: #DDDDDD;
$color-list-separator: #BDC3C7;
$color-white: #fff;
$color-alabaster: #fcfcfc;
$color-snow: #f9f9f9;
@ -34,6 +36,7 @@ $color-mystic: #eaeff2;
$color-candlelight: #ffda23;
$color-orange: #ff900b;
$color-saturated-green: #008600;
$color-confirmation-green: #25AE88;
$color-blue-yadcf: #337ab7;
// Red colors

View file

@ -58,7 +58,7 @@
float: none;
}
@media (max-width: 1000px) {
@media (max-width: 1188px) {
.navbar-header {
float: none;
}

View file

@ -0,0 +1,114 @@
@import 'constants';
@import "mixins";
#reset-tutorial-btn {
margin-top: 15px;
}
.btn-primary {
background-color: $color-theme-secondary;
border-color: darken($color-theme-secondary, 5%);
&.active,
&.focus,
&.active.focus {
background-color: darken($color-theme-secondary, 20%);
border-color: darken($color-theme-secondary, 25%);
&:hover {
background-color: darken($color-theme-secondary, 25%);
border-color: darken($color-theme-secondary, 30%);
}
}
&:active,
&:focus,
&:active:focus,
&:active:hover,
&:focus:hover,
&:active:focus:hover {
background-color: darken($color-theme-secondary, 20%);
border-color: darken($color-theme-secondary, 25%);
}
&:hover {
background-color: darken($color-theme-secondary, 5%);
border-color: darken($color-theme-secondary, 10%);
}
}
.btn-link-alt {
border-radius: 4px;
cursor: pointer;
margin-right: 5px;
padding: 3px;
}
.btn-invis-file {
display: none;
opacity: 0;
position: absolute;
z-index: -1;
}
.btn-open-file {
position: relative;
overflow: hidden;
& > input[type=file] {
position: absolute;
top: 0;
right: 0;
min-width: 100%;
min-height: 100%;
font-size: 100px;
text-align: right;
filter: alpha(opacity=0);
opacity: 0;
outline: none;
background: white;
cursor: inherit;
display: block;
}
}
/** Add users modal */
.btn-group-existing-users {
width: 100%;
label.btn {
text-align: center;
&.btn-title {
color: $color-white;
cursor: inherit;
background-color: $color-theme-primary;
&:focus, &:active, &:hover {
box-shadow: none;
background-color: $color-theme-primary;
border-color: $color-dark-gray;
}
}
}
}
.btn-greyed {
background-color: $color-silver-chalice;
border-color: $color-silver-chalice;
color: $color-white;
&:hover,
&:focus {
background-color: darken($color-silver-chalice, 15%);
border-color: darken($color-silver-chalice, 15%);
color: $color-white;
}
}
/* Sample group color picker */
.btn-group-sample-group-color {
.btn-group > .btn {
border-radius: 0 !important;
}
}

View file

@ -0,0 +1,380 @@
@import 'constants';
@import "mixins";
#main-nav {
margin-bottom: 0;
}
#notifications-dropdown {
.fa-bell {
font-size: 15px;
position: absolute;
}
#count-notifications {
background-color: $color-theme-primary;
border-radius: 5px;
color: $color-wild-sand;
display: none;
font-size: 11px;
font-weight: bold;
margin-left: 12px;
padding: 1px 6px;
position: relative;
z-index: 1;
}
}
.navbar {
border-radius: 0;
padding-right: 15px;
}
.navbar-default {
background-color: $color-white;
border-color: $color-alto;
}
.navbar-default .navbar-brand {
& > img {
margin-top: -7px;
max-width: 55px;
max-height: 38px;
}
}
.dropdown-notifications {
max-height: 500px;
overflow-x: hidden;
overflow-y: scroll;
padding-bottom: 0;
padding-top: 0;
width: 450px;
word-wrap: break-word;
.notifications-no-recent {
padding-bottom: 10px;
padding-left: 10px;
padding-top: 10px;
}
.notification {
border-bottom: 1px solid $color-alto;
padding-bottom: 10px;
padding-top: 10px;
&:hover {
background-color: $color-concrete;
}
}
.unseen {
border-left: 4px solid $color-theme-primary;
}
.text-center {
margin-left: 12px;
}
.avatar {
top: 0px;
margin-top: 5px;
height: 45px;
width: 45px;
}
.assignment {
background-color: $color-theme-primary;
border-radius: 50%;
color: $color-wild-sand;
display: block;
font-size: 23px;
height: 45px;
padding-top: 5px;
width: 45px;
}
.deliver {
background-color: $color-orange;
border-radius: 50%;
color: $color-wild-sand;
display: block;
font-size: 23px;
height: 45px;
padding-top: 5px;
width: 45px;
}
.system-message {
background-color: $color-theme-secondary;
border-radius: 50%;
color: $color-wild-sand;
display: block;
font-size: 23px;
height: 45px;
padding-top: 8px;
width: 45px;
}
.notifications-dropdown-header {
background-color: $color-theme-primary;
color: $color-wild-sand;
font-weight: bold;
padding: 8px;
a {
color: $color-white;
}
}
.notifications-dropdown-footer {
background-color: $color-mystic;
padding: 8px;
text-align: center;
}
}
.notification {
padding-right: 8px;
word-wrap: break-word;
}
#search-menu {
padding-right: 0;
.nav {
position: relative;
z-index: 1000;
}
}
#search-content {
padding-left: 0;
}
#search-container {
padding-left: 45px;
}
#search-bar {
border-color: $color-border;
.btn-default {
background-color: $color-theme-primary;
border-color: $color-theme-primary;
color: $color-white;
}
input {
border-bottom-left-radius: 0;
border-top-left-radius: 0;
width: 300px;
}
}
/** Search */
.nav-search {
li.disabled {
opacity: .8;
.badge {
background-color: $color-emperor;
opacity: .8;
}
}
.repositories-team {
padding: 10px 15px;
&.active {
color: $color-theme-primary;
}
}
.repository-search {
padding-left: 15px;
}
}
.notification-settings-container {
margin-bottom: 50px;
margin-top: 50px;
h4 {
font-weight: 600;
margin-bottom: 2rem;
}
.col-sm-4 {
padding-left: 5rem;
padding-top: .5rem;
word-break: break-word;
}
.col-sm-2 {
padding-left: 3rem;
padding-top: .7rem;
}
@media (max-width: 768px) {
.col-sm-4 {
margin-bottom: 1rem;
padding-left: 1.8rem;
}
.col-sm-2 {
padding-left: 1.8rem;
}
}
.btn-group {
margin-bottom: 15px;
}
.assignment {
background-color: $color-theme-primary;
border-radius: 50%;
color: $color-wild-sand;
display: block !important;
font-size: 15px;
height: 30px;
margin-right: 15px;
padding: 7px;
padding-bottom: 5px;
padding-top: 5px;
width: 30px;
}
.system-message {
background-color: $color-theme-secondary;
border-radius: 50%;
color: $color-wild-sand;
display: block !important;
font-size: 15px;
height: 30px;
margin-right: 15px;
padding: 8px;
padding-bottom: 5px;
padding-top: 5px;
width: 30px;
}
.img-circle {
margin-right: 15px;
}
}
// Global team switch
#team-switch {
word-wrap: break-word;
.team-name {
margin-left: 17px;
}
.glyphicon-ok-sign {
color: $color-confirmation-green;
margin-left: -10px;
margin-right: 10px;
}
.team-name-item {
border-bottom: 1px solid $color-list-separator;
padding-bottom: 8px;
padding-top: 5px;
}
.btn-default {
border-bottom-left-radius: 0;
border-top-left-radius: 0;
text-align: right;
width: 300px;
}
.btn-group {
border-color: $color-border;
margin-top: 8px;
}
.btn-primary {
background-color: $color-theme-primary;
border-color: $color-theme-primary;
}
.dropdown-menu {
width: 100%;
}
.caret {
color: $color-white;
}
i {
margin-right: 5px;
}
li {
display: block;
text-align: left;
word-wrap: break-word;
&:hover {
background-color: $color-concrete;
}
a {
color: $color-emperor;
display: block;
line-height: 1.6em;
padding: 3px 20px;
text-align: left;
text-decoration: none;
word-wrap: break-word;
}
}
}
#nav-team-switch {
margin-left: 30px;
}
.custom-nav-dropdown {
border: 1px solid $color-border;
padding: 10px 0 10px 30px;
}
// Alert
.alert {
border-radius: 0;
margin-bottom: 0;
opacity: .86;
width: 100%;
&.alert-hidden {
display: none;
}
a#hide-alert {
margin-left: 15px;
}
&.alert-floating {
position: fixed;
top: 50px;
z-index: 1000;
}
}
#content-wrapper {
margin-top: 50px;
}
// reset margins on small screens
@media (max-width: 1188px) {
#nav-team-switch {
margin-left: 0;
}
}

View file

@ -1,5 +1,7 @@
@import 'constants';
@import "mixins";
@import "main_navigation";
@import "buttons";
/** Layout **/
@ -27,10 +29,6 @@ table {
margin-bottom: 20px;
}
#main-nav {
margin-bottom: 0;
}
#project-archive-btn {
margin-left: 15px;
}
@ -60,159 +58,16 @@ table {
margin-right: 0.5em;
}
#content-wrapper {
margin-top: 50px;
margin-left: 83px;
&.alert-shown {
margin-top: 102px;
}
}
.center-block-narrow {
max-width: 400px;
}
#notifications-dropdown {
.fa-bell {
font-size: 15px;
position: absolute;
}
#count-notifications {
background-color: $color-theme-primary;
border-radius: 5px;
color: $color-wild-sand;
display: none;
font-size: 11px;
font-weight: bold;
margin-left: 12px;
padding: 1px 6px;
position: relative;
z-index: 1;
}
}
@media(max-width:450px) {
.dropdown-notifications {
width: 100%;
}
}
.dropdown-notifications {
max-height: 500px;
overflow-x: hidden;
overflow-y: scroll;
padding-bottom: 0;
padding-top: 0;
width: 450px;
word-wrap: break-word;
.notifications-no-recent {
padding-bottom: 10px;
padding-left: 10px;
padding-top: 10px;
}
.notification {
border-bottom: 1px solid $color-alto;
padding-bottom: 10px;
padding-top: 10px;
&:hover {
background-color: $color-concrete;
}
}
.unseen {
border-left: 4px solid $color-theme-primary;
}
.text-center {
margin-left: 12px;
}
.avatar {
top: 0px;
margin-top: 5px;
height: 45px;
width: 45px;
}
.assignment {
background-color: $color-theme-primary;
border-radius: 50%;
color: $color-wild-sand;
display: block;
font-size: 23px;
height: 45px;
padding-top: 5px;
width: 45px;
}
.deliver {
background-color: $color-orange;
border-radius: 50%;
color: $color-wild-sand;
display: block;
font-size: 23px;
height: 45px;
padding-top: 5px;
width: 45px;
}
.system-message {
background-color: $color-theme-secondary;
border-radius: 50%;
color: $color-wild-sand;
display: block;
font-size: 23px;
height: 45px;
padding-top: 8px;
width: 45px;
}
.notifications-dropdown-header {
background-color: $color-theme-primary;
color: $color-wild-sand;
font-weight: bold;
padding: 8px;
a {
color: $color-white;
}
}
.notifications-dropdown-footer {
background-color: $color-mystic;
padding: 8px;
text-align: center;
}
}
.notification {
padding-right: 8px;
word-wrap: break-word;
}
#search-menu {
padding-right: 0;
.nav {
position: relative;
z-index: 1000;
}
}
#search-content {
padding-left: 0;
}
#search-container {
padding-left: 45px;
}
.well-search-checklist {
background-color: $color-concrete !important;
margin-bottom: 5px;
@ -253,27 +108,6 @@ a {
background-color: inherit;
}
.alert {
border-radius: 0;
margin-bottom: 0;
opacity: 1;
width: 100%;
&.alert-hidden {
display: none;
}
a#hide-alert {
margin-left: 15px;
}
&.alert-floating {
position: fixed;
top: 50px;
z-index: 1000;
}
}
.badge {
background-color: $color-theme-primary;
font-size: 11px;
@ -302,76 +136,6 @@ a {
width: 100% !important;
}
.btn {
border-radius: 1.5em;
}
.btn-primary {
background-color: $color-theme-secondary;
border-color: darken($color-theme-secondary, 5%);
&.active,
&.focus,
&.active.focus {
background-color: darken($color-theme-secondary, 20%);
border-color: darken($color-theme-secondary, 25%);
&:hover {
background-color: darken($color-theme-secondary, 25%);
border-color: darken($color-theme-secondary, 30%);
}
}
&:active,
&:focus,
&:active:focus,
&:active:hover,
&:focus:hover,
&:active:focus:hover {
background-color: darken($color-theme-secondary, 20%);
border-color: darken($color-theme-secondary, 25%);
}
&:hover {
background-color: darken($color-theme-secondary, 5%);
border-color: darken($color-theme-secondary, 10%);
}
}
.btn-link-alt {
border-radius: 4px;
cursor: pointer;
margin-right: 5px;
padding: 3px;
}
.btn-invis-file {
display: none;
opacity: 0;
position: absolute;
z-index: -1;
}
.btn-open-file {
position: relative;
overflow: hidden;
& > input[type=file] {
position: absolute;
top: 0;
right: 0;
min-width: 100%;
min-height: 100%;
font-size: 100px;
text-align: right;
filter: alpha(opacity=0);
opacity: 0;
outline: none;
background: white;
cursor: inherit;
display: block;
}
}
mark,.mark {
background-color: $color-candlelight;
}
@ -394,43 +158,6 @@ mark,.mark {
}
}
.navbar {
border-radius: 0;
}
.navbar-default {
background-color: $color-white;
border-color: $color-alto;
}
.navbar-default .navbar-brand {
background-color: $color-theme-primary;
font-size: 23px;
& > img {
margin-top: -4px;
max-width: 132px;
max-height: 26px;
&.with-version {
margin-top: -10px;
}
}
& > span.version {
font-size: 0.6em;
color: $color-white;
float: right;
}
&:hover,
&:focus,
&:focus:active,
&:focus:visited {
background-color: $color-theme-primary;
}
}
a[data-toggle="tooltip"] {
color: inherit;
border-bottom: 1px dashed $color-emperor;
@ -587,85 +314,6 @@ a[data-toggle="tooltip"] {
padding: 10px;
}
/** Search */
.nav-search {
li.disabled {
opacity: 0.8;
.badge {
background-color: $color-emperor;
opacity: 0.8;
}
}
.repositories-team {
padding: 10px 15px;
&.active {
color: $color-theme-primary;
}
}
.repository-search {
padding-left: 15px;
}
}
.search-dropdown {
padding-right: 25px;
width: 250px;
input {
width: 230px;
}
}
@media(max-width:1000px) {
.search-dropdown {
width: 270px;
.form-group {
padding-left: 15px;
}
}
}
// Global team switch
#team-switch {
border-left: 1px solid $color-alto;
border-right: 1px solid $color-alto;
word-wrap: break-word;
.dropdown-menu {
width: 100%;
}
i {
margin-right: 5px;
}
li {
display: block;
text-align: left;
word-wrap: break-word;
&:hover {
background-color: $color-concrete;
}
a {
color: $color-emperor;
display: block;
line-height: 1.6em;
padding: 3px 20px;
text-align: left;
text-decoration: none;
word-wrap: break-word;
}
}
}
/** Settings */
.nav-settings {
margin-top: 15px;
@ -691,78 +339,6 @@ a[data-toggle="tooltip"] {
margin-top: 40px;
}
.notification-settings-container {
margin-bottom: 50px;
margin-top: 50px;
h4 {
font-weight: 600;
margin-bottom: 2rem;
}
.col-sm-4 {
padding-left: 5rem;
padding-top: .5rem;
word-break: break-word;
}
.col-sm-2 {
padding-left: 3rem;
padding-top: .7rem;
}
@media (max-width: 768px) {
.col-sm-4 {
margin-bottom: 1rem;
padding-left: 1.8rem;
}
.col-sm-2 {
padding-left: 1.8rem;
}
}
.btn-group {
margin-bottom: 15px;
}
.assignment {
background-color: $color-theme-primary;
border-radius: 50%;
color: $color-wild-sand;
display: block !important;
font-size: 15px;
height: 30px;
margin-right: 15px;
padding: 7px;
padding-bottom: 5px;
padding-top: 5px;
width: 30px;
}
.system-message {
background-color: $color-theme-secondary;
border-radius: 50%;
color: $color-wild-sand;
display: block !important;
font-size: 15px;
height: 30px;
margin-right: 15px;
padding: 8px;
padding-bottom: 5px;
padding-top: 5px;
width: 30px;
}
.img-circle {
margin-right: 15px;
}
}
#reset-tutorial-btn {
margin-top: 15px;
}
// Help link
#help-link {
padding: 13px;
@ -780,26 +356,6 @@ a[data-toggle="tooltip"] {
padding-left: 15px;
}
/** Add users modal */
.btn-group-existing-users {
width: 100%;
label.btn {
text-align: center;
&.btn-title {
color: $color-white;
cursor: inherit;
background-color: $color-theme-primary;
&:focus, &:active, &:hover {
box-shadow: none;
background-color: $color-theme-primary;
border-color: $color-dark-gray;
}
}
}
}
.existing-users-smalltext {
width: 100%;
text-align: center;
@ -1429,18 +985,6 @@ ul.content-module-activities {
width: 100%;
}
.btn-greyed {
background-color: $color-silver-chalice;
border-color: $color-silver-chalice;
color: $color-white;
&:hover,
&:focus {
background-color: darken($color-silver-chalice, 15%);
border-color: darken($color-silver-chalice, 15%);
color: $color-white;
}
}
/* Data table */
table.dataTable {
@ -1500,13 +1044,6 @@ table.dataTable {
}
}
/* Sample group color picker */
.btn-group-sample-group-color {
.btn-group > .btn {
border-radius: 0 !important;
}
}
#samples_length {
display: inline-block;
}

View file

@ -3,8 +3,10 @@ class MyModuleRepositoryRow < ApplicationRecord
foreign_key: 'assigned_by_id',
class_name: 'User',
optional: true
belongs_to :repository_row, optional: true
belongs_to :my_module, optional: true
belongs_to :repository_row,
optional: true,
inverse_of: :my_module_repository_rows
belongs_to :my_module, optional: true, inverse_of: :my_module_repository_rows
validates :repository_row, :my_module, presence: true
validates :repository_row, uniqueness: { scope: :my_module }

View file

@ -1,5 +1,6 @@
class Repository < ApplicationRecord
include SearchableModel
include RepositoryImportParser
belongs_to :team, optional: true
belongs_to :created_by,
@ -95,87 +96,8 @@ class Repository < ApplicationRecord
new_repo
end
# Imports records
def import_records(sheet, mappings, user)
errors = false
columns = []
name_index = -1
total_nr = 0
nr_of_added = 0
header_skipped = false
mappings.each.with_index do |(_k, value), index|
if value == '-1'
# Fill blank space, so our indices stay the same
columns << nil
name_index = index
else
columns << repository_columns.find_by_id(value)
end
end
# Check for duplicate columns
col_compact = columns.compact
unless col_compact.map(&:id).uniq.length == col_compact.length
return { status: :error, nr_of_added: nr_of_added, total_nr: total_nr }
end
rows = SpreadsheetParser.spreadsheet_enumerator(sheet)
# Now we can iterate through record data and save stuff into db
rows.each do |row|
# Skip empty rows
next if row.empty?
unless header_skipped
header_skipped = true
next
end
total_nr += 1
row = SpreadsheetParser.parse_row(row, sheet)
record_row = RepositoryRow.new(name: row[name_index],
repository: self,
created_by: user,
last_modified_by: user)
record_row.transaction do
unless record_row.save
errors = true
raise ActiveRecord::Rollback
end
row_cell_values = []
row.each.with_index do |value, index|
if columns[index] && value
cell_value = RepositoryTextValue.new(
data: value,
created_by: user,
last_modified_by: user,
repository_cell_attributes: {
repository_row: record_row,
repository_column: columns[index]
}
)
unless cell_value.valid?
errors = true
raise ActiveRecord::Rollback
end
row_cell_values << cell_value
end
end
if RepositoryTextValue.import(row_cell_values,
recursive: true,
validate: false).failed_instances.any?
errors = true
raise ActiveRecord::Rollback
end
nr_of_added += 1
end
end
if errors
return { status: :error, nr_of_added: nr_of_added, total_nr: total_nr }
end
{ status: :ok, nr_of_added: nr_of_added, total_nr: total_nr }
importer = RepositoryImportParser::Importer.new(sheet, mappings, user, self)
importer.run
end
end

View file

@ -1,7 +1,9 @@
class RepositoryCell < ActiveRecord::Base
belongs_to :repository_row
belongs_to :repository_column
belongs_to :value, polymorphic: true, dependent: :destroy
belongs_to :value, polymorphic: true,
inverse_of: :repository_cell,
dependent: :destroy
belongs_to :repository_text_value,
(lambda do
where(repository_cells: { value_type: 'RepositoryTextValue' })

View file

@ -0,0 +1,129 @@
# frozen_string_literal: true
# handles the import of repository records
# requires 4 parameters:
# @sheet: the csv file with imported rows
# @mappings: mappings for columns
# @user: current_user
# @repository: the repository in which we import the items
module RepositoryImportParser
class Importer
def initialize(sheet, mappings, user, repository)
@columns = []
@name_index = -1
@total_new_rows = 0
@new_rows_added = 0
@header_skipped = false
@repository = repository
@sheet = sheet
@rows = SpreadsheetParser.spreadsheet_enumerator(@sheet)
@mappings = mappings
@user = user
@repository_columns = @repository.repository_columns
end
def run
fetch_columns
return check_for_duplicate_columns if check_for_duplicate_columns
import_rows!
end
private
def fetch_columns
@mappings.each.with_index do |(_, value), index|
if value == '-1'
# Fill blank space, so our indices stay the same
@columns << nil
@name_index = index
else
@columns << @repository_columns.find_by_id(value)
end
end
end
def check_for_duplicate_columns
col_compact = @columns.compact
if col_compact.map(&:id).uniq.length != col_compact.length
return { status: :error,
nr_of_added: @new_rows_added,
total_nr: @total_new_rows }
end
end
def import_rows!
errors = false
@rows.each do |row|
# Skip empty rows
next if row.empty?
unless @header_skipped
@header_skipped = true
next
end
@total_new_rows += 1
row = SpreadsheetParser.parse_row(row, @sheet)
record_row = new_repository_row(row)
record_row.transaction do
unless record_row.save
errors = true
raise ActiveRecord::Rollback
end
row_cell_values = []
row.each.with_index do |value, index|
column = @columns[index]
if column && value.present?
# uses RepositoryCellValueResolver to retrieve the correct value
cell_value_resolver =
RepositoryImportParser::RepositoryCellValueResolver.new(
column, @user, @repository
)
cell_value = cell_value_resolver.get_value(value, record_row)
unless cell_value.valid?
errors = true
raise ActiveRecord::Rollback
end
row_cell_values << cell_value
end
end
unless import_to_database(row_cell_values)
errors = true
raise ActiveRecord::Rollback
end
@new_rows_added += 1
end
end
if errors
return { status: :error,
nr_of_added: @new_rows_added,
total_nr: @total_new_rows }
end
{ status: :ok, nr_of_added: @new_rows_added, total_nr: @total_new_rows }
end
def new_repository_row(row)
RepositoryRow.new(name: row[@name_index],
repository: @repository,
created_by: @user,
last_modified_by: @user)
end
def import_to_database(row_cell_values)
return false if RepositoryTextValue.import(
row_cell_values.select { |element| element.is_a? RepositoryTextValue },
recursive: true,
validate: false
).failed_instances.any?
return false if RepositoryListValue.import(
row_cell_values.select { |element| element.is_a? RepositoryListValue },
recursive: true,
validate: false
).failed_instances.any?
true
end
end
end

View file

@ -0,0 +1,54 @@
# frozen_string_literal: true
# this class is used to resolve the column data_type and assign
# it to the repository_row
module RepositoryImportParser
class RepositoryCellValueResolver
def initialize(column, user, repository)
@column = column
@user = user
@repository = repository
end
def get_value(value, record_row)
return unless @column
send("new_#{@column.data_type.underscore}", value, record_row)
end
private
def new_repository_text_value(value, record_row)
RepositoryTextValue.new(data: value,
created_by: @user,
last_modified_by: @user,
repository_cell_attributes: {
repository_row: record_row,
repository_column: @column
})
end
def new_repository_list_value(value, record_row)
list_item = @column.repository_list_items.find_by_data(value)
list_item ||= create_repository_list_item(value)
RepositoryListValue.new(
created_by: @user,
last_modified_by: @user,
repository_list_item: list_item,
repository_cell_attributes: {
repository_row: record_row,
repository_column: @column
}
)
end
def create_repository_list_item(value)
RepositoryListItem.create(
data: value,
created_by: @user,
last_modified_by: @user,
repository_column: @column,
repository: @repository
)
end
end
end

View file

@ -65,7 +65,7 @@
</div>
<%= render "shared/left_menu_bar" if user_signed_in? %>
<div id="content-wrapper" class="<%= "alert-shown" if flash[:success] || flash[:error] || notice || alert %>">
<div id="content-wrapper">
<%= yield :content %>
</div>

View file

@ -1,5 +1,5 @@
<nav class="navbar navbar-default navbar-fixed-top" id="main-nav">
<div class="container">
<div class="container-fluid">
<!-- header -->
<div class="navbar-header">
@ -10,166 +10,105 @@
<span class="icon-bar"></span>
</button>
<%= link_to(root_path, class: 'navbar-brand', title: t('nav.label.scinote')) do %>
<%
show_version = !Rails.env.production?
if ENV['NAVBAR_SHOW_VERSION'].present?
show_version = YAML.load(ENV['NAVBAR_SHOW_VERSION'])
end
%>
<% if show_version %>
<%= image_tag('/images/logo.png', class: 'with-version', id: 'logo') %>
<span class="version">
<%= Scinote::Application::VERSION %>
</span>
<% else %>
<%= image_tag('/images/logo.png', id: 'logo') %>
<% end %>
<%= image_tag('/images/scinote_icon.jpg', id: 'logo') %>
<% end %>
</div>
<% if user_signed_in? %>
<div class="collapse navbar-collapse" id="main-menu">
<!-- links -->
<ul class="nav navbar-nav">
<li>
<a title="<%= t('nav.label.projects') %>" href="<%= projects_path %>">
<span class="glyphicon glyphicon-home"></span>
<span class="visible-xs-inline visible-sm-inline"><%= t('nav.label.projects') %></span>
</a>
</li>
<% if current_team %>
<li>
<a id="protocol-link" title="<%= t('nav.label.protocols') %>" href="<%= protocols_path %>">
<span class="glyphicon glyphicon-list-alt"></span>
<span class="visible-xs-inline visible-sm-inline"><%= t('nav.label.protocols') %></span>
</a>
</li>
<li>
<a id="repositories-link" title="<%= t('nav.label.repositories') %>" href="<%= team_repositories_path(current_team) %>">
<i class="fa fa-cubes" aria-hidden="true"></i>
<span class="visible-xs-inline visible-sm-inline"><%= t('nav.label.repositories') %></span>
</a>
</li>
<% else %>
<li class="disabled">
<a id="protocol-link" title="<%= t('nav.label.protocols') %>" href="#">
<span class="glyphicon glyphicon-list-alt"></span>
<span class="visible-xs-inline visible-sm-inline"><%= t('nav.label.protocols') %></span>
</a>
</li>
<li class="disabled">
<a id="repositories-link" title="<%= t('nav.label.repositories') %>" href="#">
<span class="fa fa-cubes"></span>
<span class="visible-xs-inline visible-sm-inline"><%= t('nav.label.repositories') %></span>
</a>
</li>
<ul class="nav navbar-nav navbar-left" id="nav-team-switch">
<!-- Global team switch -->
<% if current_user.teams.length > 0 %>
<li id="team-switch">
<div class="btn-group">
<button type="button" class="btn btn-default"><%= truncate_team_name(current_team.name) %></button>
<button type="button"
class="btn btn-primary dropdown-toggle"
title="<%= t('nav.label.teams') %>"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<% if current_user.teams.length > 1 || can_create_teams? %>
<ul class="dropdown-menu">
<%= form_for(current_user,
url: user_current_team_path,
method: :post) do |f| %>
<%= hidden_field(:user, :current_team_id) %>
<% current_user.teams.each do |team| %>
<li class="team-name-item">
<a href="#"
data-id="<%= team.id %>"
class="text-center change-team">
<% if current_team == team %>
<span class="glyphicon glyphicon-ok-sign"></span> <strong><%= team.name %></strong>
<% else %>
<span class="team-name"><%= team.name %></span>
<% end %>
</a>
</li>
<% end %>
<% end %>
<% if current_user.teams.length > 1 && can_create_teams? %>
<li>
<%= link_to new_team_path do %>
<span class="glyphicon glyphicon-plus"></span>
<%= t('users.settings.teams.index.new_team') %>
<% end %>
</li>
<% end %>
</ul>
<% end %>
</div>
</li>
<% end %>
<% if false %>
<li>
<a href="#">
<span class="glyphicon glyphicon-calendar"></span>
<span class="visible-xs-inline visible-sm-inline"><%= t('nav.label.calendar') %></span>
</a>
</li>
<% end %>
<li>
<a class="btn-activity" title="<%= t('nav.label.activities') %>" href="<%= activities_path %>" role="button">
<span class="glyphicon glyphicon-equalizer"></span>
<span class="visible-xs-inline visible-sm-inline"><%= t('nav.label.activities') %></span>
</a>
</li>
</ul>
<!-- profile info -->
<ul class="nav navbar-nav navbar-right">
<!-- Global team switch -->
<% if current_user.teams.length > 0 %>
<li class="dropdown" id="team-switch">
<a href="#"
class="dropdown-toggle"
title="<%= t('nav.label.teams') %>"
data-toggle="dropdown"
role="button"
aria-haspopup="true"
aria-expanded="false">
<%= fa_icon 'users' %>
<span>
<%= truncate_team_name(current_team.name) %>
</span>
</a>
<% if current_user.teams.length > 1 || can_create_teams? %>
<ul class="dropdown-menu">
<%= form_for(current_user,
url: user_current_team_path,
method: :post) do |f| %>
<%= hidden_field(:user, :current_team_id) %>
<% current_user.teams.each do |team| %>
<% next unless team != current_team %>
<li>
<a href="#"
data-id="<%= team.id %>"
class="text-center change-team">
<%= team.name %>
</a>
</li>
<% end %>
<% end %>
<% if current_user.teams.length > 1 && can_create_teams? %>
<li role="separator"
class="divider"></li>
<li>
<%= link_to new_team_path do %>
<span class="glyphicon glyphicon-plus"></span>
<%= t('users.settings.teams.index.new_team') %>
<% end %>
</li>
<% end %>
</ul>
<!-- search form -->
<li>
<%= form_tag search_path,
method: :get,
id: 'search-bar',
class: 'navbar-form',
role: 'search' do %>
<div class="input-group">
<input class="form-control"
type="text"
name="q"
placeholder="<%= t('nav.search') %>" />
<span class="input-group-btn">
<button class="btn btn-default" type="submit"><%=t 'nav.search_button' %></button>
</span>
</div>
<% end %>
</li>
<% end %>
<!-- search -->
<li class="dropdown"
id="search-ico">
<!-- greetings -->
<li id="user-account-dropdown" class="dropdown">
<a href="#"
class="dropdown-toggle"
title="<%= t('nav.label.search') %>"
title="<%= t('nav.label.account') %>"
data-toggle="dropdown"
role="button"
aria-haspopup="true"
aria-expanded="false">
<span class="glyphicon glyphicon-search"></span>
<span class="visible-xs-inline visible-sm-inline"><%= t('nav.label.search') %></span>
<span>
<%= t('nav.user_greeting', full_name: current_user.full_name) %>
</span>
<%= image_tag avatar_path(current_user, :icon_small),
class: "avatar" %>
</a>
<ul class="dropdown-menu search-dropdown">
<li>
<!-- search form -->
<%= form_tag search_path,
method: :get,
id: 'search-bar',
class: 'navbar-form navbar-right',
role: 'search' do %>
<div class="form-group">
<div class="input-group">
<input class="form-control"
type="text"
name="q"
placeholder="<%= t('nav.search') %>">
<span class="input-group-btn visible-xs visible-sm">
<button id="search-button"
class="btn btn-default"
type="submit">
<span class="glyphicon glyphicon-menu-right"></span>
</button>
</span>
</div>
</div>
<% end %>
</li>
</ul>
<ul class="dropdown-menu" data-hook="navigation-user-menu">
<li>
<%= link_to t('nav.user.logout'),
destroy_user_session_path,
method: :delete %>
</li>
</ul>
</li>
<!-- notifications -->
@ -205,71 +144,6 @@
</ul>
</li>
<!-- help -->
<li class="dropdown">
<a href="#"
id="help-link"
class="dropdown-toggle"
title="<%= t('nav.label.info') %>"
data-toggle="dropdown"
role="button"
aria-haspopup="true"
aria-expanded="false">
<span class="glyphicon glyphicon-info-sign"></span>
<span class="visible-xs-inline visible-sm-inline"><%= t('nav.label.info') %></span>
</a>
<ul class="dropdown-menu" data-hook="navigation-help-menu">
<li><%= link_to t('nav.help.support'),
Constants::SUPPORT_URL,
target: "_blank" %></li>
<li><%= link_to t('nav.help.tutorials'),
Constants::TUTORIALS_URL,
target: "_blank" %></li>
<li><%= link_to t('nav.help.release_notes'),
Constants::RELEASE_NOTES_URL,
target: "_blank" %></li>
<li><%= link_to t('nav.help.premium'),
Constants::PREMIUM_URL,
target: "_blank" %></li>
<li><%= link_to t('nav.help.contact'),
Constants::CONTACT_URL,
target: "_blank" %></li>
<li role="separator" class="divider"></li>
<li>
<%= link_to '#', data: { trigger: 'about-modal' } do %>
<%= t('nav.help.about') %>
<% end %>
</li>
</ul>
</li>
<!-- greetings -->
<li id="user-account-dropdown" class="dropdown">
<a href="#"
class="dropdown-toggle"
title="<%= t('nav.label.account') %>"
data-toggle="dropdown"
role="button"
aria-haspopup="true"
aria-expanded="false">
<span>
<%= t('nav.user_greeting', full_name: current_user.full_name) %>
</span>
<%= image_tag avatar_path(current_user, :icon_small),
class: "avatar" %>
</a>
<ul class="dropdown-menu" data-hook="navigation-user-menu">
<li>
<%= link_to t('nav.user.settings'), edit_user_registration_path, data: { turbolinks: false } %>
</li>
<li role="separator" class="divider"></li>
<li>
<%= link_to t('nav.user.logout'),
destroy_user_session_path,
method: :delete %>
</li>
</ul>
</li>
</ul>
</div>
<% end %>

View file

@ -67,7 +67,8 @@ en:
body:
notice: "You need to enable JavaScript to run this app."
nav:
search: "Search"
search: "Search for something..."
search_button: 'Go!'
user_greeting: "Hi, %{full_name}"
advanced_search: "Advanced search"
title: "sciNote"
@ -986,12 +987,12 @@ en:
repository_row:
modal_info:
head_title: "Information for record '%{repository_row}'"
head_title: "Information for item '%{repository_row}'"
added_on: "Added on"
added_by: "Added by"
custom_field: "%{cf}: "
title: "This record is assigned to %{nr} tasks."
no_tasks: "This record in not assigned to any task."
title: "This item is assigned to %{nr} tasks."
no_tasks: "This item in not assigned to any task."
samples:
columns: "Columns"
columns_visibility: "Toggle visibility"

View file

@ -44,7 +44,7 @@ Scenario: Unsuccessful Log in
And I click "Log in" button
Then I should see "Invalid Email or password." flash message
@javascript
@javascript @wip
Scenario: Successful Log out
Given "night.slarker@gmail.com" is signed in with "mypassword1234"
And I'm on the home page of "BioSistemika Process" team

View file

@ -11,7 +11,7 @@ Background:
And "nonadmin@myorg.com" is in "BioSistemika Process" team as a "normal_user"
And "nonadmin@myorg.com" is signed in with "mypassword1234"
@javascript
@javascript @wip
Scenario: Successful navigate to profile page
Given I'm on the home page of "BioSistemika Process" team
And I click on Avatar

View file

@ -15,7 +15,7 @@ require 'capybara/email'
Capybara.register_driver :poltergeist do |app|
options = {
# inspector: true,
screen_size: [1200, 900],
screen_size: [2560, 900],
js_errors: false,
timeout: 30,
phantomjs: Phantomjs.path,

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

6
spec/fixtures/files/export.csv vendored Normal file
View file

@ -0,0 +1,6 @@
Name,Added on,Added by,Sample group,Sample type,Custom items
Sample 5,02.03.2018 09:53,Admin,group 3,,test 3
Sample 4,02.03.2018 09:53,Admin,group 2,type 1,test 2
Sample 3,02.03.2018 09:53,Admin,,type 1,
Sample 2,02.03.2018 09:52,Admin,group 2,type 2,
Sample 1,02.03.2018 09:52,Admin,group 1,type 2,test test
1 Name Added on Added by Sample group Sample type Custom items
2 Sample 5 02.03.2018 09:53 Admin group 3 test 3
3 Sample 4 02.03.2018 09:53 Admin group 2 type 1 test 2
4 Sample 3 02.03.2018 09:53 Admin type 1
5 Sample 2 02.03.2018 09:52 Admin group 2 type 2
6 Sample 1 02.03.2018 09:52 Admin group 1 type 2 test test

View file

@ -0,0 +1,91 @@
require 'rails_helper'
describe RepositoryImportParser::Importer do
let(:user) { create :user }
let(:team) { create :team, created_by: user }
let(:user_team) { create :user_team, user: user, team: team }
let(:repository) { create :repository, team: team, created_by: user }
let!(:sample_group_column) do
create :repository_column, repository: repository,
created_by: user,
name: 'Sample group',
data_type: 'RepositoryListValue'
end
let!(:sample_type_column) do
create :repository_column, repository: repository,
created_by: user,
name: 'Sample type',
data_type: 'RepositoryListValue'
end
let!(:custom_column) do
create :repository_column, repository: repository,
created_by: user,
name: 'Custom items',
data_type: 'RepositoryTextValue'
end
let(:sheet) do
SpreadsheetParser.open_spreadsheet(fixture_file_upload('files/export.csv'))
end
let(:mappings) do
{ '0' => '-1',
'1' => '',
'2' => '',
'3' => sample_group_column.id.to_s,
'4' => sample_type_column.id.to_s,
'5' => custom_column.id.to_s }
end
describe '#run/0' do
let(:subject) do
RepositoryImportParser::Importer.new(sheet, mappings, user, repository)
end
it 'return a message of imported records' do
expect(subject.run).to eq({ status: :ok, nr_of_added: 5, total_nr: 5 })
end
it 'generate 5 new repository rows' do
subject.run
expect(repository.repository_rows.count).to eq 5
end
it 'generate 3 new repository list items on Sample group column' do
subject.run
column = repository.repository_columns.find_by_name('Sample group')
expect(column.repository_list_items.count).to eq 3
column.repository_list_items.each do |repository_item|
expect(['group 1', 'group 2', 'group 3']).to include(repository_item.data)
end
end
it 'generate 2 new repository list items on Sample type column' do
subject.run
column = repository.repository_columns.find_by_name('Sample type')
expect(column.repository_list_items.count).to eq 2
column.repository_list_items.each do |repository_item|
expect(['type 1', 'type 2']).to include(repository_item.data)
end
end
it 'assign custom columns to imported repository row' do
subject.run
row = repository.repository_rows.find_by_name('Sample 1')
sample_group_cell = row.repository_cells
.find_by_repository_column_id(
sample_group_column.id
)
sample_type_cell = row.repository_cells
.find_by_repository_column_id(
sample_type_column.id
)
custom_column_cell = row.repository_cells
.find_by_repository_column_id(
custom_column.id
)
expect(sample_group_cell.value.formatted).to eq 'group 1'
expect(sample_type_cell.value.formatted).to eq 'type 2'
expect(custom_column_cell.value.formatted).to eq 'test test'
end
end
end

View file

@ -0,0 +1,66 @@
require 'rails_helper'
describe RepositoryImportParser::RepositoryCellValueResolver do
let(:user) { create :user }
let(:team) { create :team, created_by: user }
let(:user_team) { create :user_team, user: user, team: team }
let(:repository) { create :repository, team: team, created_by: user }
let!(:sample_group_column) do
create :repository_column, repository: repository,
created_by: user,
name: 'Sample group',
data_type: 'RepositoryListValue'
end
let!(:custom_column) do
create :repository_column, repository: repository,
created_by: user,
name: 'Custom items',
data_type: 'RepositoryTextValue'
end
let!(:repository_row) do
create :repository_row, repository: repository,
created_by: user,
name: 'Sample'
end
describe '#ruget_valuen/2' do
context 'RepositoryListValue' do
let(:subject) do
RepositoryImportParser::RepositoryCellValueResolver.new(
sample_group_column, user, repository
)
end
it 'returns a valid RepositoryListValue object' do
value = subject.get_value('leaf', repository_row)
expect(value).to be_valid
expect(value).to be_a RepositoryListValue
expect(value.formatted).to eq 'leaf'
end
it 'creates a new RepositoryListItem' do
value = subject.get_value('leaf', repository_row)
item = sample_group_column.repository_list_items.find_by_data('leaf')
expect(sample_group_column.repository_list_items.count).to eq 1
expect(item).to be_present
expect(item.data).to eq 'leaf'
end
end
context 'RepositoryTextValue' do
let(:subject) do
RepositoryImportParser::RepositoryCellValueResolver.new(
custom_column, user, repository
)
it 'returns a valid RepositoryTextValue object' do
value = subject.get_value('blood', repository_row)
expect(value).to be_valid
expect(value).to be_a RepositoryTextValue
expect(value.formatted).to eq 'blood'
end
end
end
end
end