Skip to content

Commit 96d4d1b

Browse files
authored
feat: local baserow services return human field names (baserow#4143)
See related issue.
1 parent 918dac3 commit 96d4d1b

File tree

38 files changed

+634
-447
lines changed

38 files changed

+634
-447
lines changed

backend/src/baserow/contrib/automation/data_providers/data_provider_types.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ def get_data_chunk(
4545
)
4646
raise InvalidFormulaContext(message) from exc
4747
else:
48-
return get_value_at_path(previous_node_results, rest)
48+
return previous_node.service.get_type().get_value_at_path(
49+
previous_node.service.specific, previous_node_results, rest
50+
)
4951

5052
def import_path(self, path, id_mapping, **kwargs):
5153
"""
@@ -100,7 +102,7 @@ def get_data_chunk(
100102
)
101103
raise InvalidFormulaContext(message) from exc
102104

103-
current_item = parent_node_results[current_iteration]
105+
current_item = parent_node_results["results"][current_iteration]
104106
data = {"index": current_iteration, "item": current_item}
105107

106108
return get_value_at_path(data, rest)

backend/src/baserow/contrib/automation/nodes/handler.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -369,11 +369,7 @@ def dispatch_node(
369369
return
370370

371371
if children := node.get_children():
372-
node_data = (
373-
dispatch_result.data
374-
if isinstance(dispatch_result.data, list)
375-
else [dispatch_result.data]
376-
)
372+
node_data = dispatch_result.data["results"]
377373

378374
if dispatch_context.simulate_until_node:
379375
iterations = [0]

backend/src/baserow/contrib/builder/data_providers/data_provider_types.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,9 @@ def get_data_chunk(self, dispatch_context: BuilderDispatchContext, path: List[st
180180
if data_source.service.get_type().returns_list:
181181
dispatch_result = dispatch_result["results"]
182182

183-
return get_value_at_path(dispatch_result, rest)
183+
return data_source.service.get_type().get_value_at_path(
184+
data_source.service.specific, dispatch_result, rest
185+
)
184186

185187
def import_path(self, path, id_mapping, **kwargs):
186188
"""
@@ -482,8 +484,11 @@ def get_data_chunk(self, dispatch_context: DispatchContext, path: List[str]):
482484
cache_key = self.get_dispatch_action_cache_key(
483485
dispatch_id, workflow_action.id
484486
)
485-
return get_value_at_path(cache.get(cache_key), rest)
487+
return workflow_action.service.get_type().get_value_at_path(
488+
workflow_action.service.specific, cache.get(cache_key), rest
489+
)
486490
else:
491+
# Frontend actions
487492
return get_value_at_path(previous_action_results[previous_action_id], rest)
488493

489494
def post_dispatch(

backend/src/baserow/contrib/builder/data_sources/service.py

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -278,18 +278,6 @@ def delete_data_source(self, user: AbstractUser, data_source: DataSourceForUpdat
278278
self, data_source_id=data_source.id, page=page, user=user
279279
)
280280

281-
def remove_unused_field_names(
282-
self,
283-
row: Dict[str, Any],
284-
field_names: List[str],
285-
) -> Dict[str, Any]:
286-
"""
287-
Given a row dictionary, return a version of it that only contains keys
288-
existing in the field_names list.
289-
"""
290-
291-
return {key: value for key, value in row.items() if key in field_names}
292-
293281
def dispatch_data_sources(
294282
self,
295283
user,
@@ -330,22 +318,17 @@ def dispatch_data_sources(
330318
new_results[data_source.id] = results[data_source.id]
331319
continue
332320

333-
field_names = dispatch_context.public_allowed_properties.get(
321+
allowed_field_names = dispatch_context.public_allowed_properties.get(
334322
"external", {}
335323
).get(data_source.service.id, [])
336324

337-
if data_source.service.get_type().returns_list:
338-
new_results[data_source.id] = {
339-
**results[data_source.id],
340-
"results": [
341-
self.remove_unused_field_names(row, field_names)
342-
for row in results[data_source.id]["results"]
343-
],
344-
}
345-
else:
346-
new_results[data_source.id] = self.remove_unused_field_names(
347-
results[data_source.id], field_names
348-
)
325+
new_results[
326+
data_source.id
327+
] = data_source.service.get_type().sanitize_result(
328+
data_source.service.specific,
329+
results[data_source.id],
330+
allowed_field_names,
331+
)
349332

350333
return new_results
351334

backend/src/baserow/contrib/builder/workflow_actions/service.py

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -291,18 +291,6 @@ def order_workflow_actions(
291291

292292
return full_order
293293

294-
def remove_unused_field_names(
295-
self,
296-
row: dict[str, Any],
297-
field_names: List[str],
298-
) -> dict[str, Any]:
299-
"""
300-
Given a row dictionary, return a version of it that only contains keys
301-
existing in the field_names list.
302-
"""
303-
304-
return {key: value for key, value in row.items() if key in field_names}
305-
306294
def dispatch_action(
307295
self,
308296
user,
@@ -338,11 +326,15 @@ def dispatch_action(
338326
)
339327

340328
# Remove unfiltered fields
341-
field_names = dispatch_context.public_allowed_properties.get(
329+
allowed_field_names = dispatch_context.public_allowed_properties.get(
342330
"external", {}
343331
).get(workflow_action.service.id, [])
344332

333+
data = workflow_action.service.get_type().sanitize_result(
334+
workflow_action.service.specific, result.data, allowed_field_names
335+
)
336+
345337
return DispatchResult(
346-
data=self.remove_unused_field_names(result.data, field_names),
338+
data=data,
347339
status=result.status,
348340
)

backend/src/baserow/contrib/integrations/core/service_types.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
from baserow.core.services.models import Service
6767
from baserow.core.services.registries import (
6868
DispatchTypes,
69+
ListServiceTypeMixin,
6970
ServiceType,
7071
TriggerServiceTypeMixin,
7172
)
@@ -1566,7 +1567,7 @@ def export_prepared_values(
15661567
return values
15671568

15681569

1569-
class CoreIteratorServiceType(ServiceType):
1570+
class CoreIteratorServiceType(ListServiceTypeMixin, ServiceType):
15701571
type = "iterator"
15711572
model_class = CoreIteratorService
15721573
dispatch_types = DispatchTypes.ACTION
@@ -1609,7 +1610,7 @@ def generate_schema(
16091610
allowed_fields is None or "items" in allowed_fields
16101611
):
16111612
schema_builder = SchemaBuilder()
1612-
schema_builder.add_object(service.sample_data["data"])
1613+
schema_builder.add_object(service.sample_data["data"]["results"])
16131614
schema = schema_builder.to_schema()
16141615

16151616
# Sometimes there is no items if the array is empty
@@ -1643,7 +1644,7 @@ def dispatch_data(
16431644
resolved_values: Dict[str, Any],
16441645
dispatch_context: DispatchContext,
16451646
) -> Any:
1646-
return resolved_values["source"]
1647+
return {"results": resolved_values["source"], "has_next_page": False}
16471648

16481649
def dispatch_transform(
16491650
self,

backend/src/baserow/contrib/integrations/local_baserow/service_types.py

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,58 @@ class LocalBaserowTableServiceType(LocalBaserowServiceType):
203203
class SerializedDict(ServiceDict):
204204
table_id: int
205205

206+
def _convert_allowed_field_names(self, service, allowed_fields):
207+
"""
208+
Convert the `field_x` to human fields.
209+
"""
210+
211+
mapping = {
212+
field_obj["field"].db_column: field_obj["field"].name
213+
for field_obj in self.get_table_field_objects(service)
214+
}
215+
return [mapping.get(f, f) for f in allowed_fields]
216+
217+
def sanitize_result(self, service, result, allowed_field_names):
218+
"""
219+
Remove the non public fields from the result.
220+
"""
221+
222+
allowed_field_names = self._convert_allowed_field_names(
223+
service, allowed_field_names
224+
)
225+
226+
return super().sanitize_result(service, result, allowed_field_names)
227+
228+
def get_value_at_path(self, service: Service, context: Any, path: List[str]):
229+
"""
230+
Convert the field name to a human name.
231+
"""
232+
233+
if self.returns_list:
234+
if len(path) < 2:
235+
return super().get_value_at_path(service, context, path)
236+
237+
row_index, db_column, *rest = path
238+
else:
239+
if len(path) < 1:
240+
return super().get_value_at_path(service, context, path)
241+
242+
db_column, *rest = path
243+
244+
human_name = db_column
245+
246+
for field_obj in self.get_table_field_objects(service) or []:
247+
if field_obj["field"].db_column == db_column:
248+
human_name = field_obj["field"].name
249+
break
250+
251+
if self.returns_list:
252+
return super().get_value_at_path(
253+
service, context, [row_index, human_name, *rest]
254+
)
255+
else:
256+
return super().get_value_at_path(service, context, [human_name, *rest])
257+
206258
def build_queryset(
207259
self,
208260
service: LocalBaserowTableService,
@@ -1052,6 +1104,7 @@ def dispatch_transform(self, dispatch_data: Dict[str, Any]) -> DispatchResult:
10521104
RowSerializer,
10531105
is_response=True,
10541106
field_ids=field_ids,
1107+
user_field_names=True,
10551108
)
10561109

10571110
return DispatchResult(
@@ -1591,6 +1644,7 @@ def dispatch_transform(self, dispatch_data: Dict[str, Any]) -> DispatchResult:
15911644
RowSerializer,
15921645
is_response=True,
15931646
field_ids=field_ids,
1647+
user_field_names=True,
15941648
)
15951649

15961650
serialized_row = serializer(dispatch_data["data"]).data
@@ -2081,6 +2135,7 @@ def dispatch_transform(self, dispatch_data: Dict[str, Any]) -> DispatchResult:
20812135
RowSerializer,
20822136
is_response=True,
20832137
field_ids=field_ids,
2138+
user_field_names=True,
20842139
)
20852140
serialized_row = serializer(dispatch_data["data"]).data
20862141

@@ -2243,9 +2298,7 @@ def _handle_signal(
22432298
**kwargs,
22442299
):
22452300
serializer = get_row_serializer_class(
2246-
model,
2247-
RowSerializer,
2248-
is_response=True,
2301+
model, RowSerializer, is_response=True, user_field_names=True
22492302
)
22502303

22512304
data_to_process = {

backend/src/baserow/core/services/registries.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
)
3434
from baserow.core.services.dispatch_context import DispatchContext
3535
from baserow.core.services.types import DispatchResult, FormulaToResolve
36+
from baserow.core.utils import get_value_at_path
3637

3738
from .exceptions import (
3839
DispatchException,
@@ -311,6 +312,14 @@ def resolve_service_formulas(
311312

312313
return resolved_values
313314

315+
def get_value_at_path(self, service: Service, context: Any, path: List[str]):
316+
"""
317+
Offers the opportunity to hook into way data are extracted from the context for
318+
a given path.
319+
"""
320+
321+
return get_value_at_path(context, path)
322+
314323
def dispatch_transform(
315324
self,
316325
data: Any,
@@ -384,6 +393,34 @@ def dispatch(
384393

385394
return serialized_data
386395

396+
def remove_unused_field_names(
397+
self,
398+
row: Dict[str, Any],
399+
field_names: List[str],
400+
) -> Dict[str, Any]:
401+
"""
402+
Given a row dictionary, return a version of it that only contains keys
403+
existing in the field_names list.
404+
"""
405+
406+
return {key: value for key, value in row.items() if key in field_names}
407+
408+
def sanitize_result(self, service, result, allowed_field_names):
409+
"""
410+
Remove the non public fields from the result.
411+
"""
412+
413+
if self.returns_list:
414+
return {
415+
**result,
416+
"results": [
417+
self.remove_unused_field_names(row, allowed_field_names)
418+
for row in result["results"]
419+
],
420+
}
421+
else:
422+
return self.remove_unused_field_names(result, allowed_field_names)
423+
387424
def get_schema_name(self, service: Service) -> str:
388425
"""
389426
The default schema name added to the `title` in a JSON Schema object.

backend/tests/baserow/contrib/automation/data_providers/test_data_provider_types.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,13 @@ def test_current_iteration_data_provider_get_data_chunk(data_fixture):
104104
dispatch_context = AutomationDispatchContext(workflow)
105105

106106
dispatch_context.after_dispatch(
107-
trigger, DispatchResult(data=[{"field_1": "Horse"}, {"field_1": "Duck"}])
107+
trigger,
108+
DispatchResult(data={"results": [{"field_1": "Horse"}, {"field_1": "Duck"}]}),
108109
)
109110

110111
dispatch_context.after_dispatch(
111-
iterator, DispatchResult(data=[{"field_1": "Horse"}, {"field_1": "Duck"}])
112+
iterator,
113+
DispatchResult(data={"results": [{"field_1": "Horse"}, {"field_1": "Duck"}]}),
112114
)
113115

114116
dispatch_context.set_current_iteration(iterator, 0)

backend/tests/baserow/contrib/automation/nodes/test_node_handler.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ def test_simulate_dispatch_node_action(data_fixture):
314314

315315
assert action_node.service.sample_data == {
316316
"data": {
317-
f"field_{fields[0].id}": "A new row",
317+
fields[0].name: "A new row",
318318
"id": row.id,
319319
"order": str(row.order),
320320
},
@@ -346,7 +346,7 @@ def test_simulate_dispatch_node_action_with_update_sample_data(
346346

347347
assert action_node.service.sample_data == {
348348
"data": {
349-
f"field_{fields[0].id}": "A new row",
349+
fields[0].name: "A new row",
350350
"id": AnyInt(),
351351
"order": AnyStr(),
352352
},
@@ -387,7 +387,7 @@ def test_simulate_dispatch_node_action_with_simulate_until_node(data_fixture):
387387
row = table.get_model().objects.first()
388388
assert action_node_1.service.sample_data == {
389389
"data": {
390-
f"field_{fields[0].id}": "A new row",
390+
fields[0].name: "A new row",
391391
"id": row.id,
392392
"order": str(row.order),
393393
},
@@ -544,9 +544,9 @@ def test_simulate_dispatch_node_dispatches_correct_edge_node(data_fixture):
544544

545545
node_c_2.refresh_from_db()
546546
node_c_2.service.refresh_from_db()
547-
field_id = node_c_2.service.specific.table.field_set.all()[0].id
547+
field = node_c_2.service.specific.table.field_set.all()[0]
548548
assert node_c_2.service.sample_data == {
549-
"data": {f"field_{field_id}": "cherry", "id": AnyInt(), "order": AnyStr()},
549+
"data": {field.name: "cherry", "id": AnyInt(), "order": AnyStr()},
550550
"output_uid": AnyStr(),
551551
"status": 200,
552552
}

0 commit comments

Comments
 (0)