1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-15 01:28:30 +00:00

Merge branch 'ab-load-table-fields-on-demand' into 'develop'

Remove table fields from integration data

See merge request 
This commit is contained in:
Jérémie Pardou 2025-03-24 17:10:06 +01:00
commit 40a35bcf11
30 changed files with 239 additions and 176 deletions
backend
src/baserow
api/integrations
contrib
builder
database
integrations
core
tests/baserow/contrib
changelog/entries/unreleased/refactor
enterprise/web-frontend/modules/baserow_enterprise
dashboard/components/data_source
integrations/localBaserow/components
premium/backend/tests/baserow_premium_tests/fields
web-frontend/modules

View file

@ -88,7 +88,7 @@ class IntegrationsView(APIView):
if the user has access to that application.
"""
application = CoreHandler().get_application(application_id)
application = CoreHandler().get_application(application_id, specific=False)
integrations = IntegrationService().get_integrations(request.user, application)

View file

@ -51,6 +51,8 @@ class Builder(Application):
PageHandler().create_shared_page(self)
def get_parent(self):
# If we had select related workspace we want to keep it
self.application_ptr.workspace = self.workspace
# Parent is the Application here even if it's at the "same" level
# but it's a more generic type
return self.application_ptr

View file

@ -44,7 +44,6 @@ from baserow.contrib.database.fields.operations import (
CreateFieldOperationType,
DeleteFieldOperationType,
DuplicateFieldOperationType,
ListFieldsOperationType,
ReadFieldOperationType,
UpdateFieldOperationType,
)
@ -193,51 +192,6 @@ class FieldHandler(metaclass=baserow_trace_methods(tracer)):
return field
def list_workspace_fields(
self,
user: AbstractUser,
workspace,
base_queryset=None,
include_trashed=False,
specific: bool = True,
) -> Iterable[Table]:
"""
Lists available fields for a user/workspace combination.
:user: The user on whose behalf we want to return fields.
:workspace: The workspace for which the fields should be returned.
:base_queryset: specify a base queryset to use.
:return: Iterator over returned fields.
"""
field_qs = base_queryset if base_queryset else Field.objects.all()
field_qs = field_qs.filter(table__database__workspace=workspace).select_related(
"table", "table__database", "table__database__workspace"
)
if not include_trashed:
field_qs = field_qs.filter(table__database__workspace__trashed=False)
filtered_qs = CoreHandler().filter_queryset(
user,
ListFieldsOperationType.type,
field_qs,
workspace=workspace,
)
if specific:
return specific_iterator(
filtered_qs.select_related("content_type"),
per_content_type_queryset_hook=(
lambda field, queryset: field_type_registry.get_by_model(
field
).enhance_field_queryset(queryset, field)
),
)
else:
return filtered_qs
def get_base_fields_queryset(self) -> QuerySet[Field]:
"""
Returns a base queryset with proper select and prefetch related fields to use in

View file

@ -21,6 +21,7 @@ from django.conf import settings
from django.core.cache import caches
from django.core.exceptions import ImproperlyConfigured
from baserow.core.cache import local_cache
from baserow.version import VERSION as BASEROW_VERSION
if typing.TYPE_CHECKING:
@ -73,6 +74,9 @@ def invalidate_table_in_model_cache(table_id: int):
# Send signal for other potential cached values
table_schema_changed.send(Table, table_id=table_id)
# Delete model local cache
local_cache.delete(f"database_table_model_{table_id}*")
if settings.BASEROW_DISABLE_MODEL_CACHE:
return None

View file

@ -349,7 +349,7 @@ class TableHandler(metaclass=baserow_trace_methods(tracer)):
table_qs = base_queryset if base_queryset else Table.objects.all()
table_qs = table_qs.filter(database__workspace=workspace).select_related(
"database", "database__workspace"
"database__workspace", "data_sync"
)
if not include_trashed:

View file

@ -52,6 +52,7 @@ from baserow.contrib.database.table.constants import (
from baserow.contrib.database.views.exceptions import ViewFilterTypeNotAllowedForField
from baserow.contrib.database.views.models import DEFAULT_SORT_TYPE_KEY
from baserow.contrib.database.views.registries import view_filter_type_registry
from baserow.core.cache import local_cache
from baserow.core.db import MultiFieldPrefetchQuerysetMixin, specific_iterator
from baserow.core.fields import AutoTrueBooleanField
from baserow.core.jobs.mixins import (
@ -67,7 +68,7 @@ from baserow.core.mixins import (
TrashableModelMixin,
)
from baserow.core.telemetry.utils import baserow_trace
from baserow.core.utils import split_comma_separated_string
from baserow.core.utils import are_kwargs_default, split_comma_separated_string
extract_filter_sections_regex = re.compile(r"filter__(.+)__(.+)$")
field_id_regex = re.compile(r"field_(\d+)$")
@ -974,7 +975,19 @@ class Table(
return f"{USER_TABLE_DATABASE_NAME_PREFIX}{self.id}"
@baserow_trace(tracer)
def get_model(
def get_model(self, **kwargs):
"""
Get model from local cache if the kwargs are the default values.
See `_get_model` doc for more information.
"""
if are_kwargs_default(self._get_model, **kwargs):
return local_cache.get(
f"database_table_model_{self.id}", lambda: self._get_model(**kwargs)
)
return self._get_model(**kwargs)
def _get_model(
self,
fields=None,
field_ids=None,
@ -1107,7 +1120,12 @@ class Table(
)
if use_cache:
self.refresh_from_db(fields=["version"])
# We don't need to refresh the version if it has already been refreshed for
# this session.
local_cache.get(
f"database_table_model_{self.id}_refreshed",
lambda: self.refresh_from_db(fields=["version"]),
)
field_attrs = get_cached_model_field_attrs(self)
else:
field_attrs = None

View file

@ -705,7 +705,14 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
if specific:
views = views.select_related("content_type")
return specific_iterator(views)
return specific_iterator(
views,
per_content_type_queryset_hook=(
lambda model, queryset: view_type_registry.get_by_model(
model
).enhance_queryset(queryset)
),
)
return views

View file

@ -1,8 +1,7 @@
from rest_framework import serializers
from baserow.api.applications.serializers import ApplicationSerializer
from baserow.contrib.database.api.fields.serializers import PolymorphicFieldSerializer
from baserow.contrib.database.api.tables.serializers import TableSerializer
from baserow.contrib.database.table.models import Table
from baserow.contrib.database.views.models import View
@ -12,15 +11,14 @@ class LocalBaserowViewSerializer(serializers.ModelSerializer):
fields = ("id", "table_id", "name")
class TableSerializerWithFields(TableSerializer):
fields = PolymorphicFieldSerializer(many=True, help_text="Fields of this table")
class Meta(TableSerializer.Meta):
fields = ("id", "name", "order", "database_id", "fields")
class LocalBaserowTableSerializer(serializers.ModelSerializer):
class Meta:
model = Table
fields = ("id", "database_id", "name")
class LocalBaserowDatabaseSerializer(ApplicationSerializer):
tables = TableSerializerWithFields(
tables = LocalBaserowTableSerializer(
many=True,
help_text="This field is specific to the `database` application and contains "
"an array of tables that are in the database.",

View file

@ -5,7 +5,6 @@ from django.contrib.auth import get_user_model
from django.contrib.auth.models import AbstractUser
from baserow.api.user.serializers import SubjectUserSerializer
from baserow.contrib.database.fields.handler import FieldHandler
from baserow.contrib.database.table.handler import TableHandler
from baserow.contrib.database.views.handler import ViewHandler
from baserow.contrib.integrations.api.local_baserow.serializers import (
@ -157,9 +156,17 @@ class LocalBaserowIntegrationType(IntegrationType):
tables = TableHandler().list_workspace_tables(user, workspace)
views = ViewHandler().list_workspace_views(user, workspace)
views = ViewHandler().list_workspace_views(user, workspace, specific=False)
fields = FieldHandler().list_workspace_fields(user, workspace)
views = list(
views.only(
"id",
"name",
"table_id",
"order",
"content_type",
),
)
views_by_table = defaultdict(list)
[
@ -168,9 +175,6 @@ class LocalBaserowIntegrationType(IntegrationType):
if view.get_type().can_filter or view.get_type().can_sort
]
fields_by_table = defaultdict(list)
[fields_by_table[field.table_id].append(field) for field in fields]
database_map = {}
for table in tables:
if table.database not in database_map:
@ -178,8 +182,6 @@ class LocalBaserowIntegrationType(IntegrationType):
database_map[table.database].tables = []
database_map[table.database].views = []
table.fields = fields_by_table[table.id]
database_map[table.database].tables.append(table)
database_map[table.database].views += views_by_table.get(table.id, [])

View file

@ -94,7 +94,7 @@ from baserow.contrib.integrations.local_baserow.utils import (
guess_cast_function_from_response_serializer_field,
guess_json_type_from_response_serializer_field,
)
from baserow.core.cache import global_cache, local_cache
from baserow.core.cache import global_cache
from baserow.core.formula import resolve_formula
from baserow.core.formula.registries import formula_runtime_function_registry
from baserow.core.handler import CoreHandler
@ -600,10 +600,7 @@ class LocalBaserowTableServiceType(LocalBaserowServiceType):
if not service.table_id:
return None
return local_cache.get(
f"integration_service_{service.table_id}_table_model",
lambda: service.table.get_model(),
)
return service.table.get_model()
def get_table_field_objects(
self, service: LocalBaserowTableService

View file

@ -1323,7 +1323,10 @@ class CoreHandler(metaclass=baserow_trace_methods(tracer)):
return application
def get_application(
self, application_id: int, base_queryset: Optional[QuerySet] = None
self,
application_id: int,
base_queryset: Optional[QuerySet] = None,
specific: bool = True,
) -> Application:
"""
Selects an application with a given id from the database.
@ -1331,6 +1334,7 @@ class CoreHandler(metaclass=baserow_trace_methods(tracer)):
:param application_id: The identifier of the application that must be returned.
:param base_queryset: The base queryset from where to select the application
object. This can for example be used to do a `select_related`.
:param specific: Determines whether we want the specific application or not.
:raises ApplicationDoesNotExist: When the application with the provided id
does not exist.
:return: The requested application instance of the provided id.
@ -1339,18 +1343,21 @@ class CoreHandler(metaclass=baserow_trace_methods(tracer)):
if base_queryset is None:
base_queryset = Application.objects
queryset = base_queryset.select_related("workspace").filter(id=application_id)
try:
application = specific_iterator(
base_queryset.select_related("workspace", "content_type").filter(
id=application_id
),
per_content_type_queryset_hook=(
lambda model, queryset: application_type_registry.get_by_model(
model
).enhance_queryset(queryset)
),
)[0]
except IndexError as e:
if specific:
application = specific_iterator(
queryset.select_related("content_type"),
per_content_type_queryset_hook=(
lambda model, queryset: application_type_registry.get_by_model(
model
).enhance_queryset(queryset)
),
)[0]
else:
application = queryset.get()
except (IndexError, Application.DoesNotExist) as e:
raise ApplicationDoesNotExist(
f"The application with id {application_id} does not exist."
) from e

View file

@ -2,6 +2,7 @@ from __future__ import annotations
import csv
import hashlib
import inspect
import io
import math
import os
@ -1196,3 +1197,22 @@ def are_hostnames_same(hostname1: str, hostname2: str) -> bool:
ips1 = get_all_ips(hostname1)
ips2 = get_all_ips(hostname2)
return not ips1.isdisjoint(ips2)
def are_kwargs_default(func, **kwargs):
"""Check if all provided kwargs have their default values for the given function."""
signature = inspect.signature(func)
for param_name, param_value in kwargs.items():
param = signature.parameters.get(param_name)
# Check if parameter exists and has a default
if not param or param.default is inspect.Parameter.empty:
return False
# Check if the provided value matches the default
if param_value != param.default:
return False
return True

View file

@ -17,6 +17,7 @@ from baserow.contrib.database.fields.tasks import (
)
from baserow.contrib.database.rows.handler import RowHandler
from baserow.contrib.database.table.models import RichTextFieldMention
from baserow.core.cache import local_cache
from baserow.core.trash.handler import TrashHandler
@ -174,7 +175,7 @@ def test_run_periodic_field_type_update_per_workspace(data_fixture, settings):
2023, 2, 27, 10, 0, 0, tzinfo=timezone.utc
)
with freeze_time("2023-02-27 10:30"):
with freeze_time("2023-02-27 10:30"), local_cache.context():
run_periodic_fields_updates(workspace_id=workspace.id)
row.refresh_from_db()
@ -218,7 +219,7 @@ def test_run_field_type_updates_dependant_fields(data_fixture, settings):
2023, 2, 27, 10, 15, 0, tzinfo=timezone.utc
)
with freeze_time("2023-02-27 10:45"):
with freeze_time("2023-02-27 10:45"), local_cache.context():
run_periodic_fields_updates(workspace_id=workspace.id)
row.refresh_from_db()
@ -333,7 +334,8 @@ def test_one_formula_failing_doesnt_block_others(data_fixture, settings):
assert getattr(row, f"field_{working_other_formula.id}") == now
assert getattr(row_2, f"field_{broken_first_formula.id}") == a_day_ago
run_periodic_fields_updates()
with local_cache.context():
run_periodic_fields_updates()
row_2.refresh_from_db()
# It didn't get refreshed
@ -571,7 +573,7 @@ def test_link_row_fields_deps_are_excluded_from_periodic_updates(data_fixture):
None, table_a, {link_a_to_b.db_column: [row_b.id]}
)
with freeze_time("2023-01-02"):
with freeze_time("2023-01-02"), local_cache.context():
run_periodic_fields_updates()
row_a.refresh_from_db()

View file

@ -41,6 +41,7 @@ from baserow.contrib.database.table.exceptions import (
from baserow.contrib.database.table.handler import TableHandler, TableUsageHandler
from baserow.contrib.database.table.models import Table, TableUsage, TableUsageUpdate
from baserow.contrib.database.views.models import GridView, GridViewFieldOptions, View
from baserow.core.cache import local_cache
from baserow.core.exceptions import UserNotInWorkspace
from baserow.core.handler import CoreHandler
from baserow.core.models import TrashEntry
@ -766,7 +767,9 @@ def test_create_needs_background_update_column(data_fixture):
with pytest.raises(FieldDoesNotExist):
model._meta.get_field(system_updated_on_column)
TableHandler().create_needs_background_update_field(table)
with local_cache.context():
TableHandler().create_needs_background_update_field(table)
table.refresh_from_db()
assert table.needs_background_update_column_added
@ -792,8 +795,9 @@ def test_create_last_modified_by_field(data_fixture):
table.refresh_from_db()
assert table.last_modified_by_column_added
model = table.get_model()
model._meta.get_field(LAST_MODIFIED_BY_COLUMN_NAME)
with local_cache.context():
model = table.get_model()
model._meta.get_field(LAST_MODIFIED_BY_COLUMN_NAME)
@pytest.mark.django_db

View file

@ -32,6 +32,7 @@ from baserow.contrib.database.views.exceptions import (
ViewFilterTypeDoesNotExist,
ViewFilterTypeNotAllowedForField,
)
from baserow.core.cache import local_cache
@pytest.mark.django_db
@ -1135,6 +1136,7 @@ def test_model_coming_out_of_cache_queries_correctly(
table=table, name=f"Color 1"
)
local_cache.clear()
with django_assert_num_queries(3):
original_model = table.get_model()
@ -1151,6 +1153,7 @@ def test_model_coming_out_of_cache_queries_correctly(
]
)
local_cache.clear()
with django_assert_num_queries(1):
model = table.get_model()
@ -1165,6 +1168,7 @@ def test_model_coming_out_of_cache_queries_correctly(
# creation_counter when comparing fields.
assert_no_duplicate_values(field_name_to_creation_counter_for_cached_model)
local_cache.clear()
for row in model.objects.all():
# One affect of this bug is that if you use this model with colliding
# field.creation_counter values, when you access a value from a row

View file

@ -12,6 +12,7 @@ from baserow.contrib.database.views.handler import ViewHandler, ViewSubscription
from baserow.contrib.database.views.signals import (
view_loaded_create_indexes_and_columns,
)
from baserow.core.cache import local_cache
from baserow.core.trash.handler import TrashHandler
@ -405,6 +406,7 @@ def test_rows_enter_and_exit_view_with_periodic_fields_updates(data_fixture):
year_field = data_fixture.create_formula_field(
table=table, formula="tonumber(datetime_format(field('today'), 'YYYY'))"
)
local_cache.delete(f"database_table_model_{table.id}*")
model = table.get_model()
@ -418,7 +420,7 @@ def test_rows_enter_and_exit_view_with_periodic_fields_updates(data_fixture):
with patch(
"baserow.contrib.database.views.signals.rows_entered_view.send"
) as p, freeze_time("2022-01-01"):
) as p, freeze_time("2022-01-01"), local_cache.context():
run_periodic_fields_updates(table.database.workspace_id)
p.assert_called_once()
@ -427,7 +429,7 @@ def test_rows_enter_and_exit_view_with_periodic_fields_updates(data_fixture):
with patch(
"baserow.contrib.database.views.signals.rows_exited_view.send"
) as p, freeze_time("2023-01-01"):
) as p, freeze_time("2023-01-01"), local_cache.context():
run_periodic_fields_updates(table.database.workspace_id)
p.assert_called_once()

View file

@ -194,8 +194,6 @@ def test_get_integrations_serializer(
assert response.status_code == HTTP_200_OK
assert len(response_json) == 1
field = fields[0]
assert response_json[0]["context_data"] == {
"databases": [
{
@ -215,23 +213,7 @@ def test_get_integrations_serializer(
{
"id": table.id,
"name": table.name,
"order": table.order,
"database_id": table.database_id,
"fields": [
{
"description": None,
"id": field.id,
"name": "Name",
"order": 0,
"immutable_properties": False,
"immutable_type": False,
"primary": False,
"read_only": False,
"table_id": table.id,
"text_default": "",
"type": "text",
},
],
}
],
"views": [

View file

@ -0,0 +1,8 @@
{
"type": "refactor",
"message": "Integration endpoint doesn't return the table fields anymore to improve performance",
"domain": "builder",
"issue_number": null,
"bullet_points": [],
"created_at": "2025-03-13"
}

View file

@ -124,6 +124,7 @@ import { required } from '@vuelidate/validators'
import AggregationSeriesForm from '@baserow_enterprise/dashboard/components/data_source/AggregationSeriesForm'
import AggregationGroupByForm from '@baserow_enterprise/dashboard/components/data_source/AggregationGroupByForm'
import AggregationSortByForm from '@baserow_enterprise/dashboard/components/data_source/AggregationSortByForm'
import tableFields from '@baserow/modules/database/mixins/tableFields'
const includesIfSet = (array) => (value) => {
if (value === null || value === undefined) {
@ -139,7 +140,7 @@ export default {
AggregationGroupByForm,
AggregationSortByForm,
},
mixins: [form],
mixins: [form, tableFields],
props: {
dashboard: {
type: Object,
@ -206,9 +207,6 @@ export default {
tableSelected() {
return this.tables.find(({ id }) => id === this.values.table_id)
},
tableFields() {
return this.tableSelected?.fields || []
},
primaryTableField() {
return this.tableFields.find((item) => item.primary === true)
},
@ -326,6 +324,10 @@ export default {
}
},
methods: {
/* Overrides the method in the tableFields mixin */
getTableId() {
return this.values.table_id
},
getTableFieldById(fieldId) {
return this.tableFields.find((tableField) => {
return tableField.id === fieldId

View file

@ -10,7 +10,7 @@
<Dropdown
v-model="values.password_field_id"
fixed-items
:disabled="!selectedTable"
:disabled="!selectedTable || fieldsLoading"
:placeholder="
$t('localBaserowPasswordAppAuthProviderForm.passwordFieldLabel')
"
@ -32,9 +32,10 @@
<script>
import authProviderForm from '@baserow/modules/core/mixins/authProviderForm'
import tableFields from '@baserow/modules/database/mixins/tableFields'
export default {
mixins: [authProviderForm],
mixins: [authProviderForm, tableFields],
props: {
integration: {
type: Object,
@ -73,15 +74,8 @@ export default {
}
return null
},
fields() {
if (!this.selectedTable) {
return []
} else {
return this.selectedTable.fields
}
},
passwordFields() {
return this.fields.filter(({ type }) =>
return this.tableFields.filter(({ type }) =>
this.authProviderType.allowedPasswordFieldTypes.includes(type)
)
},
@ -92,6 +86,10 @@ export default {
},
},
methods: {
/* Overrides the method in the tableFields mixin */
getTableId() {
return this.userSource.table_id
},
getIconForType(type) {
return this.fieldTypes[type].getIconClass()
},

View file

@ -18,7 +18,7 @@
<Dropdown
v-model="values.email_field_id"
fixed-items
:disabled="!selectedTable"
:disabled="!selectedTable || fieldsLoading"
:placeholder="
$t('localBaserowUserSourceForm.emailFieldLabelPlaceholder')
"
@ -45,7 +45,7 @@
<Dropdown
v-model="values.name_field_id"
fixed-items
:disabled="!selectedTable"
:disabled="!selectedTable || fieldsLoading"
:placeholder="$t('localBaserowUserSourceForm.nameFieldPlaceholder')"
size="large"
>
@ -70,7 +70,7 @@
<Dropdown
v-model="values.role_field_id"
fixed-items
:disabled="!selectedTable"
:disabled="!selectedTable || fieldsLoading"
:placeholder="$t('localBaserowUserSourceForm.roleFieldPlaceholder')"
size="large"
>
@ -99,12 +99,13 @@
<script>
import form from '@baserow/modules/core/mixins/form'
import LocalBaserowTableSelector from '@baserow/modules/integrations/localBaserow/components/services/LocalBaserowTableSelector'
import tableFields from '@baserow/modules/database/mixins/tableFields'
export default {
components: {
LocalBaserowTableSelector,
},
mixins: [form],
mixins: [form, tableFields],
props: {
application: {
type: Object,
@ -170,30 +171,27 @@ export default {
}
return null
},
fields() {
if (!this.selectedTable) {
return []
} else {
return this.selectedTable.fields
}
},
emailFields() {
return this.fields.filter(({ type }) =>
return this.tableFields.filter(({ type }) =>
this.userSourceType.allowedEmailFieldTypes.includes(type)
)
},
nameFields() {
return this.fields.filter(({ type }) =>
return this.tableFields.filter(({ type }) =>
this.userSourceType.allowedNameFieldTypes.includes(type)
)
},
roleFields() {
return this.fields.filter(({ type }) =>
return this.tableFields.filter(({ type }) =>
this.userSourceType.allowedRoleFieldTypes.includes(type)
)
},
},
methods: {
/* Overrides the method in the tableFields mixin */
getTableId() {
return this.values.table_id
},
getIconForType(type) {
return this.fieldTypes[type].getIconClass()
},

View file

@ -9,6 +9,7 @@ from baserow.contrib.database.fields.handler import FieldHandler
from baserow.contrib.database.fields.registries import field_type_registry
from baserow.contrib.database.rows.handler import RowHandler
from baserow.contrib.database.table.handler import TableHandler
from baserow.core.cache import local_cache
from baserow.core.db import specific_iterator
@ -1084,6 +1085,7 @@ def test_link_row_field_can_be_sorted_when_linking_an_ai_field(premium_data_fixt
)
model_a = table_a.get_model()
RowHandler().force_create_rows(
user,
table_a,
@ -1106,10 +1108,12 @@ def test_link_row_field_can_be_sorted_when_linking_an_ai_field(premium_data_fixt
ai_output_type="text",
)
model_a = table_a.get_model()
result = list(
model_a.objects.all()
.order_by_fields_string(f"-{link_field.db_column}") # Descending order
.values_list("id", flat=True)
)
with local_cache.context(): # After updating the field we want to get the new model
model_a = table_a.get_model()
result = list(
model_a.objects.all()
.order_by_fields_string(f"-{link_field.db_column}") # Descending order
.values_list("id", flat=True)
)
assert result == [row_b2.id, row_b1.id]

View file

@ -49,6 +49,7 @@
v-model="values.view_id"
:show-search="false"
fixed-items
:disabled="fieldsLoading"
:error="fieldHasErrors('view_id')"
@change="v$.values.view_id.$touch"
>
@ -78,7 +79,7 @@
>
<Dropdown
v-model="values.field_id"
:disabled="tableFields.length === 0"
:disabled="tableFields.length === 0 || fieldsLoading"
:error="fieldHasErrors('field_id')"
@change="v$.values.field_id.$touch"
>
@ -103,6 +104,7 @@
<Dropdown
v-model="values.aggregation_type"
:error="fieldHasErrors('aggregation_type')"
:disabled="fieldsLoading"
@change="v$.values.aggregation_type.$touch"
>
<DropdownItem
@ -125,6 +127,7 @@
import { useVuelidate } from '@vuelidate/core'
import form from '@baserow/modules/core/mixins/form'
import { required } from '@vuelidate/validators'
import tableFields from '@baserow/modules/database/mixins/tableFields'
const includes = (array) => (value) => {
return array.includes(value)
@ -139,7 +142,7 @@ const includesIfSet = (array) => (value) => {
export default {
name: 'AggregateRowsDataSourceForm',
mixins: [form],
mixins: [form, tableFields],
props: {
dashboard: {
type: Object,
@ -199,9 +202,6 @@ export default {
tableSelected() {
return this.tables.find(({ id }) => id === this.values.table_id)
},
tableFields() {
return this.tableSelected?.fields || []
},
tableFieldIds() {
return this.tableFields.map((field) => field.id)
},
@ -337,6 +337,10 @@ export default {
}
},
methods: {
/* Overrides the method in the tableFields mixin */
getTableId() {
return this.values.table_id
},
fieldIconClass(field) {
const fieldType = this.$registry.get('field', field.type)
return fieldType.iconClass

View file

@ -0,0 +1,47 @@
import FieldService from '@baserow/modules/database/services/field'
import { notifyIf } from '@baserow/modules/core/utils/error'
/**
* This mixin request the fields of the given table Id.
*/
export default {
props: {},
data() {
return { tableFields: [], fieldsLoading: false }
},
computed: {
tableId() {
return this.getTableId()
},
},
watch: {
tableId: {
async handler(newValue, oldValue) {
if (newValue !== oldValue) {
this.tableFields = []
if (newValue) {
this.fieldsLoading = true
try {
const { data } = await FieldService(this.$client).fetchAll(
newValue
)
this.tableFields = data
} catch (e) {
notifyIf(e, 'application')
} finally {
this.fieldsLoading = false
}
}
}
},
immediate: true,
},
},
methods: {
getTableId() {
throw new Error(
'Not implemented error. This method should the table id we want the field for.'
)
},
},
}

View file

@ -18,7 +18,7 @@
>
<Dropdown
v-model="values.field_id"
:disabled="tableFields.length === 0"
:disabled="tableFields.length === 0 || fieldsLoading"
>
<DropdownItem
v-for="field in tableFields"
@ -38,7 +38,7 @@
>
<Dropdown
v-model="values.aggregation_type"
:disabled="!values.field_id"
:disabled="!values.field_id || fieldsLoading"
>
<DropdownItem
v-for="viewAggregation in viewAggregationTypes"
@ -126,7 +126,6 @@ export default {
filter_type: 'AND',
aggregation_type: 'sum',
},
tableLoading: false,
}
},
computed: {

View file

@ -28,7 +28,7 @@
</FormGroup>
</div>
</div>
<div class="row">
<div v-if="!fieldsLoading" class="row">
<div class="col col-12">
<Tabs>
<Tab
@ -61,6 +61,7 @@
</Tabs>
</div>
</div>
<div v-else class="loading-spinner"></div>
</div>
</form>
</template>
@ -97,7 +98,6 @@ export default {
filters: [],
filter_type: 'AND',
},
tableLoading: false,
}
},
}

View file

@ -9,7 +9,7 @@
></LocalBaserowTableSelector>
</div>
</div>
<div class="row">
<div v-if="!fieldsLoading" class="row">
<div class="col col-12">
<Tabs>
<Tab
@ -55,6 +55,7 @@
</Tabs>
</div>
</div>
<div v-else class="loading-spinner"></div>
</form>
</template>
@ -92,7 +93,6 @@ export default {
sortings: [],
filter_type: 'AND',
},
tableLoading: false,
}
},
}

View file

@ -6,7 +6,7 @@
@table-changed="handleTableChange"
@values-changed="emitServiceChange($event)"
></LocalBaserowServiceForm>
<div v-if="tableLoading" class="loading margin-bottom-1"></div>
<div v-if="tableLoading" class="loading-spinner margin-bottom-1"></div>
<p v-if="values.integration_id && !values.table_id">
{{ $t('upsertRowWorkflowActionForm.noTableSelectedMessage') }}
</p>

View file

@ -1,4 +1,7 @@
import tableFields from '@baserow/modules/database/mixins/tableFields'
export default {
mixins: [tableFields],
props: {
builder: {
type: Object,
@ -47,18 +50,14 @@ export default {
tableSelected() {
return this.tables.find(({ id }) => id === this.values.table_id)
},
tableFields() {
return this.tableSelected?.fields || []
},
},
watch: {
'values.table_id'(newValue, oldValue) {
if (oldValue && newValue !== oldValue) {
this.tableLoading = true
}
},
},
methods: {
/**
* Overrides the method in the tableFields mixin
*/
getTableId() {
return this.values.table_id
},
/**
* Given an array of objects containing a `field` property (e.g. the data
* source filters or sortings arrays), this method will return a new array

View file

@ -293,13 +293,14 @@ export class LocalBaserowAggregateRowsServiceType extends LocalBaserowTableServi
if (service.table_id && tableSelected) {
const defaultTableDescription = `${this.name} - ${tableSelected.name}`
if (service.field_id) {
const fieldSelected = tableSelected.fields.find(
({ id }) => id === service.field_id
)
const fieldName = fieldSelected
? fieldSelected.name
: this.app.i18n.t('serviceType.trashedField')
return `${defaultTableDescription} - ${fieldName}`
if (service.context_data.field) {
const fieldName = service.context_data.field.name
return `${defaultTableDescription} - ${fieldName}`
} else {
return `${defaultTableDescription} - ${this.app.i18n.t(
'serviceType.trashedField'
)}`
}
}
return defaultTableDescription