1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-06 22:08:52 +00:00

Merge branch '3453-implement-distribution-aggregation-in-the-local-baserow-aggregate-rows-service' into 'develop'

Draft: Resolve "Implement distribution aggregation in the Local Baserow aggregate rows service."

Closes 

See merge request 
This commit is contained in:
Peter Evans 2025-03-27 17:03:16 +00:00
commit d3ba29aceb
16 changed files with 181 additions and 100 deletions
backend
src/baserow/contrib
database
integrations/local_baserow
tests/baserow/contrib/integrations/local_baserow/service_types
web-frontend/modules

View file

@ -243,6 +243,7 @@ class DatabaseConfig(AppConfig):
CheckedFieldAggregationType, CheckedFieldAggregationType,
CheckedPercentageFieldAggregationType, CheckedPercentageFieldAggregationType,
CountFieldAggregationType, CountFieldAggregationType,
DistributionFieldAggregationType,
EarliestDateFieldAggregationType, EarliestDateFieldAggregationType,
EmptyCountFieldAggregationType, EmptyCountFieldAggregationType,
EmptyPercentageFieldAggregationType, EmptyPercentageFieldAggregationType,
@ -279,6 +280,7 @@ class DatabaseConfig(AppConfig):
field_aggregation_registry.register(StdDevFieldAggregationType()) field_aggregation_registry.register(StdDevFieldAggregationType())
field_aggregation_registry.register(VarianceFieldAggregationType()) field_aggregation_registry.register(VarianceFieldAggregationType())
field_aggregation_registry.register(MedianFieldAggregationType()) field_aggregation_registry.register(MedianFieldAggregationType())
field_aggregation_registry.register(DistributionFieldAggregationType())
from .fields.field_converters import ( from .fields.field_converters import (
AutonumberFieldConverter, AutonumberFieldConverter,

View file

@ -35,6 +35,7 @@ from baserow.contrib.database.formula.types.formula_types import (
from baserow.contrib.database.views.view_aggregations import ( from baserow.contrib.database.views.view_aggregations import (
AverageViewAggregationType, AverageViewAggregationType,
CountViewAggregationType, CountViewAggregationType,
DistributionViewAggregationType,
EmptyCountViewAggregationType, EmptyCountViewAggregationType,
MaxViewAggregationType, MaxViewAggregationType,
MedianViewAggregationType, MedianViewAggregationType,
@ -341,3 +342,14 @@ class MedianFieldAggregationType(FieldAggregationType):
type = "median" type = "median"
raw_type = MedianViewAggregationType raw_type = MedianViewAggregationType
compatible_field_types = raw_type.compatible_field_types compatible_field_types = raw_type.compatible_field_types
class DistributionFieldAggregationType(FieldAggregationType):
"""
Compute the distribution of values
"""
type = "distribution"
result_type = "array"
raw_type = DistributionViewAggregationType
compatible_field_types = raw_type.compatible_field_types

View file

@ -52,7 +52,10 @@ from baserow.contrib.database.views.exceptions import (
AggregationTypeAlreadyRegistered, AggregationTypeAlreadyRegistered,
AggregationTypeDoesNotExist, AggregationTypeDoesNotExist,
) )
from baserow.contrib.database.views.utils import AnnotatedAggregation from baserow.contrib.database.views.utils import (
AnnotatedAggregation,
DistributionAggregation,
)
from baserow.core.registries import ImportExportConfig from baserow.core.registries import ImportExportConfig
from baserow.core.registry import ( from baserow.core.registry import (
APIUrlsInstanceMixin, APIUrlsInstanceMixin,
@ -2171,7 +2174,7 @@ class FieldConverter(Instance):
""" """
raise NotImplementedError( raise NotImplementedError(
"Each field converter must have an alter_field " "method." "Each field converter must have an alter_field method."
) )
@ -2235,6 +2238,7 @@ class FieldAggregationType(Instance):
raise IncompatibleField() raise IncompatibleField()
aggregation_dict = self._get_aggregation_dict(queryset, model_field, field) aggregation_dict = self._get_aggregation_dict(queryset, model_field, field)
distribution_dict = self._get_distribution_dict(queryset, model_field, field)
# Check if the returned aggregations contain a `AnnotatedAggregation`, # Check if the returned aggregations contain a `AnnotatedAggregation`,
# and if so, apply the annotations and only keep the actual aggregation in # and if so, apply the annotations and only keep the actual aggregation in
@ -2246,8 +2250,14 @@ class FieldAggregationType(Instance):
aggregation_dict[key] = value.aggregation aggregation_dict[key] = value.aggregation
results = queryset.aggregate(**aggregation_dict) results = queryset.aggregate(**aggregation_dict)
results.update(distribution_dict)
raw_aggregation_result = results.get(
f"{field.db_column}_raw", results.get(field.db_column)
)
return self._compute_final_aggregation( return self._compute_final_aggregation(
results[f"{field.db_column}_raw"], results.get("total", None) raw_aggregation_result,
results.get("total", None),
) )
def field_is_compatible(self, field: "Field") -> bool: def field_is_compatible(self, field: "Field") -> bool:
@ -2259,8 +2269,6 @@ class FieldAggregationType(Instance):
:return: True if the field is compatible, False otherwise. :return: True if the field is compatible, False otherwise.
""" """
from baserow.contrib.database.fields.registries import field_type_registry
field_type = field_type_registry.get_by_model(field.specific_class) field_type = field_type_registry.get_by_model(field.specific_class)
return any( return any(
@ -2280,6 +2288,36 @@ class FieldAggregationType(Instance):
return self.raw_type().get_aggregation(field.db_column, model_field, field) return self.raw_type().get_aggregation(field.db_column, model_field, field)
def _get_distribution_dict(
self, queryset: QuerySet, model_field: DjangoField, field: Field
) -> dict[str, any]:
"""
Returns a dictionary defining the distributions for the queryset.aggregate
call.
:param queryset: The queryset to select only the rows that should
be aggregated.
:param model_field: The Django model field of the field that
the aggregation is for.
:param field: The field that the aggregation is for.
:return:
"""
aggregation = self._get_raw_aggregation(model_field, field.specific)
if not isinstance(aggregation, DistributionAggregation):
return {}
formatted = []
raw_calculation = aggregation.calculate(queryset.all())
for result in raw_calculation:
formatted.append(
{
"value": result[0],
"count": result[1],
}
)
return {field.db_column: formatted}
def _get_aggregation_dict( def _get_aggregation_dict(
self, self,
queryset: QuerySet, queryset: QuerySet,
@ -2288,7 +2326,7 @@ class FieldAggregationType(Instance):
include_agg_type=False, include_agg_type=False,
) -> dict: ) -> dict:
""" """
Returns a dictinary defining the aggregation for the queryset.aggregate Returns a dictionary defining the aggregation for the queryset.aggregate
call. call.
:param queryset: The queryset to select only the rows that should :param queryset: The queryset to select only the rows that should
@ -2301,6 +2339,9 @@ class FieldAggregationType(Instance):
aggregation = self._get_raw_aggregation(model_field, field.specific) aggregation = self._get_raw_aggregation(model_field, field.specific)
key = f"{field.db_column}_{self.type}" if include_agg_type else field.db_column key = f"{field.db_column}_{self.type}" if include_agg_type else field.db_column
aggregation_dict = {f"{key}_raw": aggregation} aggregation_dict = {f"{key}_raw": aggregation}
if isinstance(aggregation, DistributionAggregation):
return {}
# Check if the returned aggregations contain a `AnnotatedAggregation`, # Check if the returned aggregations contain a `AnnotatedAggregation`,
# and if so, apply the annotations and only keep the actual aggregation in # and if so, apply the annotations and only keep the actual aggregation in
# the dict. This is needed because some aggregations require annotated values # the dict. This is needed because some aggregations require annotated values
@ -2314,7 +2355,9 @@ class FieldAggregationType(Instance):
return aggregation_dict return aggregation_dict
def _compute_final_aggregation(self, raw_aggregation_result, total_count: int): def _compute_final_aggregation(
self, raw_aggregation_result, total_count: Optional[int] = None
):
""" """
For field aggregation types that require 'with_total' the number of all For field aggregation types that require 'with_total' the number of all
rows to compute the final number this method will be called to compute rows to compute the final number this method will be called to compute

View file

@ -64,9 +64,6 @@ from baserow.contrib.database.views.exceptions import (
) )
from baserow.contrib.database.views.models import DEFAULT_SORT_TYPE_KEY from baserow.contrib.database.views.models import DEFAULT_SORT_TYPE_KEY
from baserow.contrib.database.views.service import ViewService from baserow.contrib.database.views.service import ViewService
from baserow.contrib.database.views.view_aggregations import (
DistributionViewAggregationType,
)
from baserow.contrib.integrations.local_baserow.api.serializers import ( from baserow.contrib.integrations.local_baserow.api.serializers import (
LocalBaserowTableServiceFieldMappingSerializer, LocalBaserowTableServiceFieldMappingSerializer,
) )
@ -1255,10 +1252,6 @@ class LocalBaserowAggregateRowsUserServiceType(
dispatch_type = DispatchTypes.DISPATCH_DATA_SOURCE dispatch_type = DispatchTypes.DISPATCH_DATA_SOURCE
serializer_mixins = LocalBaserowTableServiceFilterableMixin.mixin_serializer_mixins serializer_mixins = LocalBaserowTableServiceFilterableMixin.mixin_serializer_mixins
# Local Baserow aggregate rows does not currently support the distribution
# aggregation type, this will be resolved in a future release.
unsupported_aggregation_types = [DistributionViewAggregationType.type]
def get_schema_name(self, service: LocalBaserowAggregateRows) -> str: def get_schema_name(self, service: LocalBaserowAggregateRows) -> str:
""" """
The Local Baserow aggregation schema name added to the `title` in The Local Baserow aggregation schema name added to the `title` in
@ -1291,24 +1284,43 @@ class LocalBaserowAggregateRowsUserServiceType(
if not service.field or not service.aggregation_type: if not service.field or not service.aggregation_type:
return None return None
result_key = "results" if self.returns_list(service) else "result"
# The `result` must be an allowed field, otherwise we have no schema. # The `result` must be an allowed field, otherwise we have no schema.
if allowed_fields is not None and "result" not in allowed_fields: if allowed_fields is not None and result_key not in allowed_fields:
return {} return {}
# Pluck out the aggregation type which this service uses. We'll use its # Pluck out the aggregation type which this service uses. We'll use its
# `result_type` to inform the schema what the expected `result` format is. # `result_type` to inform the schema what the expected `result` format is.
aggregation_type = field_aggregation_registry.get(service.aggregation_type) aggregation_type = field_aggregation_registry.get(service.aggregation_type)
return { schema = {"title": self.get_schema_name(service)}
"title": self.get_schema_name(service),
"type": "object", if aggregation_type.result_type == "array":
"properties": { schema["type"] = "array"
"result": { schema["items"] = {
"type": "object",
"properties": {
"value": {
"title": f"{service.field.name} value",
"type": "string",
},
"count": {
"title": f"{service.field.name} distribution",
"type": "number",
},
},
}
else:
schema["type"] = "object"
schema["properties"] = {
f"{result_key}": {
"title": f"{service.field.name} result", "title": f"{service.field.name} result",
"type": aggregation_type.result_type, "type": aggregation_type.result_type,
} }
}, }
}
return schema
def get_context_data( def get_context_data(
self, self,
@ -1377,6 +1389,10 @@ class LocalBaserowAggregateRowsUserServiceType(
field_id: int field_id: int
aggregation_type: str aggregation_type: str
def returns_list(self, service: LocalBaserowAggregateRows) -> bool:
aggregation_type = field_aggregation_registry.get(service.aggregation_type)
return aggregation_type.result_type == "array"
def prepare_values( def prepare_values(
self, self,
values: Dict[str, Any], values: Dict[str, Any],
@ -1403,13 +1419,6 @@ class LocalBaserowAggregateRowsUserServiceType(
"aggregation_type", getattr(instance, "aggregation_type", "") "aggregation_type", getattr(instance, "aggregation_type", "")
) )
if aggregation_type in self.unsupported_aggregation_types:
raise DRFValidationError(
detail=f"The {aggregation_type} aggregation type "
"is not currently supported.",
code="unsupported_aggregation_type",
)
if "table" in values: if "table" in values:
# Reset the field if the table has changed # Reset the field if the table has changed
if ( if (
@ -1563,9 +1572,12 @@ class LocalBaserowAggregateRowsUserServiceType(
:return: Aggregations. :return: Aggregations.
""" """
only_field_names = self.get_used_field_names(service, dispatch_context) result_key = "results" if self.returns_list(service) else "result"
if only_field_names and "result" not in only_field_names:
return {"data": {"result": None}} # TODO: resolve
# only_field_names = self.get_used_field_names(service, dispatch_context)
# if only_field_names and result_key not in only_field_names:
# return {"data": {result_key: []}}
try: try:
table = resolved_values["table"] table = resolved_values["table"]
@ -1583,7 +1595,7 @@ class LocalBaserowAggregateRowsUserServiceType(
result = agg_type.aggregate(queryset, model_field, field) result = agg_type.aggregate(queryset, model_field, field)
return { return {
"data": {"result": result}, "data": {result_key: result},
"baserow_table_model": model, "baserow_table_model": model,
} }
except DjangoFieldDoesNotExist as ex: except DjangoFieldDoesNotExist as ex:
@ -1615,10 +1627,8 @@ class LocalBaserowAggregateRowsUserServiceType(
Returns the usual properties for this service type. Returns the usual properties for this service type.
""" """
if path[0] == "result": # TODO: confirm this is right
return ["result"] return ["result", "value", "count"]
return []
class LocalBaserowGetRowUserServiceType( class LocalBaserowGetRowUserServiceType(

View file

@ -587,29 +587,3 @@ def test_local_baserow_aggregate_rows_dispatch_data_field_type_not_compatible_an
exc.value.args[0] == f"The field with ID {field.id} is not compatible " exc.value.args[0] == f"The field with ID {field.id} is not compatible "
f"with the aggregation type {service.aggregation_type}" f"with the aggregation type {service.aggregation_type}"
) )
@pytest.mark.django_db
def test_create_local_baserow_aggregate_rows_service_with_unsupported_aggregation_type(
data_fixture,
):
user = data_fixture.create_user()
page = data_fixture.create_builder_page(user=user)
dashboard = page.builder
table = data_fixture.create_database_table(user=user)
field = data_fixture.create_number_field(table=table)
view = data_fixture.create_grid_view(user=user, table=table)
integration = data_fixture.create_local_baserow_integration(
application=dashboard, user=user
)
service = data_fixture.create_local_baserow_aggregate_rows_service(
table=table, field=field, integration=integration
)
service_type = service.get_type()
unsupported_agg_type = service_type.unsupported_aggregation_types[0]
with pytest.raises(
ValidationError,
match=f"The {unsupported_agg_type} aggregation type is not currently supported.",
):
service_type.prepare_values({"aggregation_type": unsupported_agg_type}, user)

View file

@ -86,7 +86,7 @@ export default {
return dataSource.name return dataSource.name
} }
const service = this.$registry.get('service', dataSource.type) const service = this.$registry.get('service', dataSource.type)
const suffix = service.returnsList const suffix = service.returnsList({ service: dataSource })
? this.$t('integrationsCommon.multipleRows') ? this.$t('integrationsCommon.multipleRows')
: this.$t('integrationsCommon.singleRow') : this.$t('integrationsCommon.singleRow')
return `${dataSource.name} (${suffix})` return `${dataSource.name} (${suffix})`

View file

@ -1,7 +1,7 @@
<template> <template>
<component <component
:is="serviceType.adhocHeaderComponent" :is="serviceType.adhocHeaderComponent"
v-if="dataSource" v-if="dataSource && elementType.adhocRefinementsSupported(dataSource)"
class="collection-element__header margin-bottom-1" class="collection-element__header margin-bottom-1"
:sortable-properties=" :sortable-properties="
elementType.adhocSortableProperties(element, dataSource) elementType.adhocSortableProperties(element, dataSource)

View file

@ -190,15 +190,17 @@ export default {
if (this.localDataSources === null) { if (this.localDataSources === null) {
return null return null
} }
return this.localDataSources.filter( return this.localDataSources.filter((dataSource) =>
(dataSource) => this.$registry
this.$registry.get('service', dataSource.type).returnsList .get('service', dataSource.type)
.returnsList({ service: dataSource })
) )
}, },
listSharedDataSources() { listSharedDataSources() {
return this.sharedDataSources.filter( return this.sharedDataSources.filter((dataSource) =>
(dataSource) => this.$registry
this.$registry.get('service', dataSource.type).returnsList .get('service', dataSource.type)
.returnsList({ service: dataSource })
) )
}, },
}, },

View file

@ -117,7 +117,7 @@ export class DataSourceDataProviderType extends DataProviderType {
const serviceType = this.app.$registry.get('service', dataSource.type) const serviceType = this.app.$registry.get('service', dataSource.type)
if (serviceType.returnsList) { if (serviceType.returnsList({ service: dataSource })) {
return dataSourceContents[dataSource.id]?.results return dataSourceContents[dataSource.id]?.results
} else { } else {
return dataSourceContents[dataSource.id] return dataSourceContents[dataSource.id]

View file

@ -1,4 +1,5 @@
import { ELEMENT_EVENTS, SHARE_TYPES } from '@baserow/modules/builder/enums' import { ELEMENT_EVENTS, SHARE_TYPES } from '@baserow/modules/builder/enums'
import { LocalBaserowAggregateRowsServiceType } from '@baserow/modules/integrations/serviceTypes'
export const ContainerElementTypeMixin = (Base) => export const ContainerElementTypeMixin = (Base) =>
class extends Base { class extends Base {
@ -54,6 +55,18 @@ export const CollectionElementTypeMixin = (Base) =>
class extends Base { class extends Base {
isCollectionElement = true isCollectionElement = true
/**
* Response for returning whether this collection element, using this dataSource,
* supports adhoc filtering (e.g. filtering, sorting, searching). Normally they
* will, but if the collection element is used by an aggregation returning a list
* of records, then it will not.
* @param dataSource
*/
adhocRefinementsSupported(dataSource) {
const serviceType = this.app.$registry.get('service', dataSource.type)
return serviceType.adhocRefinementsSupported
}
/** /**
* A helper function responsible for returning this collection element's * A helper function responsible for returning this collection element's
* schema properties. * schema properties.
@ -326,7 +339,10 @@ export const CollectionElementTypeMixin = (Base) =>
const serviceType = this.app.$registry.get('service', dataSource.type) const serviceType = this.app.$registry.get('service', dataSource.type)
// If the data source type doesn't return a list, we should have a schema_property // If the data source type doesn't return a list, we should have a schema_property
if (!serviceType.returnsList && !parentWithDataSource.schema_property) { if (
!serviceType.returnsList({ service: dataSource }) &&
!parentWithDataSource.schema_property
) {
return true return true
} }

View file

@ -49,7 +49,13 @@ export default {
* @returns {boolean} - Whether the property options are available. * @returns {boolean} - Whether the property options are available.
*/ */
propertyOptionsAvailable() { propertyOptionsAvailable() {
return this.selectedDataSource && this.selectedDataSourceReturnsList const { element } = this.applicationContext
const elementType = this.$registry.get('element', element.type)
return (
this.selectedDataSource &&
this.selectedDataSourceReturnsList &&
elementType.adhocRefinementsSupported(this.selectedDataSource)
)
}, },
/** /**
* In collection element forms, the ability to view paging options * In collection element forms, the ability to view paging options
@ -150,7 +156,9 @@ export default {
return this.$registry.get('service', this.selectedDataSource.type) return this.$registry.get('service', this.selectedDataSource.type)
}, },
selectedDataSourceReturnsList() { selectedDataSourceReturnsList() {
return this.selectedDataSourceType?.returnsList return this.selectedDataSourceType?.returnsList({
service: this.selectedDataSource,
})
}, },
maxItemPerPage() { maxItemPerPage() {
if (!this.selectedDataSourceType) { if (!this.selectedDataSourceType) {

View file

@ -179,7 +179,10 @@ const actions = {
// We have a data source, but if it doesn't return a list, // We have a data source, but if it doesn't return a list,
// it needs to have a `schema_property` to work correctly. // it needs to have a `schema_property` to work correctly.
if (!serviceType.returnsList && element.schema_property === null) { if (
!serviceType.returnsList({ service: dataSource }) &&
element.schema_property === null
) {
// If we previously had a list data source, we might have content, // If we previously had a list data source, we might have content,
// so rather than leave the content *until a schema property is set*, // so rather than leave the content *until a schema property is set*,
// clear it. // clear it.
@ -246,7 +249,7 @@ const actions = {
}) })
} }
if (serviceType.returnsList) { if (serviceType.returnsList({ service: dataSource })) {
// The service type returns a list of results, we'll set the content // The service type returns a list of results, we'll set the content
// using the results key and set the range for future paging. // using the results key and set the range for future paging.
commit('SET_CONTENT', { commit('SET_CONTENT', {

View file

@ -29,9 +29,11 @@ export class ServiceType extends Registerable {
} }
/** /**
* Whether the service returns a collection of records. * Whether the service returns a collection of records. Most of the time this
* will simply return true or false, but a service can be provided to determine
* if a specific service type returns a list of records.
*/ */
get returnsList() { returnsList({ service }) {
return false return false
} }

View file

@ -112,9 +112,6 @@
:key="viewAggregation.getType()" :key="viewAggregation.getType()"
:name="viewAggregation.getName()" :name="viewAggregation.getName()"
:value="viewAggregation.getType()" :value="viewAggregation.getType()"
:disabled="
unsupportedAggregationTypes.includes(viewAggregation.getType())
"
> >
</DropdownItem> </DropdownItem>
</Dropdown> </Dropdown>
@ -227,10 +224,6 @@ export default {
aggregationTypeNames() { aggregationTypeNames() {
return this.viewAggregationTypes.map((aggType) => aggType.getType()) return this.viewAggregationTypes.map((aggType) => aggType.getType())
}, },
unsupportedAggregationTypes() {
return this.$registry.get('service', 'local_baserow_aggregate_rows')
.unsupportedAggregationTypes
},
}, },
watch: { watch: {
dataSource: { dataSource: {

View file

@ -45,9 +45,6 @@
:key="viewAggregation.getType()" :key="viewAggregation.getType()"
:name="viewAggregation.getName()" :name="viewAggregation.getName()"
:value="viewAggregation.getType()" :value="viewAggregation.getType()"
:disabled="
unsupportedAggregationTypes.includes(viewAggregation.getType())
"
> >
</DropdownItem> </DropdownItem>
</Dropdown> </Dropdown>
@ -130,10 +127,6 @@ export default {
} }
}, },
computed: { computed: {
unsupportedAggregationTypes() {
return this.$registry.get('service', 'local_baserow_aggregate_rows')
.unsupportedAggregationTypes
},
viewAggregationTypes() { viewAggregationTypes() {
const selectedField = this.tableFields.find( const selectedField = this.tableFields.find(
(field) => field.id === this.values.field_id (field) => field.id === this.values.field_id

View file

@ -8,6 +8,11 @@ import LocalBaserowAdhocHeader from '@baserow/modules/integrations/localBaserow/
import { DistributionViewAggregationType } from '@baserow/modules/database/viewAggregationTypes' import { DistributionViewAggregationType } from '@baserow/modules/database/viewAggregationTypes'
export class LocalBaserowTableServiceType extends ServiceType { export class LocalBaserowTableServiceType extends ServiceType {
// Determines whether collection elements with data sources using this
// service are allowed to perform adhoc refinements (filtering, sorting, searching).
// By default, they cannot, only the list rows service type can.
adhocRefinementsSupported = false
get integrationType() { get integrationType() {
return this.app.$registry.get( return this.app.$registry.get(
'integration', 'integration',
@ -112,6 +117,8 @@ export class LocalBaserowGetRowServiceType extends LocalBaserowTableServiceType
} }
export class LocalBaserowListRowsServiceType extends LocalBaserowTableServiceType { export class LocalBaserowListRowsServiceType extends LocalBaserowTableServiceType {
adhocRefinementsSupported = true
static getType() { static getType() {
return 'local_baserow_list_rows' return 'local_baserow_list_rows'
} }
@ -131,7 +138,7 @@ export class LocalBaserowListRowsServiceType extends LocalBaserowTableServiceTyp
return LocalBaserowAdhocHeader return LocalBaserowAdhocHeader
} }
get returnsList() { returnsList({ service }) {
return true return true
} }
@ -234,6 +241,15 @@ export class LocalBaserowAggregateRowsServiceType extends LocalBaserowTableServi
return 'local_baserow_aggregate_rows' return 'local_baserow_aggregate_rows'
} }
returnsList({ service }) {
// TODO: store this in the registry
return service.aggregation_type === 'distribution'
}
get maxResultLimit() {
return 100
}
get name() { get name() {
return this.app.i18n.t('serviceType.localBaserowAggregateRows') return this.app.i18n.t('serviceType.localBaserowAggregateRows')
} }
@ -242,12 +258,19 @@ export class LocalBaserowAggregateRowsServiceType extends LocalBaserowTableServi
return LocalBaserowAggregateRowsForm return LocalBaserowAggregateRowsForm
} }
/** getDefaultCollectionFields(service) {
* Local Baserow aggregate rows does not currently support the distribution return Object.keys(service.schema.items.properties)
* aggregation type, this will be resolved in a future release. .filter((field) => field !== 'id')
*/ .map((field) => {
get unsupportedAggregationTypes() { const outputType = 'text'
return [DistributionViewAggregationType.getType()] const valueFormula = `get('current_record.${field}')`
return {
name: service.schema.items.properties[field].title,
type: outputType,
value: valueFormula,
id: uuid(),
}
})
} }
getResult(service, data) { getResult(service, data) {