1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-04 21:25:24 +00:00

Resolve "Allow the designer to choose which fields are mapped for upsert local baserow service"

This commit is contained in:
Peter Evans 2024-07-16 13:39:34 +00:00
parent de2c585a9d
commit a90605d8a5
16 changed files with 258 additions and 40 deletions
backend
src/baserow/contrib/integrations
tests/baserow/contrib
builder
integrations/local_baserow
changelog/entries/unreleased/feature
web-frontend/modules

View file

@ -38,4 +38,7 @@ class LocalBaserowTableServiceFieldMappingSerializer(serializers.Serializer):
field_id = serializers.IntegerField(
help_text="The primary key of the associated database table field."
)
enabled = serializers.BooleanField(
help_text="Indicates whether the field mapping is enabled or not."
)
value = FormulaSerializerField(allow_blank=True)

View file

@ -212,6 +212,12 @@ class LocalBaserowTableServiceFieldMapping(models.Model):
on_delete=models.CASCADE,
help_text="The Baserow field that this mapping relates to.",
)
enabled = models.BooleanField(
null=True, # TODO zdm remove me after v1.27
default=True,
help_text="Indicates if the field mapping is enabled. If it is disabled, "
"we will not use the `value` when creating and updating rows.",
)
value = FormulaField(default="", help_text="The field mapping's value.")
service = models.ForeignKey(
Service,

View file

@ -1261,7 +1261,10 @@ class LocalBaserowUpsertRowServiceType(LocalBaserowTableServiceType):
bulk_field_mappings.append(
LocalBaserowTableServiceFieldMapping(
field=field, service=instance, value=field_mapping["value"]
field=field,
service=instance,
enabled=field_mapping["enabled"],
value=field_mapping["value"],
)
)
LocalBaserowTableServiceFieldMapping.objects.bulk_create(
@ -1286,6 +1289,7 @@ class LocalBaserowUpsertRowServiceType(LocalBaserowTableServiceType):
{
"field_id": m.field_id,
"value": m.value,
"enabled": m.enabled,
}
for m in service.field_mappings.all()
]
@ -1321,6 +1325,7 @@ class LocalBaserowUpsertRowServiceType(LocalBaserowTableServiceType):
if prop_name == "field_mappings":
return [
{
"enabled": item["enabled"],
"value": import_formula(item["value"], id_mapping, **kwargs),
"field_id": (
id_mapping["database_fields"][item["field_id"]]
@ -1490,7 +1495,9 @@ class LocalBaserowUpsertRowServiceType(LocalBaserowTableServiceType):
integration = service.integration.specific
field_values = {}
field_mappings = service.field_mappings.select_related("field").all()
field_mappings = service.field_mappings.select_related("field").filter(
enabled=True
)
for field_mapping in field_mappings:
if field_mapping.id not in resolved_values:

View file

@ -0,0 +1,24 @@
# Generated by Django 4.2.13 on 2024-07-15 09:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
(
"integrations",
"0006_migrate_local_baserow_table_service_filter_formulas_to_value_is_formula",
),
]
operations = [
migrations.AddField(
model_name="localbaserowtableservicefieldmapping",
name="enabled",
field=models.BooleanField(
default=True,
help_text="Indicates if the field mapping is enabled. If it is disabled, we will not use the `value` when creating and updating rows.",
null=True,
),
),
]

View file

@ -390,7 +390,9 @@ def test_update_create_row_workflow_action(api_client, data_fixture):
"table_id": table.id,
"type": service_type.type,
"integration_id": workflow_action.service.integration_id,
"field_mappings": [{"field_id": field.id, "value": "'Pony'"}],
"field_mappings": [
{"field_id": field.id, "value": "'Pony'", "enabled": True}
],
}
},
format="json",
@ -406,7 +408,7 @@ def test_update_create_row_workflow_action(api_client, data_fixture):
assert response_json["service"]["table_id"] == service.table_id
assert response_json["service"]["integration_id"] == service.integration_id
assert response_json["service"]["field_mappings"] == [
{"field_id": field.id, "value": "'Pony'"}
{"field_id": field.id, "value": "'Pony'", "enabled": True}
]
@ -487,7 +489,9 @@ def test_update_update_row_workflow_action(api_client, data_fixture):
"row_id": first_row.id,
"type": service_type.type,
"integration_id": workflow_action.service.integration_id,
"field_mappings": [{"field_id": field.id, "value": "'Pony'"}],
"field_mappings": [
{"field_id": field.id, "value": "'Pony'", "enabled": True}
],
},
},
format="json",
@ -508,7 +512,7 @@ def test_update_update_row_workflow_action(api_client, data_fixture):
== workflow_action.service.integration_id
)
assert response_json["service"]["field_mappings"] == [
{"field_id": field.id, "value": "'Pony'"}
{"field_id": field.id, "value": "'Pony'", "enabled": True}
]

View file

@ -126,7 +126,9 @@ def test_export_import_upsert_row_workflow_action_type(data_fixture):
"type": "local_baserow_upsert_row",
"row_id": "",
"table_id": table.id,
"field_mappings": [{"field_id": field.id, "value": field_mapping.value}],
"field_mappings": [
{"field_id": field.id, "value": field_mapping.value, "enabled": True}
],
},
}

View file

@ -2091,6 +2091,64 @@ def test_local_baserow_upsert_row_service_dispatch_data_without_row_id(
)
@pytest.mark.django_db
def test_local_baserow_upsert_row_service_dispatch_data_disabled_field_mapping_fields(
data_fixture,
):
user = data_fixture.create_user()
page = data_fixture.create_builder_page(user=user)
integration = data_fixture.create_local_baserow_integration(
application=page.builder, user=user
)
database = data_fixture.create_database_application(
workspace=page.builder.workspace
)
table = TableHandler().create_table_and_fields(
user=user,
database=database,
name=data_fixture.fake.name(),
fields=[
("Name", "text", {}),
("Last name", "text", {}),
("Location", "text", {}),
],
)
name_field = table.field_set.get(name="Name")
last_name_field = table.field_set.get(name="Last name")
location_field = table.field_set.get(name="Location")
row = RowHandler().create_row(
user=user,
table=table,
values={
name_field.id: "Peter",
last_name_field.id: "Evans",
location_field.id: "Cornwall",
},
)
service = data_fixture.create_local_baserow_upsert_row_service(
table=table,
row_id=f"'{row.id}'",
integration=integration,
)
service_type = service.get_type()
service.field_mappings.create(field=name_field, value="'Jeff'", enabled=True)
service.field_mappings.create(field=last_name_field, value="", enabled=False)
service.field_mappings.create(field=location_field, value="", enabled=False)
fake_request = Mock()
dispatch_context = BuilderDispatchContext(fake_request, page)
dispatch_values = service_type.resolve_service_formulas(service, dispatch_context)
service_type.dispatch_data(service, dispatch_values, dispatch_context)
row.refresh_from_db()
assert getattr(row, name_field.db_column) == "Jeff"
assert getattr(row, last_name_field.db_column) == "Evans"
assert getattr(row, location_field.db_column) == "Cornwall"
@pytest.mark.django_db
def test_local_baserow_upsert_row_service_dispatch_data_with_row_id(
data_fixture,
@ -2540,7 +2598,9 @@ def test_local_baserow_upsert_row_service_after_update(data_fixture):
{
"table_id": table.id,
"integration_id": integration.id,
"field_mappings": [{"field_id": field.id, "value": "'Horse'"}],
"field_mappings": [
{"field_id": field.id, "value": "'Horse'", "enabled": True}
],
},
{},
)
@ -2551,7 +2611,7 @@ def test_local_baserow_upsert_row_service_after_update(data_fixture):
service,
{
"table_id": table.id,
"field_mappings": [{"value": "'Bread'"}],
"field_mappings": [{"value": "'Bread'", "enabled": True}],
},
{},
)
@ -2566,7 +2626,9 @@ def test_local_baserow_upsert_row_service_after_update(data_fixture):
LocalBaserowUpsertRowServiceType().after_update(
service,
{
"field_mappings": [{"field_id": field.id, "value": "'Pony'"}],
"field_mappings": [
{"field_id": field.id, "value": "'Pony'", "enabled": True}
],
},
{"table": (table, table2)},
)

View file

@ -0,0 +1,7 @@
{
"type": "feature",
"message": "[Builder] Made it easier for application builder page designers to disable fields in their create/update row actions.",
"issue_number": 2620,
"bullet_points": [],
"created_at": "2024-07-15"
}

View file

@ -608,6 +608,10 @@
"fieldMappingPlaceholder": "Choose a field value",
"noTableSelectedMessage": "Choose a table to begin configuring your fields."
},
"fieldMappingContext": {
"enableField": "Enable field",
"disableField": "Disable field"
},
"checkboxElementForm": {
"labelTitle": "Label",
"valueTitle": "Default value",

View file

@ -16,6 +16,10 @@
border-color: $color-primary-500;
}
.formula-input-field--disabled {
user-select: none;
}
.formula-input-field__reset-button {
width: 100%;
}

View file

@ -63,6 +63,11 @@ export default {
type: String,
default: '',
},
disabled: {
type: Boolean,
required: false,
default: false,
},
placeholder: {
type: String,
default: null,
@ -104,8 +109,10 @@ export default {
},
classes() {
return {
'form-input--disabled': this.disabled,
'formula-input-field--small': this.small,
'formula-input-field--focused': this.isFocused,
'formula-input-field--focused': !this.disabled && this.isFocused,
'formula-input-field--disabled': this.disabled,
}
},
placeHolderExt() {
@ -159,6 +166,9 @@ export default {
},
},
watch: {
disabled(newValue) {
this.editor.setOptions({ editable: !newValue })
},
isFocused(value) {
if (!value) {
this.$refs.dataExplorer.hide()
@ -220,7 +230,7 @@ export default {
this.content = this.toContent(this.value)
this.editor = new Editor({
content: this.htmlContent,
editable: true,
editable: !this.disabled,
onUpdate: this.onUpdate,
onFocus: this.onFocus,
onBlur: this.onBlur,
@ -251,7 +261,9 @@ export default {
this.emitChange()
},
onFocus() {
this.formulaInputFocused = true
// If the input is disabled, we don't want users to be
// able to open the data explorer and select nodes.
this.formulaInputFocused = !this.disabled
},
onBlur() {
// We have to delay the browser here by just a bit, running the below will make

View file

@ -8,6 +8,7 @@
>
<FormulaInputField
:value="value"
:disabled="disabled"
:placeholder="placeholder"
:data-providers="dataProviders"
:data-explorer-loading="dataExplorerLoading"
@ -23,6 +24,7 @@
<script>
import FormulaInputField from '@baserow/modules/core/components/formula/FormulaInputField'
export default {
components: { FormulaInputField },
props: {
@ -30,6 +32,11 @@ export default {
type: String,
required: true,
},
disabled: {
type: Boolean,
required: false,
default: false,
},
label: {
type: String,
required: false,

View file

@ -4,7 +4,11 @@
:data-providers-allowed="dataProvidersAllowed || []"
v-bind="$attrs"
v-on="$listeners"
/>
>
<template #after-input>
<slot name="after-input"></slot>
</template>
</component>
</template>
<script>

View file

@ -1,37 +1,62 @@
<template>
<InjectedFormulaInputGroup
v-model="fieldValue"
v-bind="$attrs"
class="margin-bottom-2"
/>
<div>
<InjectedFormulaInputGroup
v-model="fieldValue"
:disabled="!fieldMapping.enabled"
v-bind="$attrs"
class="margin-bottom-2"
>
<template #after-input>
<div ref="editFieldMappingOpener">
<Button
type="secondary"
size="regular"
icon="iconoir-more-vert"
@click="openContext"
></Button>
</div>
<FieldMappingContext
ref="fieldMappingContext"
:field-mapping="fieldMapping"
@edit="$emit('change', $event)"
></FieldMappingContext>
</template>
</InjectedFormulaInputGroup>
</div>
</template>
<script>
import InjectedFormulaInputGroup from '@baserow/modules/core/components/formula/InjectedFormulaInputGroup'
import FieldMappingContext from '@baserow/modules/integrations/localBaserow/components/services/FieldMappingContext'
export default {
name: 'FieldMapping',
components: { InjectedFormulaInputGroup },
components: { FieldMappingContext, InjectedFormulaInputGroup },
props: {
field: {
fieldMapping: {
type: Object,
required: true,
},
value: {
type: String,
required: false,
default: () => '',
},
},
computed: {
fieldValue: {
get() {
return this.value
return this.fieldMapping.value
},
set(value) {
this.$emit('change', value)
this.$emit('change', { value })
},
},
},
methods: {
openContext() {
this.$refs.fieldMappingContext.toggle(
this.$refs.editFieldMappingOpener,
'bottom',
'left',
4
)
},
},
}
</script>

View file

@ -0,0 +1,48 @@
<template>
<Context :overflow-scroll="true" :max-height-if-outside-viewport="true">
<ul class="context__menu">
<li class="context__menu-item">
<a
class="context__menu-item-link"
@click.prevent="
handleEditClick({ enabled: !fieldMapping.enabled, value: '' })
"
>
<i class="context__menu-item-icon" :class="enabledClass"></i>
{{ toggleEnabledText }}
</a>
</li>
</ul>
</Context>
</template>
<script>
import context from '@baserow/modules/core/mixins/context'
export default {
name: 'FieldMappingContext',
mixins: [context],
props: {
fieldMapping: {
type: Object,
required: true,
},
},
computed: {
enabledClass() {
return this.fieldMapping.enabled ? 'iconoir-eye-off' : 'iconoir-eye-empty'
},
toggleEnabledText() {
return this.fieldMapping.enabled
? this.$t('fieldMappingContext.disableField')
: this.$t('fieldMappingContext.enableField')
},
},
methods: {
handleEditClick(change) {
this.$emit('edit', change)
this.hide()
},
},
}
</script>

View file

@ -5,11 +5,10 @@
:key="field.id"
small
small-label
:field="field"
:label="field.name"
:value="getFieldMappingValue(field.id)"
:field-mapping="getFieldMapping(field.id)"
:placeholder="$t('upsertRowWorkflowActionForm.fieldMappingPlaceholder')"
@change="updateFieldMapping($event, field.id)"
@change="updateFieldMapping(field.id, $event)"
></FieldMapping>
</div>
</template>
@ -31,31 +30,31 @@ export default {
},
},
methods: {
getFieldMappingValue(fieldId) {
const mapping = this.value.find(
(fieldMapping) => fieldMapping.field_id === fieldId
getFieldMapping(fieldId) {
return (
this.value.find(
(fieldMapping) => fieldMapping.field_id === fieldId
) || { enabled: true, field_id: fieldId, value: '' }
)
return mapping?.value || ''
},
updateFieldMapping(newValue, fieldId) {
const event = { field_id: fieldId, value: newValue }
updateFieldMapping(fieldId, changes) {
const existingMapping = this.value.some(
({ field_id: existingId }) => existingId === fieldId
)
if (existingMapping) {
const newMapping = this.value.map((fieldMapping) => {
if (fieldMapping.field_id === fieldId) {
return { ...fieldMapping, ...event }
return { ...fieldMapping, ...changes }
}
return fieldMapping
})
this.$emit('input', newMapping)
} else {
// It already exists
const newMapping = [...this.value]
newMapping.push({
enabled: true,
field_id: fieldId,
...event,
...changes,
})
this.$emit('input', newMapping)
}