mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-17 10:22:36 +00:00
Resolve "WorkflowActions for the AB"
This commit is contained in:
parent
cadd8f7072
commit
d990d2852c
64 changed files with 2711 additions and 48 deletions
backend
src/baserow
api/workflow_actions
contrib/builder
core/workflow_actions
test_utils/fixtures
tests/baserow/contrib/builder
enterprise/backend/src/baserow_enterprise/role
web-frontend/modules
builder
components
event
page/sidePanels
workflowAction
locales
mixins
pages
plugin.jsservices
store
workflowActionTypes.jscore
23
backend/src/baserow/api/workflow_actions/serializers.py
Normal file
23
backend/src/baserow/api/workflow_actions/serializers.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from abc import abstractmethod
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from baserow.core.workflow_actions.models import WorkflowAction
|
||||
|
||||
|
||||
class WorkflowActionSerializer(serializers.ModelSerializer):
|
||||
type = serializers.SerializerMethodField(
|
||||
help_text="The type of the workflow action"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = WorkflowAction
|
||||
fields = ("id", "type")
|
||||
|
||||
extra_kwargs = {
|
||||
"id": {"read_only": True},
|
||||
}
|
||||
|
||||
@abstractmethod
|
||||
def get_type(self, instance: WorkflowAction):
|
||||
pass
|
|
@ -4,8 +4,15 @@ from drf_spectacular.types import OpenApiTypes
|
|||
from drf_spectacular.utils import extend_schema_field
|
||||
from rest_framework import serializers
|
||||
|
||||
from baserow.contrib.builder.api.workflow_actions.serializers import (
|
||||
BuilderWorkflowActionSerializer,
|
||||
)
|
||||
from baserow.contrib.builder.elements.models import CollectionElementField, Element
|
||||
from baserow.contrib.builder.elements.registries import element_type_registry
|
||||
from baserow.contrib.builder.elements.types import ElementsAndWorkflowActions
|
||||
from baserow.contrib.builder.workflow_actions.registries import (
|
||||
builder_workflow_action_type_registry,
|
||||
)
|
||||
from baserow.core.formula.serializers import FormulaSerializerField
|
||||
|
||||
|
||||
|
@ -109,6 +116,29 @@ class MoveElementSerializer(serializers.Serializer):
|
|||
)
|
||||
|
||||
|
||||
class DuplicateElementSerializer(serializers.Serializer):
|
||||
elements = serializers.SerializerMethodField(help_text="The duplicated elements.")
|
||||
workflow_actions = serializers.SerializerMethodField(
|
||||
help_text="The duplicated workflow actions"
|
||||
)
|
||||
|
||||
@extend_schema_field(ElementSerializer(many=True))
|
||||
def get_elements(self, obj: ElementsAndWorkflowActions):
|
||||
return [
|
||||
element_type_registry.get_serializer(element, ElementSerializer).data
|
||||
for element in obj["elements"]
|
||||
]
|
||||
|
||||
@extend_schema_field(BuilderWorkflowActionSerializer(many=True))
|
||||
def get_workflow_actions(self, obj: ElementsAndWorkflowActions):
|
||||
return [
|
||||
builder_workflow_action_type_registry.get_serializer(
|
||||
workflow_action, BuilderWorkflowActionSerializer
|
||||
).data
|
||||
for workflow_action in obj["workflow_actions"]
|
||||
]
|
||||
|
||||
|
||||
class PageParameterValueSerializer(serializers.Serializer):
|
||||
name = serializers.CharField()
|
||||
value = FormulaSerializerField(allow_blank=True)
|
||||
|
|
|
@ -26,6 +26,7 @@ from baserow.contrib.builder.api.elements.errors import (
|
|||
)
|
||||
from baserow.contrib.builder.api.elements.serializers import (
|
||||
CreateElementSerializer,
|
||||
DuplicateElementSerializer,
|
||||
ElementSerializer,
|
||||
MoveElementSerializer,
|
||||
UpdateElementSerializer,
|
||||
|
@ -340,11 +341,10 @@ class DuplicateElementView(APIView):
|
|||
],
|
||||
tags=["Builder elements"],
|
||||
operation_id="duplicate_builder_page_element",
|
||||
description="Duplicates an element and all of the elements children",
|
||||
description="Duplicates an element and all of the elements children and the "
|
||||
"associated workflow actions as well.",
|
||||
responses={
|
||||
200: DiscriminatorCustomFieldsMappingSerializer(
|
||||
element_type_registry, ElementSerializer, many=True
|
||||
),
|
||||
200: DuplicateElementSerializer,
|
||||
400: get_error_schema(["ERROR_REQUEST_BODY_VALIDATION"]),
|
||||
404: get_error_schema(
|
||||
[
|
||||
|
@ -366,13 +366,12 @@ class DuplicateElementView(APIView):
|
|||
|
||||
element = ElementHandler().get_element_for_update(element_id)
|
||||
|
||||
elements_duplicated = ElementService().duplicate_element(request.user, element)
|
||||
elements_and_workflow_actions_duplicated = ElementService().duplicate_element(
|
||||
request.user, element
|
||||
)
|
||||
|
||||
elements_serialized = [
|
||||
element_type_registry.get_serializer(
|
||||
element_current, ElementSerializer
|
||||
).data
|
||||
for element_current in elements_duplicated
|
||||
]
|
||||
serializer = DuplicateElementSerializer(
|
||||
elements_and_workflow_actions_duplicated
|
||||
)
|
||||
|
||||
return Response(elements_serialized)
|
||||
return Response(serializer.data)
|
||||
|
|
|
@ -5,6 +5,7 @@ from .domains import urls as domain_urls
|
|||
from .elements import urls as element_urls
|
||||
from .pages import urls as page_urls
|
||||
from .theme import urls as theme_urls
|
||||
from .workflow_actions import urls as workflow_action_urls
|
||||
|
||||
app_name = "baserow.contrib.builder.api"
|
||||
|
||||
|
@ -61,6 +62,16 @@ paths_without_builder_id = [
|
|||
namespace="domains",
|
||||
),
|
||||
),
|
||||
path(
|
||||
"",
|
||||
include(
|
||||
(
|
||||
workflow_action_urls.urls_without_builder_id,
|
||||
workflow_action_urls.app_name,
|
||||
),
|
||||
namespace="workflow_action",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
urlpatterns = [
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
from rest_framework.status import HTTP_404_NOT_FOUND
|
||||
|
||||
ERROR_WORKFLOW_ACTION_DOES_NOT_EXIST = (
|
||||
"ERROR_WORKFLOW_ACTION_DOES_NOT_EXIST",
|
||||
HTTP_404_NOT_FOUND,
|
||||
"The requested workflow action does not exist.",
|
||||
)
|
|
@ -0,0 +1,61 @@
|
|||
from django.utils.functional import lazy
|
||||
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import extend_schema_field
|
||||
from rest_framework import serializers
|
||||
|
||||
from baserow.api.workflow_actions.serializers import WorkflowActionSerializer
|
||||
from baserow.contrib.builder.workflow_actions.models import BuilderWorkflowAction
|
||||
from baserow.contrib.builder.workflow_actions.registries import (
|
||||
builder_workflow_action_type_registry,
|
||||
)
|
||||
|
||||
|
||||
class BuilderWorkflowActionSerializer(WorkflowActionSerializer):
|
||||
"""
|
||||
Basic builder workflow action serializer
|
||||
"""
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_type(self, instance):
|
||||
return builder_workflow_action_type_registry.get_by_model(
|
||||
instance.specific_class
|
||||
).type
|
||||
|
||||
class Meta:
|
||||
model = BuilderWorkflowAction
|
||||
fields = ("id", "element_id", "type", "event")
|
||||
|
||||
extra_kwargs = {
|
||||
"id": {"read_only": True},
|
||||
"element_id": {"read_only": True},
|
||||
}
|
||||
|
||||
|
||||
class CreateBuilderWorkflowActionSerializer(serializers.ModelSerializer):
|
||||
type = serializers.ChoiceField(
|
||||
choices=lazy(builder_workflow_action_type_registry.get_types, list)(),
|
||||
required=True,
|
||||
help_text="The type of the workflow action",
|
||||
)
|
||||
element_id = serializers.IntegerField(
|
||||
allow_null=True,
|
||||
required=False,
|
||||
help_text="The id of the element the workflow action is associated with",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = BuilderWorkflowAction
|
||||
fields = ("id", "element_id", "type", "event")
|
||||
|
||||
|
||||
class UpdateBuilderWorkflowActionsSerializer(serializers.ModelSerializer):
|
||||
type = serializers.ChoiceField(
|
||||
choices=lazy(builder_workflow_action_type_registry.get_types, list)(),
|
||||
required=False,
|
||||
help_text="The type of the workflow action",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = BuilderWorkflowAction
|
||||
fields = ("type",)
|
|
@ -0,0 +1,21 @@
|
|||
from django.urls import re_path
|
||||
|
||||
from baserow.contrib.builder.api.workflow_actions.views import (
|
||||
BuilderWorkflowActionsView,
|
||||
BuilderWorkflowActionView,
|
||||
)
|
||||
|
||||
app_name = "baserow.contrib.builder.api.workflow_actions"
|
||||
|
||||
urls_without_builder_id = [
|
||||
re_path(
|
||||
r"page/(?P<page_id>[0-9]+)/workflow_actions/$",
|
||||
BuilderWorkflowActionsView.as_view(),
|
||||
name="list",
|
||||
),
|
||||
re_path(
|
||||
r"workflow_action/(?P<workflow_action_id>[0-9]+)/$",
|
||||
BuilderWorkflowActionView.as_view(),
|
||||
name="item",
|
||||
),
|
||||
]
|
|
@ -0,0 +1,259 @@
|
|||
from typing import Dict
|
||||
|
||||
from django.db import transaction
|
||||
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import OpenApiParameter, extend_schema
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from baserow.api.decorators import validate_body_custom_fields
|
||||
from baserow.api.schemas import CLIENT_SESSION_ID_SCHEMA_PARAMETER, get_error_schema
|
||||
from baserow.api.utils import (
|
||||
CustomFieldRegistryMappingSerializer,
|
||||
DiscriminatorCustomFieldsMappingSerializer,
|
||||
map_exceptions,
|
||||
type_from_data_or_registry,
|
||||
validate_data_custom_fields,
|
||||
)
|
||||
from baserow.contrib.builder.api.elements.errors import ERROR_ELEMENT_DOES_NOT_EXIST
|
||||
from baserow.contrib.builder.api.pages.errors import ERROR_PAGE_DOES_NOT_EXIST
|
||||
from baserow.contrib.builder.api.workflow_actions.errors import (
|
||||
ERROR_WORKFLOW_ACTION_DOES_NOT_EXIST,
|
||||
)
|
||||
from baserow.contrib.builder.api.workflow_actions.serializers import (
|
||||
BuilderWorkflowActionSerializer,
|
||||
CreateBuilderWorkflowActionSerializer,
|
||||
UpdateBuilderWorkflowActionsSerializer,
|
||||
)
|
||||
from baserow.contrib.builder.elements.exceptions import ElementDoesNotExist
|
||||
from baserow.contrib.builder.pages.exceptions import PageDoesNotExist
|
||||
from baserow.contrib.builder.pages.handler import PageHandler
|
||||
from baserow.contrib.builder.workflow_actions.handler import (
|
||||
BuilderWorkflowActionHandler,
|
||||
)
|
||||
from baserow.contrib.builder.workflow_actions.registries import (
|
||||
builder_workflow_action_type_registry,
|
||||
)
|
||||
from baserow.contrib.builder.workflow_actions.service import (
|
||||
BuilderWorkflowActionService,
|
||||
)
|
||||
from baserow.core.workflow_actions.exceptions import WorkflowActionDoesNotExist
|
||||
|
||||
|
||||
class BuilderWorkflowActionsView(APIView):
|
||||
permission_classes = (IsAuthenticated,)
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="page_id",
|
||||
location=OpenApiParameter.PATH,
|
||||
type=OpenApiTypes.INT,
|
||||
description="Creates a workflow action for the builder page related to "
|
||||
"the provided value.",
|
||||
),
|
||||
CLIENT_SESSION_ID_SCHEMA_PARAMETER,
|
||||
],
|
||||
tags=["Builder workflow_actions"],
|
||||
operation_id="create_builder_page_workflow_action",
|
||||
description="Creates a new builder workflow action",
|
||||
request=DiscriminatorCustomFieldsMappingSerializer(
|
||||
builder_workflow_action_type_registry,
|
||||
CreateBuilderWorkflowActionSerializer,
|
||||
request=True,
|
||||
),
|
||||
responses={
|
||||
200: DiscriminatorCustomFieldsMappingSerializer(
|
||||
builder_workflow_action_type_registry, BuilderWorkflowActionSerializer
|
||||
),
|
||||
400: get_error_schema(
|
||||
[
|
||||
"ERROR_REQUEST_BODY_VALIDATION",
|
||||
]
|
||||
),
|
||||
404: get_error_schema(["ERROR_PAGE_DOES_NOT_EXIST"]),
|
||||
},
|
||||
)
|
||||
@transaction.atomic
|
||||
@map_exceptions(
|
||||
{
|
||||
PageDoesNotExist: ERROR_PAGE_DOES_NOT_EXIST,
|
||||
ElementDoesNotExist: ERROR_ELEMENT_DOES_NOT_EXIST,
|
||||
}
|
||||
)
|
||||
@validate_body_custom_fields(
|
||||
builder_workflow_action_type_registry,
|
||||
base_serializer_class=CreateBuilderWorkflowActionSerializer,
|
||||
)
|
||||
def post(self, request, data: Dict, page_id: int):
|
||||
type_name = data.pop("type")
|
||||
workflow_action_type = builder_workflow_action_type_registry.get(type_name)
|
||||
page = PageHandler().get_page(page_id)
|
||||
|
||||
workflow_action = BuilderWorkflowActionService().create_workflow_action(
|
||||
request.user, workflow_action_type, page, **data
|
||||
)
|
||||
|
||||
serializer = builder_workflow_action_type_registry.get_serializer(
|
||||
workflow_action, BuilderWorkflowActionSerializer
|
||||
)
|
||||
|
||||
return Response(serializer.data)
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="page_id",
|
||||
location=OpenApiParameter.PATH,
|
||||
type=OpenApiTypes.INT,
|
||||
description="Returns only the workflow actions of the page related to "
|
||||
"the provided Id.",
|
||||
)
|
||||
],
|
||||
tags=["Builder workflow_actions"],
|
||||
operation_id="list_builder_page_workflow_actions",
|
||||
description=(
|
||||
"Lists all the workflow actions of the page related to the provided parameter "
|
||||
"if the user has access to the related builder's workspace. "
|
||||
"If the workspace is related to a template, then this endpoint will be "
|
||||
"publicly accessible."
|
||||
),
|
||||
responses={
|
||||
200: DiscriminatorCustomFieldsMappingSerializer(
|
||||
builder_workflow_action_type_registry,
|
||||
BuilderWorkflowActionSerializer,
|
||||
many=True,
|
||||
),
|
||||
404: get_error_schema(["ERROR_PAGE_DOES_NOT_EXIST"]),
|
||||
},
|
||||
)
|
||||
@map_exceptions(
|
||||
{
|
||||
PageDoesNotExist: ERROR_PAGE_DOES_NOT_EXIST,
|
||||
}
|
||||
)
|
||||
def get(self, request, page_id: int):
|
||||
page = PageHandler().get_page(page_id)
|
||||
|
||||
workflow_actions = BuilderWorkflowActionService().get_workflow_actions(
|
||||
request.user, page
|
||||
)
|
||||
|
||||
data = [
|
||||
builder_workflow_action_type_registry.get_serializer(
|
||||
workflow_action, BuilderWorkflowActionSerializer
|
||||
).data
|
||||
for workflow_action in workflow_actions
|
||||
]
|
||||
|
||||
return Response(data)
|
||||
|
||||
|
||||
class BuilderWorkflowActionView(APIView):
|
||||
permission_classes = (IsAuthenticated,)
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="workflow_action_id",
|
||||
location=OpenApiParameter.PATH,
|
||||
type=OpenApiTypes.INT,
|
||||
description="The id of the workflow action",
|
||||
),
|
||||
CLIENT_SESSION_ID_SCHEMA_PARAMETER,
|
||||
],
|
||||
tags=["Builder workflow_actions"],
|
||||
operation_id="delete_builder_page_workflow_action",
|
||||
description="Deletes the workflow action related by the given id.",
|
||||
responses={
|
||||
204: None,
|
||||
400: get_error_schema(
|
||||
[
|
||||
"ERROR_REQUEST_BODY_VALIDATION",
|
||||
]
|
||||
),
|
||||
404: get_error_schema(["ERROR_WORKFLOW_ACTION_DOES_NOT_EXIST"]),
|
||||
},
|
||||
)
|
||||
@transaction.atomic
|
||||
@map_exceptions(
|
||||
{
|
||||
WorkflowActionDoesNotExist: ERROR_WORKFLOW_ACTION_DOES_NOT_EXIST,
|
||||
}
|
||||
)
|
||||
def delete(self, request, workflow_action_id: int):
|
||||
workflow_action = BuilderWorkflowActionHandler().get_workflow_action(
|
||||
workflow_action_id
|
||||
)
|
||||
|
||||
BuilderWorkflowActionService().delete_workflow_action(
|
||||
request.user, workflow_action
|
||||
)
|
||||
|
||||
return Response(status=204)
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="workflow_action_id",
|
||||
location=OpenApiParameter.PATH,
|
||||
type=OpenApiTypes.INT,
|
||||
description="The id of the workflow action",
|
||||
),
|
||||
CLIENT_SESSION_ID_SCHEMA_PARAMETER,
|
||||
],
|
||||
tags=["Builder workflow_actions"],
|
||||
operation_id="update_builder_page_workflow_action",
|
||||
description="Updates an existing builder workflow action.",
|
||||
request=CustomFieldRegistryMappingSerializer(
|
||||
builder_workflow_action_type_registry,
|
||||
UpdateBuilderWorkflowActionsSerializer,
|
||||
request=True,
|
||||
),
|
||||
responses={
|
||||
200: DiscriminatorCustomFieldsMappingSerializer(
|
||||
builder_workflow_action_type_registry, BuilderWorkflowActionSerializer
|
||||
),
|
||||
400: get_error_schema(
|
||||
[
|
||||
"ERROR_REQUEST_BODY_VALIDATION",
|
||||
]
|
||||
),
|
||||
404: get_error_schema(
|
||||
[
|
||||
"ERROR_WORKFLOW_ACTION_DOES_NOT_EXIST",
|
||||
]
|
||||
),
|
||||
},
|
||||
)
|
||||
@transaction.atomic
|
||||
@map_exceptions(
|
||||
{
|
||||
WorkflowActionDoesNotExist: ERROR_WORKFLOW_ACTION_DOES_NOT_EXIST,
|
||||
}
|
||||
)
|
||||
def patch(self, request, workflow_action_id: int):
|
||||
workflow_action = BuilderWorkflowActionHandler().get_workflow_action(
|
||||
workflow_action_id
|
||||
)
|
||||
workflow_action_type = type_from_data_or_registry(
|
||||
request.data, builder_workflow_action_type_registry, workflow_action
|
||||
)
|
||||
data = validate_data_custom_fields(
|
||||
workflow_action_type.type,
|
||||
builder_workflow_action_type_registry,
|
||||
request.data,
|
||||
base_serializer_class=UpdateBuilderWorkflowActionsSerializer,
|
||||
partial=True,
|
||||
)
|
||||
|
||||
workflow_action_updated = BuilderWorkflowActionService().update_workflow_action(
|
||||
request.user, workflow_action, **data
|
||||
)
|
||||
|
||||
serializer = builder_workflow_action_type_registry.get_serializer(
|
||||
workflow_action_updated, BuilderWorkflowActionSerializer
|
||||
)
|
||||
return Response(serializer.data)
|
|
@ -20,6 +20,12 @@ from baserow.contrib.builder.pages.models import Page
|
|||
from baserow.contrib.builder.pages.service import PageService
|
||||
from baserow.contrib.builder.theme.registries import theme_config_block_registry
|
||||
from baserow.contrib.builder.types import BuilderDict, DataSourceDict, PageDict
|
||||
from baserow.contrib.builder.workflow_actions.handler import (
|
||||
BuilderWorkflowActionHandler,
|
||||
)
|
||||
from baserow.contrib.builder.workflow_actions.registries import (
|
||||
builder_workflow_action_type_registry,
|
||||
)
|
||||
from baserow.contrib.database.constants import IMPORT_SERIALIZED_IMPORTING
|
||||
from baserow.core.db import specific_iterator
|
||||
from baserow.core.integrations.models import Integration
|
||||
|
@ -88,6 +94,15 @@ class BuilderApplicationType(ApplicationType):
|
|||
element.get_type().export_serialized(element)
|
||||
)
|
||||
|
||||
# Get serialized versions of all workflow actions of the current page
|
||||
serialized_workflow_actions = []
|
||||
for workflow_action in BuilderWorkflowActionHandler().get_workflow_actions(
|
||||
page=page
|
||||
):
|
||||
serialized_workflow_actions.append(
|
||||
workflow_action.get_type().export_serialized(workflow_action)
|
||||
)
|
||||
|
||||
# Get serialized version of all data_sources for the current page
|
||||
serialized_data_sources = []
|
||||
for data_source in DataSourceHandler().get_data_sources(page=page):
|
||||
|
@ -119,6 +134,7 @@ class BuilderApplicationType(ApplicationType):
|
|||
path_params=page.path_params,
|
||||
elements=serialized_elements,
|
||||
data_sources=serialized_data_sources,
|
||||
workflow_actions=serialized_workflow_actions,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -255,6 +271,13 @@ class BuilderApplicationType(ApplicationType):
|
|||
for page in serialized_pages
|
||||
]
|
||||
)
|
||||
+ sum(
|
||||
[
|
||||
# Inserting every workflow action
|
||||
len(page["workflow_actions"])
|
||||
for page in serialized_pages
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
def import_pages_serialized(
|
||||
|
@ -296,6 +319,9 @@ class BuilderApplicationType(ApplicationType):
|
|||
if "integrations" not in id_mapping:
|
||||
id_mapping["integrations"] = {}
|
||||
|
||||
if "builder_workflow_actions" not in id_mapping:
|
||||
id_mapping["builder_workflow_actions"] = {}
|
||||
|
||||
if "workspace_id" not in id_mapping and builder.workspace is not None:
|
||||
id_mapping["workspace_id"] = builder.workspace.id
|
||||
|
||||
|
@ -315,6 +341,7 @@ class BuilderApplicationType(ApplicationType):
|
|||
serialized_page["_object"] = page_instance
|
||||
serialized_page["_element_objects"] = []
|
||||
serialized_page["_data_source_objects"] = []
|
||||
serialized_page["_workflow_action_objects"] = []
|
||||
imported_pages.append(page_instance)
|
||||
progress.increment(state=IMPORT_SERIALIZED_IMPORTING)
|
||||
|
||||
|
@ -370,7 +397,20 @@ class BuilderApplicationType(ApplicationType):
|
|||
id_mapping,
|
||||
)
|
||||
|
||||
progress.increment(state=IMPORT_SERIALIZED_IMPORTING)
|
||||
progress.increment(state=IMPORT_SERIALIZED_IMPORTING)
|
||||
|
||||
# Then we create all the workflow actions
|
||||
for serialized_page in serialized_pages:
|
||||
for serialized_workflow_action in serialized_page["workflow_actions"]:
|
||||
page = serialized_page["_object"]
|
||||
workflow_action_type = builder_workflow_action_type_registry.get(
|
||||
serialized_workflow_action["type"]
|
||||
)
|
||||
workflow_action_type.import_serialized(
|
||||
page, serialized_workflow_action, id_mapping
|
||||
)
|
||||
|
||||
progress.increment(state=IMPORT_SERIALIZED_IMPORTING)
|
||||
|
||||
return imported_pages
|
||||
|
||||
|
|
|
@ -30,12 +30,16 @@ class BuilderConfig(AppConfig):
|
|||
from baserow.contrib.builder.pages.object_scopes import (
|
||||
BuilderPageObjectScopeType,
|
||||
)
|
||||
from baserow.contrib.builder.workflow_actions.object_scopes import (
|
||||
BuilderWorkflowActionScopeType,
|
||||
)
|
||||
|
||||
object_scope_type_registry.register(BuilderObjectScopeType())
|
||||
object_scope_type_registry.register(BuilderPageObjectScopeType())
|
||||
object_scope_type_registry.register(BuilderElementObjectScopeType())
|
||||
object_scope_type_registry.register(BuilderDomainObjectScopeType())
|
||||
object_scope_type_registry.register(BuilderDataSourceObjectScopeType())
|
||||
object_scope_type_registry.register(BuilderWorkflowActionScopeType())
|
||||
|
||||
from baserow.contrib.builder.operations import (
|
||||
ListDomainsBuilderOperationType,
|
||||
|
@ -128,6 +132,20 @@ class BuilderConfig(AppConfig):
|
|||
operation_type_registry.register(UpdateElementOperationType())
|
||||
operation_type_registry.register(DeleteElementOperationType())
|
||||
|
||||
from baserow.contrib.builder.workflow_actions.operations import (
|
||||
CreateBuilderWorkflowActionOperationType,
|
||||
DeleteBuilderWorkflowActionOperationType,
|
||||
ListBuilderWorkflowActionsPageOperationType,
|
||||
ReadBuilderWorkflowActionOperationType,
|
||||
UpdateBuilderWorkflowActionOperationType,
|
||||
)
|
||||
|
||||
operation_type_registry.register(ListBuilderWorkflowActionsPageOperationType())
|
||||
operation_type_registry.register(CreateBuilderWorkflowActionOperationType())
|
||||
operation_type_registry.register(DeleteBuilderWorkflowActionOperationType())
|
||||
operation_type_registry.register(UpdateBuilderWorkflowActionOperationType())
|
||||
operation_type_registry.register(ReadBuilderWorkflowActionOperationType())
|
||||
|
||||
from baserow.core.registries import permission_manager_type_registry
|
||||
|
||||
from .domains.permission_manager import AllowPublicBuilderManagerType
|
||||
|
@ -186,6 +204,15 @@ class BuilderConfig(AppConfig):
|
|||
|
||||
theme_config_block_registry.register(MainThemeConfigBlockType())
|
||||
|
||||
from .workflow_actions.registries import builder_workflow_action_type_registry
|
||||
from .workflow_actions.workflow_action_types import (
|
||||
NotificationWorkflowActionType,
|
||||
OpenPageWorkflowActionType,
|
||||
)
|
||||
|
||||
builder_workflow_action_type_registry.register(NotificationWorkflowActionType())
|
||||
builder_workflow_action_type_registry.register(OpenPageWorkflowActionType())
|
||||
|
||||
from .domains.receivers import connect_to_domain_pre_delete_signal
|
||||
|
||||
connect_to_domain_pre_delete_signal()
|
||||
|
|
|
@ -17,7 +17,9 @@ from baserow.core.db import specific_iterator
|
|||
from baserow.core.exceptions import IdDoesNotExist
|
||||
from baserow.core.utils import MirrorDict, extract_allowed
|
||||
|
||||
from .types import ElementForUpdate
|
||||
from ..workflow_actions.models import BuilderWorkflowAction
|
||||
from ..workflow_actions.registries import builder_workflow_action_type_registry
|
||||
from .types import ElementForUpdate, ElementsAndWorkflowActions
|
||||
|
||||
|
||||
class ElementHandler:
|
||||
|
@ -323,7 +325,18 @@ class ElementHandler:
|
|||
|
||||
Element.recalculate_full_orders(queryset=Element.objects.filter(page=page))
|
||||
|
||||
def duplicate_element(self, element: Element) -> List[Element]:
|
||||
def get_element_workflow_actions(
|
||||
self, element: Element
|
||||
) -> Iterable[BuilderWorkflowAction]:
|
||||
"""
|
||||
Get all the workflow actions that belong to an element
|
||||
:param element: The element associated with the workflow actions
|
||||
:return: All the workflow actions associated
|
||||
"""
|
||||
|
||||
return specific_iterator(element.builderworkflowaction_set.all())
|
||||
|
||||
def duplicate_element(self, element: Element) -> ElementsAndWorkflowActions:
|
||||
"""
|
||||
Duplicate an element in a recursive fashion. If the element has any children
|
||||
they will also be imported using the same method and so will their children
|
||||
|
@ -335,13 +348,13 @@ class ElementHandler:
|
|||
|
||||
# We are just creating new elements here so other data id should remain
|
||||
id_mapping = defaultdict(lambda: MirrorDict())
|
||||
id_mapping["builder_elements"] = {}
|
||||
id_mapping["builder_page_elements"] = {}
|
||||
|
||||
return self._duplicate_element_recursive(element, id_mapping)
|
||||
|
||||
def _duplicate_element_recursive(
|
||||
self, element: Element, id_mapping
|
||||
) -> List[Element]:
|
||||
) -> ElementsAndWorkflowActions:
|
||||
"""
|
||||
Duplicates an element and all of its children.
|
||||
|
||||
|
@ -360,12 +373,54 @@ class ElementHandler:
|
|||
element.page, serialized, id_mapping
|
||||
)
|
||||
|
||||
elements_duplicated = [element_duplicated]
|
||||
workflow_actions_duplicated = self._duplicate_workflow_actions_of_element(
|
||||
element, id_mapping
|
||||
)
|
||||
|
||||
elements_and_workflow_actions_duplicated = {
|
||||
"elements": [element_duplicated],
|
||||
"workflow_actions": workflow_actions_duplicated,
|
||||
}
|
||||
|
||||
for child in element.children.all():
|
||||
children_duplicated = self._duplicate_element_recursive(
|
||||
child.specific, id_mapping
|
||||
)
|
||||
elements_duplicated += children_duplicated
|
||||
elements_and_workflow_actions_duplicated["elements"] += children_duplicated[
|
||||
"elements"
|
||||
]
|
||||
elements_and_workflow_actions_duplicated[
|
||||
"workflow_actions"
|
||||
] += children_duplicated["workflow_actions"]
|
||||
|
||||
return elements_duplicated
|
||||
return elements_and_workflow_actions_duplicated
|
||||
|
||||
def _duplicate_workflow_actions_of_element(
|
||||
self,
|
||||
element: Element,
|
||||
id_mapping: MirrorDict,
|
||||
) -> List[BuilderWorkflowAction]:
|
||||
"""
|
||||
This helper function duplicates all the workflow actions associated with the
|
||||
element.
|
||||
|
||||
:param element: The original element
|
||||
:param element_duplicated: The duplicated reference of the original element
|
||||
"""
|
||||
|
||||
workflow_actions_duplicated = []
|
||||
|
||||
for workflow_action in self.get_element_workflow_actions(element):
|
||||
workflow_action_type = builder_workflow_action_type_registry.get_by_model(
|
||||
workflow_action
|
||||
)
|
||||
workflow_action_serialized = workflow_action_type.export_serialized(
|
||||
workflow_action
|
||||
)
|
||||
workflow_action_duplicated = workflow_action_type.import_serialized(
|
||||
element.page, workflow_action_serialized, id_mapping
|
||||
)
|
||||
|
||||
workflow_actions_duplicated.append(workflow_action_duplicated)
|
||||
|
||||
return workflow_actions_duplicated
|
||||
|
|
|
@ -145,9 +145,12 @@ class ElementType(
|
|||
serialized_copy.pop("type")
|
||||
|
||||
if serialized_copy.get("parent_element_id", None):
|
||||
serialized_copy["parent_element_id"] = id_mapping["builder_page_elements"][
|
||||
serialized_copy["parent_element_id"]
|
||||
]
|
||||
serialized_copy["parent_element_id"] = id_mapping[
|
||||
"builder_page_elements"
|
||||
].get(
|
||||
serialized_copy["parent_element_id"],
|
||||
serialized_copy["parent_element_id"],
|
||||
)
|
||||
|
||||
element = self.model_class(page=page, **serialized_copy)
|
||||
element.save()
|
||||
|
|
|
@ -21,7 +21,10 @@ from baserow.contrib.builder.elements.signals import (
|
|||
element_updated,
|
||||
elements_created,
|
||||
)
|
||||
from baserow.contrib.builder.elements.types import ElementForUpdate
|
||||
from baserow.contrib.builder.elements.types import (
|
||||
ElementForUpdate,
|
||||
ElementsAndWorkflowActions,
|
||||
)
|
||||
from baserow.contrib.builder.pages.models import Page
|
||||
from baserow.core.exceptions import CannotCalculateIntermediateOrder
|
||||
from baserow.core.handler import CoreHandler
|
||||
|
@ -245,7 +248,9 @@ class ElementService:
|
|||
|
||||
element_orders_recalculated.send(self, page=page)
|
||||
|
||||
def duplicate_element(self, user: AbstractUser, element: Element) -> List[Element]:
|
||||
def duplicate_element(
|
||||
self, user: AbstractUser, element: Element
|
||||
) -> ElementsAndWorkflowActions:
|
||||
"""
|
||||
Duplicate an element in a recursive fashion. If the element has any children
|
||||
they will also be imported using the same method and so will their children
|
||||
|
@ -265,8 +270,15 @@ class ElementService:
|
|||
context=page,
|
||||
)
|
||||
|
||||
elements_duplicated = self.handler.duplicate_element(element)
|
||||
elements_and_workflow_actions_duplicated = self.handler.duplicate_element(
|
||||
element
|
||||
)
|
||||
|
||||
elements_created.send(self, elements=elements_duplicated, user=user, page=page)
|
||||
elements_created.send(
|
||||
self,
|
||||
elements=elements_and_workflow_actions_duplicated["elements"],
|
||||
user=user,
|
||||
page=page,
|
||||
)
|
||||
|
||||
return elements_duplicated
|
||||
return elements_and_workflow_actions_duplicated
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
from typing import NewType, TypeVar
|
||||
from typing import List, NewType, TypedDict, TypeVar
|
||||
|
||||
from baserow.contrib.builder.types import ElementDict
|
||||
|
||||
from ..workflow_actions.models import BuilderWorkflowAction
|
||||
from .models import Element
|
||||
|
||||
ElementDictSubClass = TypeVar("ElementDictSubClass", bound=ElementDict)
|
||||
ElementSubClass = TypeVar("ElementSubClass", bound=Element)
|
||||
|
||||
ElementForUpdate = NewType("ElementForUpdate", Element)
|
||||
|
||||
|
||||
class ElementsAndWorkflowActions(TypedDict):
|
||||
elements: List[Element]
|
||||
workflow_actions: List[BuilderWorkflowAction]
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
# Generated by Django 3.2.21 on 2023-10-05 11:14
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
import baserow.core.fields
|
||||
import baserow.core.formula.field
|
||||
import baserow.core.mixins
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("contenttypes", "0002_remove_content_type_name"),
|
||||
("builder", "0023_headingelement_font_color"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="BuilderWorkflowAction",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("created_on", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_on", baserow.core.fields.SyncedDateTimeField(auto_now=True)),
|
||||
(
|
||||
"event",
|
||||
models.CharField(
|
||||
choices=[("click", "Click")],
|
||||
help_text="The event that triggers the execution",
|
||||
max_length=30,
|
||||
),
|
||||
),
|
||||
(
|
||||
"content_type",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="builder_workflow_actions",
|
||||
to="contenttypes.contenttype",
|
||||
verbose_name="content type",
|
||||
),
|
||||
),
|
||||
(
|
||||
"element",
|
||||
models.ForeignKey(
|
||||
default=None,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="builder.element",
|
||||
),
|
||||
),
|
||||
(
|
||||
"page",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="builder.page"
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
bases=(
|
||||
baserow.core.mixins.PolymorphicContentTypeMixin,
|
||||
baserow.core.mixins.OrderableMixin,
|
||||
models.Model,
|
||||
baserow.core.mixins.WithRegistry,
|
||||
),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="NotificationWorkflowAction",
|
||||
fields=[
|
||||
(
|
||||
"builderworkflowaction_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="builder.builderworkflowaction",
|
||||
),
|
||||
),
|
||||
("title", baserow.core.formula.field.FormulaField(default="")),
|
||||
("description", baserow.core.formula.field.FormulaField(default="")),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
bases=("builder.builderworkflowaction",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="OpenPageWorkflowAction",
|
||||
fields=[
|
||||
(
|
||||
"builderworkflowaction_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="builder.builderworkflowaction",
|
||||
),
|
||||
),
|
||||
("url", baserow.core.formula.field.FormulaField(default="")),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
bases=("builder.builderworkflowaction",),
|
||||
),
|
||||
]
|
|
@ -3,6 +3,7 @@ from typing import List, Optional, TypedDict
|
|||
from baserow.contrib.builder.pages.types import PagePathParams
|
||||
from baserow.core.integrations.types import IntegrationDictSubClass
|
||||
from baserow.core.services.types import ServiceDictSubClass
|
||||
from baserow.core.workflow_actions.models import WorkflowAction
|
||||
|
||||
|
||||
class ElementDict(TypedDict):
|
||||
|
@ -30,6 +31,7 @@ class PageDict(TypedDict):
|
|||
path_params: PagePathParams
|
||||
elements: List[ElementDict]
|
||||
data_sources: List[DataSourceDict]
|
||||
workflow_actions: List[WorkflowAction]
|
||||
|
||||
|
||||
class BuilderDict(TypedDict):
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
from typing import Iterable, Optional
|
||||
|
||||
from django.db.models import QuerySet
|
||||
|
||||
from baserow.contrib.builder.pages.models import Page
|
||||
from baserow.contrib.builder.workflow_actions.models import BuilderWorkflowAction
|
||||
from baserow.contrib.builder.workflow_actions.registries import (
|
||||
builder_workflow_action_type_registry,
|
||||
)
|
||||
from baserow.core.workflow_actions.handler import WorkflowActionHandler
|
||||
from baserow.core.workflow_actions.models import WorkflowAction
|
||||
|
||||
|
||||
class BuilderWorkflowActionHandler(WorkflowActionHandler):
|
||||
model = BuilderWorkflowAction
|
||||
registry = builder_workflow_action_type_registry
|
||||
|
||||
def get_workflow_actions(
|
||||
self, page: Page, base_queryset: Optional[QuerySet] = None
|
||||
) -> Iterable[WorkflowAction]:
|
||||
"""
|
||||
Get all the workflow actions of an page
|
||||
|
||||
:param page: The page associated with the workflow actions
|
||||
:param base_queryset: Optional base queryset to filter the results
|
||||
:return: A list of workflow actions
|
||||
"""
|
||||
|
||||
if base_queryset is None:
|
||||
base_queryset = self.model.objects
|
||||
|
||||
base_queryset = base_queryset.filter(page=page)
|
||||
|
||||
return super().get_all_workflow_actions(base_queryset)
|
||||
|
||||
def update_workflow_action(
|
||||
self, workflow_action: BuilderWorkflowAction, **kwargs
|
||||
) -> WorkflowAction:
|
||||
# When we are switching types we want to preserve the event and element and
|
||||
# page ids
|
||||
if "type" in kwargs and kwargs["type"] != workflow_action.get_type().type:
|
||||
kwargs["page_id"] = workflow_action.page_id
|
||||
kwargs["element_id"] = workflow_action.element_id
|
||||
kwargs["event"] = workflow_action.event
|
||||
|
||||
return super().update_workflow_action(workflow_action, **kwargs)
|
|
@ -0,0 +1,49 @@
|
|||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
|
||||
from baserow.contrib.builder.elements.models import Element
|
||||
from baserow.contrib.builder.pages.models import Page
|
||||
from baserow.contrib.builder.workflow_actions.registries import (
|
||||
builder_workflow_action_type_registry,
|
||||
)
|
||||
from baserow.core.formula.field import FormulaField
|
||||
from baserow.core.registry import ModelRegistryMixin
|
||||
from baserow.core.workflow_actions.models import WorkflowAction
|
||||
|
||||
|
||||
class EventTypes(models.TextChoices):
|
||||
CLICK = "click"
|
||||
|
||||
|
||||
class BuilderWorkflowAction(WorkflowAction):
|
||||
content_type = models.ForeignKey(
|
||||
ContentType,
|
||||
verbose_name="content type",
|
||||
related_name="builder_workflow_actions",
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
event = models.CharField(
|
||||
max_length=30,
|
||||
choices=EventTypes.choices,
|
||||
help_text="The event that triggers the execution",
|
||||
)
|
||||
page = models.ForeignKey(Page, on_delete=models.CASCADE)
|
||||
element = models.ForeignKey(
|
||||
Element, on_delete=models.CASCADE, null=True, default=None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_type_registry() -> ModelRegistryMixin:
|
||||
return builder_workflow_action_type_registry
|
||||
|
||||
def get_parent(self):
|
||||
return self.page
|
||||
|
||||
|
||||
class NotificationWorkflowAction(BuilderWorkflowAction):
|
||||
title = FormulaField(default="")
|
||||
description = FormulaField(default="")
|
||||
|
||||
|
||||
class OpenPageWorkflowAction(BuilderWorkflowAction):
|
||||
url = FormulaField(default="")
|
|
@ -0,0 +1,42 @@
|
|||
from typing import Optional
|
||||
|
||||
from django.db.models import Q
|
||||
|
||||
from baserow.contrib.builder.elements.object_scopes import BuilderElementObjectScopeType
|
||||
from baserow.contrib.builder.object_scopes import BuilderObjectScopeType
|
||||
from baserow.contrib.builder.pages.object_scopes import BuilderPageObjectScopeType
|
||||
from baserow.contrib.builder.workflow_actions.models import BuilderWorkflowAction
|
||||
from baserow.core.object_scopes import (
|
||||
ApplicationObjectScopeType,
|
||||
WorkspaceObjectScopeType,
|
||||
)
|
||||
from baserow.core.registries import ObjectScopeType, object_scope_type_registry
|
||||
|
||||
|
||||
class BuilderWorkflowActionScopeType(ObjectScopeType):
|
||||
type = "builder_workflow_action"
|
||||
model_class = BuilderWorkflowAction
|
||||
|
||||
def get_parent_scope(self) -> Optional["ObjectScopeType"]:
|
||||
return object_scope_type_registry.get("builder_element")
|
||||
|
||||
def get_filter_for_scope_type(self, scope_type, scopes):
|
||||
if scope_type.type == WorkspaceObjectScopeType.type:
|
||||
return Q(element__page__builder__workspace__in=[s.id for s in scopes])
|
||||
|
||||
if (
|
||||
scope_type.type == BuilderObjectScopeType.type
|
||||
or scope_type.type == ApplicationObjectScopeType.type
|
||||
):
|
||||
return Q(element__page__builder__in=[s.id for s in scopes])
|
||||
|
||||
if scope_type.type == BuilderPageObjectScopeType.type:
|
||||
return Q(element__page__in=[s.id for s in scopes])
|
||||
|
||||
if scope_type.type == BuilderElementObjectScopeType.type:
|
||||
return Q(element__in=[s.id for s in scopes])
|
||||
|
||||
if scope_type.type == self.type:
|
||||
return Q(id__in=[s.id for s in scopes])
|
||||
|
||||
raise TypeError("The given type is not handled.")
|
|
@ -0,0 +1,29 @@
|
|||
from abc import ABC
|
||||
|
||||
from baserow.contrib.builder.pages.operations import BuilderPageOperationType
|
||||
from baserow.core.registries import OperationType
|
||||
|
||||
|
||||
class ListBuilderWorkflowActionsPageOperationType(BuilderPageOperationType):
|
||||
type = "builder.page.list_workflow_actions"
|
||||
object_scope_name = "builder_workflow_action"
|
||||
|
||||
|
||||
class CreateBuilderWorkflowActionOperationType(BuilderPageOperationType):
|
||||
type = "builder.page.create_workflow_action"
|
||||
|
||||
|
||||
class BuilderWorkflowActionOperationType(OperationType, ABC):
|
||||
context_scope_name = "builder_workflow_action"
|
||||
|
||||
|
||||
class DeleteBuilderWorkflowActionOperationType(BuilderWorkflowActionOperationType):
|
||||
type = "builder.page.workflow_action.delete"
|
||||
|
||||
|
||||
class UpdateBuilderWorkflowActionOperationType(BuilderWorkflowActionOperationType):
|
||||
type = "builder.page.workflow_action.update"
|
||||
|
||||
|
||||
class ReadBuilderWorkflowActionOperationType(BuilderWorkflowActionOperationType):
|
||||
type = "builder.page.workflow_action.read"
|
|
@ -0,0 +1,18 @@
|
|||
from baserow.core.registry import (
|
||||
CustomFieldsRegistryMixin,
|
||||
ModelRegistryMixin,
|
||||
Registry,
|
||||
)
|
||||
|
||||
|
||||
class BuilderWorkflowActionTypeRegistry(
|
||||
Registry, ModelRegistryMixin, CustomFieldsRegistryMixin
|
||||
):
|
||||
"""
|
||||
Contains all the registered workflow action types for the builder module.
|
||||
"""
|
||||
|
||||
name = "builder_workflow_action_type"
|
||||
|
||||
|
||||
builder_workflow_action_type_registry = BuilderWorkflowActionTypeRegistry()
|
175
backend/src/baserow/contrib/builder/workflow_actions/service.py
Normal file
175
backend/src/baserow/contrib/builder/workflow_actions/service.py
Normal file
|
@ -0,0 +1,175 @@
|
|||
from typing import List
|
||||
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
|
||||
from baserow.contrib.builder.pages.models import Page
|
||||
from baserow.contrib.builder.workflow_actions.handler import (
|
||||
BuilderWorkflowActionHandler,
|
||||
)
|
||||
from baserow.contrib.builder.workflow_actions.models import (
|
||||
BuilderWorkflowAction,
|
||||
WorkflowAction,
|
||||
)
|
||||
from baserow.contrib.builder.workflow_actions.operations import (
|
||||
CreateBuilderWorkflowActionOperationType,
|
||||
DeleteBuilderWorkflowActionOperationType,
|
||||
ListBuilderWorkflowActionsPageOperationType,
|
||||
ReadBuilderWorkflowActionOperationType,
|
||||
UpdateBuilderWorkflowActionOperationType,
|
||||
)
|
||||
from baserow.contrib.builder.workflow_actions.signals import (
|
||||
workflow_action_created,
|
||||
workflow_action_deleted,
|
||||
workflow_action_updated,
|
||||
)
|
||||
from baserow.contrib.builder.workflow_actions.workflow_action_types import (
|
||||
BuilderWorkflowActionType,
|
||||
)
|
||||
from baserow.core.handler import CoreHandler
|
||||
|
||||
|
||||
class BuilderWorkflowActionService:
|
||||
def __init__(self):
|
||||
self.handler = BuilderWorkflowActionHandler()
|
||||
|
||||
def get_workflow_action(
|
||||
self, user: AbstractUser, workflow_action_id: int
|
||||
) -> WorkflowAction:
|
||||
"""
|
||||
Returns an workflow_action instance from the database. Also checks the user
|
||||
permissions.
|
||||
|
||||
:param user: The user trying to get the workflow_action
|
||||
:param workflow_action_id: The ID of the workflow_action
|
||||
:return: The workflow_action instance
|
||||
"""
|
||||
|
||||
workflow_action = self.handler.get_workflow_action(workflow_action_id)
|
||||
|
||||
CoreHandler().check_permissions(
|
||||
user,
|
||||
ReadBuilderWorkflowActionOperationType.type,
|
||||
workspace=workflow_action.page.builder.workspace,
|
||||
context=workflow_action,
|
||||
)
|
||||
|
||||
return workflow_action
|
||||
|
||||
def get_workflow_actions(
|
||||
self,
|
||||
user: AbstractUser,
|
||||
page: Page,
|
||||
) -> List[WorkflowAction]:
|
||||
"""
|
||||
Gets all the workflow_actions of a given page visible to the given user.
|
||||
|
||||
:param user: The user trying to get the workflow_actions.
|
||||
:param page: The page that holds the workflow_actions.
|
||||
:return: The workflow_actions of that page.
|
||||
"""
|
||||
|
||||
CoreHandler().check_permissions(
|
||||
user,
|
||||
ListBuilderWorkflowActionsPageOperationType.type,
|
||||
workspace=page.builder.workspace,
|
||||
context=page,
|
||||
)
|
||||
|
||||
user_workflow_actions = CoreHandler().filter_queryset(
|
||||
user,
|
||||
ListBuilderWorkflowActionsPageOperationType.type,
|
||||
BuilderWorkflowAction.objects.all(),
|
||||
workspace=page.builder.workspace,
|
||||
context=page,
|
||||
)
|
||||
|
||||
return self.handler.get_workflow_actions(
|
||||
page, base_queryset=user_workflow_actions
|
||||
)
|
||||
|
||||
def create_workflow_action(
|
||||
self,
|
||||
user: AbstractUser,
|
||||
workflow_action_type: BuilderWorkflowActionType,
|
||||
page: Page,
|
||||
**kwargs,
|
||||
) -> WorkflowAction:
|
||||
"""
|
||||
Creates a new workflow_action for a page given the user permissions.
|
||||
|
||||
:param user: The user trying to create the workflow_action.
|
||||
:param workflow_action_type: The type of the workflow_action.
|
||||
:param page: The page the workflow_action is associated with.
|
||||
:param kwargs: Additional attributes of the workflow_action.
|
||||
:return: The created workflow_action.
|
||||
"""
|
||||
|
||||
CoreHandler().check_permissions(
|
||||
user,
|
||||
CreateBuilderWorkflowActionOperationType.type,
|
||||
workspace=page.builder.workspace,
|
||||
context=page,
|
||||
)
|
||||
|
||||
new_workflow_action = self.handler.create_workflow_action(
|
||||
workflow_action_type, page=page, **kwargs
|
||||
)
|
||||
|
||||
workflow_action_created.send(
|
||||
self,
|
||||
workflow_action=new_workflow_action,
|
||||
user=user,
|
||||
)
|
||||
|
||||
return new_workflow_action
|
||||
|
||||
def update_workflow_action(
|
||||
self, user: AbstractUser, workflow_action: WorkflowAction, **kwargs
|
||||
) -> WorkflowAction:
|
||||
"""
|
||||
Updates and workflow_action with values. Will also check if the values are
|
||||
allowed to be set on the workflow_action first.
|
||||
|
||||
:param user: The user trying to update the workflow_action.
|
||||
:param workflow_action: The workflow_action that should be updated.
|
||||
:param kwargs: Additional attributes of the workflow_action.
|
||||
:return: The updated workflow_action.
|
||||
"""
|
||||
|
||||
CoreHandler().check_permissions(
|
||||
user,
|
||||
UpdateBuilderWorkflowActionOperationType.type,
|
||||
workspace=workflow_action.page.builder.workspace,
|
||||
context=workflow_action,
|
||||
)
|
||||
|
||||
workflow_action = self.handler.update_workflow_action(workflow_action, **kwargs)
|
||||
|
||||
workflow_action_updated.send(self, workflow_action=workflow_action, user=user)
|
||||
|
||||
return workflow_action
|
||||
|
||||
def delete_workflow_action(
|
||||
self, user: AbstractUser, workflow_action: WorkflowAction
|
||||
):
|
||||
"""
|
||||
Deletes a workflow_action.
|
||||
|
||||
:param user: The user trying to delete the workflow_action.
|
||||
:param workflow_action: The to-be-deleted workflow_action.
|
||||
"""
|
||||
|
||||
page = workflow_action.page
|
||||
|
||||
CoreHandler().check_permissions(
|
||||
user,
|
||||
DeleteBuilderWorkflowActionOperationType.type,
|
||||
workspace=page.builder.workspace,
|
||||
context=workflow_action,
|
||||
)
|
||||
|
||||
self.handler.delete_workflow_action(workflow_action)
|
||||
|
||||
workflow_action_deleted.send(
|
||||
self, workflow_action_id=workflow_action.id, page=page, user=user
|
||||
)
|
|
@ -0,0 +1,5 @@
|
|||
from django.dispatch import Signal
|
||||
|
||||
workflow_action_created = Signal()
|
||||
workflow_action_deleted = Signal()
|
||||
workflow_action_updated = Signal()
|
|
@ -0,0 +1,7 @@
|
|||
from baserow.core.workflow_actions.types import WorkflowActionDict
|
||||
|
||||
|
||||
class BuilderWorkflowActionDict(WorkflowActionDict):
|
||||
page_id: int
|
||||
element_id: int
|
||||
event: str
|
|
@ -0,0 +1,117 @@
|
|||
from abc import abstractmethod
|
||||
from typing import TYPE_CHECKING, Any, Dict
|
||||
|
||||
from baserow.contrib.builder.elements.handler import ElementHandler
|
||||
from baserow.contrib.builder.workflow_actions.models import (
|
||||
BuilderWorkflowAction,
|
||||
NotificationWorkflowAction,
|
||||
OpenPageWorkflowAction,
|
||||
)
|
||||
from baserow.contrib.builder.workflow_actions.types import BuilderWorkflowActionDict
|
||||
from baserow.core.formula.serializers import FormulaSerializerField
|
||||
from baserow.core.formula.types import BaserowFormula
|
||||
from baserow.core.workflow_actions.registries import WorkflowActionType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from baserow.contrib.builder.pages.models import Page
|
||||
|
||||
|
||||
class BuilderWorkflowActionType(WorkflowActionType):
|
||||
allowed_fields = ["page", "page_id", "element", "element_id", "event"]
|
||||
|
||||
def prepare_value_for_db(
|
||||
self, values: Dict, instance: BuilderWorkflowAction = None
|
||||
):
|
||||
if "element_id" in values:
|
||||
values["element"] = ElementHandler().get_element(values["element_id"])
|
||||
|
||||
return super().prepare_value_for_db(values, instance=instance)
|
||||
|
||||
@abstractmethod
|
||||
def get_sample_params(self) -> Dict[str, Any]:
|
||||
pass
|
||||
|
||||
def import_serialized(
|
||||
self, page: "Page", serialized_values: Dict[str, Any], id_mapping: Dict
|
||||
) -> BuilderWorkflowAction:
|
||||
if "builder_workflow_actions" not in id_mapping:
|
||||
id_mapping["builder_workflow_actions"] = {}
|
||||
|
||||
serialized_copy = serialized_values.copy()
|
||||
|
||||
# Remove extra keys
|
||||
workflow_action_id = serialized_copy.pop("id")
|
||||
serialized_copy.pop("type")
|
||||
|
||||
# Convert table id
|
||||
serialized_copy["page_id"] = id_mapping["builder_pages"][
|
||||
serialized_copy["page_id"]
|
||||
]
|
||||
|
||||
# Convert element id
|
||||
if "element_id" in serialized_copy:
|
||||
serialized_copy["element_id"] = id_mapping["builder_page_elements"][
|
||||
serialized_copy["element_id"]
|
||||
]
|
||||
|
||||
workflow_action = self.model_class(page=page, **serialized_copy)
|
||||
workflow_action.save()
|
||||
|
||||
id_mapping["builder_workflow_actions"][workflow_action_id] = workflow_action.id
|
||||
|
||||
return workflow_action
|
||||
|
||||
|
||||
class NotificationWorkflowActionType(BuilderWorkflowActionType):
|
||||
type = "notification"
|
||||
model_class = NotificationWorkflowAction
|
||||
serializer_field_names = ["title", "description"]
|
||||
serializer_field_overrides = {
|
||||
"title": FormulaSerializerField(
|
||||
help_text="The title of the notification. Must be an formula.",
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
default="",
|
||||
),
|
||||
"description": FormulaSerializerField(
|
||||
help_text="The description of the notification. Must be an formula.",
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
default="",
|
||||
),
|
||||
}
|
||||
|
||||
class SerializedDict(BuilderWorkflowActionDict):
|
||||
title: BaserowFormula
|
||||
description: BaserowFormula
|
||||
|
||||
@property
|
||||
def allowed_fields(self):
|
||||
return super().allowed_fields + ["title", "description"]
|
||||
|
||||
def get_sample_params(self) -> Dict[str, Any]:
|
||||
return {"title": "'hello'", "description": "'there'"}
|
||||
|
||||
|
||||
class OpenPageWorkflowActionType(BuilderWorkflowActionType):
|
||||
type = "open_page"
|
||||
model_class = OpenPageWorkflowAction
|
||||
serializer_field_names = ["url"]
|
||||
serializer_field_overrides = {
|
||||
"url": FormulaSerializerField(
|
||||
help_text="The url to open. Must be an formula.",
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
default="",
|
||||
),
|
||||
}
|
||||
|
||||
class SerializedDict(BuilderWorkflowActionDict):
|
||||
url: BaserowFormula
|
||||
|
||||
@property
|
||||
def allowed_fields(self):
|
||||
return super().allowed_fields + ["url"]
|
||||
|
||||
def get_sample_params(self) -> Dict[str, Any]:
|
||||
return {"url": "'hello'"}
|
2
backend/src/baserow/core/workflow_actions/exceptions.py
Normal file
2
backend/src/baserow/core/workflow_actions/exceptions.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
class WorkflowActionDoesNotExist(Exception):
|
||||
"""Raised when trying to get a workflow action that doesn't exist"""
|
129
backend/src/baserow/core/workflow_actions/handler.py
Normal file
129
backend/src/baserow/core/workflow_actions/handler.py
Normal file
|
@ -0,0 +1,129 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from typing import Iterable, Optional, Type, cast
|
||||
|
||||
from django.db.models import QuerySet
|
||||
|
||||
from baserow.core.db import specific_iterator
|
||||
from baserow.core.registry import Registry
|
||||
from baserow.core.utils import extract_allowed
|
||||
from baserow.core.workflow_actions.exceptions import WorkflowActionDoesNotExist
|
||||
from baserow.core.workflow_actions.models import WorkflowAction
|
||||
from baserow.core.workflow_actions.registries import WorkflowActionType
|
||||
|
||||
|
||||
class WorkflowActionHandler(ABC):
|
||||
"""
|
||||
This is an abstract handler, each module that wants to use workflow actions will
|
||||
need to implement their own handler.
|
||||
"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def model(self) -> Type[WorkflowAction]:
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def registry(self) -> Registry:
|
||||
pass
|
||||
|
||||
def get_workflow_action(self, workflow_action_id: int) -> WorkflowAction:
|
||||
"""
|
||||
Returns a workflow action from the database.
|
||||
|
||||
The queryset here is not optional since every module needs to provide their
|
||||
own model at least.
|
||||
|
||||
:param workflow_action_id: The ID of the workflow action.
|
||||
:return: The workflow action instance.
|
||||
"""
|
||||
|
||||
try:
|
||||
return self.model.objects.get(id=workflow_action_id).specific
|
||||
except self.model.DoesNotExist:
|
||||
raise WorkflowActionDoesNotExist()
|
||||
|
||||
def get_all_workflow_actions(
|
||||
self, base_queryset: Optional[QuerySet] = None
|
||||
) -> Iterable[WorkflowAction]:
|
||||
"""
|
||||
Gets all the workflow actions of the defined model.
|
||||
|
||||
:param base_queryset: A query set that lets you prefilter the results
|
||||
:return: A list of workflow actions
|
||||
"""
|
||||
|
||||
if base_queryset is None:
|
||||
base_queryset = self.model.objects
|
||||
|
||||
return specific_iterator(base_queryset)
|
||||
|
||||
def create_workflow_action(
|
||||
self, workflow_action_type: WorkflowActionType, **kwargs
|
||||
) -> WorkflowAction:
|
||||
"""
|
||||
Creates a new workflow action of the given type.
|
||||
|
||||
:param workflow_action_type: The type of the new workflow action
|
||||
:param kwargs: Any fields that need to be set for that specific type
|
||||
:return: The created workflow action
|
||||
"""
|
||||
|
||||
allowed_values = extract_allowed(kwargs, workflow_action_type.allowed_fields)
|
||||
|
||||
allowed_values = workflow_action_type.prepare_value_for_db(allowed_values)
|
||||
|
||||
model_class = cast(WorkflowAction, workflow_action_type.model_class)
|
||||
|
||||
workflow_action = model_class(**allowed_values)
|
||||
workflow_action.save()
|
||||
|
||||
return workflow_action
|
||||
|
||||
def delete_workflow_action(self, workflow_action: WorkflowAction):
|
||||
"""
|
||||
Deletes a given workflow action.
|
||||
|
||||
:param workflow_action: The workflow action to be deleted
|
||||
"""
|
||||
|
||||
workflow_action.delete()
|
||||
|
||||
def update_workflow_action(
|
||||
self, workflow_action: WorkflowAction, **kwargs
|
||||
) -> WorkflowAction:
|
||||
"""
|
||||
Update an existing workflow action.
|
||||
|
||||
:param workflow_action: The workflow action you want to update.
|
||||
:param kwargs: The updates you wish to perform on the workflow action.
|
||||
:return: The updated workflow action.
|
||||
"""
|
||||
|
||||
has_type_changed = (
|
||||
"type" in kwargs and kwargs["type"] != workflow_action.get_type().type
|
||||
)
|
||||
|
||||
if has_type_changed:
|
||||
workflow_action_type = self.registry.get(kwargs["type"])
|
||||
else:
|
||||
workflow_action_type = workflow_action.get_type()
|
||||
|
||||
allowed_updates = extract_allowed(kwargs, workflow_action_type.allowed_fields)
|
||||
|
||||
allowed_updates = workflow_action_type.prepare_value_for_db(
|
||||
allowed_updates, instance=workflow_action
|
||||
)
|
||||
|
||||
if has_type_changed:
|
||||
self.delete_workflow_action(workflow_action)
|
||||
workflow_action = self.create_workflow_action(
|
||||
workflow_action_type, **allowed_updates
|
||||
)
|
||||
else:
|
||||
for key, value in allowed_updates.items():
|
||||
setattr(workflow_action, key, value)
|
||||
|
||||
workflow_action.save()
|
||||
|
||||
return workflow_action.specific
|
28
backend/src/baserow/core/workflow_actions/models.py
Normal file
28
backend/src/baserow/core/workflow_actions/models.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
from django.db import models
|
||||
|
||||
from baserow.core.mixins import (
|
||||
CreatedAndUpdatedOnMixin,
|
||||
HierarchicalModelMixin,
|
||||
OrderableMixin,
|
||||
PolymorphicContentTypeMixin,
|
||||
WithRegistry,
|
||||
)
|
||||
from baserow.core.registry import ModelRegistryMixin
|
||||
|
||||
|
||||
class WorkflowAction(
|
||||
PolymorphicContentTypeMixin,
|
||||
CreatedAndUpdatedOnMixin,
|
||||
HierarchicalModelMixin,
|
||||
OrderableMixin,
|
||||
models.Model,
|
||||
WithRegistry,
|
||||
):
|
||||
@staticmethod
|
||||
def get_type_registry() -> ModelRegistryMixin:
|
||||
raise Exception(
|
||||
"Needs to be implement by module specific workflow actions parent"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
66
backend/src/baserow/core/workflow_actions/registries.py
Normal file
66
backend/src/baserow/core/workflow_actions/registries.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Dict, Type
|
||||
|
||||
from baserow.core.registry import (
|
||||
CustomFieldsInstanceMixin,
|
||||
ImportExportMixin,
|
||||
Instance,
|
||||
ModelInstanceMixin,
|
||||
)
|
||||
from baserow.core.workflow_actions.models import WorkflowAction
|
||||
from baserow.core.workflow_actions.types import WorkflowActionDictSubClass
|
||||
|
||||
|
||||
class WorkflowActionType(
|
||||
Instance, ModelInstanceMixin, ImportExportMixin, CustomFieldsInstanceMixin, ABC
|
||||
):
|
||||
SerializedDict: Type[WorkflowActionDictSubClass]
|
||||
|
||||
def export_serialized(self, instance: WorkflowAction) -> Dict[str, Any]:
|
||||
property_names = self.SerializedDict.__annotations__.keys()
|
||||
|
||||
serialized = self.SerializedDict(
|
||||
**{
|
||||
key: self.get_property_for_serialization(instance, key)
|
||||
for key in property_names
|
||||
}
|
||||
)
|
||||
|
||||
return serialized
|
||||
|
||||
def get_property_for_serialization(
|
||||
self, workflow_action: WorkflowAction, prop_name: str
|
||||
):
|
||||
"""
|
||||
You can customize the behavior of the serialization of a property with this
|
||||
hook.
|
||||
"""
|
||||
|
||||
if prop_name == "type":
|
||||
return self.type
|
||||
|
||||
return getattr(workflow_action, prop_name)
|
||||
|
||||
@abstractmethod
|
||||
def import_serialized(
|
||||
self, parent: Any, serialized_values: Dict[str, Any], id_mapping: Dict
|
||||
) -> WorkflowAction:
|
||||
pass
|
||||
|
||||
def prepare_value_for_db(self, values: Dict, instance: WorkflowAction = None):
|
||||
"""
|
||||
A hook which can be called when before a workflow action is created or updated
|
||||
|
||||
:param values: The values that are about to be set on the workflow action
|
||||
:param instance: The current instance (only when an update happens)
|
||||
:return: The prepared values
|
||||
"""
|
||||
|
||||
return values
|
||||
|
||||
@abstractmethod
|
||||
def get_sample_params(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Returns a sample of params for this type. This can be used to tests the element
|
||||
for instance.
|
||||
"""
|
14
backend/src/baserow/core/workflow_actions/types.py
Normal file
14
backend/src/baserow/core/workflow_actions/types.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
from typing import TypedDict, TypeVar
|
||||
|
||||
from baserow.core.workflow_actions.models import WorkflowAction
|
||||
|
||||
|
||||
class WorkflowActionDict(TypedDict):
|
||||
id: int
|
||||
type: str
|
||||
|
||||
|
||||
WorkflowActionDictSubClass = TypeVar(
|
||||
"WorkflowActionDictSubClass", bound=WorkflowActionDict
|
||||
)
|
||||
WorkflowActionSubClass = TypeVar("WorkflowActionSubClass", bound=WorkflowAction)
|
|
@ -23,6 +23,7 @@ from .user import UserFixtures
|
|||
from .user_file import UserFileFixtures
|
||||
from .view import ViewFixtures
|
||||
from .webhook import TableWebhookFixture
|
||||
from .workflow_action import WorkflowActionFixture
|
||||
from .workspace import WorkspaceFixtures
|
||||
|
||||
|
||||
|
@ -51,6 +52,7 @@ class Fixtures(
|
|||
ServiceFixtures,
|
||||
DataSourceFixtures,
|
||||
NotificationsFixture,
|
||||
WorkflowActionFixture,
|
||||
):
|
||||
def __init__(self, fake=None):
|
||||
self.fake = fake
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from copy import deepcopy
|
||||
|
||||
from baserow.contrib.builder.elements.models import (
|
||||
ButtonElement,
|
||||
CollectionElementField,
|
||||
ColumnElement,
|
||||
HeadingElement,
|
||||
|
@ -62,6 +63,10 @@ class ElementFixtures:
|
|||
|
||||
return element
|
||||
|
||||
def create_builder_button_element(self, user=None, page=None, **kwargs):
|
||||
element = self.create_builder_element(ButtonElement, user, page, **kwargs)
|
||||
return element
|
||||
|
||||
def create_builder_element(self, model_class, user=None, page=None, **kwargs):
|
||||
if user is None:
|
||||
user = self.create_user()
|
||||
|
@ -74,6 +79,6 @@ class ElementFixtures:
|
|||
if "order" not in kwargs:
|
||||
kwargs["order"] = model_class.get_last_order(page)
|
||||
|
||||
page = model_class.objects.create(page=page, **kwargs)
|
||||
element = model_class.objects.create(page=page, **kwargs)
|
||||
|
||||
return page
|
||||
return element
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
from baserow.contrib.builder.workflow_actions.models import NotificationWorkflowAction
|
||||
|
||||
|
||||
class WorkflowActionFixture:
|
||||
def create_notification_workflow_action(self, **kwargs):
|
||||
return self.create_workflow_action(NotificationWorkflowAction, **kwargs)
|
||||
|
||||
def create_workflow_action(self, model_class, **kwargs):
|
||||
return model_class.objects.create(**kwargs)
|
|
@ -426,8 +426,8 @@ def test_duplicate_element(api_client, data_fixture):
|
|||
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert response_json[0]["id"] != element.id
|
||||
assert response_json[0]["value"] == element.value
|
||||
assert response_json["elements"][0]["id"] != element.id
|
||||
assert response_json["elements"][0]["value"] == element.value
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
from django.urls import reverse
|
||||
|
||||
import pytest
|
||||
from rest_framework.status import (
|
||||
HTTP_200_OK,
|
||||
HTTP_204_NO_CONTENT,
|
||||
HTTP_400_BAD_REQUEST,
|
||||
HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
from baserow.contrib.builder.workflow_actions.workflow_action_types import (
|
||||
NotificationWorkflowActionType,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_workflow_action(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
page = data_fixture.create_builder_page(user=user)
|
||||
element = data_fixture.create_builder_button_element(page=page)
|
||||
workflow_action_type = NotificationWorkflowActionType.type
|
||||
|
||||
url = reverse("api:builder:workflow_action:list", kwargs={"page_id": page.id})
|
||||
response = api_client.post(
|
||||
url,
|
||||
{"type": workflow_action_type, "event": "click", "element_id": element.id},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert response_json["type"] == workflow_action_type
|
||||
assert response_json["element_id"] == element.id
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_workflow_action_page_does_not_exist(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
workflow_action_type = NotificationWorkflowActionType.type
|
||||
|
||||
url = reverse("api:builder:workflow_action:list", kwargs={"page_id": 99999})
|
||||
response = api_client.post(
|
||||
url,
|
||||
{"type": workflow_action_type, "event": "click"},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
|
||||
assert response.status_code == HTTP_404_NOT_FOUND
|
||||
|
||||
|
||||
@pytest.mark.django_db(transaction=True)
|
||||
def test_create_workflow_action_element_does_not_exist(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
page = data_fixture.create_builder_page(user=user)
|
||||
workflow_action_type = NotificationWorkflowActionType.type
|
||||
|
||||
url = reverse("api:builder:workflow_action:list", kwargs={"page_id": page.id})
|
||||
response = api_client.post(
|
||||
url,
|
||||
{"type": workflow_action_type, "event": "click", "element_id": 9999},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
|
||||
assert response.status_code == HTTP_404_NOT_FOUND
|
||||
|
||||
|
||||
@pytest.mark.django_db(transaction=True)
|
||||
def test_create_workflow_action_event_does_not_exist(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
page = data_fixture.create_builder_page(user=user)
|
||||
workflow_action_type = NotificationWorkflowActionType.type
|
||||
|
||||
url = reverse("api:builder:workflow_action:list", kwargs={"page_id": page.id})
|
||||
response = api_client.post(
|
||||
url,
|
||||
{"type": workflow_action_type, "event": "invalid"},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_get_workflow_actions(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
page = data_fixture.create_builder_page(user=user)
|
||||
workflow_action_one = data_fixture.create_notification_workflow_action(page=page)
|
||||
workflow_action_two = data_fixture.create_notification_workflow_action(page=page)
|
||||
|
||||
url = reverse("api:builder:workflow_action:list", kwargs={"page_id": page.id})
|
||||
response = api_client.get(
|
||||
url,
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert len(response_json) == 2
|
||||
assert response_json[0]["id"] == workflow_action_one.id
|
||||
assert response_json[1]["id"] == workflow_action_two.id
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_delete_workflow_actions(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
page = data_fixture.create_builder_page(user=user)
|
||||
workflow_action = data_fixture.create_notification_workflow_action(page=page)
|
||||
|
||||
url = reverse(
|
||||
"api:builder:workflow_action:item",
|
||||
kwargs={"workflow_action_id": workflow_action.id},
|
||||
)
|
||||
response = api_client.delete(
|
||||
url,
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
|
||||
assert response.status_code == HTTP_204_NO_CONTENT
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_patch_workflow_actions(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
page = data_fixture.create_builder_page(user=user)
|
||||
workflow_action = data_fixture.create_notification_workflow_action(page=page)
|
||||
|
||||
url = reverse(
|
||||
"api:builder:workflow_action:item",
|
||||
kwargs={"workflow_action_id": workflow_action.id},
|
||||
)
|
||||
response = api_client.patch(
|
||||
url,
|
||||
{"description": "'hello'"},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert response_json["description"] == "'hello'"
|
|
@ -368,7 +368,7 @@ def test_before_places_in_container_removed_no_change(data_fixture):
|
|||
def test_duplicate_element_single_element(data_fixture):
|
||||
element = data_fixture.create_builder_paragraph_element(value="test")
|
||||
|
||||
[element_duplicated] = ElementHandler().duplicate_element(element)
|
||||
[element_duplicated] = ElementHandler().duplicate_element(element)["elements"]
|
||||
|
||||
assert element.id != element_duplicated.id
|
||||
assert element.value == element_duplicated.value
|
||||
|
@ -389,7 +389,7 @@ def test_duplicate_element_multiple_elements(data_fixture):
|
|||
container_element_duplicated,
|
||||
child_duplicated,
|
||||
child_two_duplicated,
|
||||
] = ElementHandler().duplicate_element(container_element)
|
||||
] = ElementHandler().duplicate_element(container_element)["elements"]
|
||||
|
||||
assert container_element.id != container_element_duplicated.id
|
||||
assert container_element.column_amount == container_element_duplicated.column_amount
|
||||
|
@ -421,7 +421,7 @@ def test_duplicate_element_deeply_nested(data_fixture):
|
|||
container_element_duplicated,
|
||||
child_first_level_duplicated,
|
||||
child_second_level_duplicated,
|
||||
] = ElementHandler().duplicate_element(container_element)
|
||||
] = ElementHandler().duplicate_element(container_element)["elements"]
|
||||
|
||||
assert container_element.id != container_element_duplicated.id
|
||||
assert container_element.column_amount == container_element_duplicated.column_amount
|
||||
|
@ -441,3 +441,70 @@ def test_duplicate_element_deeply_nested(data_fixture):
|
|||
child_second_level_duplicated.parent_element_id
|
||||
== child_first_level_duplicated.id
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_duplicate_element_with_workflow_action(data_fixture):
|
||||
page = data_fixture.create_builder_page()
|
||||
element = data_fixture.create_builder_button_element(page=page)
|
||||
workflow_action = data_fixture.create_notification_workflow_action(
|
||||
page=page, element=element
|
||||
)
|
||||
|
||||
result = ElementHandler().duplicate_element(element)
|
||||
[element_duplicated] = result["elements"]
|
||||
[duplicated_workflow_action] = result["workflow_actions"]
|
||||
|
||||
assert duplicated_workflow_action.id != workflow_action.id
|
||||
assert duplicated_workflow_action.page_id == workflow_action.page_id
|
||||
assert duplicated_workflow_action.element_id == element_duplicated.id
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_get_element_workflow_actions(data_fixture):
|
||||
page = data_fixture.create_builder_page()
|
||||
element = data_fixture.create_builder_button_element()
|
||||
workflow_action_one = data_fixture.create_notification_workflow_action(
|
||||
page=page, element=element
|
||||
)
|
||||
workflow_action_two = data_fixture.create_notification_workflow_action(
|
||||
page=page, element=element
|
||||
)
|
||||
|
||||
[
|
||||
workflow_action_one_returned,
|
||||
workflow_action_two_returned,
|
||||
] = ElementHandler().get_element_workflow_actions(element)
|
||||
|
||||
assert workflow_action_one.id == workflow_action_one_returned.id
|
||||
assert workflow_action_two.id == workflow_action_two_returned.id
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_duplicate_element_with_workflow_action_in_container(data_fixture):
|
||||
page = data_fixture.create_builder_page()
|
||||
|
||||
container_element = data_fixture.create_builder_column_element(
|
||||
column_amount=2, page=page
|
||||
)
|
||||
first_child = data_fixture.create_builder_button_element(
|
||||
parent_element=container_element, page=page
|
||||
)
|
||||
second_child = data_fixture.create_builder_button_element(
|
||||
parent_element=container_element, page=page
|
||||
)
|
||||
|
||||
workflow_action1 = data_fixture.create_notification_workflow_action(
|
||||
page=page, element=first_child
|
||||
)
|
||||
workflow_action2 = data_fixture.create_notification_workflow_action(
|
||||
page=page, element=second_child
|
||||
)
|
||||
|
||||
result = ElementHandler().duplicate_element(container_element)
|
||||
|
||||
[duplicated_workflow_action1, duplicated_workflow_action2] = result[
|
||||
"workflow_actions"
|
||||
]
|
||||
assert duplicated_workflow_action1.page_id == workflow_action1.page_id
|
||||
assert duplicated_workflow_action2.page_id == workflow_action2.page_id
|
||||
|
|
|
@ -340,7 +340,7 @@ def test_duplicate_element(elements_created_mock, data_fixture):
|
|||
user = data_fixture.create_user()
|
||||
element = data_fixture.create_builder_heading_element(user=user)
|
||||
|
||||
elements_duplicated = ElementService().duplicate_element(user, element)
|
||||
elements_duplicated = ElementService().duplicate_element(user, element)["elements"]
|
||||
|
||||
assert elements_created_mock.called_with(
|
||||
elements=elements_duplicated, user=user, page=element.page
|
||||
|
|
|
@ -11,6 +11,10 @@ from baserow.contrib.builder.elements.models import (
|
|||
from baserow.contrib.builder.elements.registries import element_type_registry
|
||||
from baserow.contrib.builder.models import Builder
|
||||
from baserow.contrib.builder.pages.models import Page
|
||||
from baserow.contrib.builder.workflow_actions.handler import (
|
||||
BuilderWorkflowActionHandler,
|
||||
)
|
||||
from baserow.contrib.builder.workflow_actions.models import EventTypes
|
||||
from baserow.core.db import specific_iterator
|
||||
from baserow.core.registries import ImportExportConfig
|
||||
from baserow.core.trash.handler import TrashHandler
|
||||
|
@ -66,6 +70,14 @@ def test_builder_application_export(data_fixture):
|
|||
page=page2, data_source=datasource3
|
||||
)
|
||||
|
||||
workflow_action_1 = data_fixture.create_notification_workflow_action(
|
||||
page=page1,
|
||||
element=element1,
|
||||
event=EventTypes.CLICK,
|
||||
description="hello",
|
||||
title="there",
|
||||
)
|
||||
|
||||
serialized = BuilderApplicationType().export_serialized(
|
||||
builder, ImportExportConfig(include_permission_data=True)
|
||||
)
|
||||
|
@ -78,6 +90,17 @@ def test_builder_application_export(data_fixture):
|
|||
"order": page1.order,
|
||||
"path": page1.path,
|
||||
"path_params": page1.path_params,
|
||||
"workflow_actions": [
|
||||
{
|
||||
"id": workflow_action_1.id,
|
||||
"type": "notification",
|
||||
"element_id": element1.id,
|
||||
"event": EventTypes.CLICK.value,
|
||||
"page_id": page1.id,
|
||||
"description": "hello",
|
||||
"title": "there",
|
||||
}
|
||||
],
|
||||
"data_sources": [
|
||||
{
|
||||
"id": datasource1.id,
|
||||
|
@ -146,6 +169,7 @@ def test_builder_application_export(data_fixture):
|
|||
"order": page2.order,
|
||||
"path": page2.path,
|
||||
"path_params": page2.path_params,
|
||||
"workflow_actions": [],
|
||||
"data_sources": [
|
||||
{
|
||||
"id": datasource2.id,
|
||||
|
@ -240,6 +264,16 @@ IMPORT_REFERENCE = {
|
|||
"order": 1,
|
||||
"path": "/test",
|
||||
"path_params": {},
|
||||
"workflow_actions": [
|
||||
{
|
||||
"id": 123,
|
||||
"page_id": 999,
|
||||
"element_id": 998,
|
||||
"type": "notification",
|
||||
"description": "hello",
|
||||
"title": "there",
|
||||
}
|
||||
],
|
||||
"elements": [
|
||||
{
|
||||
"id": 998,
|
||||
|
@ -331,6 +365,7 @@ IMPORT_REFERENCE = {
|
|||
"order": 2,
|
||||
"path": "/test2",
|
||||
"path_params": {},
|
||||
"workflow_actions": [],
|
||||
"elements": [
|
||||
{
|
||||
"id": 997,
|
||||
|
@ -460,6 +495,12 @@ def test_builder_application_import(data_fixture):
|
|||
assert element_inside_container.parent_element.specific == container_element
|
||||
assert element_inside_container2.parent_element.specific == container_element
|
||||
|
||||
[workflow_action] = BuilderWorkflowActionHandler().get_workflow_actions(page1)
|
||||
|
||||
assert workflow_action.element_id == element1.id
|
||||
assert workflow_action.description == "hello"
|
||||
assert workflow_action.title == "there"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_delete_builder_application_with_published_builder(data_fixture):
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
import pytest
|
||||
|
||||
from baserow.contrib.builder.workflow_actions.registries import (
|
||||
builder_workflow_action_type_registry,
|
||||
)
|
||||
from baserow.core.workflow_actions.registries import WorkflowActionType
|
||||
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
if "workflow_action_type" in metafunc.fixturenames:
|
||||
metafunc.parametrize(
|
||||
"workflow_action_type",
|
||||
[
|
||||
pytest.param(e, id=e.type)
|
||||
for e in builder_workflow_action_type_registry.get_all()
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_export_workflow_action(data_fixture, workflow_action_type: WorkflowActionType):
|
||||
page = data_fixture.create_builder_page()
|
||||
sample_params = workflow_action_type.get_sample_params()
|
||||
workflow_action = data_fixture.create_workflow_action(
|
||||
workflow_action_type.model_class, page=page, **sample_params
|
||||
)
|
||||
|
||||
exported = workflow_action_type.export_serialized(workflow_action)
|
||||
|
||||
assert exported["id"] == workflow_action.id
|
||||
assert exported["type"] == workflow_action_type.type
|
||||
|
||||
for key, value in sample_params.items():
|
||||
assert exported[key] == value
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_import_workflow_action(data_fixture, workflow_action_type: WorkflowActionType):
|
||||
page = data_fixture.create_builder_page()
|
||||
sample_params = workflow_action_type.get_sample_params()
|
||||
|
||||
page_after_import = data_fixture.create_builder_page()
|
||||
|
||||
serialized = {"id": 9999, "type": workflow_action_type.type, "page_id": page.id}
|
||||
serialized.update(workflow_action_type.get_sample_params())
|
||||
|
||||
id_mapping = {"builder_pages": {page.id: page_after_import.id}}
|
||||
workflow_action = workflow_action_type.import_serialized(
|
||||
page, serialized, id_mapping
|
||||
)
|
||||
|
||||
assert workflow_action.id != 9999
|
||||
assert isinstance(workflow_action, workflow_action_type.model_class)
|
||||
|
||||
for key, value in sample_params.items():
|
||||
assert getattr(workflow_action, key) == value
|
|
@ -0,0 +1,124 @@
|
|||
import pytest
|
||||
|
||||
from baserow.contrib.builder.workflow_actions.handler import (
|
||||
BuilderWorkflowActionHandler,
|
||||
)
|
||||
from baserow.contrib.builder.workflow_actions.models import (
|
||||
BuilderWorkflowAction,
|
||||
EventTypes,
|
||||
)
|
||||
from baserow.contrib.builder.workflow_actions.workflow_action_types import (
|
||||
NotificationWorkflowActionType,
|
||||
OpenPageWorkflowActionType,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_workflow_action(data_fixture):
|
||||
page = data_fixture.create_builder_page()
|
||||
element = data_fixture.create_builder_button_element(page=page)
|
||||
event = EventTypes.CLICK
|
||||
workflow_action_type = NotificationWorkflowActionType()
|
||||
workflow_action = (
|
||||
BuilderWorkflowActionHandler()
|
||||
.create_workflow_action(
|
||||
workflow_action_type, page=page, element=element, event=event
|
||||
)
|
||||
.specific
|
||||
)
|
||||
|
||||
assert workflow_action is not None
|
||||
assert workflow_action.element is element
|
||||
assert BuilderWorkflowAction.objects.count() == 1
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_delete_workflow_action(data_fixture):
|
||||
page = data_fixture.create_builder_page()
|
||||
element = data_fixture.create_builder_button_element(page=page)
|
||||
event = EventTypes.CLICK
|
||||
workflow_action = data_fixture.create_notification_workflow_action(
|
||||
page=page, element=element, event=event
|
||||
)
|
||||
|
||||
assert BuilderWorkflowAction.objects.count() == 1
|
||||
|
||||
BuilderWorkflowActionHandler().delete_workflow_action(workflow_action)
|
||||
|
||||
assert BuilderWorkflowAction.objects.count() == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_update_workflow_action(data_fixture):
|
||||
page = data_fixture.create_builder_page()
|
||||
element = data_fixture.create_builder_button_element(page=page)
|
||||
event = EventTypes.CLICK
|
||||
workflow_action = data_fixture.create_notification_workflow_action(
|
||||
page=page, element=element, event=event
|
||||
)
|
||||
|
||||
element_changed = data_fixture.create_builder_button_element()
|
||||
|
||||
workflow_action = BuilderWorkflowActionHandler().update_workflow_action(
|
||||
workflow_action, element=element_changed
|
||||
)
|
||||
|
||||
workflow_action.refresh_from_db()
|
||||
assert workflow_action.element_id == element_changed.id
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_update_workflow_action_type_switching(data_fixture):
|
||||
page = data_fixture.create_builder_page()
|
||||
element = data_fixture.create_builder_button_element(page=page)
|
||||
event = EventTypes.CLICK
|
||||
workflow_action = data_fixture.create_notification_workflow_action(
|
||||
page=page, element=element, event=event
|
||||
)
|
||||
|
||||
workflow_action_changed = BuilderWorkflowActionHandler().update_workflow_action(
|
||||
workflow_action, type=OpenPageWorkflowActionType.type, url="'hello'"
|
||||
)
|
||||
|
||||
assert workflow_action_changed.get_type().type == OpenPageWorkflowActionType.type
|
||||
assert workflow_action_changed.url == "'hello'"
|
||||
assert workflow_action_changed.event == event
|
||||
assert workflow_action_changed.page_id == page.id
|
||||
assert workflow_action_changed.element_id == element.id
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_get_workflow_action(data_fixture):
|
||||
page = data_fixture.create_builder_page()
|
||||
element = data_fixture.create_builder_button_element(page=page)
|
||||
event = EventTypes.CLICK
|
||||
workflow_action = data_fixture.create_notification_workflow_action(
|
||||
page=page, element=element, event=event
|
||||
)
|
||||
|
||||
workflow_action_fetched = BuilderWorkflowActionHandler().get_workflow_action(
|
||||
workflow_action.id
|
||||
)
|
||||
|
||||
assert workflow_action_fetched.id == workflow_action.id
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_get_workflow_actions(data_fixture):
|
||||
page = data_fixture.create_builder_page()
|
||||
element = data_fixture.create_builder_button_element(page=page)
|
||||
event = EventTypes.CLICK
|
||||
workflow_action_one = data_fixture.create_notification_workflow_action(
|
||||
page=page, element=element, event=event
|
||||
)
|
||||
workflow_action_two = data_fixture.create_notification_workflow_action(
|
||||
page=page, element=element, event=event
|
||||
)
|
||||
|
||||
[
|
||||
workflow_action_one_fetched,
|
||||
workflow_action_two_fetched,
|
||||
] = BuilderWorkflowActionHandler().get_workflow_actions(page)
|
||||
|
||||
assert workflow_action_one_fetched.id == workflow_action_one.id
|
||||
assert workflow_action_two_fetched.id == workflow_action_two.id
|
|
@ -0,0 +1,186 @@
|
|||
import pytest
|
||||
|
||||
from baserow.contrib.builder.workflow_actions.models import (
|
||||
BuilderWorkflowAction,
|
||||
EventTypes,
|
||||
)
|
||||
from baserow.contrib.builder.workflow_actions.service import (
|
||||
BuilderWorkflowActionService,
|
||||
)
|
||||
from baserow.contrib.builder.workflow_actions.workflow_action_types import (
|
||||
NotificationWorkflowActionType,
|
||||
)
|
||||
from baserow.core.exceptions import UserNotInWorkspace
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_workflow_action(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
page = data_fixture.create_builder_page(user=user)
|
||||
element = data_fixture.create_builder_button_element(page=page)
|
||||
event = EventTypes.CLICK
|
||||
workflow_action_type = NotificationWorkflowActionType()
|
||||
workflow_action = (
|
||||
BuilderWorkflowActionService()
|
||||
.create_workflow_action(
|
||||
user, workflow_action_type, page=page, element=element, event=event
|
||||
)
|
||||
.specific
|
||||
)
|
||||
|
||||
assert workflow_action is not None
|
||||
assert workflow_action.element is element
|
||||
assert BuilderWorkflowAction.objects.count() == 1
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_workflow_action_no_permissions(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
page = data_fixture.create_builder_page()
|
||||
element = data_fixture.create_builder_button_element(page=page)
|
||||
event = EventTypes.CLICK
|
||||
workflow_action_type = NotificationWorkflowActionType()
|
||||
|
||||
with pytest.raises(UserNotInWorkspace):
|
||||
BuilderWorkflowActionService().create_workflow_action(
|
||||
user, workflow_action_type, page=page, element=element, event=event
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_delete_workflow_action(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
page = data_fixture.create_builder_page(user=user)
|
||||
element = data_fixture.create_builder_button_element(page=page)
|
||||
event = EventTypes.CLICK
|
||||
workflow_action = data_fixture.create_notification_workflow_action(
|
||||
page=page, element=element, event=event
|
||||
)
|
||||
|
||||
assert BuilderWorkflowAction.objects.count() == 1
|
||||
|
||||
BuilderWorkflowActionService().delete_workflow_action(user, workflow_action)
|
||||
|
||||
assert BuilderWorkflowAction.objects.count() == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_delete_workflow_action_no_permissions(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
page = data_fixture.create_builder_page()
|
||||
element = data_fixture.create_builder_button_element(page=page)
|
||||
event = EventTypes.CLICK
|
||||
workflow_action = data_fixture.create_notification_workflow_action(
|
||||
page=page, element=element, event=event
|
||||
)
|
||||
|
||||
with pytest.raises(UserNotInWorkspace):
|
||||
BuilderWorkflowActionService().delete_workflow_action(user, workflow_action)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_update_workflow_action(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
page = data_fixture.create_builder_page(user=user)
|
||||
element = data_fixture.create_builder_button_element(page=page)
|
||||
event = EventTypes.CLICK
|
||||
workflow_action = data_fixture.create_notification_workflow_action(
|
||||
page=page, element=element, event=event
|
||||
)
|
||||
|
||||
element_changed = data_fixture.create_builder_button_element()
|
||||
|
||||
workflow_action = BuilderWorkflowActionService().update_workflow_action(
|
||||
user, workflow_action, element=element_changed
|
||||
)
|
||||
|
||||
workflow_action.refresh_from_db()
|
||||
assert workflow_action.element_id == element_changed.id
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_update_workflow_action_no_permissions(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
page = data_fixture.create_builder_page()
|
||||
element = data_fixture.create_builder_button_element(page=page)
|
||||
event = EventTypes.CLICK
|
||||
workflow_action = data_fixture.create_notification_workflow_action(
|
||||
page=page, element=element, event=event
|
||||
)
|
||||
|
||||
element_changed = data_fixture.create_builder_button_element()
|
||||
|
||||
with pytest.raises(UserNotInWorkspace):
|
||||
BuilderWorkflowActionService().update_workflow_action(
|
||||
user, workflow_action, element=element_changed
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_get_workflow_action(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
page = data_fixture.create_builder_page(user=user)
|
||||
element = data_fixture.create_builder_button_element(page=page)
|
||||
event = EventTypes.CLICK
|
||||
workflow_action = data_fixture.create_notification_workflow_action(
|
||||
page=page, element=element, event=event
|
||||
)
|
||||
|
||||
workflow_action_fetched = BuilderWorkflowActionService().get_workflow_action(
|
||||
user, workflow_action.id
|
||||
)
|
||||
|
||||
assert workflow_action_fetched.id == workflow_action.id
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_get_workflow_action_no_permissions(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
page = data_fixture.create_builder_page()
|
||||
element = data_fixture.create_builder_button_element(page=page)
|
||||
event = EventTypes.CLICK
|
||||
workflow_action = data_fixture.create_notification_workflow_action(
|
||||
page=page, element=element, event=event
|
||||
)
|
||||
|
||||
with pytest.raises(UserNotInWorkspace):
|
||||
BuilderWorkflowActionService().get_workflow_action(user, workflow_action.id)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_get_workflow_actions(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
page = data_fixture.create_builder_page(user=user)
|
||||
element = data_fixture.create_builder_button_element(page=page)
|
||||
event = EventTypes.CLICK
|
||||
workflow_action_one = data_fixture.create_notification_workflow_action(
|
||||
page=page, element=element, event=event
|
||||
)
|
||||
workflow_action_two = data_fixture.create_notification_workflow_action(
|
||||
page=page, element=element, event=event
|
||||
)
|
||||
|
||||
[
|
||||
workflow_action_one_fetched,
|
||||
workflow_action_two_fetched,
|
||||
] = BuilderWorkflowActionService().get_workflow_actions(user, page)
|
||||
|
||||
assert workflow_action_one_fetched.id == workflow_action_one.id
|
||||
assert workflow_action_two_fetched.id == workflow_action_two.id
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_get_workflow_actions_no_permissions(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
page = data_fixture.create_builder_page()
|
||||
element = data_fixture.create_builder_button_element(page=page)
|
||||
event = EventTypes.CLICK
|
||||
workflow_action_one = data_fixture.create_notification_workflow_action(
|
||||
page=page, element=element, event=event
|
||||
)
|
||||
workflow_action_two = data_fixture.create_notification_workflow_action(
|
||||
page=page, element=element, event=event
|
||||
)
|
||||
|
||||
with pytest.raises(UserNotInWorkspace):
|
||||
BuilderWorkflowActionService().get_workflow_actions(user, page)
|
|
@ -48,6 +48,13 @@ from baserow.contrib.builder.pages.operations import (
|
|||
UpdatePageOperationType,
|
||||
)
|
||||
from baserow.contrib.builder.theme.operations import UpdateThemeOperationType
|
||||
from baserow.contrib.builder.workflow_actions.operations import (
|
||||
CreateBuilderWorkflowActionOperationType,
|
||||
DeleteBuilderWorkflowActionOperationType,
|
||||
ListBuilderWorkflowActionsPageOperationType,
|
||||
ReadBuilderWorkflowActionOperationType,
|
||||
UpdateBuilderWorkflowActionOperationType,
|
||||
)
|
||||
from baserow.contrib.database.airtable.operations import (
|
||||
RunAirtableImportJobOperationType,
|
||||
)
|
||||
|
@ -278,6 +285,8 @@ default_roles[VIEWER_ROLE_UID].extend(
|
|||
ReadViewSortOperationType,
|
||||
ListViewGroupByOperationType,
|
||||
ReadViewGroupByOperationType,
|
||||
ListBuilderWorkflowActionsPageOperationType,
|
||||
ReadBuilderWorkflowActionOperationType,
|
||||
ReadViewFilterGroupOperationType,
|
||||
]
|
||||
)
|
||||
|
@ -305,6 +314,7 @@ default_roles[EDITOR_ROLE_UID].extend(
|
|||
RestoreDatabaseRowOperationType,
|
||||
ListTeamSubjectsOperationType,
|
||||
ReadTeamSubjectOperationType,
|
||||
UpdateBuilderWorkflowActionOperationType,
|
||||
]
|
||||
)
|
||||
default_roles[BUILDER_ROLE_UID].extend(
|
||||
|
@ -397,6 +407,8 @@ default_roles[BUILDER_ROLE_UID].extend(
|
|||
ReadDataSourceOperationType,
|
||||
UpdateDataSourceOperationType,
|
||||
DispatchDataSourceOperationType,
|
||||
DeleteBuilderWorkflowActionOperationType,
|
||||
CreateBuilderWorkflowActionOperationType,
|
||||
]
|
||||
)
|
||||
default_roles[ADMIN_ROLE_UID].extend(
|
||||
|
|
|
@ -1,34 +1,118 @@
|
|||
<template>
|
||||
<Expandable toggle-on-click>
|
||||
<template #header="{ toggle, expanded }">
|
||||
<template #header="{ expanded }">
|
||||
<div class="event__header">
|
||||
<div class="event__label">{{ event.label }}</div>
|
||||
<a class="event__toggle" @click.stop="toggle">
|
||||
<div class="event__header-left">
|
||||
<div class="event__label">
|
||||
{{ event.label }}
|
||||
</div>
|
||||
<div
|
||||
v-if="workflowActions.length"
|
||||
class="margin-left-1 event__amount-actions"
|
||||
>
|
||||
{{ workflowActions.length }}
|
||||
</div>
|
||||
</div>
|
||||
<a class="event__toggle">
|
||||
<i :class="getIcon(expanded)"></i>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<template #default>
|
||||
This is where you will be able to define your actions in the future :)
|
||||
<WorkflowAction
|
||||
v-for="workflowAction in workflowActions"
|
||||
:key="workflowAction.id"
|
||||
class="margin-top-2 event__workflow-action"
|
||||
:available-workflow-action-types="availableWorkflowActionTypes"
|
||||
:workflow-action="workflowAction"
|
||||
@delete="deleteWorkflowAction(workflowAction)"
|
||||
@update="updateWorkflowAction(workflowAction, $event)"
|
||||
/>
|
||||
<Button
|
||||
size="tiny"
|
||||
type="link"
|
||||
prepend-icon="baserow-icon-plus"
|
||||
:loading="addingAction"
|
||||
class="margin-top-1"
|
||||
@click="addWorkflowAction"
|
||||
>
|
||||
{{ $t('event.addAction') }}
|
||||
</Button>
|
||||
</template>
|
||||
</Expandable>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Event } from '@baserow/modules/builder/eventTypes'
|
||||
import WorkflowAction from '@baserow/modules/core/components/workflowActions/WorkflowAction.vue'
|
||||
import { NotificationWorkflowActionType } from '@baserow/modules/builder/workflowActionTypes'
|
||||
import { mapActions } from 'vuex'
|
||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||
|
||||
const DEFAULT_WORKFLOW_ACTION_TYPE = NotificationWorkflowActionType.getType()
|
||||
|
||||
export default {
|
||||
name: 'Event',
|
||||
components: { WorkflowAction },
|
||||
inject: ['page'],
|
||||
props: {
|
||||
event: {
|
||||
type: Event,
|
||||
required: true,
|
||||
},
|
||||
element: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
workflowActions: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
availableWorkflowActionTypes: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
addingAction: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
actionCreateWorkflowAction: 'workflowAction/create',
|
||||
actionDeleteWorkflowAction: 'workflowAction/delete',
|
||||
}),
|
||||
getIcon(expanded) {
|
||||
return expanded ? 'iconoir-nav-arrow-down' : 'iconoir-nav-arrow-right'
|
||||
},
|
||||
async addWorkflowAction() {
|
||||
this.addingAction = true
|
||||
try {
|
||||
await this.actionCreateWorkflowAction({
|
||||
page: this.page,
|
||||
workflowActionType: DEFAULT_WORKFLOW_ACTION_TYPE,
|
||||
eventType: this.event.getType(),
|
||||
configuration: {
|
||||
element_id: this.element.id,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
notifyIf(error)
|
||||
}
|
||||
this.addingAction = false
|
||||
},
|
||||
async deleteWorkflowAction(workflowAction) {
|
||||
try {
|
||||
await this.actionDeleteWorkflowAction({
|
||||
page: this.page,
|
||||
workflowAction,
|
||||
})
|
||||
} catch (error) {
|
||||
notifyIf(error)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -3,7 +3,10 @@
|
|||
<Event
|
||||
v-for="event in elementType.getEvents()"
|
||||
:key="event.getType()"
|
||||
:element="element"
|
||||
:event="event"
|
||||
:available-workflow-action-types="availableWorkflowActionTypes"
|
||||
:workflow-actions="getWorkflowActionsForEvent(event)"
|
||||
class="margin-bottom-2"
|
||||
></Event>
|
||||
</div>
|
||||
|
@ -17,5 +20,23 @@ export default {
|
|||
name: 'EventSidePanel',
|
||||
components: { Event },
|
||||
mixins: [elementSidePanel],
|
||||
computed: {
|
||||
availableWorkflowActionTypes() {
|
||||
return Object.values(this.$registry.getAll('workflowAction'))
|
||||
},
|
||||
workflowActions() {
|
||||
return this.$store.getters['workflowAction/getElementWorkflowActions'](
|
||||
this.page,
|
||||
this.element.id
|
||||
)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getWorkflowActionsForEvent(event) {
|
||||
return this.workflowActions.filter(
|
||||
(workflowAction) => workflowAction.event === event.getType()
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<template>
|
||||
<form @submit.prevent="submit">
|
||||
<ApplicationBuilderFormulaInputGroup
|
||||
v-model="values.title"
|
||||
:placeholder="$t('notificationWorkflowActionForm.titlePlaceholder')"
|
||||
:data-providers-allowed="dataProvidersAllowed"
|
||||
:label="$t('notificationWorkflowActionForm.titleLabel')"
|
||||
/>
|
||||
<ApplicationBuilderFormulaInputGroup
|
||||
v-model="values.description"
|
||||
:placeholder="$t('notificationWorkflowActionForm.descriptionPlaceholder')"
|
||||
:data-providers-allowed="dataProvidersAllowed"
|
||||
:label="$t('notificationWorkflowActionForm.descriptionLabel')"
|
||||
/>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import workflowActionForm from '@baserow/modules/builder/mixins/workflowActionForm'
|
||||
import ApplicationBuilderFormulaInputGroup from '@baserow/modules/builder/components/ApplicationBuilderFormulaInputGroup.vue'
|
||||
|
||||
export default {
|
||||
name: 'NotificationWorkflowActionForm',
|
||||
components: { ApplicationBuilderFormulaInputGroup },
|
||||
mixins: [workflowActionForm],
|
||||
data() {
|
||||
return {
|
||||
values: {
|
||||
title: '',
|
||||
description: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,28 @@
|
|||
<template>
|
||||
<form @submit.prevent="submit">
|
||||
<ApplicationBuilderFormulaInputGroup
|
||||
v-model="values.url"
|
||||
:placeholder="$t('openPageWorkflowActionForm.urlPlaceholder')"
|
||||
:data-providers-allowed="dataProvidersAllowed"
|
||||
:label="$t('openPageWorkflowActionForm.urlLabel')"
|
||||
/>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import workflowActionForm from '@baserow/modules/builder/mixins/workflowActionForm'
|
||||
import ApplicationBuilderFormulaInputGroup from '@baserow/modules/builder/components/ApplicationBuilderFormulaInputGroup'
|
||||
|
||||
export default {
|
||||
name: 'OpenPageWorkflowActionForm',
|
||||
components: { ApplicationBuilderFormulaInputGroup },
|
||||
mixins: [workflowActionForm],
|
||||
data() {
|
||||
return {
|
||||
values: {
|
||||
url: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -318,5 +318,22 @@
|
|||
"currentRecordDataProviderType": {
|
||||
"index": "index",
|
||||
"firstPartName": "Data source: {name}"
|
||||
},
|
||||
"workflowActionTypes": {
|
||||
"notificationLabel": "Show Notification",
|
||||
"openPageLabel": "Open Page"
|
||||
},
|
||||
"notificationWorkflowActionForm": {
|
||||
"titleLabel": "Title",
|
||||
"titlePlaceholder": "Enter text...",
|
||||
"descriptionLabel": "Description",
|
||||
"descriptionPlaceholder": "Enter text..."
|
||||
},
|
||||
"openPageWorkflowActionForm": {
|
||||
"urlLabel": "URL",
|
||||
"urlPlaceholder": "Enter text..."
|
||||
},
|
||||
"event": {
|
||||
"addAction": "Add action"
|
||||
}
|
||||
}
|
||||
|
|
11
web-frontend/modules/builder/mixins/workflowActionForm.js
Normal file
11
web-frontend/modules/builder/mixins/workflowActionForm.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import form from '@baserow/modules/core/mixins/form'
|
||||
import { DATA_PROVIDERS_ALLOWED_ELEMENTS } from '@baserow/modules/builder/enums'
|
||||
|
||||
export default {
|
||||
mixins: [form],
|
||||
computed: {
|
||||
dataProvidersAllowed() {
|
||||
return DATA_PROVIDERS_ALLOWED_ELEMENTS
|
||||
},
|
||||
},
|
||||
}
|
|
@ -55,6 +55,7 @@ export default {
|
|||
page,
|
||||
}),
|
||||
store.dispatch('element/fetch', { page }),
|
||||
store.dispatch('workflowAction/fetch', { page }),
|
||||
])
|
||||
|
||||
await DataProviderType.initAll($registry.getAll('builderDataProvider'), {
|
||||
|
|
|
@ -19,6 +19,7 @@ import dataSourceStore from '@baserow/modules/builder/store/dataSource'
|
|||
import pageParameterStore from '@baserow/modules/builder/store/pageParameter'
|
||||
import dataSourceContentStore from '@baserow/modules/builder/store/dataSourceContent'
|
||||
import themeStore from '@baserow/modules/builder/store/theme'
|
||||
import workflowActionStore from '@baserow/modules/builder/store/workflowAction'
|
||||
|
||||
import { registerRealtimeEvents } from '@baserow/modules/builder/realtime'
|
||||
import {
|
||||
|
@ -73,6 +74,10 @@ import {
|
|||
} from '@baserow/modules/builder/dataProviderTypes'
|
||||
|
||||
import { MainThemeConfigBlock } from '@baserow/modules/builder/themeConfigBlockTypes'
|
||||
import {
|
||||
NotificationWorkflowActionType,
|
||||
OpenPageWorkflowActionType,
|
||||
} from '@baserow/modules/builder/workflowActionTypes'
|
||||
|
||||
export default (context) => {
|
||||
const { store, app, isDev } = context
|
||||
|
@ -99,6 +104,7 @@ export default (context) => {
|
|||
store.registerModule('pageParameter', pageParameterStore)
|
||||
store.registerModule('dataSourceContent', dataSourceContentStore)
|
||||
store.registerModule('theme', themeStore)
|
||||
store.registerModule('workflowAction', workflowActionStore)
|
||||
|
||||
app.$registry.registerNamespace('builderSettings')
|
||||
app.$registry.registerNamespace('element')
|
||||
|
@ -189,4 +195,13 @@ export default (context) => {
|
|||
new PageParameterDataProviderType(context)
|
||||
)
|
||||
app.$registry.register('themeConfigBlock', new MainThemeConfigBlock(context))
|
||||
|
||||
app.$registry.register(
|
||||
'workflowAction',
|
||||
new NotificationWorkflowActionType(context)
|
||||
)
|
||||
app.$registry.register(
|
||||
'workflowAction',
|
||||
new OpenPageWorkflowActionType(context)
|
||||
)
|
||||
}
|
||||
|
|
25
web-frontend/modules/builder/services/workflowAction.js
Normal file
25
web-frontend/modules/builder/services/workflowAction.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
export default (client) => {
|
||||
return {
|
||||
create(pageId, workflowActionType, eventType, configuration = null) {
|
||||
const payload = {
|
||||
type: workflowActionType,
|
||||
event: eventType,
|
||||
...configuration,
|
||||
}
|
||||
|
||||
return client.post(`builder/page/${pageId}/workflow_actions/`, payload)
|
||||
},
|
||||
fetchAll(pageId) {
|
||||
return client.get(`builder/page/${pageId}/workflow_actions/`)
|
||||
},
|
||||
delete(workflowActionId) {
|
||||
return client.delete(`builder/workflow_action/${workflowActionId}/`)
|
||||
},
|
||||
update(workflowActionId, values) {
|
||||
return client.patch(
|
||||
`builder/workflow_action/${workflowActionId}/`,
|
||||
values
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
|
@ -272,17 +272,24 @@ const actions = {
|
|||
}
|
||||
},
|
||||
async duplicate({ dispatch }, { page, elementId }) {
|
||||
const { data: elementsCreated } = await ElementService(
|
||||
this.$client
|
||||
).duplicate(elementId)
|
||||
const {
|
||||
data: { elements, workflow_actions: workflowActions },
|
||||
} = await ElementService(this.$client).duplicate(elementId)
|
||||
|
||||
await Promise.all(
|
||||
elementsCreated.map((element) =>
|
||||
dispatch('forceCreate', { page, element })
|
||||
const elementPromises = elements.map((element) =>
|
||||
dispatch('forceCreate', { page, element })
|
||||
)
|
||||
const workflowActionPromises = workflowActions.map((workflowAction) =>
|
||||
dispatch(
|
||||
'workflowAction/forceCreate',
|
||||
{ page, workflowAction },
|
||||
{ root: true }
|
||||
)
|
||||
)
|
||||
|
||||
return elementsCreated
|
||||
await Promise.all(elementPromises.concat(workflowActionPromises))
|
||||
|
||||
return elements
|
||||
},
|
||||
emitElementEvent({ getters }, { event, page, ...rest }) {
|
||||
const elements = getters.getElements(page)
|
||||
|
|
|
@ -12,6 +12,7 @@ export function populatePage(page) {
|
|||
|
||||
page.dataSources = []
|
||||
page.elements = []
|
||||
page.workflowActions = []
|
||||
|
||||
return page
|
||||
}
|
||||
|
|
172
web-frontend/modules/builder/store/workflowAction.js
Normal file
172
web-frontend/modules/builder/store/workflowAction.js
Normal file
|
@ -0,0 +1,172 @@
|
|||
import WorkflowActionService from '@baserow/modules/builder/services/workflowAction'
|
||||
|
||||
const updateContext = {
|
||||
updateTimeout: null,
|
||||
promiseResolve: null,
|
||||
lastUpdatedValues: null,
|
||||
}
|
||||
|
||||
const state = {}
|
||||
|
||||
const mutations = {
|
||||
ADD_ITEM(state, { page, workflowAction }) {
|
||||
page.workflowActions.push(workflowAction)
|
||||
},
|
||||
SET_ITEMS(state, { page, workflowActions }) {
|
||||
page.workflowActions = workflowActions
|
||||
},
|
||||
DELETE_ITEM(state, { page, workflowActionId }) {
|
||||
const index = page.workflowActions.findIndex(
|
||||
(workflowAction) => workflowAction.id === workflowActionId
|
||||
)
|
||||
if (index > -1) {
|
||||
page.workflowActions.splice(index, 1)
|
||||
}
|
||||
},
|
||||
UPDATE_ITEM(state, { page, workflowAction: workflowActionToUpdate, values }) {
|
||||
page.workflowActions.forEach((workflowAction) => {
|
||||
if (workflowAction.id === workflowActionToUpdate.id) {
|
||||
Object.assign(workflowAction, values)
|
||||
}
|
||||
})
|
||||
},
|
||||
SET_ITEM(state, { page, workflowAction: workflowActionToSet, values }) {
|
||||
page.workflowActions = page.workflowActions.map((workflowAction) =>
|
||||
workflowAction.id === workflowActionToSet.id ? values : workflowAction
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const actions = {
|
||||
forceCreate({ commit }, { page, workflowAction }) {
|
||||
commit('ADD_ITEM', { page, workflowAction })
|
||||
},
|
||||
forceDelete({ commit }, { page, workflowActionId }) {
|
||||
commit('DELETE_ITEM', { page, workflowActionId })
|
||||
},
|
||||
forceUpdate({ commit }, { page, workflowAction, values }) {
|
||||
commit('UPDATE_ITEM', { page, workflowAction, values })
|
||||
},
|
||||
forceSet({ commit }, { page, workflowAction, values }) {
|
||||
commit('SET_ITEM', { page, workflowAction, values })
|
||||
},
|
||||
async create(
|
||||
{ dispatch },
|
||||
{ page, workflowActionType, eventType, configuration = null }
|
||||
) {
|
||||
const { data: workflowAction } = await WorkflowActionService(
|
||||
this.$client
|
||||
).create(page.id, workflowActionType, eventType, configuration)
|
||||
|
||||
await dispatch('forceCreate', { page, workflowAction })
|
||||
|
||||
return workflowAction
|
||||
},
|
||||
async fetch({ commit }, { page }) {
|
||||
const { data: workflowActions } = await WorkflowActionService(
|
||||
this.$client
|
||||
).fetchAll(page.id)
|
||||
|
||||
commit('SET_ITEMS', { page, workflowActions })
|
||||
},
|
||||
async delete({ dispatch }, { page, workflowAction }) {
|
||||
dispatch('forceDelete', { page, workflowActionId: workflowAction.id })
|
||||
|
||||
try {
|
||||
await WorkflowActionService(this.$client).delete(workflowAction.id)
|
||||
} catch (error) {
|
||||
await dispatch('forceCreate', { page, workflowAction })
|
||||
throw error
|
||||
}
|
||||
},
|
||||
async update({ dispatch }, { page, workflowAction, values }) {
|
||||
const oldValues = {}
|
||||
const newValues = {}
|
||||
Object.keys(values).forEach((name) => {
|
||||
if (Object.prototype.hasOwnProperty.call(workflowAction, name)) {
|
||||
oldValues[name] = workflowAction[name]
|
||||
newValues[name] = values[name]
|
||||
}
|
||||
})
|
||||
|
||||
await dispatch('forceUpdate', { page, workflowAction, values: newValues })
|
||||
|
||||
try {
|
||||
const { data } = await WorkflowActionService(this.$client).update(
|
||||
workflowAction.id,
|
||||
values
|
||||
)
|
||||
await dispatch('forceSet', { page, workflowAction, values: data })
|
||||
} catch (error) {
|
||||
await dispatch('forceUpdate', { page, workflowAction, values: oldValues })
|
||||
throw error
|
||||
}
|
||||
},
|
||||
async updateDebounced({ dispatch }, { page, workflowAction, values }) {
|
||||
const oldValues = {}
|
||||
const newValues = {}
|
||||
Object.keys(values).forEach((name) => {
|
||||
if (Object.prototype.hasOwnProperty.call(workflowAction, name)) {
|
||||
oldValues[name] = workflowAction[name]
|
||||
newValues[name] = values[name]
|
||||
}
|
||||
})
|
||||
|
||||
await dispatch('forceUpdate', { page, workflowAction, values: newValues })
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const fire = async () => {
|
||||
try {
|
||||
const { data } = await WorkflowActionService(this.$client).update(
|
||||
workflowAction.id,
|
||||
values
|
||||
)
|
||||
await dispatch('forceSet', {
|
||||
page,
|
||||
workflowAction,
|
||||
values: data,
|
||||
})
|
||||
resolve()
|
||||
} catch (error) {
|
||||
await dispatch('forceUpdate', {
|
||||
page,
|
||||
workflowAction,
|
||||
values: updateContext.lastUpdatedValues,
|
||||
})
|
||||
reject(error)
|
||||
}
|
||||
updateContext.lastUpdatedValues = null
|
||||
}
|
||||
|
||||
if (updateContext.promiseResolve) {
|
||||
updateContext.promiseResolve()
|
||||
updateContext.promiseResolve = null
|
||||
}
|
||||
|
||||
clearTimeout(updateContext.updateTimeout)
|
||||
|
||||
if (!updateContext.lastUpdatedValues) {
|
||||
updateContext.lastUpdatedValues = oldValues
|
||||
}
|
||||
|
||||
updateContext.updateTimeout = setTimeout(fire, 500)
|
||||
updateContext.promiseResolve = resolve
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
const getters = {
|
||||
getElementWorkflowActions: (state) => (page, elementId) => {
|
||||
return page.workflowActions.filter(
|
||||
(workflowAction) => workflowAction.element_id === elementId
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
mutations,
|
||||
actions,
|
||||
getters,
|
||||
}
|
31
web-frontend/modules/builder/workflowActionTypes.js
Normal file
31
web-frontend/modules/builder/workflowActionTypes.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { WorkflowActionType } from '@baserow/modules/core/workflowActionTypes'
|
||||
import NotificationWorkflowActionForm from '@baserow/modules/builder/components/workflowAction/NotificationWorkflowActionForm.vue'
|
||||
import OpenPageWorkflowActionForm from '@baserow/modules/builder/components/workflowAction/OpenPageWorkflowActionForm'
|
||||
|
||||
export class NotificationWorkflowActionType extends WorkflowActionType {
|
||||
static getType() {
|
||||
return 'notification'
|
||||
}
|
||||
|
||||
get form() {
|
||||
return NotificationWorkflowActionForm
|
||||
}
|
||||
|
||||
get label() {
|
||||
return this.app.i18n.t('workflowActionTypes.notificationLabel')
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenPageWorkflowActionType extends WorkflowActionType {
|
||||
static getType() {
|
||||
return 'open_page'
|
||||
}
|
||||
|
||||
get form() {
|
||||
return OpenPageWorkflowActionForm
|
||||
}
|
||||
|
||||
get label() {
|
||||
return this.app.i18n.t('workflowActionTypes.openPageLabel')
|
||||
}
|
||||
}
|
|
@ -140,3 +140,4 @@
|
|||
@import 'data_explorer/node';
|
||||
@import 'data_explorer/root_node';
|
||||
@import 'data_explorer/data_explorer';
|
||||
@import 'anchor';
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
.anchor {
|
||||
color: $color-primary-900;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
|
@ -23,3 +23,4 @@
|
|||
@import 'preview_navigation_bar';
|
||||
@import 'add_element_zone';
|
||||
@import 'event';
|
||||
@import 'workflow_action_selector';
|
||||
|
|
|
@ -3,11 +3,26 @@
|
|||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.event__header-left {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.event__label {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.event__amount-actions {
|
||||
background-color: $color-neutral-100;
|
||||
padding: 1px 7px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.event__toggle {
|
||||
color: $color-neutral-900;
|
||||
}
|
||||
|
||||
.event__workflow-action:not(:first-child) {
|
||||
border-top: 1px solid $color-neutral-200;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
.workflow-action-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.workflow-action-selector__options {
|
||||
flex-basis: 90%;
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
<template>
|
||||
<div>
|
||||
<WorkflowActionSelector
|
||||
:available-workflow-action-types="availableWorkflowActionTypes"
|
||||
:workflow-action="workflowAction"
|
||||
@change="updateWorkflowAction({ type: $event })"
|
||||
@delete="$emit('delete')"
|
||||
/>
|
||||
<component
|
||||
:is="workflowActionType.form"
|
||||
ref="actionForm"
|
||||
:default-values="workflowAction"
|
||||
class="margin-top-2"
|
||||
@values-changed="updateWorkflowAction($event)"
|
||||
></component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WorkflowActionSelector from '@baserow/modules/core/components/workflowActions/WorkflowActionSelector.vue'
|
||||
import _ from 'lodash'
|
||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||
import { mapActions } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'WorkflowAction',
|
||||
components: { WorkflowActionSelector },
|
||||
inject: ['page'],
|
||||
props: {
|
||||
availableWorkflowActionTypes: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
workflowAction: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
workflowActionType() {
|
||||
return this.availableWorkflowActionTypes.find(
|
||||
(workflowActionType) =>
|
||||
workflowActionType.getType() === this.workflowAction.type
|
||||
)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
actionUpdateWorkflowAction: 'workflowAction/updateDebounced',
|
||||
}),
|
||||
async updateWorkflowAction(values) {
|
||||
if (!this.$refs.actionForm.isFormValid()) {
|
||||
return
|
||||
}
|
||||
|
||||
// In this case there weren't any actual changes
|
||||
if (_.isMatch(this.workflowAction, values)) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await this.actionUpdateWorkflowAction({
|
||||
page: this.page,
|
||||
workflowAction: this.workflowAction,
|
||||
values,
|
||||
})
|
||||
} catch (error) {
|
||||
this.$refs.actionForm.reset()
|
||||
notifyIf(error)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,47 @@
|
|||
<template>
|
||||
<div class="workflow-action-selector">
|
||||
<Dropdown
|
||||
class="workflow-action-selector__options"
|
||||
:value="workflowActionType.getType()"
|
||||
:show-search="false"
|
||||
@change="$emit('change', $event)"
|
||||
>
|
||||
<DropdownItem
|
||||
v-for="availableWorkflowActionType in availableWorkflowActionTypes"
|
||||
:key="availableWorkflowActionType.getType()"
|
||||
:name="availableWorkflowActionType.label"
|
||||
:value="availableWorkflowActionType.getType()"
|
||||
></DropdownItem>
|
||||
</Dropdown>
|
||||
<div class="margin-left-2">
|
||||
<a class="anchor" @click="$emit('delete')">
|
||||
<i class="iconoir-trash"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'WorkflowActionSelector',
|
||||
props: {
|
||||
availableWorkflowActionTypes: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
workflowAction: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
workflowActionType() {
|
||||
return this.availableWorkflowActionTypes.find(
|
||||
(workflowActionType) =>
|
||||
workflowActionType.getType() === this.workflowAction.type
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -99,6 +99,7 @@ export default (context, inject) => {
|
|||
registry.registerNamespace('membersPagePlugins')
|
||||
registry.registerNamespace('runtimeFormulaFunction')
|
||||
registry.registerNamespace('notification')
|
||||
registry.registerNamespace('workflowAction')
|
||||
registry.register('settings', new AccountSettingsType(context))
|
||||
registry.register('settings', new PasswordSettingsType(context))
|
||||
registry.register('settings', new EmailNotificationsSettingsType(context))
|
||||
|
|
11
web-frontend/modules/core/workflowActionTypes.js
Normal file
11
web-frontend/modules/core/workflowActionTypes.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { Registerable } from '@baserow/modules/core/registry'
|
||||
|
||||
export class WorkflowActionType extends Registerable {
|
||||
get form() {
|
||||
return null
|
||||
}
|
||||
|
||||
get label() {
|
||||
return null
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue