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 baserow/baserow!3244
This commit is contained in:
commit
40a35bcf11
30 changed files with 239 additions and 176 deletions
backend
src/baserow
api/integrations
contrib
builder
database
integrations
core
tests/baserow/contrib
database
integrations/local_baserow
changelog/entries/unreleased/refactor
enterprise/web-frontend/modules/baserow_enterprise
dashboard/components/data_source
integrations/localBaserow/components
appAuthProviders
userSources
premium/backend/tests/baserow_premium_tests/fields
web-frontend/modules
dashboard/components/data_source
database/mixins
integrations
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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, [])
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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": [
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
},
|
||||
|
|
|
@ -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()
|
||||
},
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
47
web-frontend/modules/database/mixins/tableFields.js
Normal file
47
web-frontend/modules/database/mixins/tableFields.js
Normal 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.'
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
|
@ -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: {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue