diff --git a/app/assets/javascripts/application.js.erb b/app/assets/javascripts/application.js.erb index bd6c65afd..aef9461f1 100644 --- a/app/assets/javascripts/application.js.erb +++ b/app/assets/javascripts/application.js.erb @@ -286,3 +286,5 @@ $(window).scroll(function() { scroll_function(); }) }) + +window.I18n = I18n diff --git a/app/assets/javascripts/repositories/repository_datatable.js b/app/assets/javascripts/repositories/repository_datatable.js index 7ff8bb36a..e8840ebff 100644 --- a/app/assets/javascripts/repositories/repository_datatable.js +++ b/app/assets/javascripts/repositories/repository_datatable.js @@ -1,7 +1,7 @@ /* globals I18n _ SmartAnnotation FilePreviewModal animateSpinner DataTableHelpers HelperModule RepositoryDatatableRowEditor prepareRepositoryHeaderForExport - initAssignedTasksDropdown + initAssignedTasksDropdown initBMTFilter */ //= require jquery-ui/widgets/sortable @@ -268,6 +268,7 @@ var RepositoryDatatable = (function(global) { $.getJSON($(TABLE_ID).data('toolbar-url'), (data) => { $('#toolbarButtonsDatatable').remove(); $(data.html).appendTo('div.toolbar'); + initBMTFilter(); }); TABLE.ajax.reload(null, false); @@ -406,6 +407,10 @@ var RepositoryDatatable = (function(global) { url: $(TABLE_ID).data('source'), data: function(d) { d.archived = $('.repository-show').hasClass('archived'); + + if ($('[data-external-ids]').length) { + d.external_ids = $('[data-external-ids]').attr('data-external-ids').split(','); + } }, global: false, type: 'POST' @@ -551,6 +556,7 @@ var RepositoryDatatable = (function(global) { // Append buttons to inner toolbar in the table $.getJSON($(TABLE_ID).data('toolbar-url'), (data) => { $(data.html).appendTo('div.toolbar'); + initBMTFilter(); }); $('div.toolbar-filter-buttons').prependTo('div.filter-container'); @@ -577,6 +583,7 @@ var RepositoryDatatable = (function(global) { }); initAssignedTasksDropdown(TABLE_ID); + } }); diff --git a/app/assets/javascripts/repository_columns/index.js b/app/assets/javascripts/repository_columns/index.js index 5e5907361..cd574fb99 100644 --- a/app/assets/javascripts/repository_columns/index.js +++ b/app/assets/javascripts/repository_columns/index.js @@ -253,31 +253,12 @@ var RepositoryColumns = (function() { }); } - function getColumnTypeText(el, colId) { - var colType = ''; - switch (colId) { - case 'row-id': - colType = 'RepositoryNumberValue'; - break; - case 'row-name': - colType = 'RepositoryTextValue'; - break; - case 'added-on': - colType = 'RepositoryDateTimeValue'; - break; - case 'added-by': - colType = 'RepositoryListValue'; - break; - case 'archived-on': - colType = 'RepositoryDateTimeValue'; - break; - case 'archived-by': - colType = 'RepositoryListValue'; - break; - default: - colType = $(el).attr('data-type'); - } - return I18n.t('libraries.manange_modal_column.select.' + colType.split(/(?=[A-Z])/).join('_').toLowerCase()); + function getColumnTypeText(el) { + let colType = $(el).attr('data-type'); + if (!colType) return ''; + + return I18n.t('libraries.manange_modal_column.select.' + colType.split(/(?=[A-Z])/).join('_') + .toLowerCase()); } // loads the columns names in the manage columns modal index diff --git a/app/assets/stylesheets/repositories.scss b/app/assets/stylesheets/repositories.scss index bdd14baff..455ea8bff 100644 --- a/app/assets/stylesheets/repositories.scss +++ b/app/assets/stylesheets/repositories.scss @@ -135,13 +135,12 @@ flex-wrap: nowrap; height: 6em; left: var(--repository-sidebar-margin); - overflow: hidden; padding: 1em 2em 0; position: fixed; top: calc(4em + var(--navbar-height)); transition: .4s $timing-function-sharp; width: calc(100% - var(--repository-sidebar-margin)); - z-index: 90; + z-index: 99; .filter-container { flex-shrink: 0; diff --git a/app/assets/stylesheets/repository/bmt_filters.scss b/app/assets/stylesheets/repository/bmt_filters.scss new file mode 100644 index 000000000..0927bc37f --- /dev/null +++ b/app/assets/stylesheets/repository/bmt_filters.scss @@ -0,0 +1,168 @@ +// scss-lint:disable SelectorDepth +// scss-lint:disable NestingDepth + +.bmt-filters-button.active-filters { + position: relative; + + &::after { + background: $brand-accent; + border-radius: 50%; + content: ""; + display: block; + height: 8px; + position: absolute; + right: .3em; + top: .3em; + width: 8px; + } +} + +#bmtFilterContainer { + min-width: 422px; + z-index: 100; + + .sci-input-container { + margin-bottom: .5em; + } + + .dropdown-selector-container { + width: 100%; + } + + .field-suboption { + align-items: center; + display: flex; + margin-top: .5em; + + .sci-checkbox-container { + margin-right: .25em; + } + + .distance-attribute { + align-items: center; + display: flex; + flex-shrink: 0; + margin-bottom: .5em; + margin-top: 1.5em; + + input[type=radio] { + margin: 0 .25em 0 .5em; + } + } + } + + label { + font-weight: normal; + } + + .form-group { + display: block; + width: 100%; + + .form-control, + .form-select { + margin-bottom: 1.2em; + width: 100%; + } + } + + .checkbox-label { + vertical-align: text-bottom; + } + + .filter-list-notice { + color: $color-silver-chalice; + padding: 1em; + } + + .header { + align-items: center; + border-bottom: $border-tertiary; + display: flex; + padding: 0 1em; + + .title { + @include font-h2; + } + + .clear-filters-btn { + margin-left: auto; + } + } + + .filter-container { + border-bottom: $border-tertiary; + padding: 1em 3.5em 1em 1em; + + + .filter-title { + @include font-small; + } + } + + .filter-element { + align-items: center; + display: flex; + position: relative; + } + + .filter-remove { + cursor: pointer; + padding: 0; + position: absolute; + right: -2.5em; + top: -2px; + } + + .filters-list { + border-top: $border-tertiary; + max-height: calc(100vh - 300px); + overflow-y: auto; + } + + .footer { + align-items: center; + border-top: $border-tertiary; + display: flex; + padding: 5px 1em 0; + + .add-filter { + margin-right: auto; + } + } + + .saved-filters-container { + .title { + cursor: pointer; + } + + &.open { + .fa-caret-down { + @include rotate(-180deg); + } + } + } + + .saved-filters-list { + max-height: calc(100vh - 300px); + overflow-y: auto; + padding: 0 1em; + } + + .saved-filters-element { + align-items: center; + cursor: pointer; + display: flex; + justify-content: space-between; + min-width: 200px; + + i { + color: $color-silver-chalice; + } + + } + + .saved-filters-element:hover i { + color: $color-black; + } +} diff --git a/app/assets/stylesheets/repository/repository_table.scss b/app/assets/stylesheets/repository/repository_table.scss index 734ce0bf0..b16849674 100644 --- a/app/assets/stylesheets/repository/repository_table.scss +++ b/app/assets/stylesheets/repository/repository_table.scss @@ -4,10 +4,28 @@ @import "constants"; .repository-table { + position: relative; + .dataTables_filter { float: right; } + .repository-table-error { + background: $color-white; + display: none; + height: 200px; + padding: 1em; + position: absolute; + text-align: center; + top: 176px; + width: 100%; + z-index: 1; + + &.active { + display: block; + } + } + // hack only for firefox @-moz-document url-prefix() { input.form-control[type="file"] { diff --git a/app/controllers/bio_eddie_assets_controller.rb b/app/controllers/bio_eddie_assets_controller.rb index 3237b3a68..582582bfa 100644 --- a/app/controllers/bio_eddie_assets_controller.rb +++ b/app/controllers/bio_eddie_assets_controller.rb @@ -56,18 +56,22 @@ class BioEddieAssetsController < ApplicationController end def bmt_request - return render_404 unless ENV['BIOMOLECULE_TOOLKIT_BASE_URL'] + return render_404 unless Rails.application.config.x.biomolecule_toolkit_base_url - uri = URI.parse(ENV['BIOMOLECULE_TOOLKIT_BASE_URL']) - uri.path = request.original_fullpath.remove('/biomolecule_toolkit') + uri = URI.parse(Rails.application.config.x.biomolecule_toolkit_base_url) + uri.path = File.join(uri.path, request.original_fullpath.remove('/biomolecule_toolkit')) Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http| api_request = "Net::HTTP::#{request.request_method.capitalize}".constantize.new(uri) - api_request['x-api-key'] = ENV['BIOMOLECULE_TOOLKIT_API_KEY'] if ENV['BIOMOLECULE_TOOLKIT_API_KEY'] + if Rails.application.config.x.biomolecule_toolkit_api_key + api_request['x-api-key'] = Rails.application.config.x.biomolecule_toolkit_api_key + end api_request['Content-Type'] = 'application/json' request_body = request.body.read + api_request.body = request_body if request_body.present? api_response = http.request(api_request) + render body: api_response.body, content_type: api_response.content_type, status: api_response.code end end diff --git a/app/controllers/bmt_filters_controller.rb b/app/controllers/bmt_filters_controller.rb new file mode 100644 index 000000000..4d5ff0710 --- /dev/null +++ b/app/controllers/bmt_filters_controller.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +class BmtFiltersController < ApplicationController + before_action :check_manage_permission, except: :index + + def index + render json: BmtFilter.all, each_serializer: BmtFilterSerializer + end + + def create + filter = BmtFilter.new( + name: filters_params[:name], + filters: JSON.parse(filters_params[:filters]) + ) + filter.created_by = current_user + if filter.save + render json: filter, serializer: BmtFilterSerializer + else + render json: filter.errors.full_messages, status: :unprocessable_entity + end + end + + def destroy + filter = BmtFilter.find(params[:id]) + render json: { status: filter.destroy } + end + + private + + def filters_params + params.require(:bmt_filter).permit(:name, :filters) + end + + def check_manage_permission + render_403 && return unless can_manage_bmt_filters?(@current_team) + end +end diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 4e3996630..653cebc69 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -27,7 +27,7 @@ class RepositoriesController < ApplicationController def index respond_to do |format| format.html do - render 'empty_index' if Repository.accessible_by_teams(current_team).blank? + render 'empty_index' if @repositories.blank? end format.json do render json: prepare_repositories_datatable(@repositories, current_team, params) diff --git a/app/helpers/repository_datatable_helper.rb b/app/helpers/repository_datatable_helper.rb index bdaa44103..ebf04ad35 100644 --- a/app/helpers/repository_datatable_helper.rb +++ b/app/helpers/repository_datatable_helper.rb @@ -5,18 +5,11 @@ module RepositoryDatatableHelper def prepare_row_columns(repository_rows, repository, columns_mappings, team, options = {}) repository_rows.map do |record| - row = { - 'DT_RowId': record.id, - 'DT_RowAttr': { 'data-state': row_style(record) }, - '1': assigned_row(record), - '2': record.code, - '3': escape_input(record.name), - '4': I18n.l(record.created_at, format: :full), - '5': escape_input(record.created_by.full_name), - '6': (record.archived_on ? I18n.l(record.archived_on, format: :full) : ''), - '7': escape_input(record.archived_by&.full_name), - 'recordInfoUrl': Rails.application.routes.url_helpers.repository_repository_row_path(repository, record) - } + row = public_send("#{repository.class.name.underscore}_default_columns", record) + + row['DT_RowId'] = record.id + row['DT_RowAttr'] = { 'data-state': row_style(record) } + row['recordInfoUrl'] = Rails.application.routes.url_helpers.repository_repository_row_path(repository, record) unless options[:view_mode] row['recordUpdateUrl'] = @@ -84,20 +77,39 @@ module RepositoryDatatableHelper can_manage_repository_rows?(repository) end - def default_table_order_as_js_array - Constants::REPOSITORY_TABLE_DEFAULT_STATE['order'].to_json + def repository_default_columns(record) + { + '1': assigned_row(record), + '2': record.code, + '3': escape_input(record.name), + '4': I18n.l(record.created_at, format: :full), + '5': escape_input(record.created_by.full_name), + '6': (record.archived_on ? I18n.l(record.archived_on, format: :full) : ''), + '7': escape_input(record.archived_by&.full_name) + } end - def default_table_columns - Constants::REPOSITORY_TABLE_DEFAULT_STATE['columns'].to_json + def linked_repository_default_columns(record) + { + '1': assigned_row(record), + '2': escape_input(record.external_id), + '3': record.code, + '4': escape_input(record.name), + '5': I18n.l(record.created_at, format: :full), + '6': escape_input(record.created_by.full_name), + '7': (record.archived_on ? I18n.l(record.archived_on, format: :full) : ''), + '8': escape_input(record.archived_by&.full_name) + } end - def default_snapshot_table_order_as_js_array - Constants::REPOSITORY_SNAPSHOT_TABLE_DEFAULT_STATE['order'].to_json - end - - def default_snapshot_table_columns - Constants::REPOSITORY_SNAPSHOT_TABLE_DEFAULT_STATE['columns'].to_json + def bmt_repository_default_columns(record) + { + '1': assigned_row(record), + '2': escape_input(record.external_id), + '3': record.code, + '4': escape_input(record.name), + '5': I18n.l(record.created_at, format: :full) + } end def display_cell_value(cell, team) diff --git a/app/javascript/.eslintrc.json b/app/javascript/.eslintrc.json index 602de750e..c3295ac1b 100644 --- a/app/javascript/.eslintrc.json +++ b/app/javascript/.eslintrc.json @@ -16,6 +16,7 @@ "jsx": true } }, + "ignorePatterns": ["*.vue", "**/mixins/*.js"], "rules": { "import/extensions": "off", "import/no-unresolved": "off", diff --git a/app/javascript/packs/vue/bmt_filter.js b/app/javascript/packs/vue/bmt_filter.js new file mode 100644 index 000000000..22360bad2 --- /dev/null +++ b/app/javascript/packs/vue/bmt_filter.js @@ -0,0 +1,82 @@ +import TurbolinksAdapter from 'vue-turbolinks'; +import Vue from 'vue/dist/vue.esm'; +import FilterContainer from '../../vue/bmt_filter/container.vue'; + +Vue.use(TurbolinksAdapter); +Vue.prototype.i18n = window.I18n; + +window.initBMTFilter = () => { + const bmtFilterContainer = new Vue({ + el: '#bmtFilterContainer', + data: () => { + return { + bmtApiBaseUrl: $('#bmtFilterContainer').data('bmt-api-base-url'), + canManageFilters: $('#bmtFilterContainer').data('can-manage-filters'), + savedFilters: [], + filters: [] + }; + }, + created() { + this.dataTableElement = $($('#bmtFilterContainer').data('datatable-id')); + }, + components: { + 'filter-container': FilterContainer + }, + methods: { + updateFilters(filters) { + this.filters = filters; + }, + getFilters() { + return this.filters; + }, + updateExternalIds(ids) { + this.dataTableElement.attr('data-external-ids', ids.join(',')); + this.closeFilters(); + this.reloadDataTable(); + }, + clearFilters() { + this.clearSearchError(); + this.dataTableElement.removeAttr('data-external-ids'); + }, + closeFilters() { + $(this.$el).closest('.dropdown').removeClass('open'); + }, + reloadDataTable() { + this.clearSearchError(); + this.dataTableElement.DataTable().ajax.reload(); + }, + handleSearchError(error) { + $('.repository-table-error').addClass('active').html(error); + }, + clearSearchError() { + $('.repository-table-error').removeClass('active').html(''); + } + } + }); + + // prevent closing of dropdown + $('#bmtFilterContainer').on('click', (e) => e.stopPropagation()); + + // close saved filters dropdown + $('#bmtFiltersDropdownButton').on('hidden.bs.dropdown', (e) => { + $('.saved-filters-container').removeClass('open'); + }); + + $("#saveBmtFilterForm" ) + .off() + .on('ajax:before', function() { + $('#bmt_filter_filters').val(JSON.stringify(bmtFilterContainer.getFilters())); + }) + .on('ajax:success', function(e, result) { + bmtFilterContainer.savedFilters.push(result.data); + $('#modalSaveBmtFilter').modal('hide'); + }); + + $.get($('#bmtFilterContainer').data('saved-filters-url'), function(result) { + bmtFilterContainer.savedFilters = result.data; + }) +}; + +$('.repository-show').on('click', '.open-save-bmt-modal', () => { + $('#modalSaveBmtFilter').modal('show'); +}) diff --git a/app/javascript/vue/bmt_filter/container.vue b/app/javascript/vue/bmt_filter/container.vue new file mode 100644 index 000000000..ab3a2ceb4 --- /dev/null +++ b/app/javascript/vue/bmt_filter/container.vue @@ -0,0 +1,171 @@ + + + diff --git a/app/javascript/vue/bmt_filter/filter.vue b/app/javascript/vue/bmt_filter/filter.vue new file mode 100644 index 000000000..6bce169f6 --- /dev/null +++ b/app/javascript/vue/bmt_filter/filter.vue @@ -0,0 +1,90 @@ + + + diff --git a/app/javascript/vue/bmt_filter/filters/additionalDataFilter.vue b/app/javascript/vue/bmt_filter/filters/additionalDataFilter.vue new file mode 100644 index 000000000..c908c4b07 --- /dev/null +++ b/app/javascript/vue/bmt_filter/filters/additionalDataFilter.vue @@ -0,0 +1,39 @@ + + + diff --git a/app/javascript/vue/bmt_filter/filters/entityTypeFilter.vue b/app/javascript/vue/bmt_filter/filters/entityTypeFilter.vue new file mode 100644 index 000000000..0500f4dbb --- /dev/null +++ b/app/javascript/vue/bmt_filter/filters/entityTypeFilter.vue @@ -0,0 +1,25 @@ + + + diff --git a/app/javascript/vue/bmt_filter/filters/fullSequenceFilter.vue b/app/javascript/vue/bmt_filter/filters/fullSequenceFilter.vue new file mode 100644 index 000000000..d0de3e72e --- /dev/null +++ b/app/javascript/vue/bmt_filter/filters/fullSequenceFilter.vue @@ -0,0 +1,42 @@ + + + diff --git a/app/javascript/vue/bmt_filter/filters/monomerSubstructureSearchFilter.vue b/app/javascript/vue/bmt_filter/filters/monomerSubstructureSearchFilter.vue new file mode 100644 index 000000000..efbb9dcae --- /dev/null +++ b/app/javascript/vue/bmt_filter/filters/monomerSubstructureSearchFilter.vue @@ -0,0 +1,25 @@ + + + diff --git a/app/javascript/vue/bmt_filter/filters/monomerTypeFilter.vue b/app/javascript/vue/bmt_filter/filters/monomerTypeFilter.vue new file mode 100644 index 000000000..ded823a3d --- /dev/null +++ b/app/javascript/vue/bmt_filter/filters/monomerTypeFilter.vue @@ -0,0 +1,25 @@ + + + diff --git a/app/javascript/vue/bmt_filter/filters/subsequenceFilter.vue b/app/javascript/vue/bmt_filter/filters/subsequenceFilter.vue new file mode 100644 index 000000000..8638c298f --- /dev/null +++ b/app/javascript/vue/bmt_filter/filters/subsequenceFilter.vue @@ -0,0 +1,42 @@ + + + diff --git a/app/javascript/vue/bmt_filter/filters/variantSequenceFilter.vue b/app/javascript/vue/bmt_filter/filters/variantSequenceFilter.vue new file mode 100644 index 000000000..dd6cb3b6e --- /dev/null +++ b/app/javascript/vue/bmt_filter/filters/variantSequenceFilter.vue @@ -0,0 +1,53 @@ + + + diff --git a/app/javascript/vue/bmt_filter/mixins/filter.js b/app/javascript/vue/bmt_filter/mixins/filter.js new file mode 100644 index 000000000..81ae9646f --- /dev/null +++ b/app/javascript/vue/bmt_filter/mixins/filter.js @@ -0,0 +1,19 @@ +export default { + props: { + currentData: Object + }, + data() { + return { + type: this.$options.name + } + }, + created() { + // load existing filter data + Object.keys(this.currentData).forEach((key) => { this[key] = this.currentData[key] }); + }, + methods: { + updateFilterData() { + this.$emit('filter:updateData', this.$data); + } + } +} diff --git a/app/javascript/vue/bmt_filter/saved_filter.vue b/app/javascript/vue/bmt_filter/saved_filter.vue new file mode 100644 index 000000000..beae58af0 --- /dev/null +++ b/app/javascript/vue/bmt_filter/saved_filter.vue @@ -0,0 +1,34 @@ + + + diff --git a/app/javascript/vue/shared/dropdown_selector.vue b/app/javascript/vue/shared/dropdown_selector.vue new file mode 100644 index 000000000..c5eb3778f --- /dev/null +++ b/app/javascript/vue/shared/dropdown_selector.vue @@ -0,0 +1,59 @@ + + + \ No newline at end of file diff --git a/app/models/bmt_filter.rb b/app/models/bmt_filter.rb new file mode 100644 index 000000000..d2ac5784e --- /dev/null +++ b/app/models/bmt_filter.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class BmtFilter < ApplicationRecord + + attr_accessor :delete_url + + belongs_to :created_by, + foreign_key: :created_by_id, + class_name: 'User', + optional: true + + validates :name, :filters, presence: true +end diff --git a/app/models/bmt_repository.rb b/app/models/bmt_repository.rb new file mode 100644 index 000000000..388ddac42 --- /dev/null +++ b/app/models/bmt_repository.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class BmtRepository < LinkedRepository + before_create :enforce_singleton + + def default_table_state + state = Constants::REPOSITORY_TABLE_DEFAULT_STATE.deep_dup + state['order'] = [[3, 'asc']] + state + end + + def default_sortable_columns + [ + 'assigned', + 'repository_rows.external_id', + 'repository_rows.id', + 'repository_rows.name', + 'repository_rows.created_at' + ] + end + + def default_search_fileds + super - ['users.full_name'] + end + + private + + def enforce_singleton + raise ActiveRecord::RecordNotSaved, I18n.t('repositories.bmt_singleton_error') if self.class.any? + end +end diff --git a/app/models/linked_repository.rb b/app/models/linked_repository.rb new file mode 100644 index 000000000..ac97775ec --- /dev/null +++ b/app/models/linked_repository.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +class LinkedRepository < Repository + def default_table_state + state = Constants::REPOSITORY_TABLE_DEFAULT_STATE.deep_dup + state['order'] = [[3, 'asc']] + state['ColReorder'] << state['ColReorder'].length + state['columns'].insert(1, Constants::REPOSITORY_TABLE_STATE_CUSTOM_COLUMN_TEMPLATE) + state + end + + def default_sortable_columns + [ + 'assigned', + 'repository_rows.external_id', + 'repository_rows.id', + 'repository_rows.name', + 'repository_rows.created_at', + 'users.full_name', + 'repository_rows.archived_on', + 'archived_bies_repository_rows.full_name' + ] + end + + def default_search_fileds + super << 'repository_rows.external_id' + end +end diff --git a/app/models/repository.rb b/app/models/repository.rb index 42d1dd957..af00a1012 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -97,8 +97,24 @@ class Repository < RepositoryBase end end - def default_columns_count - Constants::REPOSITORY_TABLE_DEFAULT_STATE['columns'].length + def default_table_state + Constants::REPOSITORY_TABLE_DEFAULT_STATE + end + + def default_sortable_columns + [ + 'assigned', + 'repository_rows.id', + 'repository_rows.name', + 'repository_rows.created_at', + 'users.full_name', + 'repository_rows.archived_on', + 'archived_bies_repository_rows.full_name' + ] + end + + def default_search_fileds + ['repository_rows.name', RepositoryRow::PREFIXED_ID_SQL, 'users.full_name'] end def i_shared?(team) diff --git a/app/models/repository_base.rb b/app/models/repository_base.rb index 715710107..35c924a85 100644 --- a/app/models/repository_base.rb +++ b/app/models/repository_base.rb @@ -1,6 +1,9 @@ # frozen_string_literal: true class RepositoryBase < ApplicationRecord + require 'sti_preload' + + include StiPreload include Discard::Model self.table_name = 'repositories' @@ -32,6 +35,22 @@ class RepositoryBase < ApplicationRecord cell_includes end + def default_table_state + raise NotImplementedError + end + + def default_table_columns + default_table_state['columns'].to_json + end + + def default_columns_count + default_table_state['columns'].length + end + + def default_table_order_as_js_array + default_table_state['order'].to_json + end + def destroy_discarded(discarded_by_id = nil) self.discarded_by_id = discarded_by_id destroy diff --git a/app/models/repository_column.rb b/app/models/repository_column.rb index 1df89b99b..8d5f071a4 100644 --- a/app/models/repository_column.rb +++ b/app/models/repository_column.rb @@ -55,7 +55,7 @@ class RepositoryColumn < ApplicationRecord # Calculate old_column_index - this can only be done before # record is deleted when we still have its index old_column_index = ( - Constants::REPOSITORY_TABLE_DEFAULT_STATE['columns'].length + + repository.default_columns_count + repository.repository_columns .order(id: :asc) .pluck(:id) diff --git a/app/models/repository_snapshot.rb b/app/models/repository_snapshot.rb index 620fc5aed..1c4c35f51 100644 --- a/app/models/repository_snapshot.rb +++ b/app/models/repository_snapshot.rb @@ -45,8 +45,8 @@ class RepositorySnapshot < RepositoryBase repository_snapshot.reload end - def default_columns_count - Constants::REPOSITORY_SNAPSHOT_TABLE_DEFAULT_STATE['columns'].length + def default_table_state + Constants::REPOSITORY_SNAPSHOT_TABLE_DEFAULT_STATE end def assigned_rows(my_module) diff --git a/app/permissions/repository.rb b/app/permissions/repository.rb index d0ae77879..033b4bc77 100644 --- a/app/permissions/repository.rb +++ b/app/permissions/repository.rb @@ -32,6 +32,8 @@ Canaid::Permissions.register_for(Repository) do # repository: archive, restore can :archive_repository do |user, repository| + next false if repository.is_a?(BmtRepository) + !repository.shared_with?(user.current_team) && user.is_admin_of_team?(repository.team) end @@ -52,6 +54,8 @@ Canaid::Permissions.register_for(Repository) do # repository: create/import record can :create_repository_rows do |user, repository| + next false if repository.is_a?(BmtRepository) + if repository.shared_with?(user.current_team) repository.shared_with_write?(user.current_team) && user.is_normal_user_or_admin_of_team?(user.current_team) elsif user.teams.include?(repository.team) diff --git a/app/permissions/team.rb b/app/permissions/team.rb index 116f9bc8f..a58184cae 100644 --- a/app/permissions/team.rb +++ b/app/permissions/team.rb @@ -36,6 +36,10 @@ Canaid::Permissions.register_for(Team) do user.is_normal_user_or_admin_of_team?(team) end + can :manage_bmt_filters do |user, team| + user.is_normal_user_or_admin_of_team?(team) + end + # repository: create, copy can :create_repositories do |user, team| within_limits = Repository.within_global_limits? diff --git a/app/serializers/bmt_filter_serializer.rb b/app/serializers/bmt_filter_serializer.rb new file mode 100644 index 000000000..e5357a32b --- /dev/null +++ b/app/serializers/bmt_filter_serializer.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class BmtFilterSerializer < ActiveModel::Serializer + include Rails.application.routes.url_helpers + + attributes :name, :filters, :delete_url + + def delete_url + bmt_filter_path(object.id) + end +end diff --git a/app/services/biomolecule_toolkit_client.rb b/app/services/biomolecule_toolkit_client.rb index 2909c2cc2..deb43e03a 100644 --- a/app/services/biomolecule_toolkit_client.rb +++ b/app/services/biomolecule_toolkit_client.rb @@ -2,55 +2,67 @@ class BiomoleculeToolkitClient MACROMOLECULES_PATH = '/api/macromolecules' + MACROMOLECULES_ATTRIBUTES_PATH = '/api/admin/attributes/MACROMOLECULE' class BiomoleculeToolkitClientException < StandardError; end def initialize - @host = Rails.configuration.x.biomolecule_toolkit_host - @http = Net::HTTP.new( - Rails.configuration.x.biomolecule_toolkit_host, - Rails.application.config.x.biomolecule_toolkit_port - ) + @uri = URI.parse(Rails.application.config.x.biomolecule_toolkit_base_url) + @http = Net::HTTP.new(@uri.host, @uri.port) + @http.use_ssl = (@uri.scheme == 'https') end def healthy? - request = Net::HTTP::Get.new('/api/health') + request = Net::HTTP::Get.new(build_request_path('/api/health')) process_request(request)&.dig('status') == 'UP' end + def list_attributes + request = Net::HTTP::Get.new(build_request_path(MACROMOLECULES_ATTRIBUTES_PATH)) + process_request(request) + end + def list - request = Net::HTTP::Get.new(MACROMOLECULES_PATH) + request = Net::HTTP::Get.new(build_request_path(MACROMOLECULES_PATH)) process_request(request) end def create(params:) - request = Net::HTTP::Post.new(MACROMOLECULES_PATH, 'Content-Type': 'application/json') + request = Net::HTTP::Post.new(build_request_path(MACROMOLECULES_PATH)) request.body = params process_request(request) end def get(cid:) - request = Net::HTTP::Get.new("#{MACROMOLECULES_PATH}/#{CGI.escape(cid)}") + request = Net::HTTP::Get.new(build_request_path("#{MACROMOLECULES_PATH}/#{CGI.escape(cid)}")) process_request(request) end def update(cid:, params:) - request = Net::HTTP::Put.new("#{MACROMOLECULES_PATH}/#{CGI.escape(cid)}", 'Content-Type': 'application/json') + request = Net::HTTP::Put.new(build_request_path("#{MACROMOLECULES_PATH}/#{CGI.escape(cid)}")) request.body = params process_request(request) end def delete - request = Net::HTTP::Delete.new("#{MACROMOLECULES_PATH}/#{CGI.escape(cid)}") + request = Net::HTTP::Delete.new(build_request_path("#{MACROMOLECULES_PATH}/#{CGI.escape(cid)}")) process_request(request) end private + def build_request_path(sub_path) + File.join(@uri.path, sub_path) + end + def process_request(request) + if Rails.application.config.x.biomolecule_toolkit_api_key + request['x-api-key'] = Rails.application.config.x.biomolecule_toolkit_api_key + end + request['Content-Type'] = 'application/json' response = @http.request(request) - case response.class + case response when Net::HTTPOK JSON.parse(response.body) when Net::HTTPNoContent diff --git a/app/services/repository_datatable_service.rb b/app/services/repository_datatable_service.rb index efa66d9b8..9c3f1c5e6 100644 --- a/app/services/repository_datatable_service.rb +++ b/app/services/repository_datatable_service.rb @@ -75,16 +75,14 @@ class RepositoryDatatableService repository_rows.count end - if search_value.present? - matched_by_user = repository_rows.joins(:created_by).where_attributes_like('users.full_name', search_value) + repository_rows = repository_rows.where(external_id: @params[:external_ids]) if @params[:external_ids] - repository_row_matches = repository_rows - .where_attributes_like( - ['repository_rows.name', RepositoryRow::PREFIXED_ID_SQL], - search_value - ) + if search_value.present? + if @repository.default_search_fileds.include?('users.full_name') + repository_rows = repository_rows.joins(:created_by) + end + repository_row_matches = repository_rows.where_attributes_like(@repository.default_search_fileds, search_value) results = repository_rows.where(id: repository_row_matches) - results = results.or(repository_rows.where(id: matched_by_user)) data_types = @repository.repository_columns.pluck(:data_type).uniq @@ -114,15 +112,7 @@ class RepositoryDatatableService end def build_sortable_columns - array = [ - 'assigned', - 'repository_rows.id', - 'repository_rows.name', - 'repository_rows.created_at', - 'users.full_name', - 'repository_rows.archived_on', - 'archived_bies_repository_rows.full_name', - ] + array = @repository.default_sortable_columns @repository.repository_columns.count.times do array << 'repository_cell.value' end diff --git a/app/services/repository_table_state_column_update_service.rb b/app/services/repository_table_state_column_update_service.rb index 302e35d79..dcf3d0432 100644 --- a/app/services/repository_table_state_column_update_service.rb +++ b/app/services/repository_table_state_column_update_service.rb @@ -9,7 +9,7 @@ class RepositoryTableStateColumnUpdateService def update_states_with_new_column(repository) raise ArgumentError, 'repository is empty' if repository.blank? - columns_num = Constants::REPOSITORY_TABLE_DEFAULT_STATE['columns'].length + repository.repository_columns.count + columns_num = repository.default_columns_count + repository.repository_columns.count RepositoryTableState.where( repository: repository ).find_each do |table_state| @@ -49,7 +49,7 @@ class RepositoryTableStateColumnUpdateService if state.dig('order', 0, 0).to_i == old_column_index # Fallback to default order if user had table ordered by # the deleted column - state['order'] = Constants::REPOSITORY_TABLE_DEFAULT_STATE['order'] + state['order'] = repository.default_table_state['order'] elsif state.dig('order', 0, 0).to_i > old_column_index state['order'][0][0] -= 1 end diff --git a/app/services/repository_table_state_service.rb b/app/services/repository_table_state_service.rb index 44122b464..2fd722c40 100644 --- a/app/services/repository_table_state_service.rb +++ b/app/services/repository_table_state_service.rb @@ -26,7 +26,7 @@ class RepositoryTableStateService def update_state(state) saved_state = load_state - state['order'] = Constants::REPOSITORY_TABLE_DEFAULT_STATE['order'] if state.dig('order', 0, 0).to_i < 1 + state['order'] = @repository.default_table_state['order'] if state.dig('order', 0, 0).to_i < 1 return if saved_state.state.except('time') == state.except('time') @@ -47,10 +47,10 @@ class RepositoryTableStateService private def generate_default_state - default_columns_num = Constants::REPOSITORY_TABLE_DEFAULT_STATE['columns'].length + default_columns_num = @repository.default_columns_count # This state should be strings-only - state = Constants::REPOSITORY_TABLE_DEFAULT_STATE.deep_dup + state = @repository.default_table_state.deep_dup @repository.repository_columns.each_with_index do |_, index| real_index = default_columns_num + index state['columns'][real_index] = Constants::REPOSITORY_TABLE_STATE_CUSTOM_COLUMN_TEMPLATE diff --git a/app/views/my_modules/repositories/_full_view_snapshot_table.html.erb b/app/views/my_modules/repositories/_full_view_snapshot_table.html.erb index 0df8097c9..d18650e48 100644 --- a/app/views/my_modules/repositories/_full_view_snapshot_table.html.erb +++ b/app/views/my_modules/repositories/_full_view_snapshot_table.html.erb @@ -5,8 +5,8 @@ data-repository-name="<%= @repository_snapshot.name %>" data-repository-snapshot-created="<%= l(@repository_snapshot.created_at, format: :full) %>" data-assigned-items-count="<%= @repository_snapshot.repository_rows.count %>" - data-default-order="<%= default_snapshot_table_order_as_js_array %>" - data-default-table-columns="<%= default_snapshot_table_columns %>" + data-default-order="<%= @repository_snapshot.default_table_order_as_js_array %>" + data-default-table-columns="<%= @repository_snapshot.default_table_columns %>" data-load-state-url="<%= repository_load_table_state_path(@repository_snapshot) %>" data-export-url="<%= export_repository_snapshot_my_module_repository_snapshot_path(@my_module, @repository_snapshot) %>" data-versions-sidebar-url="<%= full_view_sidebar_my_module_repository_snapshots_path(@my_module, @repository_snapshot.parent_id) %>" diff --git a/app/views/my_modules/repositories/_full_view_table.html.erb b/app/views/my_modules/repositories/_full_view_table.html.erb index 9b7873577..bd999ff37 100644 --- a/app/views/my_modules/repositories/_full_view_table.html.erb +++ b/app/views/my_modules/repositories/_full_view_table.html.erb @@ -2,8 +2,8 @@ data-id="<%= @repository.id %>" data-type="live" data-source="<%= index_dt_my_module_repository_path(@my_module, @repository) %>" - data-default-order="<%= default_table_order_as_js_array %>" - data-default-table-columns="<%= default_table_columns %>" + data-default-order="<%= @repository.default_table_order_as_js_array %>" + data-default-table-columns="<%= @repository.default_table_columns %>" data-repository-name="<%= @repository.name %>" data-assigned-items-count="<%= @my_module.repository_rows_count(@repository) %>" data-load-state-url="<%= repository_load_table_state_path(@repository) %>" diff --git a/app/views/repositories/_repository_table.html.erb b/app/views/repositories/_repository_table.html.erb index fe11fa6c9..ccd98e89b 100644 --- a/app/views/repositories/_repository_table.html.erb +++ b/app/views/repositories/_repository_table.html.erb @@ -1,4 +1,5 @@
+
@@ -32,12 +33,17 @@ + <% if @repository.is_a?(LinkedRepository) %> + + <% end %> - - - + <% unless @repository.is_a?(BmtRepository) %> + + + + <% end %> <% repository.repository_columns.order(:id).each do |column| %>
<%= t("repositories.table.assigned") %><%= t('repositories.table.external_id') %><%= t("repositories.table.id") %> <%= t("repositories.table.row_name") %> <%= t("repositories.table.added_on") %><%= t("repositories.table.added_by") %><%= t("repositories.table.archived_on") %><%= t("repositories.table.archived_by") %><%= t("repositories.table.added_by") %><%= t("repositories.table.archived_on") %><%= t("repositories.table.archived_by") %> + + diff --git a/app/views/repositories/_toolbar_buttons.html.erb b/app/views/repositories/_toolbar_buttons.html.erb index f0b933e4d..ad97f1a9b 100644 --- a/app/views/repositories/_toolbar_buttons.html.erb +++ b/app/views/repositories/_toolbar_buttons.html.erb @@ -85,5 +85,44 @@
<% end %>
- - + <% if @repository.is_a?(BmtRepository) %> + + <% if can_manage_bmt_filters?(current_team) %> + + <% end %> + <% end %> diff --git a/app/views/repositories/show.html.erb b/app/views/repositories/show.html.erb index d65034f59..a82927b54 100644 --- a/app/views/repositories/show.html.erb +++ b/app/views/repositories/show.html.erb @@ -93,7 +93,7 @@ <% end %> - <% if can_create_repositories?(current_team) && !@repository.shared_with?(current_team) %> + <% if !@repository.is_a?(BmtRepository) && can_create_repositories?(current_team) && !@repository.shared_with?(current_team) %>
  • <%= link_to t('repositories.index.options_dropdown.copy'), team_repository_copy_modal_path(current_team, repository_id: @repository), @@ -129,15 +129,17 @@ <% end %> - <%= render layout: "shared/view_switch", locals: {disabled: @repository.archived?} do %> -
  • - - <%= t('repositories.show.show_archived_items') %> -
  • -
  • - - <%= t('repositories.show.show_active_items') %> -
  • + <% unless @repository.is_a?(BmtRepository) %> + <%= render layout: "shared/view_switch", locals: {disabled: @repository.archived?} do %> +
  • + + <%= t('repositories.show.show_archived_items') %> +
  • +
  • + + <%= t('repositories.show.show_active_items') %> +
  • + <% end %> <% end %>
    @@ -165,7 +167,11 @@ locals: { repository: @repository } %> <%= render partial: 'repository_columns/manage_column_modal', locals: { my_module_page: false } %> +<%= render partial: 'save_bmt_filter_modal' %> +<% if @repository.is_a?(BmtRepository) %> + <%= javascript_pack_tag 'vue/bmt_filter' %> +<% end %> <%= javascript_include_tag 'repositories/edit' %> <%= javascript_include_tag 'repositories/repository_datatable' %> <%= javascript_include_tag "repositories/show" %> @@ -174,6 +180,8 @@ <%= javascript_pack_tag 'pdfjs/pdf_js' %> <%= stylesheet_pack_tag 'pdfjs/pdf_js_styles' %> + + diff --git a/config/initializers/biomolecule_toolkit_client.rb b/config/initializers/biomolecule_toolkit_client.rb index 241d21088..c898ff103 100644 --- a/config/initializers/biomolecule_toolkit_client.rb +++ b/config/initializers/biomolecule_toolkit_client.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true -Rails.application.config.x.biomolecule_toolkit_host = ENV['BIOMOLECULE_TOOLKIT_HOST'] -Rails.application.config.x.biomolecule_toolkit_port = ENV['BIOMOLECULE_TOOLKIT_PORT'] || 80 +Rails.application.config.x.biomolecule_toolkit_base_url = ENV['BIOMOLECULE_TOOLKIT_BASE_URL'] +Rails.application.config.x.biomolecule_toolkit_api_key = ENV['BIOMOLECULE_TOOLKIT_API_KEY'] diff --git a/config/initializers/extends.rb b/config/initializers/extends.rb index c12b79586..d2e7cdbbd 100644 --- a/config/initializers/extends.rb +++ b/config/initializers/extends.rb @@ -392,6 +392,8 @@ class Extends change_user_role_on_experiment change_user_role_on_my_module ) + + STI_PRELOAD_CLASSES = %w(LinkedRepository BmtRepository) end # rubocop:enable Style/MutableConstant diff --git a/config/locales/en.yml b/config/locales/en.yml index 3dc6ef7e7..52551d2b1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1304,6 +1304,7 @@ en: i_shared: "Shared inventory (owned by your Team)" shared_edit: "Shared inventory (owned by %{team_name}). You can edit." shared_read: "Shared inventory (owned by %{team_name}). You can view." + bmt_singleton_error: "Only one BMT inventory can be present." index: head_title: "Inventories" title: "Inventories" @@ -1387,9 +1388,49 @@ en: no_items_matched: "No items matched your search request" no_archived_items: "No archived items here" no_archived_items_matched: "No archived items matched your search request" + error_searching: "Error searching, please try again" + bmt_search: + bmt_filter: "Biomolecule filter" + save_filters: "Save filters" + title: "Filters" + clear_all: "Clear all" + add_filter: "Add filter" + apply: "Apply" + filter: "Filter" + no_filters: "No active filters." + filters: + types: + additionalDataFilter: + name: "Additional data" + entityTypeFilter: + name: "Entity type" + placeholder: "Enter entity type" + monomerTypeFilter: + name: "Monomer type" + placeholder: "Enter monomer type" + subsequenceFilter: + name: "Subsequence" + placeholder: "Enter subsequence" + derivatives_included: "Derivatives included" + variantSequenceFilter: + name: "Variant sequence" + placeholder: "Enter variant sequence" + derivatives_included: "Derivatives included" + distance: "Distance" + distance_type: "Distance type" + exact: "Exact" + maximum: "Maximum" + fullSequenceFilter: + name: "Full sequence" + placeholder: "Enter full sequence" + derivatives_included: "Derivatives included" + monomerSubstructureSearchFilter: + name: "Monomer substructure" + placeholder: "Enter substructure" table: id: 'ID' + external_id: 'External ID' assigned: "Assigned" assigned_search: 'Search...' assigned_tooltip: "%{tasks} tasks in %{experiments} experiments, %{projects} projects" @@ -2791,7 +2832,7 @@ en: bio_eddie: new_molecule: "New Molecule" - new_button: "Create Biomolecule" + new_button: "Biomolecule" molecule_name_placeholder: "Click here to enter Molecule name" no_molecules_found: "No Molecules Found" save_and_register: "Save & Register to Biomolecule Toolkit" diff --git a/config/routes.rb b/config/routes.rb index 6ca2b1df2..6ca4aa9e8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -800,11 +800,14 @@ Rails.application.routes.draw do end end + resources :bmt_filters, only: %i(index create destroy) + match '/marvin4js-license.cxl', to: 'bio_eddie_assets#license', via: :get match 'biomolecule_toolkit/*path', to: 'bio_eddie_assets#bmt_request', via: %i(get post put delete), - defaults: { format: 'json' } + defaults: { format: 'json' }, + as: 'bmt_request' post 'global_activities', to: 'global_activities#index' diff --git a/db/migrate/20210812095254_add_external_id_to_repositories.rb b/db/migrate/20210812095254_add_external_id_to_repositories.rb new file mode 100644 index 000000000..68d01cb46 --- /dev/null +++ b/db/migrate/20210812095254_add_external_id_to_repositories.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require File.expand_path('app/helpers/database_helper') + +class AddExternalIdToRepositories < ActiveRecord::Migration[6.1] + include DatabaseHelper + + def up + add_column :repository_rows, :external_id, :string, null: true + add_index :repository_rows, :external_id, unique: true, name: 'unique_index_repository_rows_on_external_id' + add_gin_index_without_tags(:repository_rows, :external_id) + end + + def down + remove_index :repository_rows, name: 'index_repository_rows_on_external_id' + remove_index :repository_rows, :external_id, unique: true, name: 'unique_index_repository_rows_on_external_id' + remove_column :repository_rows, :external_id, :string, null: true + end +end diff --git a/db/migrate/20210825112050_create_bmt_filters.rb b/db/migrate/20210825112050_create_bmt_filters.rb new file mode 100644 index 000000000..3d410fd31 --- /dev/null +++ b/db/migrate/20210825112050_create_bmt_filters.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class CreateBmtFilters < ActiveRecord::Migration[6.1] + def change + create_table :bmt_filters do |t| + t.string :name, null: false + t.json :filters, null: false + t.references :created_by, index: true, foreign_key: { to_table: :users } + t.timestamps + end + end +end diff --git a/db/structure.sql b/db/structure.sql index 8319638b6..2be419e0c 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -313,6 +313,39 @@ CREATE SEQUENCE public.assets_id_seq ALTER SEQUENCE public.assets_id_seq OWNED BY public.assets.id; +-- +-- Name: bmt_filters; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.bmt_filters ( + id bigint NOT NULL, + name character varying NOT NULL, + filters json NOT NULL, + created_by_id bigint, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL +); + + +-- +-- Name: bmt_filters_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.bmt_filters_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: bmt_filters_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.bmt_filters_id_seq OWNED BY public.bmt_filters.id; + + -- -- Name: checklist_items; Type: TABLE; Schema: public; Owner: - -- @@ -1877,7 +1910,8 @@ CREATE TABLE public.repository_rows ( archived_on timestamp without time zone, restored_on timestamp without time zone, archived_by_id bigint, - restored_by_id bigint + restored_by_id bigint, + external_id character varying ); @@ -3155,6 +3189,13 @@ ALTER TABLE ONLY public.asset_text_data ALTER COLUMN id SET DEFAULT nextval('pub ALTER TABLE ONLY public.assets ALTER COLUMN id SET DEFAULT nextval('public.assets_id_seq'::regclass); +-- +-- Name: bmt_filters id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.bmt_filters ALTER COLUMN id SET DEFAULT nextval('public.bmt_filters_id_seq'::regclass); + + -- -- Name: checklist_items id; Type: DEFAULT; Schema: public; Owner: - -- @@ -3758,6 +3799,14 @@ ALTER TABLE ONLY public.assets ADD CONSTRAINT assets_pkey PRIMARY KEY (id); +-- +-- Name: bmt_filters bmt_filters_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.bmt_filters + ADD CONSTRAINT bmt_filters_pkey PRIMARY KEY (id); + + -- -- Name: checklist_items checklist_items_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -4529,6 +4578,13 @@ CREATE INDEX index_assets_on_last_modified_by_id ON public.assets USING btree (l CREATE INDEX index_assets_on_team_id ON public.assets USING btree (team_id); +-- +-- Name: index_bmt_filters_on_created_by_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_bmt_filters_on_created_by_id ON public.bmt_filters USING btree (created_by_id); + + -- -- Name: index_checklist_items_on_checklist_id; Type: INDEX; Schema: public; Owner: - -- @@ -5502,6 +5558,13 @@ CREATE INDEX index_repository_number_values_on_last_modified_by_id ON public.rep CREATE INDEX index_repository_rows_on_archived_by_id ON public.repository_rows USING btree (archived_by_id); +-- +-- Name: index_repository_rows_on_external_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_repository_rows_on_external_id ON public.repository_rows USING gin (public.trim_html_tags((external_id)::text) public.gin_trgm_ops); + + -- -- Name: index_repository_rows_on_id_text; Type: INDEX; Schema: public; Owner: - -- @@ -6195,6 +6258,13 @@ CREATE INDEX index_wopi_actions_on_extension_and_action ON public.wopi_actions U CREATE INDEX index_zip_exports_on_user_id ON public.zip_exports USING btree (user_id); +-- +-- Name: unique_index_repository_rows_on_external_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX unique_index_repository_rows_on_external_id ON public.repository_rows USING btree (external_id); + + -- -- Name: comments fk_rails_03de2dc08c; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -7339,6 +7409,14 @@ ALTER TABLE ONLY public.protocols ADD CONSTRAINT fk_rails_dcb4ab6aa9 FOREIGN KEY (parent_id) REFERENCES public.protocols(id); +-- +-- Name: bmt_filters fk_rails_de5b654b84; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.bmt_filters + ADD CONSTRAINT fk_rails_de5b654b84 FOREIGN KEY (created_by_id) REFERENCES public.users(id); + + -- -- Name: my_modules fk_rails_e21638fa54; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -7700,7 +7778,10 @@ INSERT INTO "schema_migrations" (version) VALUES ('20210716124649'), ('20210720112050'), ('20210811103123'), +('20210812095254'), +('20210825112050'), ('20210906132120'), ('20211103115450'); +>>>>>>> features/bmt-search diff --git a/lib/sti_preload.rb b/lib/sti_preload.rb new file mode 100644 index 000000000..08342cf69 --- /dev/null +++ b/lib/sti_preload.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module StiPreload + unless Rails.application.config.eager_load + extend ActiveSupport::Concern + + included do + cattr_accessor :preloaded, instance_accessor: false + end + + class_methods do + def descendants + preload_sti unless preloaded + super + end + + def preload_sti + Extends::STI_PRELOAD_CLASSES.each do |type| + logger.debug("Preloading STI type #{type}") + type.constantize + end + + self.preloaded = true + end + end + end +end diff --git a/lib/tasks/biomolecule_toolkit.rake b/lib/tasks/biomolecule_toolkit.rake new file mode 100644 index 000000000..53949583b --- /dev/null +++ b/lib/tasks/biomolecule_toolkit.rake @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +namespace :biomolecule_toolkit do + desc 'Creates new BMT inventory and maps BMT macromolecules attributes to its columns' + task :init_repository, [:team_id] => :environment do |_, args| + raise StandardError, 'BMT repository already exists!' if BmtRepository.any? + + bmt_client = BiomoleculeToolkitClient.new + attributes = bmt_client.list_attributes + + team = Team.find(args[:team_id]) + BmtRepository.transaction do + repository = BmtRepository.create!(name: 'Biomolecule registry', team: team, created_by: team.created_by) + attributes.each do |attribute| + repository.repository_columns.create!(name: attribute['name'], + data_type: 'RepositoryTextValue', + created_by: team.created_by) + end + end + end + + desc 'Syncs BMT inventory columns with BMT macromolecules attributes' + task sync_repository: :environment do + raise StandardError, 'BMT repository does not exist!' if BmtRepository.none? + + bmt_client = BiomoleculeToolkitClient.new + attributes = bmt_client.list_attributes + + BmtRepository.transaction do + repository = BmtRepository.take + attributes.each do |attribute| + next if repository.repository_columns.find_by(name: attribute['name']).present? + + repository.repository_columns.create!(name: attribute['name'], + data_type: 'RepositoryTextValue', + created_by: repository.created_by) + end + + repository.repository_columns.each do |repository_column| + next if attributes.pluck('name').include?(repository_column.name) + + repository_column.destroy! + end + end + end +end diff --git a/package.json b/package.json index 6204c3c0a..e97b072d7 100644 --- a/package.json +++ b/package.json @@ -83,8 +83,8 @@ "postcss-smart-import": "^0.7.6", "precss": "^2.0.0", "prop-types": "^15.7.2", - "rails-erb-loader": "^5.4.2", "react": "^16.8.6", + "rails-erb-loader": "^5.4.2", "react-bootstrap": "^0.31.5", "react-bootstrap-table": "^4.3.1", "react-bootstrap-timezone-picker": "^1.0.12", @@ -113,10 +113,10 @@ "tui-image-editor": "git://github.com/biosistemika/tui.image-editor", "twemoji": "^12.1.4", "typeface-lato": "^0.0.75", + "vue": "^2.6.11", "vue-loader": "^15.9.1", "vue-template-compiler": "^2.6.12", "vue-turbolinks": "^2.2.1", - "vue": "^2.6.11", "webpack": "^4.35.2", "webpack-cli": "^3.3.5", "webpack-manifest-plugin": "^1.3.2", diff --git a/spec/controllers/repository_rows_controller_spec.rb b/spec/controllers/repository_rows_controller_spec.rb index 10d4bf671..e7a6e9c4d 100644 --- a/spec/controllers/repository_rows_controller_spec.rb +++ b/spec/controllers/repository_rows_controller_spec.rb @@ -13,7 +13,7 @@ describe RepositoryRowsController, type: :controller do RepositoryTableState.create( repository: repository, user: user, - state: Constants::REPOSITORY_TABLE_DEFAULT_STATE + state: repository.default_teble_state ) end let!(:repository_row) do