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 @@
+
+
+
+
+
+ {{ i18n.t('repositories.show.bmt_search.no_filters') }}
+
+
+
+
+
+
+
+
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 @@
+
+
+ {{ savedFilter.attributes.name }}
+
+
+
+
+
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 @@
| <%= t("repositories.table.assigned") %> |
+ <% if @repository.is_a?(LinkedRepository) %>
+ <%= t('repositories.table.external_id') %> |
+ <% end %>
<%= 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") %> |
+ <% unless @repository.is_a?(BmtRepository) %>
+ <%= t("repositories.table.added_by") %> |
+ <%= t("repositories.table.archived_on") %> |
+ <%= t("repositories.table.archived_by") %> |
+ <% end %>
<% repository.repository_columns.order(:id).each do |column| %>
+
+
+
+ <%= form_with model: BmtFilter.new, method: :post, :html => { :id => "saveBmtFilterForm" } do |f| %>
+
+ <%= f.hidden_field :filters, class: "sci-input-field" %>
+
+ <%= f.label :name %>
+ <%= f.text_field :name, class: "sci-input-field" %>
+
+
+
+
+ <% end %>
+
+
+
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