mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-14 17:18:33 +00:00
Resolve "Add Button collection field"
This commit is contained in:
parent
189c87327c
commit
ce868c341d
41 changed files with 583 additions and 136 deletions
backend
src/baserow
tests/baserow/contrib/builder
changelog/entries/unreleased/feature
enterprise/web-frontend/modules/baserow_enterprise/builder
web-frontend/modules
builder
collectionFieldTypes.jsdataProviderTypes.jselementTypes.jseventTypes.js
components
elements/components
BaserowTable.vueButtonElement.vueFormContainerElement.vueTableElement.vue
collectionField
forms/general
event
page
locales
mixins
pageSidePanelTypes.jsplugin.jsstore
workflowActionTypes.jscore/components
|
@ -212,7 +212,7 @@ class CollectionFieldSerializer(serializers.ModelSerializer):
|
|||
object.
|
||||
"""
|
||||
|
||||
default_allowed_fields = ["name", "type", "id"]
|
||||
default_allowed_fields = ["name", "type", "id", "uid"]
|
||||
|
||||
config = serializers.DictField(
|
||||
required=False,
|
||||
|
|
|
@ -260,6 +260,7 @@ class BuilderConfig(AppConfig):
|
|||
|
||||
from .elements.collection_field_types import (
|
||||
BooleanCollectionFieldType,
|
||||
ButtonCollectionFieldType,
|
||||
LinkCollectionFieldType,
|
||||
TagsCollectionFieldType,
|
||||
TextCollectionFieldType,
|
||||
|
@ -270,6 +271,7 @@ class BuilderConfig(AppConfig):
|
|||
collection_field_type_registry.register(TextCollectionFieldType())
|
||||
collection_field_type_registry.register(LinkCollectionFieldType())
|
||||
collection_field_type_registry.register(TagsCollectionFieldType())
|
||||
collection_field_type_registry.register(ButtonCollectionFieldType())
|
||||
|
||||
from .domains.receivers import connect_to_domain_pre_delete_signal
|
||||
|
||||
|
|
|
@ -3,8 +3,10 @@ from typing import Any, Dict, Optional, TypedDict
|
|||
from rest_framework import serializers
|
||||
|
||||
from baserow.contrib.builder.elements.element_types import NavigationElementManager
|
||||
from baserow.contrib.builder.elements.models import CollectionField
|
||||
from baserow.contrib.builder.elements.registries import CollectionFieldType
|
||||
from baserow.contrib.builder.formula_importer import import_formula
|
||||
from baserow.contrib.builder.workflow_actions.models import BuilderWorkflowAction
|
||||
from baserow.core.formula.serializers import FormulaSerializerField
|
||||
|
||||
|
||||
|
@ -185,3 +187,41 @@ class TagsCollectionFieldType(CollectionFieldType):
|
|||
return super().deserialize_property(
|
||||
prop_name, value, id_mapping, data_source_id
|
||||
)
|
||||
|
||||
|
||||
class ButtonCollectionFieldType(CollectionFieldType):
|
||||
type = "button"
|
||||
allowed_fields = ["label"]
|
||||
serializer_field_names = ["label"]
|
||||
|
||||
class SerializedDict(TypedDict):
|
||||
label: str
|
||||
|
||||
@property
|
||||
def serializer_field_overrides(self):
|
||||
return {
|
||||
"label": FormulaSerializerField(
|
||||
help_text="The string value.",
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
default="",
|
||||
),
|
||||
}
|
||||
|
||||
def deserialize_property(
|
||||
self,
|
||||
prop_name: str,
|
||||
value: Any,
|
||||
id_mapping: Dict[str, Any],
|
||||
data_source_id: Optional[int] = None,
|
||||
) -> Any:
|
||||
if prop_name == "label" and data_source_id:
|
||||
return import_formula(value, id_mapping, data_source_id=data_source_id)
|
||||
|
||||
return super().deserialize_property(
|
||||
prop_name, value, id_mapping, data_source_id
|
||||
)
|
||||
|
||||
def before_delete(self, instance: CollectionField):
|
||||
# We delete the related workflow actions
|
||||
BuilderWorkflowAction.objects.filter(event__startswith=instance.uid).delete()
|
||||
|
|
|
@ -135,9 +135,11 @@ class ElementHandler:
|
|||
"""
|
||||
|
||||
element = ElementHandler().get_element(element_id)
|
||||
if isinstance(element.get_type(), target_type):
|
||||
return element
|
||||
|
||||
for ancestor in self.get_ancestors(element_id, element.page):
|
||||
ancestor_type = element_type_registry.get_by_model(ancestor.specific_class)
|
||||
if isinstance(ancestor_type, target_type):
|
||||
if isinstance(ancestor.get_type(), target_type):
|
||||
return ancestor
|
||||
|
||||
def get_element_for_update(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from typing import Any, Dict, List, Optional, Type
|
||||
|
||||
from django.db.models import QuerySet
|
||||
from django.db.models import Q, QuerySet
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from rest_framework import serializers
|
||||
|
@ -311,6 +311,17 @@ class CollectionElementWithFieldsTypeMixin(CollectionElementTypeMixin):
|
|||
|
||||
def after_update(self, instance: CollectionElementSubClass, values):
|
||||
if "fields" in values:
|
||||
# If the collection element contains fields that are being deleted,
|
||||
# we also need to delete the associated workflow actions.
|
||||
query = Q()
|
||||
for field in values["fields"]:
|
||||
if "uid" in field:
|
||||
query |= Q(uid=field["uid"])
|
||||
|
||||
# Call before delete hook of removed fields
|
||||
for field in instance.fields.exclude(query):
|
||||
field.get_type().before_delete(field)
|
||||
|
||||
# Remove previous fields
|
||||
instance.fields.all().delete()
|
||||
|
||||
|
@ -323,6 +334,10 @@ class CollectionElementWithFieldsTypeMixin(CollectionElementTypeMixin):
|
|||
instance.fields.add(*created_fields)
|
||||
|
||||
def before_delete(self, instance: CollectionElementSubClass):
|
||||
# Call the before_delete hook of all fields
|
||||
for field in instance.fields.all():
|
||||
field.get_type().before_delete(field)
|
||||
|
||||
instance.fields.all().delete()
|
||||
|
||||
def create_instance_from_serialized(
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import uuid
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
@ -685,6 +686,7 @@ class CollectionField(models.Model):
|
|||
A field of a Collection element
|
||||
"""
|
||||
|
||||
uid = models.UUIDField(default=uuid.uuid4)
|
||||
order = models.PositiveIntegerField()
|
||||
name = models.CharField(
|
||||
max_length=225,
|
||||
|
|
|
@ -203,6 +203,7 @@ class CollectionFieldType(
|
|||
)
|
||||
|
||||
serialized = {
|
||||
"uid": instance.uid,
|
||||
"name": instance.name,
|
||||
"type": instance.type,
|
||||
"config": serialized_config,
|
||||
|
@ -270,6 +271,7 @@ class CollectionFieldType(
|
|||
)
|
||||
|
||||
deserialized_values = {
|
||||
"uid": serialized_values["uid"],
|
||||
"config": deserialized_config,
|
||||
"type": serialized_values["type"],
|
||||
"name": serialized_values["name"],
|
||||
|
@ -310,6 +312,12 @@ class CollectionFieldType(
|
|||
|
||||
return serializer_class(model_instance_or_instances, context=context, **kwargs)
|
||||
|
||||
def before_delete(self, instance: CollectionField):
|
||||
"""
|
||||
This hooks is called before we delete a collection field and gives the
|
||||
opportunity to clean up things.
|
||||
"""
|
||||
|
||||
|
||||
CollectionFieldTypeSubClass = TypeVar(
|
||||
"CollectionFieldTypeSubClass", bound=CollectionFieldType
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# Generated by Django 4.1.13 on 2024-05-22 14:50
|
||||
|
||||
import uuid
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
from baserow.contrib.database.db.functions import RandomUUID
|
||||
|
||||
|
||||
def gen_uuid(apps, schema_editor):
|
||||
"""
|
||||
Generates the uid for all user sources.
|
||||
"""
|
||||
|
||||
CollectionField = apps.get_model("builder", "collectionfield")
|
||||
CollectionField.objects.update(uid=RandomUUID())
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("builder", "0019_repeat_element_styling"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="collectionfield",
|
||||
name="uid",
|
||||
field=models.UUIDField(default=uuid.uuid4),
|
||||
),
|
||||
migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop),
|
||||
migrations.AlterField(
|
||||
model_name="builderworkflowaction",
|
||||
name="event",
|
||||
field=models.CharField(
|
||||
help_text="The event that triggers the execution", max_length=60
|
||||
),
|
||||
),
|
||||
]
|
|
@ -28,8 +28,7 @@ class BuilderWorkflowAction(
|
|||
on_delete=models.CASCADE,
|
||||
)
|
||||
event = models.CharField(
|
||||
max_length=30,
|
||||
choices=EventTypes.choices,
|
||||
max_length=60,
|
||||
help_text="The event that triggers the execution",
|
||||
)
|
||||
page = models.ForeignKey(Page, on_delete=models.CASCADE)
|
||||
|
|
|
@ -1332,6 +1332,9 @@ class LocalBaserowUpsertRowServiceType(LocalBaserowTableServiceType):
|
|||
)
|
||||
raise ServiceImproperlyConfigured(message) from e
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
message = (
|
||||
"Unknown error in formula for "
|
||||
f"field {field_mapping.field.name}({field_mapping.field.id}): {str(e)}"
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from django.db import transaction
|
||||
|
||||
from baserow.core.models import WorkspaceUser
|
||||
from baserow.core.handler import CoreHandler
|
||||
from baserow.core.models import Workspace, WorkspaceUser
|
||||
from baserow.core.user.exceptions import UserAlreadyExist
|
||||
from baserow.core.user.handler import UserHandler
|
||||
|
||||
|
@ -24,9 +25,18 @@ def load_test_data():
|
|||
admin.is_staff = True
|
||||
admin.save()
|
||||
|
||||
workspace = admin.workspaceuser_set.all().order_by("id").first().workspace
|
||||
workspace.name = f"Acme Corp ({i +1})" if i > 0 else "Acme Corp"
|
||||
workspace.save()
|
||||
try:
|
||||
workspace = Workspace.objects.get(
|
||||
name=f"Acme Corp ({i +1})" if i > 0 else "Acme Corp"
|
||||
)
|
||||
except Workspace.DoesNotExist:
|
||||
workspace = (
|
||||
CoreHandler()
|
||||
.create_workspace(
|
||||
admin, name=f"Acme Corp ({i +1})" if i > 0 else "Acme Corp"
|
||||
)
|
||||
.workspace
|
||||
)
|
||||
|
||||
# Create a second admin for the workspace
|
||||
email = f"admin{i + 1}_bis@baserow.io" if i > 0 else "admin_bis@baserow.io"
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import uuid
|
||||
|
||||
from django.urls import reverse
|
||||
|
||||
import pytest
|
||||
|
@ -48,20 +50,32 @@ def test_can_update_a_table_element_fields(api_client, data_fixture):
|
|||
table_element = data_fixture.create_builder_table_element(user=user)
|
||||
|
||||
url = reverse("api:builder:element:item", kwargs={"element_id": table_element.id})
|
||||
uuids = [str(uuid.uuid4()), str(uuid.uuid4()), str(uuid.uuid4())]
|
||||
|
||||
response = api_client.patch(
|
||||
url,
|
||||
{
|
||||
"fields": [
|
||||
{"name": "Name", "type": "text", "value": "get('test1')"},
|
||||
{
|
||||
"name": "Name",
|
||||
"type": "text",
|
||||
"value": "get('test1')",
|
||||
"uid": uuids[0],
|
||||
},
|
||||
{
|
||||
"name": "Color",
|
||||
"type": "link",
|
||||
"navigate_to_url": "get('test2')",
|
||||
"link_name": "get('test3')",
|
||||
"target": "self",
|
||||
"uid": uuids[1],
|
||||
},
|
||||
{
|
||||
"name": "Question",
|
||||
"type": "text",
|
||||
"value": "get('test3')",
|
||||
"uid": uuids[2],
|
||||
},
|
||||
{"name": "Question", "type": "text", "value": "get('test3')"},
|
||||
],
|
||||
},
|
||||
format="json",
|
||||
|
@ -70,10 +84,10 @@ def test_can_update_a_table_element_fields(api_client, data_fixture):
|
|||
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert [
|
||||
{key: value for key, value in f.items() if key != "id"}
|
||||
{key: value for key, value in f.items() if key not in ["id"]}
|
||||
for f in response.json()["fields"]
|
||||
] == [
|
||||
{"name": "Name", "type": "text", "value": "get('test1')"},
|
||||
{"name": "Name", "type": "text", "value": "get('test1')", "uid": uuids[0]},
|
||||
{
|
||||
"name": "Color",
|
||||
"type": "link",
|
||||
|
@ -81,8 +95,9 @@ def test_can_update_a_table_element_fields(api_client, data_fixture):
|
|||
"navigate_to_url": "get('test2')",
|
||||
"link_name": "get('test3')",
|
||||
"target": "self",
|
||||
"uid": uuids[1],
|
||||
},
|
||||
{"name": "Question", "type": "text", "value": "get('test3')"},
|
||||
{"name": "Question", "type": "text", "value": "get('test3')", "uid": uuids[2]},
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -96,7 +96,9 @@ def test_create_workflow_action_event_does_not_exist(api_client, data_fixture):
|
|||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
# NOTE: Event names are no longer bound to a list of choices, so
|
||||
# the API will not raise a 400 Bad Request error
|
||||
assert response.status_code == HTTP_404_NOT_FOUND
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
|
|
@ -573,3 +573,9 @@ def test_get_first_ancestor_of_type(data_fixture, django_assert_num_queries):
|
|||
)
|
||||
|
||||
assert nearest_column_ancestor.specific == grandparent
|
||||
|
||||
nearest_column_ancestor = ElementHandler().get_first_ancestor_of_type(
|
||||
grandparent.id, ColumnElementType
|
||||
)
|
||||
|
||||
assert nearest_column_ancestor.specific == grandparent
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
import uuid
|
||||
from collections import defaultdict
|
||||
|
||||
import pytest
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from baserow.contrib.builder.elements.handler import ElementHandler
|
||||
from baserow.contrib.builder.elements.models import CollectionField, Element
|
||||
from baserow.contrib.builder.elements.models import (
|
||||
CollectionField,
|
||||
Element,
|
||||
TableElement,
|
||||
)
|
||||
from baserow.contrib.builder.elements.registries import element_type_registry
|
||||
from baserow.contrib.builder.elements.service import ElementService
|
||||
from baserow.contrib.builder.workflow_actions.models import NotificationWorkflowAction
|
||||
from baserow.core.utils import MirrorDict
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
@ -137,6 +146,7 @@ def test_update_table_element_with_fields(data_fixture):
|
|||
user = data_fixture.create_user()
|
||||
page = data_fixture.create_builder_page(user=user)
|
||||
table_element = data_fixture.create_builder_table_element(page=page)
|
||||
uuids = [str(uuid.uuid4()), str(uuid.uuid4()), str(uuid.uuid4())]
|
||||
|
||||
ElementService().update_element(
|
||||
user,
|
||||
|
@ -146,11 +156,13 @@ def test_update_table_element_with_fields(data_fixture):
|
|||
"name": "New field 1",
|
||||
"type": "text",
|
||||
"config": {"value": "get('test1')"},
|
||||
"uid": uuids[0],
|
||||
},
|
||||
{
|
||||
"name": "New field 2",
|
||||
"type": "text",
|
||||
"config": {"value": "get('test2')"},
|
||||
"uid": uuids[1],
|
||||
},
|
||||
],
|
||||
)
|
||||
|
@ -172,11 +184,13 @@ def test_update_table_element_with_fields(data_fixture):
|
|||
"name": "New field 3",
|
||||
"type": "text",
|
||||
"config": {"value": "get('test3')"},
|
||||
"uid": uuids[0],
|
||||
},
|
||||
{
|
||||
"name": "New field 4",
|
||||
"type": "text",
|
||||
"config": {"value": "get('test4')"},
|
||||
"uid": uuids[1],
|
||||
},
|
||||
],
|
||||
)
|
||||
|
@ -277,6 +291,7 @@ def test_import_table_element_with_current_record_formulas_with_update(data_fixt
|
|||
data_source1 = data_fixture.create_builder_local_baserow_list_rows_data_source(
|
||||
table=table, page=page
|
||||
)
|
||||
uuids = [str(uuid.uuid4()), str(uuid.uuid4()), str(uuid.uuid4())]
|
||||
|
||||
IMPORT_REF = {
|
||||
"id": 42,
|
||||
|
@ -293,6 +308,7 @@ def test_import_table_element_with_current_record_formulas_with_update(data_fixt
|
|||
"name": "Field 1",
|
||||
"config": {"value": f"get('current_record.field_42')"},
|
||||
"type": "text",
|
||||
"uid": uuids[0],
|
||||
},
|
||||
{
|
||||
"name": "Field 2",
|
||||
|
@ -305,6 +321,7 @@ def test_import_table_element_with_current_record_formulas_with_update(data_fixt
|
|||
"target": "self",
|
||||
},
|
||||
"type": "link",
|
||||
"uid": uuids[1],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@ -331,3 +348,102 @@ def test_import_table_element_with_current_record_formulas_with_update(data_fixt
|
|||
"target": "self",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_delete_table_element_removes_associated_workflow_actions(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
page = data_fixture.create_builder_page(user=user)
|
||||
data_source1 = data_fixture.create_builder_local_baserow_list_rows_data_source(
|
||||
page=page
|
||||
)
|
||||
table_element = data_fixture.create_builder_table_element(
|
||||
page=page,
|
||||
data_source=data_source1,
|
||||
fields=[
|
||||
{
|
||||
"name": "Field",
|
||||
"type": "button",
|
||||
"config": {"label": "Click me"},
|
||||
},
|
||||
],
|
||||
)
|
||||
data_fixture.create_workflow_action(
|
||||
NotificationWorkflowAction,
|
||||
page=page,
|
||||
element=table_element,
|
||||
event=f"{table_element.fields.first().uid}_click",
|
||||
)
|
||||
|
||||
assert NotificationWorkflowAction.objects.count() == 1
|
||||
ElementService().delete_element(user, table_element)
|
||||
assert NotificationWorkflowAction.objects.count() == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_delete_table_field_removes_associated_workflow_actions(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
page = data_fixture.create_builder_page(user=user)
|
||||
data_source1 = data_fixture.create_builder_local_baserow_list_rows_data_source(
|
||||
page=page
|
||||
)
|
||||
table_element = data_fixture.create_builder_table_element(
|
||||
page=page,
|
||||
data_source=data_source1,
|
||||
fields=[
|
||||
{
|
||||
"name": "Field",
|
||||
"type": "button",
|
||||
"config": {"label": "Click me"},
|
||||
},
|
||||
],
|
||||
)
|
||||
workflow_action = data_fixture.create_workflow_action(
|
||||
NotificationWorkflowAction,
|
||||
page=page,
|
||||
element=table_element,
|
||||
event=f"{table_element.fields.first().uid}_click",
|
||||
)
|
||||
|
||||
assert NotificationWorkflowAction.objects.count() == 1
|
||||
ElementService().update_element(user, table_element, fields=[])
|
||||
assert NotificationWorkflowAction.objects.count() == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_table_element_import_export(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
page = data_fixture.create_builder_page(user=user)
|
||||
data_source1 = data_fixture.create_builder_local_baserow_list_rows_data_source(
|
||||
page=page
|
||||
)
|
||||
table_element = data_fixture.create_builder_table_element(
|
||||
page=page,
|
||||
data_source=data_source1,
|
||||
fields=[
|
||||
{
|
||||
"name": "Field",
|
||||
"type": "button",
|
||||
"config": {"label": "Click me"},
|
||||
},
|
||||
],
|
||||
)
|
||||
table_element_type = table_element.get_type()
|
||||
|
||||
# Export the table element and check there are no table elements after deleting it
|
||||
exported = table_element_type.export_serialized(table_element)
|
||||
ElementService().delete_element(user, table_element)
|
||||
assert TableElement.objects.count() == 0
|
||||
|
||||
# After importing the table element the fields should be properly imported too
|
||||
id_mapping = defaultdict(lambda: MirrorDict())
|
||||
table_element_type.import_serialized(page, exported, id_mapping)
|
||||
assert (
|
||||
TableElement.objects.filter(
|
||||
page=page,
|
||||
data_source=data_source1,
|
||||
fields__name="Field",
|
||||
fields__type="button",
|
||||
).count()
|
||||
== 1
|
||||
)
|
||||
|
|
|
@ -393,7 +393,12 @@ def test_builder_application_export(data_fixture):
|
|||
"items_per_page": 42,
|
||||
"data_source_id": element4.data_source.id,
|
||||
"fields": [
|
||||
{"name": f.name, "type": f.type, "config": f.config}
|
||||
{
|
||||
"name": f.name,
|
||||
"type": f.type,
|
||||
"config": f.config,
|
||||
"uid": f.uid,
|
||||
}
|
||||
for f in element4.fields.all()
|
||||
],
|
||||
},
|
||||
|
@ -523,10 +528,12 @@ IMPORT_REFERENCE = {
|
|||
"name": "F 1",
|
||||
"type": "text",
|
||||
"config": {"value": "get('current_record.field_25')"},
|
||||
"uid": uuid.uuid4(),
|
||||
},
|
||||
{
|
||||
"name": "F 2",
|
||||
"type": "link",
|
||||
"uid": uuid.uuid4(),
|
||||
"config": {
|
||||
"page_parameters": [],
|
||||
"navigation_type": "custom",
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "feature",
|
||||
"message": "Add a new \"button\" field to table element that can trigger actions based on current rows.",
|
||||
"issue_number": 2522,
|
||||
"bullet_points": [],
|
||||
"created_at": "2024-05-23"
|
||||
}
|
|
@ -156,7 +156,9 @@ export default {
|
|||
this.values.password = ''
|
||||
this.values.email = ''
|
||||
this.$v.$reset()
|
||||
this.fireAfterLoginEvent()
|
||||
this.fireEvent(
|
||||
this.elementType.getEventByName(this.element, 'after_login')
|
||||
)
|
||||
} catch (error) {
|
||||
if (error.handler) {
|
||||
const response = error.handler.response
|
||||
|
|
|
@ -28,8 +28,8 @@ export class AuthFormElementType extends ElementType {
|
|||
return AuthFormElementForm
|
||||
}
|
||||
|
||||
get events() {
|
||||
return [AfterLoginEvent]
|
||||
getEvents(element) {
|
||||
return [new AfterLoginEvent({ ...this.app })]
|
||||
}
|
||||
|
||||
isInError({ builder, element }) {
|
||||
|
|
|
@ -2,6 +2,8 @@ import { Registerable } from '@baserow/modules/core/registry'
|
|||
import BooleanField from '@baserow/modules/builder/components/elements/components/collectionField/BooleanField'
|
||||
import TextField from '@baserow/modules/builder/components/elements/components/collectionField/TextField'
|
||||
import LinkField from '@baserow/modules/builder/components/elements/components/collectionField/LinkField'
|
||||
import ButtonField from '@baserow/modules/builder/components/elements/components/collectionField/ButtonField.vue'
|
||||
import ButtonFieldForm from '@baserow/modules/builder/components/elements/components/collectionField/form/ButtonFieldForm.vue'
|
||||
import BooleanFieldForm from '@baserow/modules/builder/components/elements/components/collectionField/form/BooleanFieldForm'
|
||||
import TagsField from '@baserow/modules/builder/components/elements/components/collectionField/TagsField.vue'
|
||||
import TextFieldForm from '@baserow/modules/builder/components/elements/components/collectionField/form/TextFieldForm'
|
||||
|
@ -14,6 +16,7 @@ import {
|
|||
} from '@baserow/modules/core/utils/validator'
|
||||
import resolveElementUrl from '@baserow/modules/builder/utils/urlResolution'
|
||||
import { pathParametersInError } from '@baserow/modules/builder/utils/params'
|
||||
import { ClickEvent } from '@baserow/modules/builder/eventTypes'
|
||||
|
||||
export class CollectionFieldType extends Registerable {
|
||||
get name() {
|
||||
|
@ -28,6 +31,10 @@ export class CollectionFieldType extends Registerable {
|
|||
return null
|
||||
}
|
||||
|
||||
get events() {
|
||||
return []
|
||||
}
|
||||
|
||||
getProps(field, { resolveFormula, applicationContext }) {
|
||||
return {}
|
||||
}
|
||||
|
@ -183,3 +190,29 @@ export class TagsCollectionFieldType extends CollectionFieldType {
|
|||
return 10
|
||||
}
|
||||
}
|
||||
|
||||
export class ButtonCollectionFieldType extends CollectionFieldType {
|
||||
static getType() {
|
||||
return 'button'
|
||||
}
|
||||
|
||||
get name() {
|
||||
return 'Button'
|
||||
}
|
||||
|
||||
get component() {
|
||||
return ButtonField
|
||||
}
|
||||
|
||||
get formComponent() {
|
||||
return ButtonFieldForm
|
||||
}
|
||||
|
||||
get events() {
|
||||
return [ClickEvent]
|
||||
}
|
||||
|
||||
getProps(field, { resolveFormula, applicationContext }) {
|
||||
return { label: ensureString(resolveFormula(field.label)) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,9 +12,18 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody v-if="rows.length">
|
||||
<tr v-for="row in rows" :key="row.__id__" class="baserow-table__row">
|
||||
<tr
|
||||
v-for="(row, index) in rows"
|
||||
:key="row.__id__"
|
||||
class="baserow-table__row"
|
||||
>
|
||||
<td v-for="field in fields" :key="field.id" class="baserow-table__cell">
|
||||
<slot name="cell-content" :value="row[field.name]" :field="field">
|
||||
<slot
|
||||
name="cell-content"
|
||||
:value="row[field.name]"
|
||||
:field="field"
|
||||
:row-index="index"
|
||||
>
|
||||
{{ value }}
|
||||
</slot>
|
||||
</td>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<ABButton
|
||||
:full-width="element.width === WIDTHS.FULL.value"
|
||||
:loading="workflowActionsInProgress"
|
||||
@click="fireClickEvent"
|
||||
@click="fireEvent(elementType.getEventByName(element, 'click'))"
|
||||
>
|
||||
{{ resolvedValue || $t('buttonElement.noValue') }}
|
||||
</ABButton>
|
||||
|
|
|
@ -160,7 +160,7 @@ export default {
|
|||
validateAndSubmitEvent() {
|
||||
this.setFormElementDescendantsTouched(true)
|
||||
if (!this.formElementChildrenAreInvalid) {
|
||||
this.fireSubmitEvent()
|
||||
this.fireEvent(this.elementType.getEventByName(this.element, 'submit'))
|
||||
this.resetFormContainerElements()
|
||||
}
|
||||
},
|
||||
|
|
|
@ -6,9 +6,14 @@
|
|||
class="table-element"
|
||||
>
|
||||
<BaserowTable :fields="element.fields" :rows="rows">
|
||||
<template #cell-content="{ field, value }">
|
||||
<template #cell-content="{ rowIndex, field, value }">
|
||||
<component
|
||||
:is="collectionFieldTypes[field.type].component"
|
||||
:element="element"
|
||||
:field="field"
|
||||
:application-context-additions="{
|
||||
recordIndex: rowIndex,
|
||||
}"
|
||||
v-bind="value"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
<template>
|
||||
<ABButton
|
||||
:loading="workflowActionsInProgress"
|
||||
@click="fireEvent(elementType.getEventByName(element, eventName))"
|
||||
>
|
||||
{{ labelWithDefault }}
|
||||
</ABButton>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import element from '@baserow/modules/builder/mixins/element'
|
||||
|
||||
export default {
|
||||
name: 'ButtonField',
|
||||
mixins: [element],
|
||||
props: {
|
||||
element: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
field: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
eventName() {
|
||||
return `${this.field.uid}_click`
|
||||
},
|
||||
workflowActionsInProgress() {
|
||||
const workflowActions = this.$store.getters[
|
||||
'workflowAction/getElementWorkflowActions'
|
||||
](this.page, this.element.id)
|
||||
return workflowActions
|
||||
.filter((wa) => wa.event === this.eventName)
|
||||
.some((workflowAction) =>
|
||||
this.$store.getters['workflowAction/getDispatching'](workflowAction)
|
||||
)
|
||||
},
|
||||
labelWithDefault() {
|
||||
if (this.label) {
|
||||
return this.label
|
||||
} else {
|
||||
return this.$t('buttonField.noLabel')
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,49 @@
|
|||
<template>
|
||||
<form @submit.prevent @keydown.enter.prevent>
|
||||
<ApplicationBuilderFormulaInputGroup
|
||||
v-model="values.label"
|
||||
small
|
||||
:label="$t('generalForm.labelTitle')"
|
||||
:placeholder="$t('buttonFieldForm.labelPlaceholder')"
|
||||
:data-providers-allowed="DATA_PROVIDERS_ALLOWED_ELEMENTS"
|
||||
:application-context-additions="{
|
||||
element,
|
||||
}"
|
||||
horizontal
|
||||
/>
|
||||
<Alert>
|
||||
{{ $t('buttonFieldForm.infoMessage') }}
|
||||
</Alert>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DATA_PROVIDERS_ALLOWED_ELEMENTS } from '@baserow/modules/builder/enums'
|
||||
import form from '@baserow/modules/core/mixins/form'
|
||||
import ApplicationBuilderFormulaInputGroup from '@baserow/modules/builder/components/ApplicationBuilderFormulaInputGroup'
|
||||
|
||||
export default {
|
||||
name: 'ButtonFieldForm',
|
||||
components: { ApplicationBuilderFormulaInputGroup },
|
||||
mixins: [form],
|
||||
props: {
|
||||
element: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
allowedValues: ['label'],
|
||||
values: {
|
||||
label: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
DATA_PROVIDERS_ALLOWED_ELEMENTS() {
|
||||
return DATA_PROVIDERS_ALLOWED_ELEMENTS
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -212,12 +212,20 @@ export default {
|
|||
value: '',
|
||||
type: 'text',
|
||||
id: uuid(), // Temporary id
|
||||
uid: uuid(),
|
||||
})
|
||||
},
|
||||
changeFieldType(fieldToUpdate, newType) {
|
||||
this.values.fields = this.values.fields.map((field) => {
|
||||
if (field.id === fieldToUpdate.id) {
|
||||
return { id: field.id, name: field.name, type: newType }
|
||||
// When the type of the workflow action changes we assign a new UID to
|
||||
// trigger the backend workflow action removal
|
||||
return {
|
||||
id: field.id,
|
||||
uid: uuid(),
|
||||
name: field.name,
|
||||
type: newType,
|
||||
}
|
||||
}
|
||||
return field
|
||||
})
|
||||
|
|
|
@ -127,7 +127,7 @@ export default {
|
|||
await this.actionCreateWorkflowAction({
|
||||
page: this.page,
|
||||
workflowActionType: DEFAULT_WORKFLOW_ACTION_TYPE,
|
||||
eventType: this.event.getType(),
|
||||
eventType: this.event.name,
|
||||
configuration: {
|
||||
element_id: this.element.id,
|
||||
},
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
:is="component"
|
||||
:element="element"
|
||||
:children="children"
|
||||
:application-context-additions="{
|
||||
element,
|
||||
}"
|
||||
class="element"
|
||||
/>
|
||||
</div>
|
||||
|
@ -29,20 +32,12 @@ import { resolveColor } from '@baserow/modules/core/utils/colors'
|
|||
import { themeToColorVariables } from '@baserow/modules/builder/utils/theme'
|
||||
|
||||
import { BACKGROUND_TYPES, WIDTH_TYPES } from '@baserow/modules/builder/enums'
|
||||
import applicationContextMixin from '@baserow/modules/builder/mixins/applicationContext'
|
||||
|
||||
export default {
|
||||
name: 'PageElement',
|
||||
inject: ['builder', 'page', 'mode', 'applicationContext'],
|
||||
provide() {
|
||||
return {
|
||||
mode: this.elementMode,
|
||||
applicationContext: {
|
||||
...this.applicationContext,
|
||||
element: this.element,
|
||||
...this.applicationContextAdditions,
|
||||
},
|
||||
}
|
||||
},
|
||||
mixins: [applicationContextMixin],
|
||||
inject: ['builder', 'page', 'mode'],
|
||||
props: {
|
||||
element: {
|
||||
type: Object,
|
||||
|
@ -53,11 +48,6 @@ export default {
|
|||
required: false,
|
||||
default: null,
|
||||
},
|
||||
applicationContextAdditions: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
BACKGROUND_TYPES: () => BACKGROUND_TYPES,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div>
|
||||
<Event
|
||||
v-for="event in elementType.getEvents()"
|
||||
:key="event.getType()"
|
||||
v-for="event in elementType.getEvents(element)"
|
||||
:key="event.name"
|
||||
:element="element"
|
||||
:event="event"
|
||||
:available-workflow-action-types="availableWorkflowActionTypes"
|
||||
|
@ -34,7 +34,7 @@ export default {
|
|||
methods: {
|
||||
getWorkflowActionsForEvent(event) {
|
||||
return this.workflowActions.filter(
|
||||
(workflowAction) => workflowAction.event === event.getType()
|
||||
(workflowAction) => workflowAction.event === event.name
|
||||
)
|
||||
},
|
||||
},
|
||||
|
|
|
@ -503,7 +503,7 @@ export class PreviousActionDataProviderType extends DataProviderType {
|
|||
|
||||
const previousActions = this.app.store.getters[
|
||||
'workflowAction/getElementPreviousWorkflowActions'
|
||||
](page, applicationContext.element.id, applicationContext.workflowAction.id)
|
||||
](page, applicationContext.element.id, applicationContext.workflowAction)
|
||||
|
||||
const previousActionSchema = _.chain(previousActions)
|
||||
// Retrieve the associated schema for each action
|
||||
|
|
|
@ -87,10 +87,6 @@ export class ElementType extends Registerable {
|
|||
return this.stylesAll
|
||||
}
|
||||
|
||||
get events() {
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a display name for this element, so it can be distinguished from
|
||||
* other elements of the same type.
|
||||
|
@ -102,8 +98,12 @@ export class ElementType extends Registerable {
|
|||
return this.name
|
||||
}
|
||||
|
||||
getEvents() {
|
||||
return this.events.map((EventType) => new EventType(this.app))
|
||||
getEvents(element) {
|
||||
return []
|
||||
}
|
||||
|
||||
getEventByName(element, name) {
|
||||
return this.getEvents(element).find((event) => event.name === name)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -505,14 +505,14 @@ export class FormContainerElementType extends ContainerElementTypeMixin(
|
|||
return this.elementTypesAll.filter((type) => !type.isFormElement)
|
||||
}
|
||||
|
||||
get events() {
|
||||
return [SubmitEvent]
|
||||
}
|
||||
|
||||
get childStylesForbidden() {
|
||||
return ['style_width']
|
||||
}
|
||||
|
||||
getEvents(element) {
|
||||
return [new SubmitEvent({ ...this.app })]
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of placements that are disallowed for the elements to move
|
||||
* in their container.
|
||||
|
@ -701,6 +701,24 @@ export class TableElementType extends CollectionElementTypeMixin(ElementType) {
|
|||
return TableElementForm
|
||||
}
|
||||
|
||||
getEvents(element) {
|
||||
return (element.fields || [])
|
||||
.map(({ type, name, uid }) => {
|
||||
const collectionFieldType = this.app.$registry.get(
|
||||
'collectionField',
|
||||
type
|
||||
)
|
||||
return collectionFieldType.events.map((EventType) => {
|
||||
return new EventType({
|
||||
...this.app,
|
||||
namePrefix: uid,
|
||||
labelSuffix: `- ${name}`,
|
||||
})
|
||||
})
|
||||
})
|
||||
.flat()
|
||||
}
|
||||
|
||||
async onElementEvent(event, { page, element, dataSourceId }) {
|
||||
if (event === ELEMENT_EVENTS.DATA_SOURCE_REMOVED) {
|
||||
if (element.data_source_id === dataSourceId) {
|
||||
|
@ -1148,8 +1166,8 @@ export class ButtonElementType extends ElementType {
|
|||
return ButtonElementForm
|
||||
}
|
||||
|
||||
get events() {
|
||||
return [ClickEvent]
|
||||
getEvents(element) {
|
||||
return [new ClickEvent({ ...this.app })]
|
||||
}
|
||||
|
||||
getDisplayName(element, applicationContext) {
|
||||
|
|
|
@ -8,41 +8,31 @@ import { resolveFormula } from '@baserow/modules/core/formula'
|
|||
* registry required.
|
||||
*/
|
||||
export class Event {
|
||||
constructor({ i18n, store, registry }) {
|
||||
constructor({ i18n, store, $registry, name, label }) {
|
||||
this.i18n = i18n
|
||||
this.store = store
|
||||
this.registry = registry
|
||||
}
|
||||
|
||||
static getType() {
|
||||
throw new Error('getType needs to be implemented')
|
||||
}
|
||||
|
||||
getType() {
|
||||
return this.constructor.getType()
|
||||
}
|
||||
|
||||
get label() {
|
||||
return null
|
||||
this.$registry = $registry
|
||||
this.name = name
|
||||
this.label = label
|
||||
}
|
||||
|
||||
async fire({ workflowActions, applicationContext }) {
|
||||
const additionalContext = {}
|
||||
for (let i = 0; i < workflowActions.length; i += 1) {
|
||||
const workflowAction = workflowActions[i]
|
||||
const workflowActionType = this.registry.get(
|
||||
const workflowActionType = this.$registry.get(
|
||||
'workflowAction',
|
||||
workflowAction.type
|
||||
)
|
||||
const localResolveFormula = (formula) => {
|
||||
const formulaFunctions = {
|
||||
get: (name) => {
|
||||
return this.registry.get('runtimeFormulaFunction', name)
|
||||
return this.$registry.get('runtimeFormulaFunction', name)
|
||||
},
|
||||
}
|
||||
const runtimeFormulaContext = new Proxy(
|
||||
new RuntimeFormulaContext(
|
||||
this.registry.getAll('builderDataProvider'),
|
||||
this.$registry.getAll('builderDataProvider'),
|
||||
{ ...applicationContext, previousActionResults: additionalContext }
|
||||
),
|
||||
{
|
||||
|
@ -89,31 +79,33 @@ export class Event {
|
|||
}
|
||||
|
||||
export class ClickEvent extends Event {
|
||||
static getType() {
|
||||
return 'click'
|
||||
}
|
||||
|
||||
get label() {
|
||||
return this.i18n.t('eventTypes.clickLabel')
|
||||
constructor({ namePrefix, labelSuffix, ...rest }) {
|
||||
super({
|
||||
...rest,
|
||||
name: namePrefix ? `${namePrefix}_click` : 'click',
|
||||
label: labelSuffix
|
||||
? `${rest.i18n.t('eventTypes.clickLabel')} ${labelSuffix}`
|
||||
: rest.i18n.t('eventTypes.clickLabel'),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class SubmitEvent extends Event {
|
||||
static getType() {
|
||||
return 'submit'
|
||||
}
|
||||
|
||||
get label() {
|
||||
return this.i18n.t('eventTypes.submitLabel')
|
||||
constructor(args) {
|
||||
super({
|
||||
name: 'submit',
|
||||
label: args.i18n.t('eventTypes.submitLabel'),
|
||||
...args,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class AfterLoginEvent extends Event {
|
||||
static getType() {
|
||||
return 'after_login'
|
||||
}
|
||||
|
||||
get label() {
|
||||
return this.i18n.t('eventTypes.afterLoginLabel')
|
||||
constructor(args) {
|
||||
super({
|
||||
name: 'after_login',
|
||||
label: args.i18n.t('eventTypes.afterLoginLabel'),
|
||||
...args,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -566,5 +566,12 @@
|
|||
"id": "Id",
|
||||
"email": "Email",
|
||||
"username": "Username"
|
||||
},
|
||||
"buttonField": {
|
||||
"noLabel": "Unnamed..."
|
||||
},
|
||||
"buttonFieldForm": {
|
||||
"infoMessage": "To configure actions for this button, open the \"Events\" tab of the current element.",
|
||||
"labelPlaceholder": "Enter a label..."
|
||||
}
|
||||
}
|
||||
|
|
23
web-frontend/modules/builder/mixins/applicationContext.js
Normal file
23
web-frontend/modules/builder/mixins/applicationContext.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
export default {
|
||||
inject: { injectedApplicationContext: { from: 'applicationContext' } },
|
||||
provide() {
|
||||
return {
|
||||
applicationContext: this.applicationContext,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
applicationContextAdditions: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
applicationContext() {
|
||||
return {
|
||||
...this.injectedApplicationContext,
|
||||
...this.applicationContextAdditions,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
|
@ -1,34 +1,17 @@
|
|||
import RuntimeFormulaContext from '@baserow/modules/core/runtimeFormulaContext'
|
||||
import { resolveFormula } from '@baserow/modules/core/formula'
|
||||
import {
|
||||
ClickEvent,
|
||||
SubmitEvent,
|
||||
AfterLoginEvent,
|
||||
} from '@baserow/modules/builder/eventTypes'
|
||||
import { resolveColor } from '@baserow/modules/core/utils/colors'
|
||||
import { themeToColorVariables } from '@baserow/modules/builder/utils/theme'
|
||||
import applicationContextMixin from '@baserow/modules/builder/mixins/applicationContext'
|
||||
|
||||
export default {
|
||||
inject: ['workspace', 'builder', 'page', 'mode', 'applicationContext'],
|
||||
provide() {
|
||||
return {
|
||||
applicationContext: {
|
||||
...this.applicationContext,
|
||||
element: this.element,
|
||||
...this.applicationContextAdditions,
|
||||
},
|
||||
}
|
||||
},
|
||||
inject: ['workspace', 'builder', 'page', 'mode'],
|
||||
mixins: [applicationContextMixin],
|
||||
props: {
|
||||
element: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
applicationContextAdditions: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
workflowActionsInProgress() {
|
||||
|
@ -84,7 +67,7 @@ export default {
|
|||
return ''
|
||||
}
|
||||
},
|
||||
async fireEvent(EventType) {
|
||||
async fireEvent(event) {
|
||||
if (this.mode !== 'editing') {
|
||||
if (this.workflowActionsInProgress) {
|
||||
return false
|
||||
|
@ -92,14 +75,12 @@ export default {
|
|||
|
||||
const workflowActions = this.$store.getters[
|
||||
'workflowAction/getElementWorkflowActions'
|
||||
](this.page, this.element.id)
|
||||
](this.page, this.element.id).filter(
|
||||
({ event: eventName }) => eventName === event.name
|
||||
)
|
||||
|
||||
try {
|
||||
await new EventType({
|
||||
i18n: this.$i18n,
|
||||
store: this.$store,
|
||||
registry: this.$registry,
|
||||
}).fire({
|
||||
await event.fire({
|
||||
workflowActions,
|
||||
resolveFormula: this.resolveFormula,
|
||||
applicationContext: this.applicationContext,
|
||||
|
@ -126,15 +107,6 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
fireClickEvent() {
|
||||
return this.fireEvent(ClickEvent)
|
||||
},
|
||||
fireSubmitEvent() {
|
||||
this.fireEvent(SubmitEvent)
|
||||
},
|
||||
fireAfterLoginEvent() {
|
||||
this.fireEvent(AfterLoginEvent)
|
||||
},
|
||||
|
||||
resolveColor,
|
||||
},
|
||||
|
|
|
@ -100,7 +100,7 @@ export class EventsPageSidePanelType extends pageSidePanelType {
|
|||
|
||||
isDeactivated(element) {
|
||||
const elementType = this.app.$registry.get('element', element.type)
|
||||
return elementType.getEvents().length === 0
|
||||
return elementType.getEvents(element).length === 0
|
||||
}
|
||||
|
||||
getOrder() {
|
||||
|
|
|
@ -98,6 +98,7 @@ import {
|
|||
BooleanCollectionFieldType,
|
||||
TextCollectionFieldType,
|
||||
LinkCollectionFieldType,
|
||||
ButtonCollectionFieldType,
|
||||
TagsCollectionFieldType,
|
||||
} from '@baserow/modules/builder/collectionFieldTypes'
|
||||
|
||||
|
@ -283,4 +284,8 @@ export default (context) => {
|
|||
'collectionField',
|
||||
new TagsCollectionFieldType(context)
|
||||
)
|
||||
app.$registry.register(
|
||||
'collectionField',
|
||||
new ButtonCollectionFieldType(context)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -244,10 +244,15 @@ const getters = {
|
|||
.sort((a, b) => a.order - b.order)
|
||||
},
|
||||
getElementPreviousWorkflowActions:
|
||||
(state, getters) => (page, elementId, workflowActionId) => {
|
||||
(state, getters) => (page, elementId, workflowAction) => {
|
||||
return _.takeWhile(
|
||||
getters.getElementWorkflowActions(page, elementId),
|
||||
(workflowAction) => workflowAction.id !== workflowActionId
|
||||
getters
|
||||
.getElementWorkflowActions(page, elementId)
|
||||
.filter(
|
||||
(workflowActionToFilter) =>
|
||||
workflowActionToFilter.event === workflowAction.event
|
||||
),
|
||||
(workflowActionAll) => workflowActionAll.id !== workflowAction.id
|
||||
)
|
||||
},
|
||||
getLoading: (state) => (workflowAction) => {
|
||||
|
|
|
@ -142,6 +142,10 @@ export class RefreshDataSourceWorkflowActionType extends WorkflowActionType {
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
getDataSchema(workflowAction) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export class WorkflowActionServiceType extends WorkflowActionType {
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<ul class="tabs__header">
|
||||
<li
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="tab.title"
|
||||
:key="`${tab.title} ${tab.tooltip}`"
|
||||
v-tooltip="tab.tooltip"
|
||||
class="tabs__item"
|
||||
:class="{
|
||||
|
|
Loading…
Add table
Reference in a new issue