mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-15 01:24:53 +08:00
Implement filters and JSON building [SCI-5956]
This commit is contained in:
parent
0b74f34de7
commit
9a87bad740
14 changed files with 410 additions and 24 deletions
|
@ -1,12 +1,36 @@
|
||||||
#bmtFilterContainer {
|
#bmtFilterContainer {
|
||||||
min-width: 300px;
|
min-width: 422px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.form-control,
|
||||||
|
.form-select {
|
||||||
|
margin-bottom: 21px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label {
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-list-notice {
|
||||||
|
color: $color-silver-chalice;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-bottom: $border-tertiary;
|
border-bottom: $border-tertiary;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: .5em 1em;
|
padding: 0 1em;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
@include font-h2;
|
@include font-h2;
|
||||||
|
@ -16,19 +40,37 @@
|
||||||
|
|
||||||
.filter-element {
|
.filter-element {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
border-bottom: $border-tertiary;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
padding: 15px 52px 15px 15px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-remove {
|
||||||
|
padding: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
top: 16px;
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
height: 16px;
|
||||||
|
padding: 0;
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
background: $color-white;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.filters-list {
|
.filters-list {
|
||||||
border-top: $border-tertiary;
|
border-top: $border-tertiary;
|
||||||
padding: .5em 1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-top: $border-tertiary;
|
border-top: $border-tertiary;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: .5em 1em;
|
|
||||||
|
|
||||||
.add-filter {
|
.add-filter {
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
"jsx": true
|
"jsx": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ignorePatterns": ["*.vue", "**/mixins/*.js"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"import/extensions": "off",
|
"import/extensions": "off",
|
||||||
"import/no-unresolved": "off",
|
"import/no-unresolved": "off",
|
||||||
|
|
|
@ -3,7 +3,7 @@ import Vue from 'vue/dist/vue.esm';
|
||||||
import FilterContainer from '../../vue/bmt_filter/container.vue';
|
import FilterContainer from '../../vue/bmt_filter/container.vue';
|
||||||
|
|
||||||
Vue.use(TurbolinksAdapter);
|
Vue.use(TurbolinksAdapter);
|
||||||
|
Vue.prototype.i18n = window.I18n;
|
||||||
|
|
||||||
window.initBMTFilter = () => {
|
window.initBMTFilter = () => {
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
|
@ -13,8 +13,5 @@ window.initBMTFilter = () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#bmtFilterContainer').on('click', (e) => {
|
$('#bmtFilterContainer').on('click', (e) => e.stopPropagation());
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,7 +10,16 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="filters-list">
|
<div class="filters-list">
|
||||||
<FilterElement v-for="(filter, index) in filters" :key="filter.id" :filter.sync="filters[index]" @delete:filter="filters.splice(index, 1)"></FilterElement>
|
<div v-if="filters.length == 0" class="filter-list-notice">
|
||||||
|
<p class="text-muted"><em>{{ i18n.t('repositories.show.bmt_search.no_filters') }}</em></p>
|
||||||
|
</div>
|
||||||
|
<FilterElement
|
||||||
|
v-for="(filter, index) in filters"
|
||||||
|
:key="filter.id"
|
||||||
|
:filter.sync="filters[index]"
|
||||||
|
@filter:delete="filters.splice(index, 1)"
|
||||||
|
@filter:update="updateFilter"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<button class="btn btn-light add-filter" @click="addFilter">
|
<button class="btn btn-light add-filter" @click="addFilter">
|
||||||
|
@ -31,30 +40,44 @@
|
||||||
name: 'FilterContainer',
|
name: 'FilterContainer',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
filters: [],
|
filters: []
|
||||||
i18n: I18n
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
container: Object
|
container: Object
|
||||||
},
|
},
|
||||||
components: { FilterElement },
|
components: { FilterElement },
|
||||||
|
computed: {
|
||||||
|
searchJSON() {
|
||||||
|
return this.filters.map((f) => f.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addFilter: function() {
|
addFilter() {
|
||||||
var id;
|
let id;
|
||||||
|
|
||||||
if (this.filters.length > 0) {
|
if (this.filters.length > 0) {
|
||||||
id = this.filters[this.filters.length - 1].id + 1
|
id = this.filters[this.filters.length - 1].id + 1
|
||||||
} else {
|
} else {
|
||||||
id = 1
|
id = 1
|
||||||
};
|
};
|
||||||
|
this.filters.push({ id: id, data: { type: "fullSequenceFilter" } });
|
||||||
this.filters.push({
|
|
||||||
id: id
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
clearAllFilters: function() {
|
updateFilter(filter) {
|
||||||
this.filters = []
|
this.filters.find((f) => f.id === filter.id).data = filter.data;
|
||||||
|
},
|
||||||
|
clearAllFilters() {
|
||||||
|
this.filters = [];
|
||||||
|
},
|
||||||
|
loadFilters(filters) {
|
||||||
|
this.clearAllFilters();
|
||||||
|
filters.forEach((filter, index) => {
|
||||||
|
this.filters.push(
|
||||||
|
{
|
||||||
|
id: index,
|
||||||
|
data: filter
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,72 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="filter-element">
|
<div class="filter-element">
|
||||||
<div class="filter-action">
|
<div class="form-group filter-action">
|
||||||
Filter {{ filter.id }}
|
<div class="form-select">
|
||||||
|
<select @change="updateFilter({ type: $event.target.value })" v-model="type">
|
||||||
|
<option
|
||||||
|
v-for="type in types"
|
||||||
|
:key="type.name" :value="type">
|
||||||
|
{{ i18n.t('repositories.show.bmt_search.filters.types.' + type + '.name') }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<component :is="type" @filter:updateData="updateFilter" :currentData="filter.data" />
|
||||||
</div>
|
</div>
|
||||||
<div class="filter-remove">
|
<div class="filter-remove">
|
||||||
<button class="btn btn-light icon-btn" @click="$emit('delete:filter')">
|
<button class="btn btn-light icon-btn" @click="$emit('filter:delete')">
|
||||||
<i class="fas fa-times-circle"></i>
|
<i class="fas fa-times-circle"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<hr>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<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'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
filter: Object
|
filter: Object
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
type: this.filter.data.type,
|
||||||
|
types: [
|
||||||
|
'additionalDataFilter',
|
||||||
|
'entityTypeFilter',
|
||||||
|
'monomerTypeFilter',
|
||||||
|
'subsequenceFilter',
|
||||||
|
'variantSequenceFilter',
|
||||||
|
'fullSequenceFilter',
|
||||||
|
'monomerSubstructureSearchFilter'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
additionalDataFilter,
|
||||||
|
entityTypeFilter,
|
||||||
|
monomerTypeFilter,
|
||||||
|
subsequenceFilter,
|
||||||
|
variantSequenceFilter,
|
||||||
|
fullSequenceFilter,
|
||||||
|
monomerSubstructureSearchFilter
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateFilter(data) {
|
||||||
|
this.$emit(
|
||||||
|
'filter:update',
|
||||||
|
{
|
||||||
|
id: this.filter.id,
|
||||||
|
data: data
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
<template>
|
||||||
|
<h1>TODO: additionalData</h1>
|
||||||
|
</template>
|
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="filter-form">
|
||||||
|
<input
|
||||||
|
@input="updateFilterData"
|
||||||
|
class="form-control"
|
||||||
|
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="filter-form">
|
||||||
|
<input
|
||||||
|
@input="updateFilterData"
|
||||||
|
class="form-control"
|
||||||
|
type="text"
|
||||||
|
name="sequence"
|
||||||
|
v-model="sequence"
|
||||||
|
:placeholder="i18n.t('repositories.show.bmt_search.filters.types.fullSequenceFilter.placeholder')"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
<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="filter-form">
|
||||||
|
<input
|
||||||
|
@input="updateFilterData"
|
||||||
|
class="form-control"
|
||||||
|
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="filter-form">
|
||||||
|
<input
|
||||||
|
@input="updateFilterData"
|
||||||
|
class="form-control"
|
||||||
|
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="filter-form">
|
||||||
|
<input
|
||||||
|
@input="updateFilterData"
|
||||||
|
class="form-control"
|
||||||
|
type="text"
|
||||||
|
name="sequence"
|
||||||
|
v-model="sequence"
|
||||||
|
:placeholder="i18n.t('repositories.show.bmt_search.filters.types.subsequenceFilter.placeholder')"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
<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,59 @@
|
||||||
|
<template>
|
||||||
|
<div class="filter-form">
|
||||||
|
<input
|
||||||
|
@input="updateFilterData"
|
||||||
|
class="form-control"
|
||||||
|
type="text"
|
||||||
|
name="sequence"
|
||||||
|
v-model="sequence"
|
||||||
|
:placeholder="i18n.t('repositories.show.bmt_search.filters.types.variantSequenceFilter.placeholder')"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label for="distance">{{ i18n.t('repositories.show.bmt_search.filters.types.variantSequenceFilter.distance') }}</label>
|
||||||
|
<input class="form-control" @input="updateFilterData" type="text" v-model="distance" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
@change="updateFilterData"
|
||||||
|
type="radio"
|
||||||
|
v-model="distanceType"
|
||||||
|
value="EXACT"
|
||||||
|
/>
|
||||||
|
<span>{{ i18n.t('repositories.show.bmt_search.filters.types.variantSequenceFilter.exact') }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
@change="updateFilterData"
|
||||||
|
type="radio"
|
||||||
|
v-model="distanceType"
|
||||||
|
value="MAX"
|
||||||
|
/>
|
||||||
|
<span>{{ i18n.t('repositories.show.bmt_search.filters.types.variantSequenceFilter.maximum') }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1369,6 +1369,37 @@ en:
|
||||||
clear_all: "Clear all"
|
clear_all: "Clear all"
|
||||||
add_filter: "Add filter"
|
add_filter: "Add filter"
|
||||||
apply: "Apply"
|
apply: "Apply"
|
||||||
|
filter: "Filter"
|
||||||
|
no_filters: "Filter through Chemaxon's database. No active filters are applied at the moment."
|
||||||
|
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:
|
table:
|
||||||
id: 'ID'
|
id: 'ID'
|
||||||
external_id: 'External ID'
|
external_id: 'External ID'
|
||||||
|
|
Loading…
Add table
Reference in a new issue