diff --git a/app/assets/javascripts/application.js.erb b/app/assets/javascripts/application.js.erb index f5051cee4..ec2825632 100644 --- a/app/assets/javascripts/application.js.erb +++ b/app/assets/javascripts/application.js.erb @@ -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(){ '' + ''; $('#notifications').html(htmlSnippet); - $('#content-wrapper').addClass('alert-shown'); helpers.hideFlashMsg(); } diff --git a/app/assets/javascripts/navigation.js b/app/assets/javascripts/navigation.js index bce30d4ad..633d5eaf3 100644 --- a/app/assets/javascripts/navigation.js +++ b/app/assets/javascripts/navigation.js @@ -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(); })(); diff --git a/app/assets/javascripts/sitewide/url_handling.js b/app/assets/javascripts/sitewide/url_handling.js index db6eaf43a..2e7631c7f 100644 --- a/app/assets/javascripts/sitewide/url_handling.js +++ b/app/assets/javascripts/sitewide/url_handling.js @@ -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(); diff --git a/app/assets/stylesheets/constants.scss b/app/assets/stylesheets/constants.scss index b12a24d64..771cc6433 100644 --- a/app/assets/stylesheets/constants.scss +++ b/app/assets/stylesheets/constants.scss @@ -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 diff --git a/app/assets/stylesheets/extend/bootstrap.scss b/app/assets/stylesheets/extend/bootstrap.scss index e04bb81c9..ac2030716 100644 --- a/app/assets/stylesheets/extend/bootstrap.scss +++ b/app/assets/stylesheets/extend/bootstrap.scss @@ -58,7 +58,7 @@ float: none; } -@media (max-width: 1000px) { +@media (max-width: 1188px) { .navbar-header { float: none; } diff --git a/app/assets/stylesheets/themes/buttons.scss b/app/assets/stylesheets/themes/buttons.scss new file mode 100644 index 000000000..447837728 --- /dev/null +++ b/app/assets/stylesheets/themes/buttons.scss @@ -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; + } +} diff --git a/app/assets/stylesheets/themes/main_navigation.scss b/app/assets/stylesheets/themes/main_navigation.scss new file mode 100644 index 000000000..ace42350b --- /dev/null +++ b/app/assets/stylesheets/themes/main_navigation.scss @@ -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; + } +} diff --git a/app/assets/stylesheets/themes/scinote.scss b/app/assets/stylesheets/themes/scinote.scss index 5bd651454..39ae3b334 100644 --- a/app/assets/stylesheets/themes/scinote.scss +++ b/app/assets/stylesheets/themes/scinote.scss @@ -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; } diff --git a/app/models/my_module_repository_row.rb b/app/models/my_module_repository_row.rb index b03d754ae..f17cc8743 100644 --- a/app/models/my_module_repository_row.rb +++ b/app/models/my_module_repository_row.rb @@ -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 } diff --git a/app/models/repository.rb b/app/models/repository.rb index 6c0a16563..d32863b70 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -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 diff --git a/app/models/repository_cell.rb b/app/models/repository_cell.rb index 19474bc56..650dcba78 100644 --- a/app/models/repository_cell.rb +++ b/app/models/repository_cell.rb @@ -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' }) diff --git a/app/utilities/repository_import_parser/importer.rb b/app/utilities/repository_import_parser/importer.rb new file mode 100644 index 000000000..c3123f9a5 --- /dev/null +++ b/app/utilities/repository_import_parser/importer.rb @@ -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 diff --git a/app/utilities/repository_import_parser/repository_cell_value_resolver.rb b/app/utilities/repository_import_parser/repository_cell_value_resolver.rb new file mode 100644 index 000000000..2ae63e988 --- /dev/null +++ b/app/utilities/repository_import_parser/repository_cell_value_resolver.rb @@ -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 diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 4d1b16281..0355b8dde 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -65,7 +65,7 @@ <%= render "shared/left_menu_bar" if user_signed_in? %> -