mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-04 13:15:24 +00:00
Merge branch 'chart-prevent-two-same-series' into 'develop'
Prevent multiple series to be configured with the same field and aggregation type See merge request baserow/baserow!3221
This commit is contained in:
commit
f750a27909
4 changed files with 137 additions and 3 deletions
enterprise
backend
src/baserow_enterprise/integrations/local_baserow
tests/baserow_enterprise_tests/integrations/local_baserow/service_types
web-frontend/modules/baserow_enterprise/dashboard/components/data_source
|
@ -128,6 +128,7 @@ class LocalBaserowGroupedAggregateRowsUserServiceType(
|
|||
with atomic_if_not_already():
|
||||
table_fields = service.table.field_set.all()
|
||||
table_field_ids = [field.id for field in table_fields]
|
||||
series_agg_used = set()
|
||||
|
||||
def validate_agg_series(agg_series):
|
||||
if agg_series["aggregation_type"]:
|
||||
|
@ -164,6 +165,18 @@ class LocalBaserowGroupedAggregateRowsUserServiceType(
|
|||
code="invalid_aggregation_raw_type",
|
||||
)
|
||||
|
||||
series_aggregation_reference = (
|
||||
f"field_{agg_series['field_id']}_{agg_series['aggregation_type']}"
|
||||
)
|
||||
if series_aggregation_reference in series_agg_used:
|
||||
raise DRFValidationError(
|
||||
detail=f"The series with the field ID {agg_series['field_id']} and "
|
||||
f"aggregation type {agg_series['aggregation_type']} can only be defined once.",
|
||||
code="invalid_aggregation_raw_type",
|
||||
)
|
||||
else:
|
||||
series_agg_used.add(series_aggregation_reference)
|
||||
|
||||
return True
|
||||
|
||||
service.service_aggregation_series.all().delete()
|
||||
|
@ -505,6 +518,8 @@ class LocalBaserowGroupedAggregateRowsUserServiceType(
|
|||
raise ServiceImproperlyConfigured(
|
||||
f"There are no aggregation series defined."
|
||||
)
|
||||
|
||||
series_agg_used = set()
|
||||
for agg_series in defined_agg_series:
|
||||
if agg_series.field is None:
|
||||
raise ServiceImproperlyConfigured(
|
||||
|
@ -527,6 +542,17 @@ class LocalBaserowGroupedAggregateRowsUserServiceType(
|
|||
f"with the aggregation type {agg_series.aggregation_type}."
|
||||
)
|
||||
|
||||
series_aggregation_reference = (
|
||||
f"field_{agg_series.field.id}_{agg_series.aggregation_type}"
|
||||
)
|
||||
if series_aggregation_reference in series_agg_used:
|
||||
raise ServiceImproperlyConfigured(
|
||||
f"The series with field ID {agg_series.field.id} and "
|
||||
f"aggregation type {agg_series.aggregation_type} can only be defined once."
|
||||
)
|
||||
else:
|
||||
series_agg_used.add(series_aggregation_reference)
|
||||
|
||||
combined_agg_dict |= agg_type._get_aggregation_dict(
|
||||
queryset, model_field, agg_series.field, include_agg_type=True
|
||||
)
|
||||
|
|
|
@ -255,6 +255,41 @@ def test_create_grouped_aggregate_rows_service_group_by_field_not_compatible(
|
|||
ServiceHandler().create_service(service_type, **values)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_grouped_aggregate_rows_service_duplicate_series(
|
||||
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_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_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"},
|
||||
{"field_id": field_2.id, "aggregation_type": "sum"},
|
||||
{"field_id": field_2.id, "aggregation_type": "sum"},
|
||||
],
|
||||
},
|
||||
user,
|
||||
)
|
||||
|
||||
with pytest.raises(
|
||||
ValidationError,
|
||||
match=f"The series with the field ID {field_2.id} and aggregation type sum can only be defined once.",
|
||||
):
|
||||
ServiceHandler().create_service(service_type, **values)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_grouped_aggregate_rows_service_max_series_exceeded(
|
||||
data_fixture,
|
||||
|
@ -1318,6 +1353,41 @@ def test_grouped_aggregate_rows_service_dispatch_incompatible_aggregation(data_f
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_dispatch_grouped_aggregate_rows_service_duplicate_series(
|
||||
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)
|
||||
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_service(
|
||||
LocalBaserowGroupedAggregateRows,
|
||||
integration=integration,
|
||||
table=table,
|
||||
view=view,
|
||||
)
|
||||
LocalBaserowTableServiceAggregationSeries.objects.create(
|
||||
service=service, field=field, aggregation_type="sum", order=1
|
||||
)
|
||||
LocalBaserowTableServiceAggregationSeries.objects.create(
|
||||
service=service, field=field, aggregation_type="sum", order=1
|
||||
)
|
||||
|
||||
dispatch_context = FakeDispatchContext()
|
||||
|
||||
with pytest.raises(ServiceImproperlyConfigured) as exc:
|
||||
ServiceHandler().dispatch_service(service, dispatch_context)
|
||||
assert (
|
||||
exc.value.args[0]
|
||||
== f"The series with field ID {field.id} and aggregation type sum can only be defined once."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_grouped_aggregate_rows_service_agg_series_field_trashed(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
@change="aggregationTypeChanged"
|
||||
>
|
||||
<DropdownItem
|
||||
v-for="aggregation in groupedAggregationTypes"
|
||||
v-for="aggregation in aggregationTypesAvailableForSelection"
|
||||
:key="aggregation.getType()"
|
||||
:name="aggregation.getName()"
|
||||
:value="aggregation.getType()"
|
||||
|
@ -33,11 +33,11 @@
|
|||
<Dropdown
|
||||
v-model="values.field_id"
|
||||
:error="fieldHasErrors('field_id')"
|
||||
:disabled="compatibleFields.length === 0"
|
||||
:disabled="fieldsAvailableForSelection.length === 0"
|
||||
@change="v$.values.field_id.$touch"
|
||||
>
|
||||
<DropdownItem
|
||||
v-for="field in compatibleFields"
|
||||
v-for="field in fieldsAvailableForSelection"
|
||||
:key="field.id"
|
||||
:name="field.name"
|
||||
:value="field.id"
|
||||
|
@ -75,6 +75,10 @@ export default {
|
|||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
aggregationSeries: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
seriesIndex: {
|
||||
type: Number,
|
||||
required: true,
|
||||
|
@ -97,9 +101,29 @@ export default {
|
|||
groupedAggregationTypes() {
|
||||
return this.$registry.getOrderedList('groupedAggregation')
|
||||
},
|
||||
aggregationTypesAvailableForSelection() {
|
||||
return this.groupedAggregationTypes.filter(
|
||||
(aggregationType) =>
|
||||
this.isSeriesRepeated(
|
||||
this.values.field_id,
|
||||
aggregationType.getType()
|
||||
) === false ||
|
||||
this.values.aggregation_type === aggregationType.getType()
|
||||
)
|
||||
},
|
||||
aggregationTypeNames() {
|
||||
return this.groupedAggregationTypes.map((aggType) => aggType.getType())
|
||||
},
|
||||
usedSeries() {
|
||||
return this.aggregationSeries
|
||||
.filter(
|
||||
(series) =>
|
||||
series.aggregation_type !== null && series.field_id !== null
|
||||
)
|
||||
.map((series) => {
|
||||
return `${series.field_id}_${series.aggregation_type}`
|
||||
})
|
||||
},
|
||||
compatibleFields() {
|
||||
if (!this.values.aggregation_type) {
|
||||
return []
|
||||
|
@ -112,6 +136,13 @@ export default {
|
|||
aggType.fieldIsCompatible(tableField)
|
||||
)
|
||||
},
|
||||
fieldsAvailableForSelection() {
|
||||
return this.compatibleFields.filter(
|
||||
(field) =>
|
||||
this.isSeriesRepeated(field.id, this.values.aggregation_type) ===
|
||||
false || this.values.field_id === field.id
|
||||
)
|
||||
},
|
||||
compatibleTableFieldIds() {
|
||||
return this.compatibleFields.map((field) => field.id)
|
||||
},
|
||||
|
@ -141,6 +172,12 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
isSeriesRepeated(fieldId, aggregationType) {
|
||||
if (fieldId === null || aggregationType === null) {
|
||||
return false
|
||||
}
|
||||
return this.usedSeries.includes(`${fieldId}_${aggregationType}`)
|
||||
},
|
||||
fieldIconClass(field) {
|
||||
const fieldType = this.$registry.get('field', field.type)
|
||||
return fieldType.iconClass
|
||||
|
|
|
@ -85,6 +85,7 @@
|
|||
v-for="(series, index) in values.aggregation_series"
|
||||
:key="index"
|
||||
:table-fields="tableFields"
|
||||
:aggregation-series="values.aggregation_series"
|
||||
:series-index="index"
|
||||
:default-values="series"
|
||||
@delete-series="deleteSeries"
|
||||
|
|
Loading…
Add table
Reference in a new issue