mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-11-08 07:21:03 +08:00
Merge branch 'features/bmt-search' into features/repository-filters
This commit is contained in:
commit
a44dd8c915
55 changed files with 1496 additions and 123 deletions
|
|
@ -286,3 +286,5 @@ $(window).scroll(function() {
|
|||
scroll_function();
|
||||
})
|
||||
})
|
||||
|
||||
window.I18n = I18n
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
168
app/assets/stylesheets/repository/bmt_filters.scss
Normal file
168
app/assets/stylesheets/repository/bmt_filters.scss
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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"] {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
37
app/controllers/bmt_filters_controller.rb
Normal file
37
app/controllers/bmt_filters_controller.rb
Normal file
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
"jsx": true
|
||||
}
|
||||
},
|
||||
"ignorePatterns": ["*.vue", "**/mixins/*.js"],
|
||||
"rules": {
|
||||
"import/extensions": "off",
|
||||
"import/no-unresolved": "off",
|
||||
|
|
|
|||
82
app/javascript/packs/vue/bmt_filter.js
Normal file
82
app/javascript/packs/vue/bmt_filter.js
Normal file
|
|
@ -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');
|
||||
})
|
||||
171
app/javascript/vue/bmt_filter/container.vue
Normal file
171
app/javascript/vue/bmt_filter/container.vue
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
<template>
|
||||
<div class="filters-container">
|
||||
<div class="header">
|
||||
<div id="savedFiltersContainer" class="dropdown saved-filters-container" @click="toggleSavedFilters">
|
||||
<div class="title" id="savedFilterDropdown">
|
||||
{{ i18n.t('repositories.show.bmt_search.title') }}
|
||||
<i v-if="savedFilters.length" class="fas fa-caret-down"></i>
|
||||
</div>
|
||||
<div v-if="savedFilters.length" class="dropdown-menu saved-filters-list">
|
||||
<SavedFilterElement
|
||||
v-for="(savedFilter, index) in savedFilters"
|
||||
:key="savedFilter.id"
|
||||
:savedFilter.sync="savedFilters[index]"
|
||||
:canManageFilters="canManageFilters"
|
||||
@savedFilter:load="loadFilters"
|
||||
@savedFilter:delete="savedFilters.splice(index, 1)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-light clear-filters-btn" @click="closeSavedFilters() && clearFilters()">
|
||||
<i class="fas fa-times-circle"></i>
|
||||
{{ i18n.t('repositories.show.bmt_search.clear_all') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="filters-list" @click="closeSavedFilters">
|
||||
<div v-if="filters.length == 0" class="filter-list-notice">
|
||||
{{ i18n.t('repositories.show.bmt_search.no_filters') }}
|
||||
</div>
|
||||
<FilterElement
|
||||
v-for="(filter, index) in filters"
|
||||
:key="filter.id"
|
||||
:filter.sync="filters[index]"
|
||||
:additionalDataAttributes="additionalDataAttributes"
|
||||
@filter:delete="filters.splice(index, 1)"
|
||||
@filter:update="updateFilter"
|
||||
/>
|
||||
</div>
|
||||
<div class="footer" @click="closeSavedFilters">
|
||||
<button class="btn btn-secondary add-filter" @click="addFilter">
|
||||
<i class="fas fa-plus"></i>
|
||||
{{ i18n.t('repositories.show.bmt_search.add_filter') }}
|
||||
</button>
|
||||
<button @click="fetchCIDs" class="btn btn-primary">
|
||||
{{ i18n.t('repositories.show.bmt_search.apply') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FilterElement from 'vue/bmt_filter/filter.vue'
|
||||
import SavedFilterElement from 'vue/bmt_filter/saved_filter.vue'
|
||||
|
||||
export default {
|
||||
name: 'FilterContainer',
|
||||
data() {
|
||||
return {
|
||||
filters: [],
|
||||
additionalDataAttributes: []
|
||||
}
|
||||
},
|
||||
props: {
|
||||
container: Object,
|
||||
savedFilters: Array,
|
||||
bmtApiBaseUrl: String,
|
||||
canManageFilters: Boolean
|
||||
},
|
||||
created() {
|
||||
this.fetchAdditionalDataAttributes();
|
||||
},
|
||||
components: { FilterElement, SavedFilterElement },
|
||||
computed: {
|
||||
searchJSON() {
|
||||
let mergedFilters = this.filters.map(f => f.data).filter(f => f.type !== 'additionalDataFilter');
|
||||
let additionalDataFilters = this.filters.map(f => f.data).filter(f => f.type === 'additionalDataFilter');
|
||||
|
||||
mergedFilters.push(
|
||||
{
|
||||
'type': 'additionalDataFilter',
|
||||
'dataList': additionalDataFilters.map(f => {
|
||||
return { 'attribute': f.attribute, 'value': f.value }
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
'filters': mergedFilters,
|
||||
'resultAttributeNames': ['Cid']
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
filters() {
|
||||
$('.open-save-bmt-modal').toggleClass('hidden', !this.filters.length)
|
||||
$('.bmt-filters-button').toggleClass('active-filters', !!this.filters.length)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addFilter() {
|
||||
const id = this.filters.length ? this.filters[this.filters.length - 1].id + 1 : 1
|
||||
this.filters.push({ id: id, data: { type: "fullSequenceFilter" } });
|
||||
},
|
||||
updateFilter(filter) {
|
||||
this.filters.find((f) => f.id === filter.id).data = filter.data;
|
||||
this.$emit('filters:update', this.searchJSON.filters);
|
||||
},
|
||||
clearFilters() {
|
||||
this.filters = [];
|
||||
this.$emit('filters:clear');
|
||||
},
|
||||
fetchCIDs() {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: this.bmtApiBaseUrl + '/macromolecule/search',
|
||||
data: JSON.stringify(this.searchJSON),
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
dataType: 'json',
|
||||
success: (data) => {
|
||||
this.$emit('cids:update', data.map(i => i.Cid))
|
||||
},
|
||||
error: ()=> {
|
||||
this.$emit('cids:update', [])
|
||||
this.$emit('cids:error', this.i18n.t('repositories.show.error_searching'));
|
||||
}
|
||||
});
|
||||
},
|
||||
closeSavedFilters() {
|
||||
$('.saved-filters-container').removeClass('open');
|
||||
return true;
|
||||
},
|
||||
toggleSavedFilters() {
|
||||
$('.saved-filters-container').toggleClass('open');
|
||||
},
|
||||
fetchAdditionalDataAttributes() {
|
||||
$.get(this.bmtApiBaseUrl + '/admin/macromolecules/attributes', (data) => {
|
||||
this.additionalDataAttributes = data
|
||||
});
|
||||
},
|
||||
loadFilters(filters) {
|
||||
this.clearFilters();
|
||||
|
||||
let id = 1;
|
||||
|
||||
filters.forEach(filter => {
|
||||
// extract additional filters
|
||||
if(filter.type === 'additionalDataFilter') {
|
||||
filter.dataList.forEach(additionalDataFilter => {
|
||||
this.filters.push(
|
||||
{
|
||||
'id': id++,
|
||||
'data': {
|
||||
'type': 'additionalDataFilter',
|
||||
'attribute': additionalDataFilter.attribute,
|
||||
'value': additionalDataFilter.value
|
||||
}
|
||||
}
|
||||
)
|
||||
});
|
||||
} else {
|
||||
this.filters.push(
|
||||
{
|
||||
'id': id++,
|
||||
'data': filter
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
90
app/javascript/vue/bmt_filter/filter.vue
Normal file
90
app/javascript/vue/bmt_filter/filter.vue
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
<template>
|
||||
<div class="filter-container">
|
||||
<b class='filter-title'>Filter</b>
|
||||
<div class="filter-element">
|
||||
<div class="form-group filter-action">
|
||||
|
||||
<div class="sci-input-container">
|
||||
<DropdownSelector
|
||||
:options="prepareTypesOptions()"
|
||||
:selectorId="`bmtFilter${this.filter.id}`"
|
||||
@dropdown:changed="updateFilter"
|
||||
/>
|
||||
</div>
|
||||
<component
|
||||
:is="type"
|
||||
@filter:updateData="updateFilter"
|
||||
:additionalDataAttributes="additionalDataAttributes"
|
||||
:currentData="filter.data" />
|
||||
</div>
|
||||
<div class="filter-remove">
|
||||
<button class="btn btn-light icon-btn" @click="$emit('filter:delete')">
|
||||
<i class="fas fa-times-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import additionalDataFilter from 'vue/bmt_filter/filters/additionalDataFilter.vue'
|
||||
import entityTypeFilter from 'vue/bmt_filter/filters/entityTypeFilter.vue'
|
||||
import monomerTypeFilter from 'vue/bmt_filter/filters/monomerTypeFilter.vue'
|
||||
import subsequenceFilter from 'vue/bmt_filter/filters/subsequenceFilter.vue'
|
||||
import variantSequenceFilter from 'vue/bmt_filter/filters/variantSequenceFilter.vue'
|
||||
import fullSequenceFilter from 'vue/bmt_filter/filters/fullSequenceFilter.vue'
|
||||
import monomerSubstructureSearchFilter from 'vue/bmt_filter/filters/monomerSubstructureSearchFilter.vue'
|
||||
import DropdownSelector from 'vue/shared/dropdown_selector.vue'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
filter: Object,
|
||||
additionalDataAttributes: Array
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
type: this.filter.data.type,
|
||||
types: [
|
||||
'additionalDataFilter',
|
||||
'entityTypeFilter',
|
||||
'monomerTypeFilter',
|
||||
'subsequenceFilter',
|
||||
'variantSequenceFilter',
|
||||
'fullSequenceFilter',
|
||||
'monomerSubstructureSearchFilter'
|
||||
],
|
||||
additionaDataAttributes: []
|
||||
}
|
||||
},
|
||||
components: {
|
||||
additionalDataFilter,
|
||||
entityTypeFilter,
|
||||
monomerTypeFilter,
|
||||
subsequenceFilter,
|
||||
variantSequenceFilter,
|
||||
fullSequenceFilter,
|
||||
monomerSubstructureSearchFilter,
|
||||
DropdownSelector
|
||||
},
|
||||
methods: {
|
||||
prepareTypesOptions() {
|
||||
return this.types.map(option => {
|
||||
return {label: this.i18n.t(`repositories.show.bmt_search.filters.types.${option}.name`), value: option}
|
||||
})
|
||||
},
|
||||
updateFilter(value) {
|
||||
this.type = value;
|
||||
this.$emit(
|
||||
'filter:update',
|
||||
{
|
||||
id: this.filter.id,
|
||||
data: {
|
||||
type: value
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<div class="filter-form">
|
||||
<div class="sci-input-container">
|
||||
<select @change="updateFilterData" v-model="attribute" class="sci-input-field">
|
||||
<option
|
||||
v-for="attribute in additionalDataAttributes"
|
||||
:key="attribute.name" :value="attribute.name">
|
||||
{{ attribute.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="sci-input-container">
|
||||
<input
|
||||
@input="updateFilterData"
|
||||
class="sci-input-field"
|
||||
type="text"
|
||||
name="value"
|
||||
v-model="value"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FilterMixin from 'vue/bmt_filter/mixins/filter.js'
|
||||
export default {
|
||||
name: 'additionalDataFilter',
|
||||
mixins: [FilterMixin],
|
||||
props: {
|
||||
additionalDataAttributes: Array
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
attribute: null,
|
||||
value: null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
25
app/javascript/vue/bmt_filter/filters/entityTypeFilter.vue
Normal file
25
app/javascript/vue/bmt_filter/filters/entityTypeFilter.vue
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<div class="sci-input-container">
|
||||
<input
|
||||
@input="updateFilterData"
|
||||
class="sci-input-field"
|
||||
type="text"
|
||||
name="entityType"
|
||||
v-model="entityType"
|
||||
:placeholder="i18n.t('repositories.show.bmt_search.filters.types.entityTypeFilter.placeholder')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FilterMixin from 'vue/bmt_filter/mixins/filter.js'
|
||||
export default {
|
||||
name: 'entityTypeFilter',
|
||||
mixins: [FilterMixin],
|
||||
data() {
|
||||
return {
|
||||
entityType: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
42
app/javascript/vue/bmt_filter/filters/fullSequenceFilter.vue
Normal file
42
app/javascript/vue/bmt_filter/filters/fullSequenceFilter.vue
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<div class="sci-input-container">
|
||||
<input
|
||||
@input="updateFilterData"
|
||||
class="sci-input-field"
|
||||
type="text"
|
||||
name="sequence"
|
||||
v-model="sequence"
|
||||
:placeholder="i18n.t('repositories.show.bmt_search.filters.types.fullSequenceFilter.placeholder')"
|
||||
/>
|
||||
<div>
|
||||
<label class="field-suboption">
|
||||
<div class="sci-checkbox-container">
|
||||
<input
|
||||
@change="updateFilterData"
|
||||
class="sci-checkbox"
|
||||
type="checkbox"
|
||||
v-model="derivativesIncluded"
|
||||
/>
|
||||
<label class="sci-checkbox-label"></label>
|
||||
</div>
|
||||
<span class="checkbox-label">
|
||||
{{ i18n.t('repositories.show.bmt_search.filters.types.fullSequenceFilter.derivatives_included') }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FilterMixin from 'vue/bmt_filter/mixins/filter.js'
|
||||
export default {
|
||||
name: 'fullsequenceFilter',
|
||||
mixins: [FilterMixin],
|
||||
data() {
|
||||
return {
|
||||
sequence: "",
|
||||
derivativesIncluded: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<div class="sci-input-container">
|
||||
<input
|
||||
@input="updateFilterData"
|
||||
class="sci-input-field"
|
||||
type="text"
|
||||
name="structure"
|
||||
v-model="structure"
|
||||
:placeholder="i18n.t('repositories.show.bmt_search.filters.types.monomerSubstructureSearchFilter.placeholder')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FilterMixin from 'vue/bmt_filter/mixins/filter.js'
|
||||
export default {
|
||||
name: 'monomerSubstructureSearchFilter',
|
||||
mixins: [FilterMixin],
|
||||
data() {
|
||||
return {
|
||||
structure: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
25
app/javascript/vue/bmt_filter/filters/monomerTypeFilter.vue
Normal file
25
app/javascript/vue/bmt_filter/filters/monomerTypeFilter.vue
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<div class="sci-input-container">
|
||||
<input
|
||||
@input="updateFilterData"
|
||||
class="sci-input-field"
|
||||
type="text"
|
||||
name="monomerType"
|
||||
v-model="monomerType"
|
||||
:placeholder="i18n.t('repositories.show.bmt_search.filters.types.monomerTypeFilter.placeholder')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FilterMixin from 'vue/bmt_filter/mixins/filter.js'
|
||||
export default {
|
||||
name: 'monomerTypeFilter',
|
||||
mixins: [FilterMixin],
|
||||
data() {
|
||||
return {
|
||||
monomerType: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
42
app/javascript/vue/bmt_filter/filters/subsequenceFilter.vue
Normal file
42
app/javascript/vue/bmt_filter/filters/subsequenceFilter.vue
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<div class="sci-input-container">
|
||||
<input
|
||||
@input="updateFilterData"
|
||||
class="sci-input-field"
|
||||
type="text"
|
||||
name="sequence"
|
||||
v-model="sequence"
|
||||
:placeholder="i18n.t('repositories.show.bmt_search.filters.types.subsequenceFilter.placeholder')"
|
||||
/>
|
||||
<div>
|
||||
<label class="field-suboption">
|
||||
<div class="sci-checkbox-container">
|
||||
<input
|
||||
@change="updateFilterData"
|
||||
class="sci-checkbox"
|
||||
type="checkbox"
|
||||
v-model="derivativesIncluded"
|
||||
/>
|
||||
<label class="sci-checkbox-label"></label>
|
||||
</div>
|
||||
<span class="checkbox-label">
|
||||
{{ i18n.t('repositories.show.bmt_search.filters.types.subsequenceFilter.derivatives_included') }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FilterMixin from 'vue/bmt_filter/mixins/filter.js'
|
||||
export default {
|
||||
name: 'subsequenceFilter',
|
||||
mixins: [FilterMixin],
|
||||
data() {
|
||||
return {
|
||||
sequence: "",
|
||||
derivativesIncluded: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<template>
|
||||
<div class="sci-input-container">
|
||||
<input
|
||||
@input="updateFilterData"
|
||||
class="sci-input-field"
|
||||
type="text"
|
||||
name="sequence"
|
||||
v-model="sequence"
|
||||
:placeholder="i18n.t('repositories.show.bmt_search.filters.types.variantSequenceFilter.placeholder')"
|
||||
/>
|
||||
<div>
|
||||
<label class="field-suboption">
|
||||
<div class="sci-input-container">
|
||||
<label for="distance">{{ i18n.t('repositories.show.bmt_search.filters.types.variantSequenceFilter.distance') }}</label>
|
||||
<input class="sci-input-field" @input="updateFilterData" type="text" v-model="distance" />
|
||||
</div>
|
||||
<label class="distance-attribute">
|
||||
<input
|
||||
@change="updateFilterData"
|
||||
type="radio"
|
||||
v-model="distanceType"
|
||||
value="EXACT"
|
||||
/>
|
||||
<span>{{ i18n.t('repositories.show.bmt_search.filters.types.variantSequenceFilter.exact') }}</span>
|
||||
</label>
|
||||
<label class="distance-attribute">
|
||||
<input
|
||||
@change="updateFilterData"
|
||||
type="radio"
|
||||
v-model="distanceType"
|
||||
value="MAX"
|
||||
/>
|
||||
<span>{{ i18n.t('repositories.show.bmt_search.filters.types.variantSequenceFilter.maximum') }}</span>
|
||||
</label>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FilterMixin from 'vue/bmt_filter/mixins/filter.js'
|
||||
export default {
|
||||
name: 'variantSequenceFilter',
|
||||
mixins: [FilterMixin],
|
||||
data() {
|
||||
return {
|
||||
sequence: "",
|
||||
distance: null,
|
||||
distanceType: "EXACT"
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
19
app/javascript/vue/bmt_filter/mixins/filter.js
Normal file
19
app/javascript/vue/bmt_filter/mixins/filter.js
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
34
app/javascript/vue/bmt_filter/saved_filter.vue
Normal file
34
app/javascript/vue/bmt_filter/saved_filter.vue
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<template>
|
||||
<div class="saved-filters-element">
|
||||
<span @click="loadFilters">{{ savedFilter.attributes.name }}</span>
|
||||
<button v-if="canManageFilters" class="btn btn-light icon-btn" @click="deleteFilter">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SavedFilterElement',
|
||||
props: {
|
||||
savedFilter: Object,
|
||||
canManageFilters: Boolean
|
||||
},
|
||||
methods: {
|
||||
loadFilters() {
|
||||
this.$emit('savedFilter:load', this.savedFilter.attributes.filters)
|
||||
},
|
||||
deleteFilter() {
|
||||
let filter = this
|
||||
$.ajax({
|
||||
url: this.savedFilter.attributes.delete_url,
|
||||
type: 'DELETE',
|
||||
dataType: 'json',
|
||||
success: function() {
|
||||
filter.$emit('savedFilter:delete')
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
59
app/javascript/vue/shared/dropdown_selector.vue
Normal file
59
app/javascript/vue/shared/dropdown_selector.vue
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<template>
|
||||
<div class="dropdown-selector">
|
||||
<select :id="this.selectorId">
|
||||
<option
|
||||
v-for="option in this.options"
|
||||
:key="option.label" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'DropdownSelector',
|
||||
props: {
|
||||
options: Array,
|
||||
selectorId: String,
|
||||
noEmptyOption: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
singleSelect: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
closeOnSelect: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
selectAppearance: {
|
||||
type: String,
|
||||
default: 'simple'
|
||||
},
|
||||
onChange: Function
|
||||
|
||||
},
|
||||
mounted: function() {
|
||||
dropdownSelector.init(`#${this.selectorId}`, {
|
||||
noEmptyOption: this.noEmptyOption,
|
||||
singleSelect: this.singleSelect,
|
||||
closeOnSelect: this.closeOnSelect,
|
||||
selectAppearance: this.selectAppearance,
|
||||
onChange: () => {
|
||||
if (this.onChange) this.onChange();
|
||||
this.selectChanged(dropdownSelector.getValues(`#${this.selectorId}`))
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
selectChanged(value) {
|
||||
this.$emit(
|
||||
'dropdown:changed',
|
||||
value
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
13
app/models/bmt_filter.rb
Normal file
13
app/models/bmt_filter.rb
Normal file
|
|
@ -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
|
||||
31
app/models/bmt_repository.rb
Normal file
31
app/models/bmt_repository.rb
Normal file
|
|
@ -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
|
||||
28
app/models/linked_repository.rb
Normal file
28
app/models/linked_repository.rb
Normal file
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
11
app/serializers/bmt_filter_serializer.rb
Normal file
11
app/serializers/bmt_filter_serializer.rb
Normal file
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) %>"
|
||||
|
|
|
|||
|
|
@ -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) %>"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<div class="repository-table">
|
||||
<div class="repository-table-error"></div>
|
||||
<table id="repository-table-<%= repository.id %>" class="table"
|
||||
data-current-uri="<%= request.original_url %>"
|
||||
data-repository-id="<%= repository.id %>"
|
||||
|
|
@ -20,8 +21,8 @@
|
|||
data-columns-delete-text="<%= I18n.t('repositories.columns_delete') %>"
|
||||
data-available-columns="<%= repository_available_columns_path(repository) %>"
|
||||
data-columns-changed="<%= I18n.t('repositories.columns_changed') %>"
|
||||
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-editable="<%= can_manage_repository_rows?(repository) %>"
|
||||
data-snapshot-provisioning="<%= @snapshot_provisioning %>"
|
||||
data-status-url="<%= repository_status_path(@repository) %>">
|
||||
|
|
@ -32,12 +33,17 @@
|
|||
<span class="sci-checkbox-label"></span>
|
||||
</th>
|
||||
<th id="assigned" data-unmanageable="true"><%= t("repositories.table.assigned") %></th>
|
||||
<% if @repository.is_a?(LinkedRepository) %>
|
||||
<th id="row-external-id"><%= t('repositories.table.external_id') %></th>
|
||||
<% end %>
|
||||
<th id="row-id"><%= t("repositories.table.id") %></th>
|
||||
<th id="row-name"><%= t("repositories.table.row_name") %></th>
|
||||
<th id="added-on" ><%= t("repositories.table.added_on") %></th>
|
||||
<th id="added-by" ><%= t("repositories.table.added_by") %></th>
|
||||
<th id="archived-on"><%= t("repositories.table.archived_on") %></th>
|
||||
<th id="archived-by"><%= t("repositories.table.archived_by") %></th>
|
||||
<% unless @repository.is_a?(BmtRepository) %>
|
||||
<th id="added-by" ><%= t("repositories.table.added_by") %></th>
|
||||
<th id="archived-on"><%= t("repositories.table.archived_on") %></th>
|
||||
<th id="archived-by"><%= t("repositories.table.archived_by") %></th>
|
||||
<% end %>
|
||||
<% repository.repository_columns.order(:id).each do |column| %>
|
||||
<th
|
||||
class="repository-column"
|
||||
|
|
|
|||
26
app/views/repositories/_save_bmt_filter_modal.html.erb
Normal file
26
app/views/repositories/_save_bmt_filter_modal.html.erb
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<div class="modal fade" id="modalSaveBmtFilter" tabindex="-1" role="dialog" aria-labelledby="modal-import-records-label">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">
|
||||
<%= t('repositories.show.bmt_search.save_filters') %>
|
||||
</h4>
|
||||
</div>
|
||||
<%= form_with model: BmtFilter.new, method: :post, :html => { :id => "saveBmtFilterForm" } do |f| %>
|
||||
<div class="modal-body">
|
||||
<%= f.hidden_field :filters, class: "sci-input-field" %>
|
||||
<div class="sci-input-container">
|
||||
<%= f.label :name %>
|
||||
<%= f.text_field :name, class: "sci-input-field" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal"><%= t('general.cancel')%></button>
|
||||
<%= f.submit t('general.save'), class: "btn btn-success" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -85,5 +85,44 @@
|
|||
</table>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<% if @repository.is_a?(BmtRepository) %>
|
||||
<div class="dropdown" id="bmtFiltersDropdownButton">
|
||||
<button id="bmtFiltersDropdown"
|
||||
class="btn btn-secondary bmt-filters-button"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
>
|
||||
<span class="fas fa-microscope"></span>
|
||||
<%= t('repositories.show.bmt_search.bmt_filter') %>
|
||||
</button>
|
||||
<div
|
||||
class="dropdown-menu bmt-filters-container"
|
||||
id="bmtFilterContainer"
|
||||
data-datatable-id="#repository-table-<%= @repository.id %>"
|
||||
data-saved-filters-url="<%= bmt_filters_url %>"
|
||||
data-can-manage-filters="<%= can_manage_bmt_filters?(current_team) %>"
|
||||
data-bmt-api-base-url="<%= bmt_request_url(path: 'api') %>"
|
||||
>
|
||||
<filter-container
|
||||
@filters:update="updateFilters"
|
||||
@filters:clear="clearFilters"
|
||||
@cids:update="updateExternalIds"
|
||||
@cids:error="handleSearchError"
|
||||
:can-manage-filters="canManageFilters"
|
||||
:bmt-api-base-url="bmtApiBaseUrl"
|
||||
:saved-filters.sync="savedFilters"
|
||||
:filters.sync="filters" />
|
||||
</div>
|
||||
</div>
|
||||
<% if can_manage_bmt_filters?(current_team) %>
|
||||
<button
|
||||
class="btn btn-light open-save-bmt-modal hidden"
|
||||
type="button"
|
||||
aria-haspopup="true"
|
||||
>
|
||||
<span class="fas fa-save"></span>
|
||||
<%= t('repositories.show.bmt_search.save_filters') %>
|
||||
</button>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@
|
|||
</a>
|
||||
</li>
|
||||
<% 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) %>
|
||||
<li data-view-mode="active">
|
||||
<%= link_to t('repositories.index.options_dropdown.copy'),
|
||||
team_repository_copy_modal_path(current_team, repository_id: @repository),
|
||||
|
|
@ -129,15 +129,17 @@
|
|||
</ul>
|
||||
<% end %>
|
||||
</div>
|
||||
<%= render layout: "shared/view_switch", locals: {disabled: @repository.archived?} do %>
|
||||
<li class="view-switch-archived" data-view-mode="active">
|
||||
<i class="fas fa-archive button-icon"></i>
|
||||
<%= t('repositories.show.show_archived_items') %>
|
||||
</li>
|
||||
<li class="view-switch-active" data-view-mode="archived">
|
||||
<i class="fas fa-rocket button-icon"></i>
|
||||
<%= t('repositories.show.show_active_items') %>
|
||||
</li>
|
||||
<% unless @repository.is_a?(BmtRepository) %>
|
||||
<%= render layout: "shared/view_switch", locals: {disabled: @repository.archived?} do %>
|
||||
<li class="view-switch-archived" data-view-mode="active">
|
||||
<i class="fas fa-archive button-icon"></i>
|
||||
<%= t('repositories.show.show_archived_items') %>
|
||||
</li>
|
||||
<li class="view-switch-active" data-view-mode="archived">
|
||||
<i class="fas fa-rocket button-icon"></i>
|
||||
<%= t('repositories.show.show_active_items') %>
|
||||
</li>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="toolbar-delimiter"></div>
|
||||
|
|
@ -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' %>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
var formatJS = "<%= datetime_picker_format_date_only %>"
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
19
db/migrate/20210812095254_add_external_id_to_repositories.rb
Normal file
19
db/migrate/20210812095254_add_external_id_to_repositories.rb
Normal file
|
|
@ -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
|
||||
12
db/migrate/20210825112050_create_bmt_filters.rb
Normal file
12
db/migrate/20210825112050_create_bmt_filters.rb
Normal file
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
27
lib/sti_preload.rb
Normal file
27
lib/sti_preload.rb
Normal file
|
|
@ -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
|
||||
46
lib/tasks/biomolecule_toolkit.rake
Normal file
46
lib/tasks/biomolecule_toolkit.rake
Normal file
|
|
@ -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
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue