mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-06 22:08:52 +00:00
Resolve "Implement the collection element filter, sort and search menu."
This commit is contained in:
parent
8cd1bcc45a
commit
c85b64b82d
37 changed files with 690 additions and 198 deletions
backend
src/baserow
api/services
contrib
tests/baserow/contrib/integrations/local_baserow
changelog/entries/unreleased/feature
web-frontend
modules
builder
core
assets/scss/components
builder
integrations
components
mixins
plugins
database
integrations
test/unit
builder/components/elements/components/__snapshots__
core/components/__snapshots__
database/components
|
@ -65,6 +65,7 @@ class PublicServiceSerializer(serializers.ModelSerializer):
|
|||
"""
|
||||
|
||||
type = serializers.SerializerMethodField(help_text="The type of the service.")
|
||||
schema = serializers.SerializerMethodField(help_text="The schema of the service.")
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_type(self, instance):
|
||||
|
@ -74,12 +75,17 @@ class PublicServiceSerializer(serializers.ModelSerializer):
|
|||
def get_context_data(self, instance):
|
||||
return instance.get_type().get_context_data(instance.specific)
|
||||
|
||||
@extend_schema_field(OpenApiTypes.OBJECT)
|
||||
def get_schema(self, instance):
|
||||
return instance.get_type().generate_schema(instance.specific)
|
||||
|
||||
class Meta:
|
||||
model = Service
|
||||
fields = ("id", "type")
|
||||
fields = ("id", "type", "schema")
|
||||
extra_kwargs = {
|
||||
"id": {"read_only": True},
|
||||
"type": {"read_only": True},
|
||||
"schema": {"read_only": True},
|
||||
"context_data": {"read_only": True},
|
||||
}
|
||||
|
||||
|
|
|
@ -138,7 +138,8 @@ class AdHocFilters:
|
|||
data[key] = sanitize_adhoc_filter_value(value)
|
||||
|
||||
api_filters = None
|
||||
if (filters := data.get("filters", None)) and len(filters) > 0:
|
||||
filter_object = {"filters": data}
|
||||
if (filters := filter_object.get("filters", None)) and len(filters) > 0:
|
||||
api_filters = validate_api_grouped_filters(
|
||||
data, user_field_names=user_field_names, deserialize_filters=False
|
||||
)
|
||||
|
|
|
@ -359,7 +359,7 @@ class LocalBaserowTableServiceSortableMixin:
|
|||
queryset = super().get_queryset(service, table, dispatch_context, model)
|
||||
|
||||
adhoc_sort = dispatch_context.sortings()
|
||||
if adhoc_sort is not None and dispatch_context.is_publicly_sortable:
|
||||
if adhoc_sort and dispatch_context.is_publicly_sortable:
|
||||
field_names = [field.strip("-") for field in adhoc_sort.split(",")]
|
||||
dispatch_context.validate_filter_search_sort_fields(
|
||||
field_names, ServiceAdhocRefinements.SORT
|
||||
|
|
|
@ -429,7 +429,7 @@ class LocalBaserowTableServiceType(LocalBaserowServiceType):
|
|||
"id": {
|
||||
"type": "number",
|
||||
"title": "Id",
|
||||
"sortable": True,
|
||||
"sortable": False,
|
||||
"filterable": False,
|
||||
"searchable": False,
|
||||
}
|
||||
|
|
|
@ -868,7 +868,7 @@ def test_local_baserow_table_service_generate_schema_with_interesting_test_table
|
|||
"type": "number",
|
||||
"title": "Id",
|
||||
"metadata": {},
|
||||
"sortable": True,
|
||||
"sortable": False,
|
||||
"filterable": False,
|
||||
"searchable": False,
|
||||
},
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "feature",
|
||||
"message": "[Builder] Allow collection elements to be filtered, sorted and searched against.",
|
||||
"issue_number": 2516,
|
||||
"bullet_points": [],
|
||||
"created_at": "2024-10-24"
|
||||
}
|
|
@ -59,7 +59,7 @@
|
|||
class="select__search-input"
|
||||
:placeholder="searchText === null ? $t('action.search') : searchText"
|
||||
tabindex="0"
|
||||
@keyup="search(query)"
|
||||
@keyup="emitSearch ? $emit('query-change', query) : search(query)"
|
||||
/>
|
||||
</div>
|
||||
<ul
|
||||
|
@ -91,5 +91,15 @@ import dropdown from '@baserow/modules/core/mixins/dropdown'
|
|||
export default {
|
||||
name: 'ABDropdown',
|
||||
mixins: [dropdown],
|
||||
props: {
|
||||
/**
|
||||
* When `emitSearch` is set to `true`, this will emit the search
|
||||
* query instead of performing local, per dropdown-item, search.
|
||||
*/
|
||||
emitSearch: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<component
|
||||
:is="serviceType.adhocHeaderComponent"
|
||||
v-if="dataSource"
|
||||
class="collection-element__header margin-bottom-1"
|
||||
:sortable-properties="
|
||||
elementType.adhocSortableProperties(element, dataSource)
|
||||
"
|
||||
:filterable-properties="
|
||||
elementType.adhocFilterableProperties(element, dataSource)
|
||||
"
|
||||
:searchable-properties="
|
||||
elementType.adhocSearchableProperties(element, dataSource)
|
||||
"
|
||||
@filters-changed="$emit('filters-changed', $event)"
|
||||
@sortings-changed="$emit('sortings-changed', $event)"
|
||||
@search-changed="$emit('search-changed', $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: ['builder', 'page'],
|
||||
props: {
|
||||
element: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
sharedPage() {
|
||||
return this.$store.getters['page/getSharedPage'](this.builder)
|
||||
},
|
||||
dataSource() {
|
||||
return this.$store.getters['dataSource/getPagesDataSourceById'](
|
||||
[this.page, this.sharedPage],
|
||||
this.element.data_source_id
|
||||
)
|
||||
},
|
||||
elementType() {
|
||||
return this.$registry.get('element', this.element.type)
|
||||
},
|
||||
serviceType() {
|
||||
return this.$registry.get('service', this.dataSource?.type)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -8,12 +8,14 @@
|
|||
<ABDropdown
|
||||
ref="recordSelectorDropdown"
|
||||
v-model="inputValue"
|
||||
:show-search="adhocSearchEnabled"
|
||||
:emit-search="adhocSearchEnabled"
|
||||
class="choice-element"
|
||||
:show-search="false"
|
||||
:placeholder="resolvedPlaceholder"
|
||||
:multiple="element.multiple"
|
||||
:before-show="beforeShow"
|
||||
@hide="onFormElementTouch"
|
||||
@query-change="adhocSearch = $event"
|
||||
@scroll="$refs.infiniteScroll.handleScroll($event)"
|
||||
>
|
||||
<template #value>
|
||||
|
@ -24,6 +26,15 @@
|
|||
{{ selectedValueDisplay }}
|
||||
</span>
|
||||
</template>
|
||||
<template #emptyState>
|
||||
{{
|
||||
adhocSearchEnabled
|
||||
? $t('recordSelectorElement.emptyAdhocState', {
|
||||
query: adhocSearch,
|
||||
})
|
||||
: $t('recordSelectorElement.emptyState')
|
||||
}}
|
||||
</template>
|
||||
<template #defaultValue>
|
||||
<template v-if="loading">
|
||||
<div class="loading" />
|
||||
|
@ -99,6 +110,14 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
adhocSearchEnabled() {
|
||||
return (
|
||||
this.elementType.adhocSearchableProperties(
|
||||
this.element,
|
||||
this.dataSource
|
||||
).length > 0
|
||||
)
|
||||
},
|
||||
resolvedLabel() {
|
||||
return ensureString(this.resolveFormula(this.element.label))
|
||||
},
|
||||
|
|
|
@ -1,109 +1,121 @@
|
|||
<template>
|
||||
<div
|
||||
:class="{
|
||||
[`repeat-element--orientation-${element.orientation}`]: true,
|
||||
}"
|
||||
>
|
||||
<!-- If we have any contents to repeat... -->
|
||||
<template v-if="elementContent.length > 0">
|
||||
<div
|
||||
class="repeat-element__repeated-elements"
|
||||
:style="repeatedElementsStyles"
|
||||
>
|
||||
<!-- Iterate over each content -->
|
||||
<div v-for="(content, index) in elementContent" :key="content.id">
|
||||
<!-- If the container has an children -->
|
||||
<template v-if="children.length > 0">
|
||||
<!-- Iterate over each child -->
|
||||
<template v-for="child in children">
|
||||
<!-- The first iteration is editable if we're in editing mode -->
|
||||
<ElementPreview
|
||||
v-if="index === 0 && isEditMode"
|
||||
:key="`${child.id}-${index}`"
|
||||
:element="child"
|
||||
:application-context-additions="{
|
||||
recordIndexPath: [
|
||||
...applicationContext.recordIndexPath,
|
||||
index,
|
||||
],
|
||||
}"
|
||||
@move="moveElement(child, $event)"
|
||||
/>
|
||||
<!-- Other iterations are not editable -->
|
||||
<!-- Override the mode so that any children are in public mode -->
|
||||
<PageElement
|
||||
v-else
|
||||
v-show="!isCollapsed"
|
||||
:key="`${child.id}_${index}`"
|
||||
:element="child"
|
||||
:force-mode="isEditMode ? 'public' : mode"
|
||||
:application-context-additions="{
|
||||
recordIndexPath: [
|
||||
...applicationContext.recordIndexPath,
|
||||
index,
|
||||
],
|
||||
}"
|
||||
:class="{
|
||||
'repeat-element__preview': index > 0 && isEditMode,
|
||||
}"
|
||||
/>
|
||||
<div class="repeat-element--container">
|
||||
<CollectionElementHeader
|
||||
:element="element"
|
||||
@filters-changed="adhocFilters = $event"
|
||||
@sortings-changed="adhocSortings = $event"
|
||||
@search-changed="adhocSearch = $event"
|
||||
></CollectionElementHeader>
|
||||
<div
|
||||
:class="{
|
||||
[`repeat-element--orientation-${element.orientation}`]: true,
|
||||
}"
|
||||
>
|
||||
<!-- If we have any contents to repeat... -->
|
||||
<template v-if="elementContent.length > 0">
|
||||
<div
|
||||
class="repeat-element__repeated-elements"
|
||||
:style="repeatedElementsStyles"
|
||||
>
|
||||
<!-- Iterate over each content -->
|
||||
<div v-for="(content, index) in elementContent" :key="content.id">
|
||||
<!-- If the container has an children -->
|
||||
<template v-if="children.length > 0">
|
||||
<!-- Iterate over each child -->
|
||||
<template v-for="child in children">
|
||||
<!-- The first iteration is editable if we're in editing mode -->
|
||||
<ElementPreview
|
||||
v-if="index === 0 && isEditMode"
|
||||
:key="`${child.id}-${index}`"
|
||||
:element="child"
|
||||
:application-context-additions="{
|
||||
recordIndexPath: [
|
||||
...applicationContext.recordIndexPath,
|
||||
index,
|
||||
],
|
||||
}"
|
||||
@move="moveElement(child, $event)"
|
||||
/>
|
||||
<!-- Other iterations are not editable -->
|
||||
<!-- Override the mode so that any children are in public mode -->
|
||||
<PageElement
|
||||
v-else
|
||||
v-show="!isCollapsed"
|
||||
:key="`${child.id}_${index}`"
|
||||
:element="child"
|
||||
:force-mode="isEditMode ? 'public' : mode"
|
||||
:application-context-additions="{
|
||||
recordIndexPath: [
|
||||
...applicationContext.recordIndexPath,
|
||||
index,
|
||||
],
|
||||
}"
|
||||
:class="{
|
||||
'repeat-element__preview': index > 0 && isEditMode,
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- We have contents, but the container has no children... -->
|
||||
<template v-if="children.length === 0 && isEditMode">
|
||||
<!-- Give the designer the chance to add child elements -->
|
||||
<AddElementZone
|
||||
:disabled="elementIsInError && !elementHasSourceOfData"
|
||||
:tooltip="addElementErrorTooltipMessage"
|
||||
@add-element="showAddElementModal"
|
||||
></AddElementZone>
|
||||
<AddElementModal
|
||||
ref="addElementModal"
|
||||
:page="page"
|
||||
:element-types-allowed="elementType.childElementTypes(page, element)"
|
||||
></AddElementModal>
|
||||
</template>
|
||||
</template>
|
||||
<!-- We have no contents to repeat -->
|
||||
<template v-else>
|
||||
<!-- If we also have no children, allow the designer to add elements -->
|
||||
<template v-if="children.length === 0 && isEditMode">
|
||||
<AddElementZone
|
||||
:disabled="elementIsInError && !elementHasSourceOfData"
|
||||
:tooltip="addElementErrorTooltipMessage"
|
||||
@add-element="showAddElementModal"
|
||||
></AddElementZone>
|
||||
<AddElementModal
|
||||
ref="addElementModal"
|
||||
:page="page"
|
||||
:element-types-allowed="elementType.childElementTypes(page, element)"
|
||||
></AddElementModal>
|
||||
</template>
|
||||
<!-- We have no contents, but we do have children in edit mode -->
|
||||
<template v-else-if="isEditMode">
|
||||
<div v-if="contentLoading" class="loading"></div>
|
||||
<template v-else>
|
||||
<ElementPreview
|
||||
v-for="child in children"
|
||||
:key="child.id"
|
||||
:element="child"
|
||||
@move="moveElement(child, $event)"
|
||||
/>
|
||||
<!-- We have contents, but the container has no children... -->
|
||||
<template v-if="children.length === 0 && isEditMode">
|
||||
<!-- Give the designer the chance to add child elements -->
|
||||
<AddElementZone
|
||||
:disabled="elementIsInError && !elementHasSourceOfData"
|
||||
:tooltip="addElementErrorTooltipMessage"
|
||||
@add-element="showAddElementModal"
|
||||
></AddElementZone>
|
||||
<AddElementModal
|
||||
ref="addElementModal"
|
||||
:page="page"
|
||||
:element-types-allowed="
|
||||
elementType.childElementTypes(page, element)
|
||||
"
|
||||
></AddElementModal>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
<div class="repeat-element__footer">
|
||||
<ABButton
|
||||
v-if="hasMorePage && children.length > 0"
|
||||
:style="getStyleOverride('button')"
|
||||
:disabled="contentLoading || !contentFetchEnabled"
|
||||
:loading="contentLoading"
|
||||
@click="loadMore()"
|
||||
>
|
||||
{{ resolvedButtonLoadMoreLabel || $t('repeatElement.showMore') }}
|
||||
</ABButton>
|
||||
<!-- We have no contents to repeat -->
|
||||
<template v-else>
|
||||
<!-- If we also have no children, allow the designer to add elements -->
|
||||
<template v-if="children.length === 0 && isEditMode">
|
||||
<AddElementZone
|
||||
:disabled="elementIsInError && !elementHasSourceOfData"
|
||||
:tooltip="addElementErrorTooltipMessage"
|
||||
@add-element="showAddElementModal"
|
||||
></AddElementZone>
|
||||
<AddElementModal
|
||||
ref="addElementModal"
|
||||
:page="page"
|
||||
:element-types-allowed="
|
||||
elementType.childElementTypes(page, element)
|
||||
"
|
||||
></AddElementModal>
|
||||
</template>
|
||||
<!-- We have no contents, but we do have children in edit mode -->
|
||||
<template v-else-if="isEditMode">
|
||||
<div v-if="contentLoading" class="loading"></div>
|
||||
<template v-else>
|
||||
<ElementPreview
|
||||
v-for="child in children"
|
||||
:key="child.id"
|
||||
:element="child"
|
||||
@move="moveElement(child, $event)"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
<div class="repeat-element__footer">
|
||||
<ABButton
|
||||
v-if="hasMorePage && children.length > 0"
|
||||
:style="getStyleOverride('button')"
|
||||
:disabled="contentLoading || !contentFetchEnabled"
|
||||
:loading="contentLoading"
|
||||
@click="loadMore()"
|
||||
>
|
||||
{{ resolvedButtonLoadMoreLabel || $t('repeatElement.showMore') }}
|
||||
</ABButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -120,10 +132,12 @@ import PageElement from '@baserow/modules/builder/components/page/PageElement'
|
|||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||
import { ensureString } from '@baserow/modules/core/utils/validator'
|
||||
import { RepeatElementType } from '@baserow/modules/builder/elementTypes'
|
||||
import CollectionElementHeader from '@baserow/modules/builder/components/elements/components/CollectionElementHeader'
|
||||
|
||||
export default {
|
||||
name: 'RepeatElement',
|
||||
components: {
|
||||
CollectionElementHeader,
|
||||
PageElement,
|
||||
ElementPreview,
|
||||
AddElementModal,
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
<template>
|
||||
<div class="table-element">
|
||||
<CollectionElementHeader
|
||||
:element="element"
|
||||
@filters-changed="adhocFilters = $event"
|
||||
@sortings-changed="adhocSortings = $event"
|
||||
@search-changed="adhocSearch = $event"
|
||||
></CollectionElementHeader>
|
||||
<ABTable
|
||||
:fields="fields"
|
||||
:rows="rows"
|
||||
|
@ -62,10 +68,11 @@ import { uuid } from '@baserow/modules/core/utils/string'
|
|||
import BaserowTable from '@baserow/modules/builder/components/elements/components/BaserowTable'
|
||||
import collectionElement from '@baserow/modules/builder/mixins/collectionElement'
|
||||
import { ensureString } from '@baserow/modules/core/utils/validator'
|
||||
import CollectionElementHeader from '@baserow/modules/builder/components/elements/components/CollectionElementHeader'
|
||||
|
||||
export default {
|
||||
name: 'TableElement',
|
||||
components: { BaserowTable },
|
||||
components: { CollectionElementHeader, BaserowTable },
|
||||
mixins: [element, collectionElement],
|
||||
props: {
|
||||
/**
|
||||
|
|
|
@ -194,6 +194,19 @@ export default {
|
|||
)
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'values.data_source_id': {
|
||||
handler(value) {
|
||||
this.values.data_source_id = value
|
||||
|
||||
// If the data source was removed we should also delete the name formula
|
||||
if (value === null) {
|
||||
this.values.option_name_suffix = ''
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
validations() {
|
||||
return {
|
||||
values: {
|
||||
|
@ -208,18 +221,5 @@ export default {
|
|||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'values.data_source_id': {
|
||||
handler(value) {
|
||||
this.values.data_source_id = value
|
||||
|
||||
// If the data source was removed we should also delete the name formula
|
||||
if (value === null) {
|
||||
this.values.option_name_suffix = ''
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -96,6 +96,9 @@ export default {
|
|||
.get('service', this.dataSource.type)
|
||||
.getDataSchema(this.dataSource)
|
||||
},
|
||||
elementType() {
|
||||
return this.$registry.get('element', this.element.type)
|
||||
},
|
||||
/**
|
||||
* Returns an object with schema properties as keys and their corresponding
|
||||
* property options as values. It's a convenience computed method to easily
|
||||
|
|
|
@ -822,6 +822,87 @@ const CollectionElementTypeMixin = (Base) =>
|
|||
class extends Base {
|
||||
isCollectionElement = true
|
||||
|
||||
/**
|
||||
* A helper function responsible for returning this collection element's
|
||||
* schema properties.
|
||||
*/
|
||||
getSchemaProperties(dataSource) {
|
||||
const serviceType = this.app.$registry.get('service', dataSource.type)
|
||||
const schema = serviceType.getDataSchema(dataSource)
|
||||
if (!schema) {
|
||||
return []
|
||||
}
|
||||
return schema.type === 'array'
|
||||
? schema.items.properties
|
||||
: schema.properties
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a schema property name, is responsible for finding the matching
|
||||
* property option in the element. If it doesn't exist, then we return
|
||||
* an empty object, and it won't be included in the adhoc header.
|
||||
* @param {object} element - the element we want to extract options from.
|
||||
* @param {string} schemaProperty - the schema property name to check.
|
||||
* @returns {object} - the matching property option, or an empty object.
|
||||
*/
|
||||
getPropertyOptionsByProperty(element, schemaProperty) {
|
||||
return (
|
||||
element.property_options.find((option) => {
|
||||
return option.schema_property === schemaProperty
|
||||
}) || {}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsible for iterating over the schema's properties, filtering
|
||||
* the results down to the properties which are `filterable`, `sortable`,
|
||||
* and `searchable`, and then returning the property value.
|
||||
* @param {string} option - the `filterable`, `sortable` or `searchable`
|
||||
* property option. If the value is `true` then the property will be
|
||||
* included in the adhoc header component.
|
||||
* @param {object} element - the element we want to extract options from.
|
||||
* @param {object} dataSource - the dataSource used by `element`.
|
||||
* @returns {array} - an array of schema properties which are present
|
||||
* in the element's property options where `option` = `true`.
|
||||
*/
|
||||
getPropertyOptionByType(option, element, dataSource) {
|
||||
const schemaProperties = dataSource
|
||||
? this.getSchemaProperties(dataSource)
|
||||
: []
|
||||
return Object.entries(schemaProperties)
|
||||
.filter(
|
||||
([schemaProperty, _]) =>
|
||||
this.getPropertyOptionsByProperty(element, schemaProperty)[
|
||||
option
|
||||
] || false
|
||||
)
|
||||
.map(([_, property]) => property)
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of properties within this element which have been flagged
|
||||
* as filterable by the page designer.
|
||||
*/
|
||||
adhocFilterableProperties(element, dataSource) {
|
||||
return this.getPropertyOptionByType('filterable', element, dataSource)
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of properties within this element which have been flagged
|
||||
* as sortable by the page designer.
|
||||
*/
|
||||
adhocSortableProperties(element, dataSource) {
|
||||
return this.getPropertyOptionByType('sortable', element, dataSource)
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of properties within this element which have been flagged
|
||||
* as searchable by the page designer.
|
||||
*/
|
||||
adhocSearchableProperties(element, dataSource) {
|
||||
return this.getPropertyOptionByType('searchable', element, dataSource)
|
||||
}
|
||||
|
||||
/**
|
||||
* By default collection element will load their content at loading time
|
||||
* but if you don't want that you can return false here.
|
||||
|
|
|
@ -599,6 +599,10 @@
|
|||
"toggleEditorRepetitionsLabel": "Temporarily disable repetitions",
|
||||
"propertySelectorMissingArrays": "No multiple valued fields found to repeat with."
|
||||
},
|
||||
"recordSelectorElement": {
|
||||
"emptyAdhocState": "No records matching '{query}' found.",
|
||||
"emptyState": "No records found."
|
||||
},
|
||||
"recordSelectorElementForm": {
|
||||
"selectRecordsFrom": "Select records from",
|
||||
"noDataSourceMessage": "Choose a data source with multiple rows to list all results.",
|
||||
|
|
|
@ -6,6 +6,9 @@ import _ from 'lodash'
|
|||
export default {
|
||||
data() {
|
||||
return {
|
||||
adhocFilters: undefined,
|
||||
adhocSortings: undefined,
|
||||
adhocSearch: undefined,
|
||||
currentOffset: 0,
|
||||
errorNotified: false,
|
||||
resetTimeout: null,
|
||||
|
@ -61,6 +64,13 @@ export default {
|
|||
elementHasSourceOfData() {
|
||||
return this.elementType.hasSourceOfData(this.element)
|
||||
},
|
||||
adhocRefinements() {
|
||||
return {
|
||||
filters: this.adhocFilters,
|
||||
sortings: this.adhocSortings,
|
||||
search: this.adhocSearch,
|
||||
}
|
||||
},
|
||||
elementIsInError() {
|
||||
return this.elementType.isInError({
|
||||
page: this.page,
|
||||
|
@ -92,6 +102,13 @@ export default {
|
|||
},
|
||||
deep: true,
|
||||
},
|
||||
adhocRefinements: {
|
||||
handler(newValue, prevValue) {
|
||||
if (!_.isEqual(newValue, prevValue)) {
|
||||
this.debouncedReset()
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
async fetch() {
|
||||
if (!this.elementIsInError && this.elementType.fetchAtLoad) {
|
||||
|
@ -122,6 +139,9 @@ export default {
|
|||
dataSource: this.dataSource,
|
||||
data: this.dispatchContext,
|
||||
range,
|
||||
filters: this.adhocRefinements.filters,
|
||||
sortings: this.adhocRefinements.sortings,
|
||||
search: this.adhocRefinements.search,
|
||||
mode: this.applicationContext.mode,
|
||||
replace,
|
||||
})
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { mapGetters } from 'vuex'
|
||||
import applicationContextMixin from '@baserow/modules/builder/mixins/applicationContext'
|
||||
import { CurrentRecordDataProviderType } from '@baserow/modules/builder/dataProviderTypes'
|
||||
import { FF_PROPERTY_OPTIONS } from '@baserow/modules/core/plugins/featureFlags'
|
||||
|
||||
export default {
|
||||
mixins: [applicationContextMixin],
|
||||
|
@ -49,11 +48,7 @@ export default {
|
|||
* @returns {boolean} - Whether the property options are available.
|
||||
*/
|
||||
propertyOptionsAvailable() {
|
||||
return (
|
||||
this.selectedDataSource &&
|
||||
this.selectedDataSourceReturnsList &&
|
||||
this.$featureFlagIsEnabled(FF_PROPERTY_OPTIONS)
|
||||
)
|
||||
return this.selectedDataSource && this.selectedDataSourceReturnsList
|
||||
},
|
||||
/**
|
||||
* In collection element forms, the ability to view paging options
|
||||
|
|
|
@ -24,14 +24,35 @@ export default (client) => {
|
|||
`builder/domains/published/page/${pageId}/workflow_actions/`
|
||||
)
|
||||
},
|
||||
dispatch(dataSourceId, dispatchContext, { range }) {
|
||||
dispatch(
|
||||
dataSourceId,
|
||||
dispatchContext,
|
||||
{ range, filters = {}, sortings = null, search = '', searchMode = '' }
|
||||
) {
|
||||
// Using POST Http method here is not Restful but it the cleanest way to send
|
||||
// data with the call without relying on GET parameter and serialization of
|
||||
// complex object.
|
||||
const params = {}
|
||||
const params = new URLSearchParams()
|
||||
if (range) {
|
||||
params.offset = range[0]
|
||||
params.count = range[1]
|
||||
params.append('offset', range[0])
|
||||
params.append('count', range[1])
|
||||
}
|
||||
|
||||
Object.keys(filters).forEach((key) => {
|
||||
filters[key].forEach((value) => {
|
||||
params.append(key, value)
|
||||
})
|
||||
})
|
||||
|
||||
if (sortings || sortings === '') {
|
||||
params.append('order_by', sortings)
|
||||
}
|
||||
|
||||
if (search) {
|
||||
params.append('search_query', search)
|
||||
if (searchMode) {
|
||||
params.append('search_mode', searchMode)
|
||||
}
|
||||
}
|
||||
|
||||
return client.post(
|
||||
|
|
|
@ -58,6 +58,11 @@ const actions = {
|
|||
* @param {object} element - the element object
|
||||
* @param {object} dataSource - the data source we want to dispatch
|
||||
* @param {object} range - the range of the data we want to fetch
|
||||
* @param {object} filters - the adhoc filters to apply to the data
|
||||
* @param {object} sortings - the adhoc sortings to apply to the data
|
||||
* @param {object} search - the adhoc search to apply to the data
|
||||
* @param {string} searchMode - the search mode to apply to the data.
|
||||
* @param {string} mode - the mode of the application
|
||||
* @param {object} dispatchContext - the context to dispatch to the data
|
||||
* @param {bool} replace - if we want to replace the current content
|
||||
* @param {object} data - the query body
|
||||
|
@ -69,6 +74,10 @@ const actions = {
|
|||
element,
|
||||
dataSource,
|
||||
range,
|
||||
filters = {},
|
||||
sortings = null,
|
||||
search = '',
|
||||
searchMode = '',
|
||||
mode,
|
||||
data: dispatchContext,
|
||||
replace = false,
|
||||
|
@ -203,7 +212,7 @@ const actions = {
|
|||
const { data } = await service(this.app.$client).dispatch(
|
||||
dataSource.id,
|
||||
dispatchContext,
|
||||
{ range: rangeToFetch }
|
||||
{ range: rangeToFetch, filters, sortings, search, searchMode }
|
||||
)
|
||||
|
||||
// With a list-type data source, the data object will return
|
||||
|
|
|
@ -34,3 +34,4 @@
|
|||
@import 'padding_selector';
|
||||
@import 'page';
|
||||
@import 'data_source_item';
|
||||
@import 'collection_element_header';
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
.element--read-only .collection-element__header {
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
@import 'local_baserow/local_baserow_form';
|
||||
@import 'local_baserow/local_baserow_adhoc_header';
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
.local-baserow-adhoc-header__container {
|
||||
.header__filter-item {
|
||||
margin-left: 0;
|
||||
|
||||
&.header__filter-item--right {
|
||||
margin-left: auto;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -140,9 +140,10 @@ export default {
|
|||
// direction, then it will break out of it. We will therefore close it. This can
|
||||
// happen the height or width of the viewport decreases.
|
||||
if (
|
||||
(css.bottom && css.bottom < 0) ||
|
||||
(css.bottom && css.bottom < this.getWindowScrollHeight()) ||
|
||||
(css.right && css.right < 0) ||
|
||||
(css.top && css.top > window.innerHeight)
|
||||
(css.top &&
|
||||
css.top > window.innerHeight + this.getWindowScrollHeight())
|
||||
) {
|
||||
this.hide()
|
||||
return
|
||||
|
@ -161,7 +162,9 @@ export default {
|
|||
const maxHeight =
|
||||
css.top || css.bottom
|
||||
? `calc(100vh - ${
|
||||
(css.top || css.bottom) + this.maxHeightOffset
|
||||
(css.top || css.bottom) +
|
||||
this.maxHeightOffset -
|
||||
this.getWindowScrollHeight()
|
||||
}px)`
|
||||
: 'none'
|
||||
this.$el.style['max-height'] = maxHeight
|
||||
|
@ -426,15 +429,21 @@ export default {
|
|||
}
|
||||
|
||||
if (verticalAdjusted === 'bottom') {
|
||||
positions.top = targetBottom + verticalOffset
|
||||
positions.top =
|
||||
targetBottom + verticalOffset + this.getWindowScrollHeight()
|
||||
}
|
||||
|
||||
if (verticalAdjusted === 'over-bottom' || verticalAdjusted === 'over') {
|
||||
positions.top = targetTop + verticalOffset
|
||||
positions.top =
|
||||
targetTop + verticalOffset + this.getWindowScrollHeight()
|
||||
}
|
||||
|
||||
if (verticalAdjusted === 'top') {
|
||||
positions.bottom = window.innerHeight - targetTop + verticalOffset
|
||||
positions.bottom =
|
||||
window.innerHeight -
|
||||
targetTop +
|
||||
verticalOffset +
|
||||
this.getWindowScrollHeight()
|
||||
}
|
||||
|
||||
if (verticalAdjusted === 'over-top' || verticalAdjusted === 'over') {
|
||||
|
@ -469,9 +478,15 @@ export default {
|
|||
// with the full height of the element without scrollbar to calculate the optimal
|
||||
// position.
|
||||
const scrollHeight = this.$el.scrollHeight
|
||||
const canTop = targetRect.top - scrollHeight - verticalOffset > 0
|
||||
const canTop =
|
||||
targetRect.top -
|
||||
scrollHeight -
|
||||
verticalOffset +
|
||||
this.getWindowScrollHeight() >
|
||||
0
|
||||
const canBottom =
|
||||
window.innerHeight -
|
||||
window.innerHeight +
|
||||
this.getWindowScrollHeight() -
|
||||
targetRect.bottom -
|
||||
scrollHeight -
|
||||
this.maxHeightOffset -
|
||||
|
@ -507,6 +522,9 @@ export default {
|
|||
|
||||
return { vertical, horizontal }
|
||||
},
|
||||
getWindowScrollHeight() {
|
||||
return window?.scrollY || 0
|
||||
},
|
||||
isOpen() {
|
||||
return this.open
|
||||
},
|
||||
|
|
|
@ -225,7 +225,9 @@ export default {
|
|||
: [...items, ...traverse(child.$children)],
|
||||
[]
|
||||
)
|
||||
return traverse(this.$children)
|
||||
const components = traverse(this.$children)
|
||||
this.hasDropdownItem = components.length > 0
|
||||
return components
|
||||
},
|
||||
focusout(event) {
|
||||
// Hide only if we loose focus in favor of another element which is not a
|
||||
|
@ -271,8 +273,6 @@ export default {
|
|||
this.opener = isElementOrigin ? target : null
|
||||
this.$emit('show')
|
||||
|
||||
this.hasDropdownItem = this.getDropdownItemComponents().length > 0
|
||||
|
||||
this.$nextTick(() => {
|
||||
// We have to wait for the input to be visible before we can focus.
|
||||
this.showSearch && this.$refs.search.focus()
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
const FF_ENABLE_ALL = '*'
|
||||
export const FF_EXPORT_WORKSPACE = 'export_workspace'
|
||||
export const FF_DASHBOARDS = 'dashboards'
|
||||
export const FF_PROPERTY_OPTIONS = 'property_options'
|
||||
|
||||
/**
|
||||
* A comma separated list of feature flags used to enable in-progress or not ready
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
ref="context"
|
||||
:view="view"
|
||||
:fields="fields"
|
||||
:read-only="readOnly"
|
||||
:store-prefix="storePrefix"
|
||||
:always-hide-rows-not-matching-search="alwaysHideRowsNotMatchingSearch"
|
||||
@refresh="$emit('refresh', $event)"
|
||||
|
@ -38,9 +39,15 @@ export default {
|
|||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
storePrefix: {
|
||||
type: String,
|
||||
required: true,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
alwaysHideRowsNotMatchingSearch: {
|
||||
type: Boolean,
|
||||
|
@ -53,6 +60,18 @@ export default {
|
|||
headerSearchTerm: '',
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$props: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
if (!this.storePrefix.length && !this.readOnly) {
|
||||
throw new Error(
|
||||
'A storePrefix is required when the search is not read-only.'
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$priorityBus.$on(
|
||||
'start-search',
|
||||
|
|
|
@ -48,6 +48,10 @@ export default {
|
|||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
storePrefix: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
@ -88,6 +92,11 @@ export default {
|
|||
this.lastHide = this.hideRowsNotMatchingSearch
|
||||
},
|
||||
search() {
|
||||
if (this.readOnly) {
|
||||
this.$emit('refresh', { activeSearchTerm: this.activeSearchTerm })
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
|
||||
// When the user toggles from hiding rows to not hiding rows we still
|
||||
|
@ -114,6 +123,7 @@ export default {
|
|||
)
|
||||
this.$emit('refresh', {
|
||||
callback: this.finishedLoading,
|
||||
activeSearchTerm: this.activeSearchTerm,
|
||||
})
|
||||
}, 400),
|
||||
// Debounce even the client side only refreshes as otherwise spamming the keyboard
|
||||
|
|
|
@ -36,12 +36,12 @@
|
|||
</a>
|
||||
|
||||
<div class="sortings__description">
|
||||
<template v-if="index === 0">{{
|
||||
$t('viewSortContext.sortBy')
|
||||
}}</template>
|
||||
<template v-if="index > 0">{{
|
||||
$t('viewSortContext.thenBy')
|
||||
}}</template>
|
||||
<template v-if="index === 0"
|
||||
>{{ $t('viewSortContext.sortBy') }}
|
||||
</template>
|
||||
<template v-if="index > 0"
|
||||
>{{ $t('viewSortContext.thenBy') }}
|
||||
</template>
|
||||
</div>
|
||||
<div class="sortings__field">
|
||||
<Dropdown
|
||||
|
@ -69,9 +69,9 @@
|
|||
:class="{ active: sort.order === 'ASC' }"
|
||||
@click="updateSort(sort, { order: 'ASC' })"
|
||||
>
|
||||
<template v-if="getSortIndicator(field, 0) === 'text'">{{
|
||||
getSortIndicator(field, 1)
|
||||
}}</template>
|
||||
<template v-if="getSortIndicator(field, 0) === 'text'"
|
||||
>{{ getSortIndicator(field, 1) }}
|
||||
</template>
|
||||
<i
|
||||
v-if="getSortIndicator(field, 0) === 'icon'"
|
||||
:class="getSortIndicator(field, 1)"
|
||||
|
@ -79,9 +79,9 @@
|
|||
|
||||
<i class="iconoir-arrow-right"></i>
|
||||
|
||||
<template v-if="getSortIndicator(field, 0) === 'text'">{{
|
||||
getSortIndicator(field, 2)
|
||||
}}</template>
|
||||
<template v-if="getSortIndicator(field, 0) === 'text'"
|
||||
>{{ getSortIndicator(field, 2) }}
|
||||
</template>
|
||||
<i
|
||||
v-if="getSortIndicator(field, 0) === 'icon'"
|
||||
:class="getSortIndicator(field, 2)"
|
||||
|
@ -92,9 +92,9 @@
|
|||
:class="{ active: sort.order === 'DESC' }"
|
||||
@click="updateSort(sort, { order: 'DESC' })"
|
||||
>
|
||||
<template v-if="getSortIndicator(field, 0) === 'text'">{{
|
||||
getSortIndicator(field, 2)
|
||||
}}</template>
|
||||
<template v-if="getSortIndicator(field, 0) === 'text'"
|
||||
>{{ getSortIndicator(field, 2) }}
|
||||
</template>
|
||||
<i
|
||||
v-if="getSortIndicator(field, 0) === 'icon'"
|
||||
:class="getSortIndicator(field, 2)"
|
||||
|
@ -102,9 +102,9 @@
|
|||
|
||||
<i class="iconoir-arrow-right"></i>
|
||||
|
||||
<template v-if="getSortIndicator(field, 0) === 'text'">{{
|
||||
getSortIndicator(field, 1)
|
||||
}}</template>
|
||||
<template v-if="getSortIndicator(field, 0) === 'text'"
|
||||
>{{ getSortIndicator(field, 1) }}
|
||||
</template>
|
||||
<i
|
||||
v-if="getSortIndicator(field, 0) === 'icon'"
|
||||
:class="getSortIndicator(field, 1)"
|
||||
|
@ -124,8 +124,8 @@
|
|||
$refs.addContext.toggle($refs.addContextToggle, 'bottom', 'left', 4)
|
||||
"
|
||||
>
|
||||
{{ $t('viewSortContext.addSort') }}</ButtonText
|
||||
>
|
||||
{{ $t('viewSortContext.addSort') }}
|
||||
</ButtonText>
|
||||
<Context
|
||||
ref="addContext"
|
||||
class="sortings__add-context"
|
||||
|
@ -142,7 +142,7 @@
|
|||
<a class="context__menu-item-link" @click="addSort(field)">
|
||||
<i
|
||||
class="context__menu-item-icon"
|
||||
:class="field._.type.iconClass"
|
||||
:class="getFieldType(field).iconClass"
|
||||
></i>
|
||||
{{ field.name }}
|
||||
</a>
|
||||
|
@ -188,8 +188,11 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
getFieldType(field) {
|
||||
return this.$registry.get('field', field.type)
|
||||
},
|
||||
getCanSortInView(field) {
|
||||
return this.$registry.get('field', field.type).getCanSortInView(field)
|
||||
return this.getFieldType(field).getCanSortInView(field)
|
||||
},
|
||||
getField(fieldId) {
|
||||
for (const i in this.fields) {
|
||||
|
@ -249,9 +252,9 @@ export default {
|
|||
}
|
||||
},
|
||||
getSortIndicator(field, index) {
|
||||
return this.$registry
|
||||
.get('field', field.type)
|
||||
.getSortIndicator(field, this.$registry)[index]
|
||||
return this.getFieldType(field).getSortIndicator(field, this.$registry)[
|
||||
index
|
||||
]
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -140,11 +140,15 @@ export const mutations = {
|
|||
if (!state.items.some((existingItem) => existingItem.id === item.id))
|
||||
state.items = [...state.items, item].sort((a, b) => a.order - b.order)
|
||||
},
|
||||
UPDATE_ITEM(state, { id, values, repopulate }) {
|
||||
const index = state.items.findIndex((item) => item.id === id)
|
||||
Object.assign(state.items[index], state.items[index], values)
|
||||
if (repopulate === true) {
|
||||
populateView(state.items[index], this.$registry)
|
||||
UPDATE_ITEM(state, { id, view, values, repopulate, readOnly }) {
|
||||
if (!readOnly) {
|
||||
const index = state.items.findIndex((item) => item.id === id)
|
||||
Object.assign(state.items[index], state.items[index], values)
|
||||
if (repopulate === true) {
|
||||
populateView(state.items[index], this.$registry)
|
||||
}
|
||||
} else {
|
||||
Object.assign(view, view, values)
|
||||
}
|
||||
},
|
||||
ORDER_ITEMS(state, { ownershipType, order }) {
|
||||
|
@ -440,7 +444,12 @@ export const actions = {
|
|||
}
|
||||
|
||||
if (optimisticUpdate) {
|
||||
dispatch('forceUpdate', { view, values: newValues, repopulate: true })
|
||||
dispatch('forceUpdate', {
|
||||
view,
|
||||
values: newValues,
|
||||
repopulate: true,
|
||||
readOnly,
|
||||
})
|
||||
}
|
||||
try {
|
||||
if (!readOnly) {
|
||||
|
@ -484,8 +493,17 @@ export const actions = {
|
|||
/**
|
||||
* Forcefully update an existing view without making a request to the backend.
|
||||
*/
|
||||
forceUpdate({ commit }, { view, values, repopulate = false }) {
|
||||
commit('UPDATE_ITEM', { id: view.id, values, repopulate })
|
||||
forceUpdate(
|
||||
{ commit },
|
||||
{ view, values, repopulate = false, readOnly = false }
|
||||
) {
|
||||
commit('UPDATE_ITEM', {
|
||||
id: view.id,
|
||||
view,
|
||||
values,
|
||||
repopulate,
|
||||
readOnly,
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Duplicates an existing view.
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
<template>
|
||||
<div class="local-baserow-adhoc-header__container">
|
||||
<ul class="header__filter">
|
||||
<li class="header__filter-item">
|
||||
<ViewFilter
|
||||
v-if="filterableFields.length"
|
||||
read-only
|
||||
:view="view"
|
||||
:fields="filterableFields"
|
||||
:disable-filter="false"
|
||||
@changed="handleFiltersChange"
|
||||
></ViewFilter>
|
||||
</li>
|
||||
<li class="header__filter-item">
|
||||
<ViewSort
|
||||
v-if="sortableFields.length"
|
||||
read-only
|
||||
:view="view"
|
||||
:fields="sortableFields"
|
||||
@changed="handleSortingsChange"
|
||||
></ViewSort>
|
||||
</li>
|
||||
<li class="header__filter-item header__filter-item--right">
|
||||
<ViewSearch
|
||||
v-if="searchableFields.length"
|
||||
read-only
|
||||
always-hide-rows-not-matching-search
|
||||
:view="view"
|
||||
:fields="searchableFields"
|
||||
@refresh="handleSearchChange"
|
||||
></ViewSearch>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ViewFilter from '@baserow/modules/database/components/view/ViewFilter'
|
||||
import ViewSort from '@baserow/modules/database/components/view/ViewSort'
|
||||
import ViewSearch from '@baserow/modules/database/components/view/ViewSearch'
|
||||
import { getFilters, getOrderBy } from '@baserow/modules/database/utils/view'
|
||||
|
||||
export default {
|
||||
components: { ViewSearch, ViewSort, ViewFilter },
|
||||
props: {
|
||||
/**
|
||||
* An array of filterable, sortable and searchable *schema* properties.
|
||||
* To access the Baserow field response, these need to be reduced down
|
||||
* to just their `metadata`. This happens in the `computed` methods below.
|
||||
*/
|
||||
filterableProperties: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
sortableProperties: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
searchableProperties: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
view: {
|
||||
filters: [],
|
||||
sortings: [],
|
||||
filter_groups: [],
|
||||
filter_type: 'AND',
|
||||
filters_disabled: false,
|
||||
_: { loading: false },
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filterableFields() {
|
||||
return this.filterableProperties.map((prop) => prop.metadata)
|
||||
},
|
||||
sortableFields() {
|
||||
return this.sortableProperties.map((prop) => prop.metadata)
|
||||
},
|
||||
searchableFields() {
|
||||
return this.searchableProperties.map((prop) => prop.metadata)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleFiltersChange() {
|
||||
this.$emit('filters-changed', getFilters(this.view, true))
|
||||
},
|
||||
handleSortingsChange() {
|
||||
this.$emit('sortings-changed', getOrderBy(this.view, true))
|
||||
},
|
||||
handleSearchChange(value) {
|
||||
this.$emit('search-changed', value.activeSearchTerm)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -3,6 +3,7 @@ import { LocalBaserowIntegrationType } from '@baserow/modules/integrations/integ
|
|||
import LocalBaserowGetRowForm from '@baserow/modules/integrations/localBaserow/components/services/LocalBaserowGetRowForm'
|
||||
import LocalBaserowListRowsForm from '@baserow/modules/integrations/localBaserow/components/services/LocalBaserowListRowsForm'
|
||||
import { uuid } from '@baserow/modules/core/utils/string'
|
||||
import LocalBaserowAdhocHeader from '@baserow/modules/integrations/localBaserow/components/integrations/LocalBaserowAdhocHeader'
|
||||
|
||||
export class LocalBaserowGetRowServiceType extends ServiceType {
|
||||
static getType() {
|
||||
|
@ -67,12 +68,12 @@ export class LocalBaserowGetRowServiceType extends ServiceType {
|
|||
|
||||
const databases = integration.context_data?.databases
|
||||
|
||||
if (service.table_id && databases) {
|
||||
const tableSelected = databases
|
||||
.map((database) => database.tables)
|
||||
.flat()
|
||||
.find(({ id }) => id === service.table_id)
|
||||
const tableSelected = databases
|
||||
.map((database) => database.tables)
|
||||
.flat()
|
||||
.find(({ id }) => id === service.table_id)
|
||||
|
||||
if (service.table_id && tableSelected) {
|
||||
return `${this.name} - ${tableSelected.name}`
|
||||
}
|
||||
|
||||
|
@ -106,6 +107,13 @@ export class LocalBaserowListRowsServiceType extends ServiceType {
|
|||
return LocalBaserowListRowsForm
|
||||
}
|
||||
|
||||
/**
|
||||
* The Local Baserow adhoc filtering, sorting and searching component.
|
||||
*/
|
||||
get adhocHeaderComponent() {
|
||||
return LocalBaserowAdhocHeader
|
||||
}
|
||||
|
||||
isValid(service) {
|
||||
return super.isValid(service) && Boolean(service.table_id)
|
||||
}
|
||||
|
@ -199,12 +207,12 @@ export class LocalBaserowListRowsServiceType extends ServiceType {
|
|||
|
||||
const databases = integration.context_data?.databases
|
||||
|
||||
if (service.table_id && databases) {
|
||||
const tableSelected = databases
|
||||
.map((database) => database.tables)
|
||||
.flat()
|
||||
.find(({ id }) => id === service.table_id)
|
||||
const tableSelected = databases
|
||||
.map((database) => database.tables)
|
||||
.flat()
|
||||
.find(({ id }) => id === service.table_id)
|
||||
|
||||
if (service.table_id && tableSelected) {
|
||||
return `${this.name} - ${tableSelected.name}`
|
||||
}
|
||||
|
||||
|
|
|
@ -47,10 +47,17 @@ exports[`ChoiceElement as default 1`] = `
|
|||
|
||||
<ul
|
||||
class="select__items"
|
||||
style="display: none;"
|
||||
tabindex="-1"
|
||||
/>
|
||||
|
||||
<!---->
|
||||
<div
|
||||
class="select__items--empty"
|
||||
>
|
||||
|
||||
dropdown.empty
|
||||
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
|
@ -180,6 +187,7 @@ exports[`ChoiceElement as manual dropdown 1`] = `
|
|||
|
||||
<ul
|
||||
class="select__items"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
|
|
@ -49,9 +49,10 @@ exports[`RecordSelectorElement does not paginate if API returns 400/404 1`] = `
|
|||
|
||||
<ul
|
||||
class="select__items"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
|
||||
|
||||
<section
|
||||
class="infinite-scroll"
|
||||
>
|
||||
|
@ -263,9 +264,10 @@ exports[`RecordSelectorElement does not paginate if API returns 400/404 2`] = `
|
|||
|
||||
<ul
|
||||
class="select__items"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
|
||||
|
||||
<section
|
||||
class="infinite-scroll"
|
||||
>
|
||||
|
@ -477,9 +479,10 @@ exports[`RecordSelectorElement does not paginate if API returns 400/404 3`] = `
|
|||
|
||||
<ul
|
||||
class="select__items"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
|
||||
|
||||
<section
|
||||
class="infinite-scroll"
|
||||
>
|
||||
|
@ -691,9 +694,10 @@ exports[`RecordSelectorElement resolves suffix formulas 1`] = `
|
|||
|
||||
<ul
|
||||
class="select__items"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
|
||||
|
||||
<section
|
||||
class="infinite-scroll"
|
||||
>
|
||||
|
@ -827,9 +831,10 @@ exports[`RecordSelectorElement resolves suffix formulas 2`] = `
|
|||
|
||||
<ul
|
||||
class="select__items"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
|
||||
|
||||
<section
|
||||
class="infinite-scroll"
|
||||
>
|
||||
|
|
|
@ -44,6 +44,7 @@ exports[`Dropdown component Test slots 1`] = `
|
|||
|
||||
<ul
|
||||
class="select__items"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
@ -114,6 +115,7 @@ exports[`Dropdown component With items 1`] = `
|
|||
|
||||
<ul
|
||||
class="select__items"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
@ -199,6 +201,7 @@ exports[`Dropdown component With items 2`] = `
|
|||
|
||||
<ul
|
||||
class="select__items"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
@ -510,10 +513,17 @@ exports[`Dropdown component basics 1`] = `
|
|||
|
||||
<ul
|
||||
class="select__items"
|
||||
style="display: none;"
|
||||
tabindex="-1"
|
||||
/>
|
||||
|
||||
<!---->
|
||||
<div
|
||||
class="select__items--empty"
|
||||
>
|
||||
|
||||
dropdown.empty
|
||||
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
|
@ -546,10 +556,17 @@ exports[`Dropdown component basics 2`] = `
|
|||
|
||||
<ul
|
||||
class="select__items"
|
||||
style="display: none;"
|
||||
tabindex="-1"
|
||||
/>
|
||||
|
||||
<!---->
|
||||
<div
|
||||
class="select__items--empty"
|
||||
>
|
||||
|
||||
dropdown.empty
|
||||
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
|
@ -595,10 +612,17 @@ exports[`Dropdown component basics 3`] = `
|
|||
|
||||
<ul
|
||||
class="select__items"
|
||||
style="display: none;"
|
||||
tabindex="-1"
|
||||
/>
|
||||
|
||||
<!---->
|
||||
<div
|
||||
class="select__items--empty"
|
||||
>
|
||||
|
||||
dropdown.empty
|
||||
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
|
@ -649,6 +673,7 @@ exports[`Dropdown component change value prop 1`] = `
|
|||
|
||||
<ul
|
||||
class="select__items"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
@ -765,6 +790,7 @@ exports[`Dropdown component test focus 1`] = `
|
|||
|
||||
<ul
|
||||
class="select__items prevent-scroll"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
@ -850,6 +876,7 @@ exports[`Dropdown component test focus 2`] = `
|
|||
|
||||
<ul
|
||||
class="select__items prevent-scroll"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
@ -935,6 +962,7 @@ exports[`Dropdown component test interactions 1`] = `
|
|||
|
||||
<ul
|
||||
class="select__items prevent-scroll"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
@ -1051,6 +1079,7 @@ exports[`Dropdown component test interactions 2`] = `
|
|||
|
||||
<ul
|
||||
class="select__items prevent-scroll"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
|
|
@ -95,6 +95,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
|
||||
<ul
|
||||
class="select__items"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
@ -314,6 +315,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
|
||||
<ul
|
||||
class="select__items"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
@ -589,6 +591,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
|
||||
<ul
|
||||
class="select__items"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
@ -1874,6 +1877,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
|
||||
<ul
|
||||
class="select__items"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
@ -2093,6 +2097,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
|
||||
<ul
|
||||
class="select__items"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
@ -2368,6 +2373,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
|
||||
<ul
|
||||
class="select__items"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
|
|
@ -146,6 +146,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
|
||||
<ul
|
||||
class="select__items select__items--no-max-height"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
@ -293,6 +294,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
|
||||
<ul
|
||||
class="select__items select__items--no-max-height"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
@ -653,6 +655,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
|
||||
<ul
|
||||
class="select__items select__items--no-max-height"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
@ -776,6 +779,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
|
||||
<ul
|
||||
class="select__items select__items--no-max-height"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
@ -923,6 +927,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
|
||||
<ul
|
||||
class="select__items select__items--no-max-height"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
@ -1291,6 +1296,7 @@ exports[`ViewFilterForm match snapshots Test rating filter 1`] = `
|
|||
|
||||
<ul
|
||||
class="select__items select__items--no-max-height"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
@ -1439,6 +1445,7 @@ exports[`ViewFilterForm match snapshots Test rating filter 1`] = `
|
|||
|
||||
<ul
|
||||
class="select__items select__items--no-max-height prevent-scroll"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
@ -1807,6 +1814,7 @@ exports[`ViewFilterForm match snapshots Test rating filter 2`] = `
|
|||
|
||||
<ul
|
||||
class="select__items select__items--no-max-height"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
@ -1955,6 +1963,7 @@ exports[`ViewFilterForm match snapshots Test rating filter 2`] = `
|
|||
|
||||
<ul
|
||||
class="select__items select__items--no-max-height prevent-scroll"
|
||||
style=""
|
||||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
|
|
Loading…
Add table
Reference in a new issue