Skip to content

Commit d01cd84

Browse files
jrmipicklepete
andauthored
Workflow action perf issues for create/update action with many fields (baserow#4302)
* improve performances for workflow automations * Improve performances of local baserow upsert form * Revert to the old placeholder string. --------- Co-authored-by: peter_baserow <peter@baserow.io>
1 parent 0f522a2 commit d01cd84

File tree

9 files changed

+227
-125
lines changed

9 files changed

+227
-125
lines changed

backend/src/baserow/core/populate.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ def load_test_data():
1414
print("Add basic users...")
1515
user_handler = UserHandler()
1616

17+
# Allow to import any external archive
18+
core_settings = CoreHandler().get_settings()
19+
core_settings.verify_import_signature = False
20+
core_settings.save()
21+
1722
for i in range(3):
1823
# Create main admin
1924
email = f"admin{i + 1}@baserow.io" if i > 0 else "admin@baserow.io"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "bug",
3+
"message": "Improve performances when editing a create/update workflow action with a lot of table fields",
4+
"issue_origin": "github",
5+
"issue_number": null,
6+
"domain": "builder",
7+
"bullet_points": [],
8+
"created_at": "2025-11-20"
9+
}

web-frontend/modules/core/assets/scss/components/integrations/local_baserow/local_baserow_form.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,11 @@
88
flex: 1 0 auto;
99
}
1010
}
11+
12+
.field-mapping-form__placeholder {
13+
width: 100%;
14+
height: 36px;
15+
background-color: $white;
16+
17+
@include rounded($rounded-md);
18+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<template>
2+
<div ref="rootEl">
3+
<template v-if="isVisible">
4+
<slot />
5+
</template>
6+
<template v-else><slot name="placeholder">...</slot></template>
7+
</div>
8+
</template>
9+
10+
<script setup>
11+
import { ref, onMounted, onBeforeUnmount } from 'vue'
12+
13+
const isVisible = ref(false)
14+
const rootEl = ref(null)
15+
let observer = null
16+
17+
onMounted(() => {
18+
observer = new IntersectionObserver(
19+
(entries) => {
20+
// Update visibility based on intersection
21+
if (entries[0].isIntersecting) isVisible.value = true
22+
},
23+
{ threshold: 0.1 }
24+
)
25+
26+
if (rootEl.value) observer.observe(rootEl.value)
27+
})
28+
29+
onBeforeUnmount(() => {
30+
if (observer && rootEl.value) observer.unobserve(rootEl.value)
31+
})
32+
</script>

web-frontend/modules/integrations/localBaserow/components/services/FieldMapping.vue

Lines changed: 0 additions & 32 deletions
This file was deleted.

web-frontend/modules/integrations/localBaserow/components/services/FieldMappingContext.vue

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,7 @@
22
<Context :overflow-scroll="true" :max-height-if-outside-viewport="true">
33
<ul class="context__menu">
44
<li class="context__menu-item">
5-
<a
6-
class="context__menu-item-link"
7-
@click.prevent="
8-
handleEditClick({ enabled: !fieldMapping.enabled, value: '' })
9-
"
10-
>
5+
<a class="context__menu-item-link" @click.prevent="handleEditClick()">
116
<i class="context__menu-item-icon" :class="enabledClass"></i>
127
{{ toggleEnabledText }}
138
</a>
@@ -39,8 +34,11 @@ export default {
3934
},
4035
},
4136
methods: {
42-
handleEditClick(change) {
43-
this.$emit('edit', change)
37+
handleEditClick() {
38+
this.$emit(
39+
'edit',
40+
this.fieldMapping.enabled ? undefined : { enabled: true, formula: '' }
41+
)
4442
this.hide()
4543
},
4644
},
Lines changed: 70 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,116 +1,107 @@
11
<template>
2-
<div>
3-
<FormGroup
4-
v-for="field in filteredFields"
5-
:key="field.id"
6-
small-label
7-
:label="field.name"
8-
required
9-
class="margin-bottom-2"
10-
>
11-
<FieldMapping
12-
:field-mapping="field.mapping"
13-
:placeholder="$t('upsertRowWorkflowActionForm.fieldMappingPlaceholder')"
14-
@change="updateFieldMapping(field.id, $event)"
15-
/>
2+
<div v-if="mapping?.enabled">
3+
<FormGroup small-label :label="field.name" required class="margin-bottom-2">
4+
<InViewport>
5+
<InjectedFormulaInput
6+
:key="`${field.id} ${mapping.enabled}`"
7+
v-model="fieldValue"
8+
:disabled="!mapping.enabled"
9+
:placeholder="
10+
$t('upsertRowWorkflowActionForm.fieldMappingPlaceholder')
11+
"
12+
/>
13+
<template #placeholder>
14+
<div class="field-mapping-form__placeholder" />
15+
</template>
16+
</InViewport>
1617
<template #after-input>
17-
<div :ref="`editFieldMappingOpener-${field.id}`">
18+
<div :ref="`editFieldMappingOpener`">
1819
<ButtonIcon
1920
type="secondary"
2021
icon="iconoir-more-vert"
21-
@click="openContext(field)"
22+
@click="openContext()"
2223
/>
2324
</div>
2425
<FieldMappingContext
25-
:ref="`fieldMappingContext-${field.id}`"
26-
:field-mapping="field.mapping"
27-
@edit="updateFieldMapping(field.id, $event)"
26+
:ref="`fieldMappingContext`"
27+
:field-mapping="mapping"
28+
@edit="$emit('update', $event)"
2829
/>
2930
</template>
3031
</FormGroup>
3132
</div>
33+
<div v-else>
34+
<FormGroup small-label :label="field.name" required class="margin-bottom-2">
35+
<Button type="secondary" @click="$emit('update', defaultEmptyFormula())">
36+
{{ $t('fieldMappingContext.enableField') }}
37+
</Button>
38+
</FormGroup>
39+
</div>
3240
</template>
3341

3442
<script>
35-
import FieldMapping from '@baserow/modules/integrations/localBaserow/components/services/FieldMapping'
3643
import FieldMappingContext from '@baserow/modules/integrations/localBaserow/components/services/FieldMappingContext'
44+
import InjectedFormulaInput from '@baserow/modules/core/components/formula/InjectedFormulaInput'
45+
import InViewport from '@baserow/modules/core/components/InViewport'
3746
3847
export default {
3948
name: 'FieldMappingForm',
40-
components: { FieldMappingContext, FieldMapping },
49+
components: { FieldMappingContext, InjectedFormulaInput, InViewport },
4150
inject: ['workspace'],
4251
props: {
43-
value: {
44-
type: Array,
52+
field: {
53+
type: Object,
4554
required: true,
4655
},
47-
fields: {
48-
type: Array,
49-
required: true,
56+
mapping: {
57+
type: Object,
58+
required: false,
59+
default: undefined,
5060
},
5161
},
62+
data() {
63+
return {
64+
localValue: this.mapping?.value,
65+
debounceTimeout: null,
66+
}
67+
},
5268
computed: {
53-
filteredFields() {
54-
return this.fields
55-
.map((field) => ({
56-
...field,
57-
mapping: this.getFieldMapping(field.id),
58-
}))
59-
.filter((field) => this.canWriteFieldValues(field))
69+
fieldValue: {
70+
get() {
71+
return this.localValue
72+
},
73+
set(value) {
74+
this.localValue = value
75+
76+
// Debouncing value update as it produces performance issues when they are
77+
// a lot of fields
78+
clearTimeout(this.debounceTimeout)
79+
this.debounceTimeout = setTimeout(() => {
80+
this.$emit('update', { value })
81+
}, 500)
82+
},
83+
},
84+
},
85+
watch: {
86+
'mapping.value'(newValue) {
87+
this.localValue = newValue
6088
},
6189
},
6290
methods: {
63-
openContext(field) {
64-
this.$refs[`fieldMappingContext-${field.id}`][0].toggle(
65-
this.$refs[`editFieldMappingOpener-${field.id}`][0],
91+
defaultEmptyFormula() {
92+
return {
93+
enabled: true,
94+
value: { formula: '""' },
95+
}
96+
},
97+
openContext() {
98+
this.$refs.fieldMappingContext.toggle(
99+
this.$refs.editFieldMappingOpener,
66100
'bottom',
67101
'left',
68102
4
69103
)
70104
},
71-
canWriteFieldValues(field) {
72-
return this.$hasPermission(
73-
'database.table.field.write_values',
74-
field,
75-
this.workspace.id
76-
)
77-
},
78-
getFieldMapping(fieldId) {
79-
return (
80-
this.value.find(
81-
(fieldMapping) => fieldMapping.field_id === fieldId
82-
) || { enabled: true, field_id: fieldId, value: {} }
83-
)
84-
},
85-
updateFieldMapping(fieldId, changes) {
86-
const existingMapping = this.value.some(
87-
({ field_id: existingId }) => existingId === fieldId
88-
)
89-
const existingFieldIds = this.fields.map(({ id }) => id)
90-
91-
// If the field has been removed in the meantime we want to ignore it
92-
const filteredValue = this.value.filter(({ field_id: fieldId }) =>
93-
existingFieldIds.includes(fieldId)
94-
)
95-
96-
if (existingMapping) {
97-
const newMapping = filteredValue.map((fieldMapping) => {
98-
if (fieldMapping.field_id === fieldId) {
99-
return { ...fieldMapping, ...changes }
100-
}
101-
return fieldMapping
102-
})
103-
this.$emit('input', newMapping)
104-
} else {
105-
const newMapping = filteredValue
106-
newMapping.push({
107-
enabled: true,
108-
field_id: fieldId,
109-
...changes,
110-
})
111-
this.$emit('input', newMapping)
112-
}
113-
},
114105
},
115106
}
116107
</script>

0 commit comments

Comments
 (0)