mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-06 22:08:52 +00:00
Grouped aggregate service group by registry
This commit is contained in:
parent
054468ec06
commit
ddd4a4d257
6 changed files with 247 additions and 5 deletions
enterprise
backend
src/baserow_enterprise
tests/baserow_enterprise_tests/integrations/local_baserow/service_types
web-frontend/modules/baserow_enterprise
|
@ -156,6 +156,33 @@ class BaserowEnterpriseConfig(AppConfig):
|
|||
grouped_aggregation_registry.register(VarianceFieldAggregationType())
|
||||
grouped_aggregation_registry.register(MedianFieldAggregationType())
|
||||
|
||||
from baserow.contrib.database.fields.field_types import (
|
||||
AutonumberFieldType,
|
||||
BooleanFieldType,
|
||||
EmailFieldType,
|
||||
LongTextFieldType,
|
||||
NumberFieldType,
|
||||
PhoneNumberFieldType,
|
||||
RatingFieldType,
|
||||
SingleSelectFieldType,
|
||||
TextFieldType,
|
||||
URLFieldType,
|
||||
)
|
||||
from baserow_enterprise.integrations.registries import (
|
||||
grouped_aggregation_group_by_registry,
|
||||
)
|
||||
|
||||
grouped_aggregation_group_by_registry.register(TextFieldType())
|
||||
grouped_aggregation_group_by_registry.register(LongTextFieldType())
|
||||
grouped_aggregation_group_by_registry.register(URLFieldType())
|
||||
grouped_aggregation_group_by_registry.register(EmailFieldType())
|
||||
grouped_aggregation_group_by_registry.register(NumberFieldType())
|
||||
grouped_aggregation_group_by_registry.register(RatingFieldType())
|
||||
grouped_aggregation_group_by_registry.register(BooleanFieldType())
|
||||
grouped_aggregation_group_by_registry.register(PhoneNumberFieldType())
|
||||
grouped_aggregation_group_by_registry.register(AutonumberFieldType())
|
||||
grouped_aggregation_group_by_registry.register(SingleSelectFieldType())
|
||||
|
||||
from baserow.core.registries import subject_type_registry
|
||||
|
||||
subject_type_registry.register(TeamSubjectType())
|
||||
|
|
|
@ -3,6 +3,7 @@ from django.db.models import F
|
|||
|
||||
from rest_framework.exceptions import ValidationError as DRFValidationError
|
||||
|
||||
from baserow.contrib.database.fields.exceptions import FieldTypeDoesNotExist
|
||||
from baserow.contrib.database.views.exceptions import AggregationTypeDoesNotExist
|
||||
from baserow.contrib.database.views.utils import AnnotatedAggregation
|
||||
from baserow.contrib.integrations.local_baserow.integration_types import (
|
||||
|
@ -28,7 +29,10 @@ from baserow_enterprise.api.integrations.local_baserow.serializers import (
|
|||
from baserow_enterprise.integrations.local_baserow.models import (
|
||||
LocalBaserowGroupedAggregateRows,
|
||||
)
|
||||
from baserow_enterprise.integrations.registries import grouped_aggregation_registry
|
||||
from baserow_enterprise.integrations.registries import (
|
||||
grouped_aggregation_group_by_registry,
|
||||
grouped_aggregation_registry,
|
||||
)
|
||||
from baserow_enterprise.services.types import (
|
||||
ServiceAggregationGroupByDict,
|
||||
ServiceAggregationSeriesDict,
|
||||
|
@ -186,7 +190,8 @@ class LocalBaserowGroupedAggregateRowsUserServiceType(
|
|||
group_bys: list[ServiceAggregationGroupByDict] | None = None,
|
||||
):
|
||||
with atomic_if_not_already():
|
||||
table_field_ids = service.table.field_set.values_list("id", flat=True)
|
||||
table_fields = service.table.field_set.all()
|
||||
table_field_ids = [field.id for field in table_fields]
|
||||
|
||||
def validate_agg_group_by(group_by):
|
||||
if (
|
||||
|
@ -199,6 +204,23 @@ class LocalBaserowGroupedAggregateRowsUserServiceType(
|
|||
code="invalid_field",
|
||||
)
|
||||
|
||||
field = next(
|
||||
(
|
||||
field
|
||||
for field in table_fields
|
||||
if field.id == group_by["field_id"]
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
grouped_aggregation_group_by_registry.get_by_type(field.get_type())
|
||||
except FieldTypeDoesNotExist:
|
||||
raise DRFValidationError(
|
||||
detail=f"The field with ID {group_by['field_id']} cannot "
|
||||
"be used as a group by field.",
|
||||
code="invalid_field",
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
service.service_aggregation_group_bys.all().delete()
|
||||
|
@ -405,6 +427,14 @@ class LocalBaserowGroupedAggregateRowsUserServiceType(
|
|||
raise ServiceImproperlyConfigured(
|
||||
f"The field with ID {group_by.field.id} is trashed."
|
||||
)
|
||||
try:
|
||||
grouped_aggregation_group_by_registry.get_by_type(
|
||||
group_by.field.get_type()
|
||||
)
|
||||
except FieldTypeDoesNotExist:
|
||||
raise ServiceImproperlyConfigured(
|
||||
f"The field with ID {group_by.field.id} cannot be used for group by."
|
||||
)
|
||||
group_by_values.append(group_by.field.db_column)
|
||||
|
||||
if len(group_by_values) > 0:
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
from baserow.contrib.database.fields.registries import FieldAggregationType
|
||||
from baserow.contrib.database.fields.exceptions import (
|
||||
FieldTypeAlreadyRegistered,
|
||||
FieldTypeDoesNotExist,
|
||||
)
|
||||
from baserow.contrib.database.fields.registries import FieldAggregationType, FieldType
|
||||
from baserow.contrib.database.views.exceptions import (
|
||||
AggregationTypeAlreadyRegistered,
|
||||
AggregationTypeDoesNotExist,
|
||||
|
@ -20,3 +24,20 @@ class GroupedAggregationTypeRegistry(Registry[FieldAggregationType]):
|
|||
grouped_aggregation_registry: GroupedAggregationTypeRegistry = (
|
||||
GroupedAggregationTypeRegistry()
|
||||
)
|
||||
|
||||
|
||||
class GroupedAggregationGroupByRegistry(Registry[FieldType]):
|
||||
"""
|
||||
The main registry for storing field types compatible
|
||||
with the grouped aggregate service to be used as group by
|
||||
fields.
|
||||
"""
|
||||
|
||||
name = "grouped_aggregations_group_by"
|
||||
does_not_exist_exception_class = FieldTypeDoesNotExist
|
||||
already_registered_exception_class = FieldTypeAlreadyRegistered
|
||||
|
||||
|
||||
grouped_aggregation_group_by_registry: GroupedAggregationGroupByRegistry = (
|
||||
GroupedAggregationGroupByRegistry()
|
||||
)
|
||||
|
|
|
@ -220,6 +220,41 @@ def test_create_grouped_aggregate_rows_service_group_by_field_not_in_table(
|
|||
ServiceHandler().create_service(service_type, **values)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_grouped_aggregate_rows_service_group_by_field_not_compatible(
|
||||
data_fixture,
|
||||
):
|
||||
user = data_fixture.create_user()
|
||||
dashboard = data_fixture.create_dashboard_application(user=user)
|
||||
table = data_fixture.create_database_table(user=user)
|
||||
table_2 = data_fixture.create_database_table(user=user)
|
||||
field = data_fixture.create_number_field(table=table)
|
||||
field_2 = data_fixture.create_uuid_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_type = service_type_registry.get("local_baserow_grouped_aggregate_rows")
|
||||
values = service_type.prepare_values(
|
||||
{
|
||||
"view_id": view.id,
|
||||
"table_id": view.table_id,
|
||||
"integration_id": integration.id,
|
||||
"service_aggregation_series": [
|
||||
{"field_id": field.id, "aggregation_type": "sum"},
|
||||
],
|
||||
"service_aggregation_group_bys": [{"field_id": field_2.id}],
|
||||
},
|
||||
user,
|
||||
)
|
||||
|
||||
with pytest.raises(
|
||||
ValidationError,
|
||||
match=f"The field with ID {field_2.id} cannot be used as a group by field.",
|
||||
):
|
||||
ServiceHandler().create_service(service_type, **values)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_grouped_aggregate_rows_service_max_series_exceeded(
|
||||
data_fixture,
|
||||
|
@ -662,6 +697,46 @@ def test_update_grouped_aggregate_rows_service_group_by_field_not_in_table(
|
|||
ServiceHandler().update_service(service_type, service=service, **values)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_update_grouped_aggregate_rows_service_group_by_field_not_in_compatible(
|
||||
data_fixture,
|
||||
):
|
||||
user = data_fixture.create_user()
|
||||
dashboard = data_fixture.create_dashboard_application(user=user)
|
||||
table = data_fixture.create_database_table(user=user)
|
||||
field = data_fixture.create_number_field(table=table)
|
||||
field_2 = data_fixture.create_uuid_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_type = service_type_registry.get("local_baserow_grouped_aggregate_rows")
|
||||
service = data_fixture.create_service(
|
||||
LocalBaserowGroupedAggregateRows,
|
||||
integration=integration,
|
||||
table=table,
|
||||
view=view,
|
||||
)
|
||||
|
||||
values = service_type.prepare_values(
|
||||
{
|
||||
"table_id": table.id,
|
||||
"service_aggregation_series": [
|
||||
{"field_id": field.id, "aggregation_type": "sum"},
|
||||
],
|
||||
"service_aggregation_group_bys": [{"field_id": field_2.id}],
|
||||
},
|
||||
user,
|
||||
service,
|
||||
)
|
||||
|
||||
with pytest.raises(
|
||||
ValidationError,
|
||||
match=f"The field with ID {field_2.id} cannot be used as a group by field.",
|
||||
):
|
||||
ServiceHandler().update_service(service_type, service=service, **values)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_update_grouped_aggregate_rows_service_max_series_exceeded(
|
||||
data_fixture,
|
||||
|
@ -1298,6 +1373,38 @@ def test_grouped_aggregate_rows_service_group_by_field_trashed(data_fixture):
|
|||
assert exc.value.args[0] == f"The field with ID {field_2.id} is trashed."
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_grouped_aggregate_rows_service_group_by_field_not_compatible(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
dashboard = data_fixture.create_dashboard_application(user=user)
|
||||
table = data_fixture.create_database_table(user=user)
|
||||
field = data_fixture.create_number_field(table=table)
|
||||
field_2 = data_fixture.create_uuid_field(table=table)
|
||||
integration = data_fixture.create_local_baserow_integration(
|
||||
application=dashboard, user=user
|
||||
)
|
||||
service = data_fixture.create_service(
|
||||
LocalBaserowGroupedAggregateRows,
|
||||
integration=integration,
|
||||
table=table,
|
||||
)
|
||||
LocalBaserowTableServiceAggregationSeries.objects.create(
|
||||
service=service, field=field, aggregation_type="sum", order=1
|
||||
)
|
||||
LocalBaserowTableServiceAggregationGroupBy.objects.create(
|
||||
service=service, field=field_2, order=1
|
||||
)
|
||||
|
||||
dispatch_context = FakeDispatchContext()
|
||||
|
||||
with pytest.raises(ServiceImproperlyConfigured) as exc:
|
||||
ServiceHandler().dispatch_service(service, dispatch_context)
|
||||
assert (
|
||||
exc.value.args[0]
|
||||
== f"The field with ID {field_2.id} cannot be used for group by."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_grouped_aggregate_rows_service_table_trashed(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
|
|
|
@ -40,8 +40,13 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
compatibleFields() {
|
||||
return this.tableFields.filter((field) =>
|
||||
this.$registry.exists('groupedAggregationGroupedBy', field.type)
|
||||
)
|
||||
},
|
||||
groupByOptions() {
|
||||
const tableFieldOptions = this.tableFields.map((field) => {
|
||||
const tableFieldOptions = this.compatibleFields.map((field) => {
|
||||
return {
|
||||
name: field.name,
|
||||
value: field.id,
|
||||
|
|
|
@ -72,7 +72,18 @@ import {
|
|||
MedianViewAggregationType,
|
||||
} from '@baserow/modules/database/viewAggregationTypes'
|
||||
import { PeriodicDataSyncDeactivatedNotificationType } from '@baserow_enterprise/notificationTypes'
|
||||
|
||||
import {
|
||||
TextFieldType,
|
||||
LongTextFieldType,
|
||||
URLFieldType,
|
||||
EmailFieldType,
|
||||
NumberFieldType,
|
||||
RatingFieldType,
|
||||
BooleanFieldType,
|
||||
SingleSelectFieldType,
|
||||
PhoneNumberFieldType,
|
||||
AutonumberFieldType,
|
||||
} from '@baserow/modules/database/fieldTypes'
|
||||
import {
|
||||
FF_AB_SSO,
|
||||
FF_DASHBOARDS,
|
||||
|
@ -244,6 +255,47 @@ export default (context) => {
|
|||
new UniqueCountViewAggregationType(context)
|
||||
)
|
||||
|
||||
app.$registry.register(
|
||||
'groupedAggregationGroupedBy',
|
||||
new TextFieldType(context)
|
||||
)
|
||||
app.$registry.register(
|
||||
'groupedAggregationGroupedBy',
|
||||
new LongTextFieldType(context)
|
||||
)
|
||||
app.$registry.register(
|
||||
'groupedAggregationGroupedBy',
|
||||
new NumberFieldType(context)
|
||||
)
|
||||
app.$registry.register(
|
||||
'groupedAggregationGroupedBy',
|
||||
new URLFieldType(context)
|
||||
)
|
||||
app.$registry.register(
|
||||
'groupedAggregationGroupedBy',
|
||||
new RatingFieldType(context)
|
||||
)
|
||||
app.$registry.register(
|
||||
'groupedAggregationGroupedBy',
|
||||
new BooleanFieldType(context)
|
||||
)
|
||||
app.$registry.register(
|
||||
'groupedAggregationGroupedBy',
|
||||
new EmailFieldType(context)
|
||||
)
|
||||
app.$registry.register(
|
||||
'groupedAggregationGroupedBy',
|
||||
new SingleSelectFieldType(context)
|
||||
)
|
||||
app.$registry.register(
|
||||
'groupedAggregationGroupedBy',
|
||||
new PhoneNumberFieldType(context)
|
||||
)
|
||||
app.$registry.register(
|
||||
'groupedAggregationGroupedBy',
|
||||
new AutonumberFieldType(context)
|
||||
)
|
||||
|
||||
app.$registry.register(
|
||||
'notification',
|
||||
new PeriodicDataSyncDeactivatedNotificationType(context)
|
||||
|
|
Loading…
Add table
Reference in a new issue