diff --git a/BUILD b/BUILD index a30bd37195..73311d976a 100644 --- a/BUILD +++ b/BUILD @@ -67,6 +67,15 @@ java_library( ], ) +java_library( + name = "feign", + exports = [ + "@maven//:io_github_openfeign_feign_core", + "@maven//:io_github_openfeign_feign_jackson", + "@maven//:io_github_openfeign_feign_okhttp", + ], +) + java_plugin( name = "lombok_plugin", generates_api = True, diff --git a/VERSION b/VERSION index a758a09aae..5c4503b704 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.48.0 +0.49.0 diff --git a/WORKSPACE b/WORKSPACE index 8a8a6dce0b..22130018c4 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -10,9 +10,9 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") git_repository( name = "com_github_airyhq_bazel_tools", - commit = "6ea38fe01069589ad57e66ae43c6d320fd18e3e5", + commit = "f33ecc4e2e3349f7f7634bb8491b2e431dd41fa6", remote = "https://github.com/airyhq/bazel-tools.git", - shallow_since = "1660208058 +0200", + shallow_since = "1660918023 +0200", ) load("@com_github_airyhq_bazel_tools//:repositories.bzl", "airy_bazel_tools_dependencies", "airy_jvm_deps") diff --git a/backend/api/admin/BUILD b/backend/components/admin/BUILD similarity index 100% rename from backend/api/admin/BUILD rename to backend/components/admin/BUILD diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/ChannelsController.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/ChannelsController.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/ChannelsController.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/ChannelsController.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/Stores.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/Stores.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/Stores.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/Stores.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/TagsController.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/TagsController.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/TagsController.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/TagsController.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/TemplatesController.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/TemplatesController.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/TemplatesController.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/TemplatesController.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/TimestampExtractor.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/TimestampExtractor.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/TimestampExtractor.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/TimestampExtractor.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/UsersController.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/UsersController.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/UsersController.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/UsersController.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/WebhooksController.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/WebhooksController.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/WebhooksController.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/WebhooksController.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/dto/LogWithTimestamp.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/dto/LogWithTimestamp.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/dto/LogWithTimestamp.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/dto/LogWithTimestamp.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/ChannelsResponsePayload.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/payload/ChannelsResponsePayload.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/payload/ChannelsResponsePayload.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/payload/ChannelsResponsePayload.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/CreateTagRequestPayload.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/payload/CreateTagRequestPayload.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/payload/CreateTagRequestPayload.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/payload/CreateTagRequestPayload.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/CreateTemplateRequestPayload.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/payload/CreateTemplateRequestPayload.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/payload/CreateTemplateRequestPayload.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/payload/CreateTemplateRequestPayload.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/DeleteTagRequestPayload.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/payload/DeleteTagRequestPayload.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/payload/DeleteTagRequestPayload.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/payload/DeleteTagRequestPayload.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/DeleteTemplateRequestPayload.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/payload/DeleteTemplateRequestPayload.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/payload/DeleteTemplateRequestPayload.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/payload/DeleteTemplateRequestPayload.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/GetTemplateRequestPayload.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/payload/GetTemplateRequestPayload.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/payload/GetTemplateRequestPayload.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/payload/GetTemplateRequestPayload.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/ListTagsResponsePayload.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/payload/ListTagsResponsePayload.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/payload/ListTagsResponsePayload.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/payload/ListTagsResponsePayload.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/ListTemplatesRequestPayload.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/payload/ListTemplatesRequestPayload.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/payload/ListTemplatesRequestPayload.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/payload/ListTemplatesRequestPayload.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/ListUsersResponsePayload.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/payload/ListUsersResponsePayload.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/payload/ListUsersResponsePayload.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/payload/ListUsersResponsePayload.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/PaginationData.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/payload/PaginationData.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/payload/PaginationData.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/payload/PaginationData.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/TemplatesResponsePayload.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/payload/TemplatesResponsePayload.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/payload/TemplatesResponsePayload.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/payload/TemplatesResponsePayload.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/UpdateTagRequestPayload.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/payload/UpdateTagRequestPayload.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/payload/UpdateTagRequestPayload.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/payload/UpdateTagRequestPayload.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/UpdateTemplateRequestPayload.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/payload/UpdateTemplateRequestPayload.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/payload/UpdateTemplateRequestPayload.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/payload/UpdateTemplateRequestPayload.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/UserResponsePayload.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/payload/UserResponsePayload.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/payload/UserResponsePayload.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/payload/UserResponsePayload.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/UsersListRequestPayload.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/payload/UsersListRequestPayload.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/payload/UsersListRequestPayload.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/payload/UsersListRequestPayload.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/WebhookInfoRequestPayload.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/payload/WebhookInfoRequestPayload.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/payload/WebhookInfoRequestPayload.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/payload/WebhookInfoRequestPayload.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/WebhookListPayload.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/payload/WebhookListPayload.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/payload/WebhookListPayload.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/payload/WebhookListPayload.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/WebhookListResponsePayload.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/payload/WebhookListResponsePayload.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/payload/WebhookListResponsePayload.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/payload/WebhookListResponsePayload.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/WebhookResponsePayload.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/payload/WebhookResponsePayload.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/payload/WebhookResponsePayload.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/payload/WebhookResponsePayload.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/WebhookSubscribePayload.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/payload/WebhookSubscribePayload.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/payload/WebhookSubscribePayload.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/payload/WebhookSubscribePayload.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/WebhookUnsubscribePayload.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/payload/WebhookUnsubscribePayload.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/payload/WebhookUnsubscribePayload.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/payload/WebhookUnsubscribePayload.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/WebhookUpdatePayload.java b/backend/components/admin/src/main/java/co/airy/core/api/admin/payload/WebhookUpdatePayload.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/admin/payload/WebhookUpdatePayload.java rename to backend/components/admin/src/main/java/co/airy/core/api/admin/payload/WebhookUpdatePayload.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/config/ClientConfigController.java b/backend/components/admin/src/main/java/co/airy/core/api/config/ClientConfigController.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/config/ClientConfigController.java rename to backend/components/admin/src/main/java/co/airy/core/api/config/ClientConfigController.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/config/ClientControllerConfig.java b/backend/components/admin/src/main/java/co/airy/core/api/config/ClientControllerConfig.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/config/ClientControllerConfig.java rename to backend/components/admin/src/main/java/co/airy/core/api/config/ClientControllerConfig.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/config/HealthApi.java b/backend/components/admin/src/main/java/co/airy/core/api/config/HealthApi.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/config/HealthApi.java rename to backend/components/admin/src/main/java/co/airy/core/api/config/HealthApi.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/config/ServiceDiscovery.java b/backend/components/admin/src/main/java/co/airy/core/api/config/ServiceDiscovery.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/config/ServiceDiscovery.java rename to backend/components/admin/src/main/java/co/airy/core/api/config/ServiceDiscovery.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/config/dto/ComponentInfo.java b/backend/components/admin/src/main/java/co/airy/core/api/config/dto/ComponentInfo.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/config/dto/ComponentInfo.java rename to backend/components/admin/src/main/java/co/airy/core/api/config/dto/ComponentInfo.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/config/dto/ServiceInfo.java b/backend/components/admin/src/main/java/co/airy/core/api/config/dto/ServiceInfo.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/config/dto/ServiceInfo.java rename to backend/components/admin/src/main/java/co/airy/core/api/config/dto/ServiceInfo.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/config/payload/ClientConfigResponsePayload.java b/backend/components/admin/src/main/java/co/airy/core/api/config/payload/ClientConfigResponsePayload.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/config/payload/ClientConfigResponsePayload.java rename to backend/components/admin/src/main/java/co/airy/core/api/config/payload/ClientConfigResponsePayload.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/config/payload/ServicesResponsePayload.java b/backend/components/admin/src/main/java/co/airy/core/api/config/payload/ServicesResponsePayload.java similarity index 100% rename from backend/api/admin/src/main/java/co/airy/core/api/config/payload/ServicesResponsePayload.java rename to backend/components/admin/src/main/java/co/airy/core/api/config/payload/ServicesResponsePayload.java diff --git a/backend/api/admin/src/main/resources/application.properties b/backend/components/admin/src/main/resources/application.properties similarity index 100% rename from backend/api/admin/src/main/resources/application.properties rename to backend/components/admin/src/main/resources/application.properties diff --git a/backend/api/admin/src/main/resources/tagConfig.json b/backend/components/admin/src/main/resources/tagConfig.json similarity index 100% rename from backend/api/admin/src/main/resources/tagConfig.json rename to backend/components/admin/src/main/resources/tagConfig.json diff --git a/backend/api/admin/src/test/java/co/airy/core/api/admin/ChannelsControllerTest.java b/backend/components/admin/src/test/java/co/airy/core/api/admin/ChannelsControllerTest.java similarity index 100% rename from backend/api/admin/src/test/java/co/airy/core/api/admin/ChannelsControllerTest.java rename to backend/components/admin/src/test/java/co/airy/core/api/admin/ChannelsControllerTest.java diff --git a/backend/api/admin/src/test/java/co/airy/core/api/admin/TagsControllerTest.java b/backend/components/admin/src/test/java/co/airy/core/api/admin/TagsControllerTest.java similarity index 100% rename from backend/api/admin/src/test/java/co/airy/core/api/admin/TagsControllerTest.java rename to backend/components/admin/src/test/java/co/airy/core/api/admin/TagsControllerTest.java diff --git a/backend/api/admin/src/test/java/co/airy/core/api/admin/TemplatesControllerTest.java b/backend/components/admin/src/test/java/co/airy/core/api/admin/TemplatesControllerTest.java similarity index 100% rename from backend/api/admin/src/test/java/co/airy/core/api/admin/TemplatesControllerTest.java rename to backend/components/admin/src/test/java/co/airy/core/api/admin/TemplatesControllerTest.java diff --git a/backend/api/admin/src/test/java/co/airy/core/api/admin/UsersControllerTest.java b/backend/components/admin/src/test/java/co/airy/core/api/admin/UsersControllerTest.java similarity index 100% rename from backend/api/admin/src/test/java/co/airy/core/api/admin/UsersControllerTest.java rename to backend/components/admin/src/test/java/co/airy/core/api/admin/UsersControllerTest.java diff --git a/backend/api/admin/src/test/java/co/airy/core/api/admin/WebhooksControllerTest.java b/backend/components/admin/src/test/java/co/airy/core/api/admin/WebhooksControllerTest.java similarity index 100% rename from backend/api/admin/src/test/java/co/airy/core/api/admin/WebhooksControllerTest.java rename to backend/components/admin/src/test/java/co/airy/core/api/admin/WebhooksControllerTest.java diff --git a/backend/api/admin/src/test/java/co/airy/core/api/admin/util/Topics.java b/backend/components/admin/src/test/java/co/airy/core/api/admin/util/Topics.java similarity index 100% rename from backend/api/admin/src/test/java/co/airy/core/api/admin/util/Topics.java rename to backend/components/admin/src/test/java/co/airy/core/api/admin/util/Topics.java diff --git a/backend/api/admin/src/test/java/co/airy/core/api/config/ClientConfigControllerTest.java b/backend/components/admin/src/test/java/co/airy/core/api/config/ClientConfigControllerTest.java similarity index 100% rename from backend/api/admin/src/test/java/co/airy/core/api/config/ClientConfigControllerTest.java rename to backend/components/admin/src/test/java/co/airy/core/api/config/ClientConfigControllerTest.java diff --git a/backend/api/admin/src/test/resources/test.properties b/backend/components/admin/src/test/resources/test.properties similarity index 100% rename from backend/api/admin/src/test/resources/test.properties rename to backend/components/admin/src/test/resources/test.properties diff --git a/backend/sources/chat-plugin/BUILD b/backend/components/chat-plugin/BUILD similarity index 100% rename from backend/sources/chat-plugin/BUILD rename to backend/components/chat-plugin/BUILD diff --git a/backend/api/contacts/helm/BUILD b/backend/components/chat-plugin/helm/BUILD similarity index 100% rename from backend/api/contacts/helm/BUILD rename to backend/components/chat-plugin/helm/BUILD diff --git a/backend/sources/chat-plugin/helm/Chart.yaml b/backend/components/chat-plugin/helm/Chart.yaml similarity index 100% rename from backend/sources/chat-plugin/helm/Chart.yaml rename to backend/components/chat-plugin/helm/Chart.yaml diff --git a/backend/sources/chat-plugin/helm/templates/backend/deployment.yaml b/backend/components/chat-plugin/helm/templates/backend/deployment.yaml similarity index 100% rename from backend/sources/chat-plugin/helm/templates/backend/deployment.yaml rename to backend/components/chat-plugin/helm/templates/backend/deployment.yaml diff --git a/backend/api/contacts/helm/templates/service.yaml b/backend/components/chat-plugin/helm/templates/backend/service.yaml similarity index 100% rename from backend/api/contacts/helm/templates/service.yaml rename to backend/components/chat-plugin/helm/templates/backend/service.yaml diff --git a/backend/api/contacts/helm/templates/configmap.yaml b/backend/components/chat-plugin/helm/templates/configmap.yaml similarity index 100% rename from backend/api/contacts/helm/templates/configmap.yaml rename to backend/components/chat-plugin/helm/templates/configmap.yaml diff --git a/backend/sources/chat-plugin/helm/templates/frontend/deployment.yaml b/backend/components/chat-plugin/helm/templates/frontend/deployment.yaml similarity index 100% rename from backend/sources/chat-plugin/helm/templates/frontend/deployment.yaml rename to backend/components/chat-plugin/helm/templates/frontend/deployment.yaml diff --git a/backend/sources/chat-plugin/helm/templates/frontend/ingress.yaml b/backend/components/chat-plugin/helm/templates/frontend/ingress.yaml similarity index 100% rename from backend/sources/chat-plugin/helm/templates/frontend/ingress.yaml rename to backend/components/chat-plugin/helm/templates/frontend/ingress.yaml diff --git a/backend/sources/chat-plugin/helm/templates/frontend/service.yaml b/backend/components/chat-plugin/helm/templates/frontend/service.yaml similarity index 100% rename from backend/sources/chat-plugin/helm/templates/frontend/service.yaml rename to backend/components/chat-plugin/helm/templates/frontend/service.yaml diff --git a/backend/sources/chat-plugin/helm/values.yaml b/backend/components/chat-plugin/helm/values.yaml similarity index 100% rename from backend/sources/chat-plugin/helm/values.yaml rename to backend/components/chat-plugin/helm/values.yaml diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/ChatController.java b/backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/ChatController.java similarity index 100% rename from backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/ChatController.java rename to backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/ChatController.java diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/Headers.java b/backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/Headers.java similarity index 100% rename from backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/Headers.java rename to backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/Headers.java diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/Principal.java b/backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/Principal.java similarity index 100% rename from backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/Principal.java rename to backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/Principal.java diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/Stores.java b/backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/Stores.java similarity index 100% rename from backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/Stores.java rename to backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/Stores.java diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/WebSocketController.java b/backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/WebSocketController.java similarity index 100% rename from backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/WebSocketController.java rename to backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/WebSocketController.java diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/AuthConfig.java b/backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/AuthConfig.java similarity index 100% rename from backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/AuthConfig.java rename to backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/AuthConfig.java diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/Jwt.java b/backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/Jwt.java similarity index 100% rename from backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/Jwt.java rename to backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/Jwt.java diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/JwtAuthenticationFilter.java b/backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/JwtAuthenticationFilter.java similarity index 100% rename from backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/JwtAuthenticationFilter.java rename to backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/JwtAuthenticationFilter.java diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/WebSocketConfig.java b/backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/WebSocketConfig.java similarity index 100% rename from backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/WebSocketConfig.java rename to backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/WebSocketConfig.java diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/dto/MessagesTreeSet.java b/backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/dto/MessagesTreeSet.java similarity index 100% rename from backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/dto/MessagesTreeSet.java rename to backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/dto/MessagesTreeSet.java diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/AuthenticationRequestPayload.java b/backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/AuthenticationRequestPayload.java similarity index 100% rename from backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/AuthenticationRequestPayload.java rename to backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/AuthenticationRequestPayload.java diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/AuthenticationResponsePayload.java b/backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/AuthenticationResponsePayload.java similarity index 100% rename from backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/AuthenticationResponsePayload.java rename to backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/AuthenticationResponsePayload.java diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/MessageUpsertPayload.java b/backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/MessageUpsertPayload.java similarity index 100% rename from backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/MessageUpsertPayload.java rename to backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/MessageUpsertPayload.java diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/RequestErrorResponsePayload.java b/backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/RequestErrorResponsePayload.java similarity index 100% rename from backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/RequestErrorResponsePayload.java rename to backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/RequestErrorResponsePayload.java diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/ResumeTokenRequestPayload.java b/backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/ResumeTokenRequestPayload.java similarity index 100% rename from backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/ResumeTokenRequestPayload.java rename to backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/ResumeTokenRequestPayload.java diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/ResumeTokenResponsePayload.java b/backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/ResumeTokenResponsePayload.java similarity index 100% rename from backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/ResumeTokenResponsePayload.java rename to backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/ResumeTokenResponsePayload.java diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/SendMessageRequestPayload.java b/backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/SendMessageRequestPayload.java similarity index 100% rename from backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/SendMessageRequestPayload.java rename to backend/components/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/SendMessageRequestPayload.java diff --git a/backend/sources/chat-plugin/src/main/resources/application.properties b/backend/components/chat-plugin/src/main/resources/application.properties similarity index 100% rename from backend/sources/chat-plugin/src/main/resources/application.properties rename to backend/components/chat-plugin/src/main/resources/application.properties diff --git a/backend/sources/chat-plugin/src/test/java/co/airy/core/chat_plugin/ChatControllerTest.java b/backend/components/chat-plugin/src/test/java/co/airy/core/chat_plugin/ChatControllerTest.java similarity index 98% rename from backend/sources/chat-plugin/src/test/java/co/airy/core/chat_plugin/ChatControllerTest.java rename to backend/components/chat-plugin/src/test/java/co/airy/core/chat_plugin/ChatControllerTest.java index 9a00df87b5..5d288cb5d5 100644 --- a/backend/sources/chat-plugin/src/test/java/co/airy/core/chat_plugin/ChatControllerTest.java +++ b/backend/components/chat-plugin/src/test/java/co/airy/core/chat_plugin/ChatControllerTest.java @@ -10,7 +10,7 @@ import co.airy.spring.core.AirySpringBootApplication; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; import org.apache.kafka.clients.producer.ProducerRecord; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -215,7 +215,7 @@ public StompSession connect(String jwtToken, int port) throws ExecutionException final WebSocketStompClient stompClient = new WebSocketStompClient(new StandardWebSocketClient()); MappingJackson2MessageConverter messageConverter = new MappingJackson2MessageConverter(); - ObjectMapper objectMapper = new ObjectMapper().setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + ObjectMapper objectMapper = new ObjectMapper().setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); messageConverter.setObjectMapper(objectMapper); stompClient.setMessageConverter(messageConverter); diff --git a/backend/sources/chat-plugin/src/test/resources/test.properties b/backend/components/chat-plugin/src/test/resources/test.properties similarity index 100% rename from backend/sources/chat-plugin/src/test/resources/test.properties rename to backend/components/chat-plugin/src/test/resources/test.properties diff --git a/backend/api/communication/BUILD b/backend/components/communication/BUILD similarity index 100% rename from backend/api/communication/BUILD rename to backend/components/communication/BUILD diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/AsyncSendMessagesHandler.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/AsyncSendMessagesHandler.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/AsyncSendMessagesHandler.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/AsyncSendMessagesHandler.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/ConversationsController.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/ConversationsController.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/ConversationsController.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/ConversationsController.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/MessagesController.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/MessagesController.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/MessagesController.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/MessagesController.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/MetadataController.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/MetadataController.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/MetadataController.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/MetadataController.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/SendMessageController.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/SendMessageController.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/SendMessageController.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/SendMessageController.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/Stores.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/Stores.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/Stores.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/Stores.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/dto/ConversationIndex.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/dto/ConversationIndex.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/dto/ConversationIndex.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/dto/ConversationIndex.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/dto/CountAction.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/dto/CountAction.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/dto/CountAction.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/dto/CountAction.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/dto/LuceneQueryResult.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/dto/LuceneQueryResult.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/dto/LuceneQueryResult.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/dto/LuceneQueryResult.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/dto/Messages.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/dto/Messages.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/dto/Messages.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/dto/Messages.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/dto/UnreadCountState.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/dto/UnreadCountState.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/dto/UnreadCountState.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/dto/UnreadCountState.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/AiryAnalyzer.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/lucene/AiryAnalyzer.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/AiryAnalyzer.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/lucene/AiryAnalyzer.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/DocumentMapper.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/lucene/DocumentMapper.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/DocumentMapper.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/lucene/DocumentMapper.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/ExtendedQueryParser.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/lucene/ExtendedQueryParser.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/ExtendedQueryParser.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/lucene/ExtendedQueryParser.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/IndexingProcessor.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/lucene/IndexingProcessor.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/IndexingProcessor.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/lucene/IndexingProcessor.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/LuceneDiskStore.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/lucene/LuceneDiskStore.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/LuceneDiskStore.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/lucene/LuceneDiskStore.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/LuceneProvider.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/lucene/LuceneProvider.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/LuceneProvider.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/lucene/LuceneProvider.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/LuceneStore.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/lucene/LuceneStore.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/LuceneStore.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/lucene/LuceneStore.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/ReadOnlyLuceneStore.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/lucene/ReadOnlyLuceneStore.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/ReadOnlyLuceneStore.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/lucene/ReadOnlyLuceneStore.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/StoreChangeLogger.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/lucene/StoreChangeLogger.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/lucene/StoreChangeLogger.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/lucene/StoreChangeLogger.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationByIdRequestPayload.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/payload/ConversationByIdRequestPayload.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationByIdRequestPayload.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/payload/ConversationByIdRequestPayload.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationListRequestPayload.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/payload/ConversationListRequestPayload.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationListRequestPayload.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/payload/ConversationListRequestPayload.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationListResponsePayload.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/payload/ConversationListResponsePayload.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationListResponsePayload.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/payload/ConversationListResponsePayload.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationResponsePayload.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/payload/ConversationResponsePayload.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationResponsePayload.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/payload/ConversationResponsePayload.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationSetStateRequestPayload.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/payload/ConversationSetStateRequestPayload.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationSetStateRequestPayload.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/payload/ConversationSetStateRequestPayload.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationTagRequestPayload.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/payload/ConversationTagRequestPayload.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationTagRequestPayload.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/payload/ConversationTagRequestPayload.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationUpdateContactRequestPayload.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/payload/ConversationUpdateContactRequestPayload.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationUpdateContactRequestPayload.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/payload/ConversationUpdateContactRequestPayload.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/MessageListRequestPayload.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/payload/MessageListRequestPayload.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/payload/MessageListRequestPayload.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/payload/MessageListRequestPayload.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/MessageListResponsePayload.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/payload/MessageListResponsePayload.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/payload/MessageListResponsePayload.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/payload/MessageListResponsePayload.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/MessageSuggestRepliesRequestPayload.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/payload/MessageSuggestRepliesRequestPayload.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/payload/MessageSuggestRepliesRequestPayload.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/payload/MessageSuggestRepliesRequestPayload.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/MessageUpsertPayload.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/payload/MessageUpsertPayload.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/payload/MessageUpsertPayload.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/payload/MessageUpsertPayload.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/PaginationData.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/payload/PaginationData.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/payload/PaginationData.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/payload/PaginationData.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/RemoveMetadataRequestPayload.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/payload/RemoveMetadataRequestPayload.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/payload/RemoveMetadataRequestPayload.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/payload/RemoveMetadataRequestPayload.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ResendMessageRequestPayload.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/payload/ResendMessageRequestPayload.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ResendMessageRequestPayload.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/payload/ResendMessageRequestPayload.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/SendMessageRequestPayload.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/payload/SendMessageRequestPayload.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/payload/SendMessageRequestPayload.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/payload/SendMessageRequestPayload.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/SetMetadataRequestPayload.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/payload/SetMetadataRequestPayload.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/payload/SetMetadataRequestPayload.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/payload/SetMetadataRequestPayload.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/UnreadCountPayload.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/payload/UnreadCountPayload.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/payload/UnreadCountPayload.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/payload/UnreadCountPayload.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/UpsertMetadataRequestPayload.java b/backend/components/communication/src/main/java/co/airy/core/api/communication/payload/UpsertMetadataRequestPayload.java similarity index 100% rename from backend/api/communication/src/main/java/co/airy/core/api/communication/payload/UpsertMetadataRequestPayload.java rename to backend/components/communication/src/main/java/co/airy/core/api/communication/payload/UpsertMetadataRequestPayload.java diff --git a/backend/api/communication/src/main/resources/application.properties b/backend/components/communication/src/main/resources/application.properties similarity index 100% rename from backend/api/communication/src/main/resources/application.properties rename to backend/components/communication/src/main/resources/application.properties diff --git a/backend/api/communication/src/test/java/co/airy/core/api/communication/ConversationsInfoTest.java b/backend/components/communication/src/test/java/co/airy/core/api/communication/ConversationsInfoTest.java similarity index 100% rename from backend/api/communication/src/test/java/co/airy/core/api/communication/ConversationsInfoTest.java rename to backend/components/communication/src/test/java/co/airy/core/api/communication/ConversationsInfoTest.java diff --git a/backend/api/communication/src/test/java/co/airy/core/api/communication/ConversationsListTest.java b/backend/components/communication/src/test/java/co/airy/core/api/communication/ConversationsListTest.java similarity index 100% rename from backend/api/communication/src/test/java/co/airy/core/api/communication/ConversationsListTest.java rename to backend/components/communication/src/test/java/co/airy/core/api/communication/ConversationsListTest.java diff --git a/backend/api/communication/src/test/java/co/airy/core/api/communication/ConversationsTagTest.java b/backend/components/communication/src/test/java/co/airy/core/api/communication/ConversationsTagTest.java similarity index 100% rename from backend/api/communication/src/test/java/co/airy/core/api/communication/ConversationsTagTest.java rename to backend/components/communication/src/test/java/co/airy/core/api/communication/ConversationsTagTest.java diff --git a/backend/api/communication/src/test/java/co/airy/core/api/communication/ConversationsUpdateTest.java b/backend/components/communication/src/test/java/co/airy/core/api/communication/ConversationsUpdateTest.java similarity index 100% rename from backend/api/communication/src/test/java/co/airy/core/api/communication/ConversationsUpdateTest.java rename to backend/components/communication/src/test/java/co/airy/core/api/communication/ConversationsUpdateTest.java diff --git a/backend/api/communication/src/test/java/co/airy/core/api/communication/MessagesTest.java b/backend/components/communication/src/test/java/co/airy/core/api/communication/MessagesTest.java similarity index 100% rename from backend/api/communication/src/test/java/co/airy/core/api/communication/MessagesTest.java rename to backend/components/communication/src/test/java/co/airy/core/api/communication/MessagesTest.java diff --git a/backend/api/communication/src/test/java/co/airy/core/api/communication/MetadataControllerTest.java b/backend/components/communication/src/test/java/co/airy/core/api/communication/MetadataControllerTest.java similarity index 100% rename from backend/api/communication/src/test/java/co/airy/core/api/communication/MetadataControllerTest.java rename to backend/components/communication/src/test/java/co/airy/core/api/communication/MetadataControllerTest.java diff --git a/backend/api/communication/src/test/java/co/airy/core/api/communication/SendMessageControllerTest.java b/backend/components/communication/src/test/java/co/airy/core/api/communication/SendMessageControllerTest.java similarity index 100% rename from backend/api/communication/src/test/java/co/airy/core/api/communication/SendMessageControllerTest.java rename to backend/components/communication/src/test/java/co/airy/core/api/communication/SendMessageControllerTest.java diff --git a/backend/api/communication/src/test/java/co/airy/core/api/communication/UnreadCountTest.java b/backend/components/communication/src/test/java/co/airy/core/api/communication/UnreadCountTest.java similarity index 100% rename from backend/api/communication/src/test/java/co/airy/core/api/communication/UnreadCountTest.java rename to backend/components/communication/src/test/java/co/airy/core/api/communication/UnreadCountTest.java diff --git a/backend/api/communication/src/test/java/co/airy/core/api/communication/util/TestConversation.java b/backend/components/communication/src/test/java/co/airy/core/api/communication/util/TestConversation.java similarity index 100% rename from backend/api/communication/src/test/java/co/airy/core/api/communication/util/TestConversation.java rename to backend/components/communication/src/test/java/co/airy/core/api/communication/util/TestConversation.java diff --git a/backend/api/communication/src/test/java/co/airy/core/api/communication/util/Topics.java b/backend/components/communication/src/test/java/co/airy/core/api/communication/util/Topics.java similarity index 100% rename from backend/api/communication/src/test/java/co/airy/core/api/communication/util/Topics.java rename to backend/components/communication/src/test/java/co/airy/core/api/communication/util/Topics.java diff --git a/backend/api/communication/src/test/resources/test.properties b/backend/components/communication/src/test/resources/test.properties similarity index 100% rename from backend/api/communication/src/test/resources/test.properties rename to backend/components/communication/src/test/resources/test.properties diff --git a/backend/api/contacts/BUILD b/backend/components/contacts/BUILD similarity index 100% rename from backend/api/contacts/BUILD rename to backend/components/contacts/BUILD diff --git a/backend/media/helm/BUILD b/backend/components/contacts/helm/BUILD similarity index 100% rename from backend/media/helm/BUILD rename to backend/components/contacts/helm/BUILD diff --git a/backend/api/contacts/helm/Chart.yaml b/backend/components/contacts/helm/Chart.yaml similarity index 100% rename from backend/api/contacts/helm/Chart.yaml rename to backend/components/contacts/helm/Chart.yaml diff --git a/backend/media/helm/templates/configmap.yaml b/backend/components/contacts/helm/templates/configmap.yaml similarity index 100% rename from backend/media/helm/templates/configmap.yaml rename to backend/components/contacts/helm/templates/configmap.yaml diff --git a/backend/api/contacts/helm/templates/deployment.yaml b/backend/components/contacts/helm/templates/deployment.yaml similarity index 100% rename from backend/api/contacts/helm/templates/deployment.yaml rename to backend/components/contacts/helm/templates/deployment.yaml diff --git a/backend/media/helm/templates/service.yaml b/backend/components/contacts/helm/templates/service.yaml similarity index 100% rename from backend/media/helm/templates/service.yaml rename to backend/components/contacts/helm/templates/service.yaml diff --git a/backend/api/contacts/helm/values.yaml b/backend/components/contacts/helm/values.yaml similarity index 100% rename from backend/api/contacts/helm/values.yaml rename to backend/components/contacts/helm/values.yaml diff --git a/backend/api/contacts/src/main/java/co/airy/core/contacts/ContactsController.java b/backend/components/contacts/src/main/java/co/airy/core/contacts/ContactsController.java similarity index 100% rename from backend/api/contacts/src/main/java/co/airy/core/contacts/ContactsController.java rename to backend/components/contacts/src/main/java/co/airy/core/contacts/ContactsController.java diff --git a/backend/api/contacts/src/main/java/co/airy/core/contacts/Stores.java b/backend/components/contacts/src/main/java/co/airy/core/contacts/Stores.java similarity index 100% rename from backend/api/contacts/src/main/java/co/airy/core/contacts/Stores.java rename to backend/components/contacts/src/main/java/co/airy/core/contacts/Stores.java diff --git a/backend/api/contacts/src/main/java/co/airy/core/contacts/payload/ContactInfoRequestPayload.java b/backend/components/contacts/src/main/java/co/airy/core/contacts/payload/ContactInfoRequestPayload.java similarity index 100% rename from backend/api/contacts/src/main/java/co/airy/core/contacts/payload/ContactInfoRequestPayload.java rename to backend/components/contacts/src/main/java/co/airy/core/contacts/payload/ContactInfoRequestPayload.java diff --git a/backend/api/contacts/src/main/java/co/airy/core/contacts/payload/ContactResponsePayload.java b/backend/components/contacts/src/main/java/co/airy/core/contacts/payload/ContactResponsePayload.java similarity index 100% rename from backend/api/contacts/src/main/java/co/airy/core/contacts/payload/ContactResponsePayload.java rename to backend/components/contacts/src/main/java/co/airy/core/contacts/payload/ContactResponsePayload.java diff --git a/backend/api/contacts/src/main/java/co/airy/core/contacts/payload/ContactWithMergeHistoryResponsePayload.java b/backend/components/contacts/src/main/java/co/airy/core/contacts/payload/ContactWithMergeHistoryResponsePayload.java similarity index 100% rename from backend/api/contacts/src/main/java/co/airy/core/contacts/payload/ContactWithMergeHistoryResponsePayload.java rename to backend/components/contacts/src/main/java/co/airy/core/contacts/payload/ContactWithMergeHistoryResponsePayload.java diff --git a/backend/api/contacts/src/main/java/co/airy/core/contacts/payload/CreateContactPayload.java b/backend/components/contacts/src/main/java/co/airy/core/contacts/payload/CreateContactPayload.java similarity index 100% rename from backend/api/contacts/src/main/java/co/airy/core/contacts/payload/CreateContactPayload.java rename to backend/components/contacts/src/main/java/co/airy/core/contacts/payload/CreateContactPayload.java diff --git a/backend/api/contacts/src/main/java/co/airy/core/contacts/payload/DeleteContactPayload.java b/backend/components/contacts/src/main/java/co/airy/core/contacts/payload/DeleteContactPayload.java similarity index 100% rename from backend/api/contacts/src/main/java/co/airy/core/contacts/payload/DeleteContactPayload.java rename to backend/components/contacts/src/main/java/co/airy/core/contacts/payload/DeleteContactPayload.java diff --git a/backend/api/contacts/src/main/java/co/airy/core/contacts/payload/ImportContactsResponsePayload.java b/backend/components/contacts/src/main/java/co/airy/core/contacts/payload/ImportContactsResponsePayload.java similarity index 100% rename from backend/api/contacts/src/main/java/co/airy/core/contacts/payload/ImportContactsResponsePayload.java rename to backend/components/contacts/src/main/java/co/airy/core/contacts/payload/ImportContactsResponsePayload.java diff --git a/backend/api/contacts/src/main/java/co/airy/core/contacts/payload/ListContactsRequestPayload.java b/backend/components/contacts/src/main/java/co/airy/core/contacts/payload/ListContactsRequestPayload.java similarity index 100% rename from backend/api/contacts/src/main/java/co/airy/core/contacts/payload/ListContactsRequestPayload.java rename to backend/components/contacts/src/main/java/co/airy/core/contacts/payload/ListContactsRequestPayload.java diff --git a/backend/api/contacts/src/main/java/co/airy/core/contacts/payload/ListContactsResponsePayload.java b/backend/components/contacts/src/main/java/co/airy/core/contacts/payload/ListContactsResponsePayload.java similarity index 100% rename from backend/api/contacts/src/main/java/co/airy/core/contacts/payload/ListContactsResponsePayload.java rename to backend/components/contacts/src/main/java/co/airy/core/contacts/payload/ListContactsResponsePayload.java diff --git a/backend/api/contacts/src/main/java/co/airy/core/contacts/payload/MergeContactsRequestPayload.java b/backend/components/contacts/src/main/java/co/airy/core/contacts/payload/MergeContactsRequestPayload.java similarity index 100% rename from backend/api/contacts/src/main/java/co/airy/core/contacts/payload/MergeContactsRequestPayload.java rename to backend/components/contacts/src/main/java/co/airy/core/contacts/payload/MergeContactsRequestPayload.java diff --git a/backend/api/contacts/src/main/java/co/airy/core/contacts/payload/PaginationData.java b/backend/components/contacts/src/main/java/co/airy/core/contacts/payload/PaginationData.java similarity index 100% rename from backend/api/contacts/src/main/java/co/airy/core/contacts/payload/PaginationData.java rename to backend/components/contacts/src/main/java/co/airy/core/contacts/payload/PaginationData.java diff --git a/backend/api/contacts/src/main/java/co/airy/core/contacts/payload/UpdateContactPayload.java b/backend/components/contacts/src/main/java/co/airy/core/contacts/payload/UpdateContactPayload.java similarity index 100% rename from backend/api/contacts/src/main/java/co/airy/core/contacts/payload/UpdateContactPayload.java rename to backend/components/contacts/src/main/java/co/airy/core/contacts/payload/UpdateContactPayload.java diff --git a/backend/api/contacts/src/main/resources/application.properties b/backend/components/contacts/src/main/resources/application.properties similarity index 100% rename from backend/api/contacts/src/main/resources/application.properties rename to backend/components/contacts/src/main/resources/application.properties diff --git a/backend/api/contacts/src/test/java/co/airy/core/contacts/CreateContactsTest.java b/backend/components/contacts/src/test/java/co/airy/core/contacts/CreateContactsTest.java similarity index 100% rename from backend/api/contacts/src/test/java/co/airy/core/contacts/CreateContactsTest.java rename to backend/components/contacts/src/test/java/co/airy/core/contacts/CreateContactsTest.java diff --git a/backend/api/contacts/src/test/java/co/airy/core/contacts/DeleteContactTest.java b/backend/components/contacts/src/test/java/co/airy/core/contacts/DeleteContactTest.java similarity index 100% rename from backend/api/contacts/src/test/java/co/airy/core/contacts/DeleteContactTest.java rename to backend/components/contacts/src/test/java/co/airy/core/contacts/DeleteContactTest.java diff --git a/backend/api/contacts/src/test/java/co/airy/core/contacts/ImportContactsTest.java b/backend/components/contacts/src/test/java/co/airy/core/contacts/ImportContactsTest.java similarity index 100% rename from backend/api/contacts/src/test/java/co/airy/core/contacts/ImportContactsTest.java rename to backend/components/contacts/src/test/java/co/airy/core/contacts/ImportContactsTest.java diff --git a/backend/api/contacts/src/test/java/co/airy/core/contacts/ListContactsTest.java b/backend/components/contacts/src/test/java/co/airy/core/contacts/ListContactsTest.java similarity index 100% rename from backend/api/contacts/src/test/java/co/airy/core/contacts/ListContactsTest.java rename to backend/components/contacts/src/test/java/co/airy/core/contacts/ListContactsTest.java diff --git a/backend/api/contacts/src/test/java/co/airy/core/contacts/MergeContactsTest.java b/backend/components/contacts/src/test/java/co/airy/core/contacts/MergeContactsTest.java similarity index 100% rename from backend/api/contacts/src/test/java/co/airy/core/contacts/MergeContactsTest.java rename to backend/components/contacts/src/test/java/co/airy/core/contacts/MergeContactsTest.java diff --git a/backend/api/contacts/src/test/java/co/airy/core/contacts/UpdateContactsTest.java b/backend/components/contacts/src/test/java/co/airy/core/contacts/UpdateContactsTest.java similarity index 100% rename from backend/api/contacts/src/test/java/co/airy/core/contacts/UpdateContactsTest.java rename to backend/components/contacts/src/test/java/co/airy/core/contacts/UpdateContactsTest.java diff --git a/backend/api/contacts/src/test/java/co/airy/core/contacts/util/TestContact.java b/backend/components/contacts/src/test/java/co/airy/core/contacts/util/TestContact.java similarity index 93% rename from backend/api/contacts/src/test/java/co/airy/core/contacts/util/TestContact.java rename to backend/components/contacts/src/test/java/co/airy/core/contacts/util/TestContact.java index 43f67c8bfc..0f3225411e 100644 --- a/backend/api/contacts/src/test/java/co/airy/core/contacts/util/TestContact.java +++ b/backend/components/contacts/src/test/java/co/airy/core/contacts/util/TestContact.java @@ -5,7 +5,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; import org.springframework.stereotype.Component; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -19,7 +19,7 @@ public class TestContact { public TestContact(WebTestHelper webTestHelper) { this.webTestHelper = webTestHelper; this.objectMapper = new ObjectMapper(); - objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); } diff --git a/backend/api/contacts/src/test/java/co/airy/core/contacts/util/Topics.java b/backend/components/contacts/src/test/java/co/airy/core/contacts/util/Topics.java similarity index 100% rename from backend/api/contacts/src/test/java/co/airy/core/contacts/util/Topics.java rename to backend/components/contacts/src/test/java/co/airy/core/contacts/util/Topics.java diff --git a/backend/api/contacts/src/test/resources/test.properties b/backend/components/contacts/src/test/resources/test.properties similarity index 100% rename from backend/api/contacts/src/test/resources/test.properties rename to backend/components/contacts/src/test/resources/test.properties diff --git a/backend/sources/facebook/connector/BUILD b/backend/components/facebook/connector/BUILD similarity index 100% rename from backend/sources/facebook/connector/BUILD rename to backend/components/facebook/connector/BUILD diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/ChannelsController.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/ChannelsController.java similarity index 100% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/ChannelsController.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/ChannelsController.java diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/Connector.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/Connector.java similarity index 99% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/Connector.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/Connector.java index 04e6f269fd..7ef47dfa0e 100644 --- a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/Connector.java +++ b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/Connector.java @@ -88,6 +88,7 @@ public List> sendMessage(SendMessageRequest results.add(KeyValue.pair(getId(errorPayload).toString(), errorPayload)); } updateDeliveryState(message, DeliveryState.FAILED); + results.add(KeyValue.pair(message.getId(), message)); return results; } catch (Exception e) { log.error(String.format("Failed to send a \n SendMessageRequest: %s", sendMessageRequest), e); diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/Stores.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/Stores.java similarity index 100% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/Stores.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/Stores.java diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/WebhookController.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/WebhookController.java similarity index 100% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/WebhookController.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/WebhookController.java diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/Api.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/Api.java similarity index 99% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/Api.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/Api.java index 4264715ff1..34455bb851 100644 --- a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/Api.java +++ b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/Api.java @@ -12,7 +12,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationReadyEvent; @@ -65,7 +65,7 @@ public Api(RestTemplateBuilder restTemplateBuilder, this.objectMapper = new ObjectMapper() .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .configure(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY, false) - .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); this.restTemplateBuilder = restTemplateBuilder; this.appId = appId; this.apiSecret = apiSecret; diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/ApiException.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/ApiException.java similarity index 100% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/ApiException.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/ApiException.java diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/Mapper.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/Mapper.java similarity index 100% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/Mapper.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/Mapper.java diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/FaceBookMetadataKeys.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/FaceBookMetadataKeys.java similarity index 100% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/FaceBookMetadataKeys.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/FaceBookMetadataKeys.java diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/InstagramProfile.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/InstagramProfile.java similarity index 100% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/InstagramProfile.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/InstagramProfile.java diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/LongLivingUserAccessToken.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/LongLivingUserAccessToken.java similarity index 100% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/LongLivingUserAccessToken.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/LongLivingUserAccessToken.java diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/PageWithConnectInfo.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/PageWithConnectInfo.java similarity index 100% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/PageWithConnectInfo.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/PageWithConnectInfo.java diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/Pages.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/Pages.java similarity index 100% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/Pages.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/Pages.java diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/Participants.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/Participants.java similarity index 100% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/Participants.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/Participants.java diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/SendMessagePayload.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/SendMessagePayload.java similarity index 100% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/SendMessagePayload.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/SendMessagePayload.java diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/SendMessageResponse.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/SendMessageResponse.java similarity index 100% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/SendMessageResponse.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/SendMessageResponse.java diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/UserProfile.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/UserProfile.java similarity index 100% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/UserProfile.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/api/model/UserProfile.java diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/dto/Conversation.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/dto/Conversation.java similarity index 100% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/dto/Conversation.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/dto/Conversation.java diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/dto/SendMessageRequest.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/dto/SendMessageRequest.java similarity index 100% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/dto/SendMessageRequest.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/dto/SendMessageRequest.java diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/ConnectInstagramRequestPayload.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/ConnectInstagramRequestPayload.java similarity index 100% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/ConnectInstagramRequestPayload.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/ConnectInstagramRequestPayload.java diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/ConnectPageRequestPayload.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/ConnectPageRequestPayload.java similarity index 100% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/ConnectPageRequestPayload.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/ConnectPageRequestPayload.java diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/DisconnectChannelRequestPayload.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/DisconnectChannelRequestPayload.java similarity index 100% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/DisconnectChannelRequestPayload.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/DisconnectChannelRequestPayload.java diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/ExploreRequestPayload.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/ExploreRequestPayload.java similarity index 100% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/ExploreRequestPayload.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/ExploreRequestPayload.java diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/ExploreResponsePayload.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/ExploreResponsePayload.java similarity index 100% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/ExploreResponsePayload.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/ExploreResponsePayload.java diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/PageInfoResponsePayload.java b/backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/PageInfoResponsePayload.java similarity index 100% rename from backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/PageInfoResponsePayload.java rename to backend/components/facebook/connector/src/main/java/co/airy/core/sources/facebook/payload/PageInfoResponsePayload.java diff --git a/backend/sources/facebook/connector/src/main/resources/application.properties b/backend/components/facebook/connector/src/main/resources/application.properties similarity index 100% rename from backend/sources/facebook/connector/src/main/resources/application.properties rename to backend/components/facebook/connector/src/main/resources/application.properties diff --git a/backend/sources/facebook/connector/src/test/java/co/airy/core/sources/facebook/ChannelsControllerTest.java b/backend/components/facebook/connector/src/test/java/co/airy/core/sources/facebook/ChannelsControllerTest.java similarity index 99% rename from backend/sources/facebook/connector/src/test/java/co/airy/core/sources/facebook/ChannelsControllerTest.java rename to backend/components/facebook/connector/src/test/java/co/airy/core/sources/facebook/ChannelsControllerTest.java index b36c2a3173..dd2ca31e01 100644 --- a/backend/sources/facebook/connector/src/test/java/co/airy/core/sources/facebook/ChannelsControllerTest.java +++ b/backend/components/facebook/connector/src/test/java/co/airy/core/sources/facebook/ChannelsControllerTest.java @@ -36,7 +36,6 @@ import java.util.Arrays; import java.util.List; -import java.util.UUID; import static co.airy.test.Timing.retryOnException; import static org.apache.kafka.streams.KafkaStreams.State.RUNNING; diff --git a/backend/sources/facebook/connector/src/test/java/co/airy/core/sources/facebook/FetchMetadataTest.java b/backend/components/facebook/connector/src/test/java/co/airy/core/sources/facebook/FetchMetadataTest.java similarity index 100% rename from backend/sources/facebook/connector/src/test/java/co/airy/core/sources/facebook/FetchMetadataTest.java rename to backend/components/facebook/connector/src/test/java/co/airy/core/sources/facebook/FetchMetadataTest.java index 77f40ef6d3..ca753af344 100644 --- a/backend/sources/facebook/connector/src/test/java/co/airy/core/sources/facebook/FetchMetadataTest.java +++ b/backend/components/facebook/connector/src/test/java/co/airy/core/sources/facebook/FetchMetadataTest.java @@ -35,13 +35,13 @@ import java.util.List; import static co.airy.model.metadata.MetadataKeys.ConversationKeys; +import static co.airy.test.Timing.retryOnException; import static org.apache.kafka.streams.KafkaStreams.State.RUNNING; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.collection.IsCollectionWithSize.hasSize; import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static co.airy.test.Timing.retryOnException; @SpringBootTest(classes = AirySpringBootApplication.class) @TestPropertySource(value = "classpath:test.properties") diff --git a/backend/sources/facebook/connector/src/test/java/co/airy/core/sources/facebook/SendMessageTest.java b/backend/components/facebook/connector/src/test/java/co/airy/core/sources/facebook/SendMessageTest.java similarity index 100% rename from backend/sources/facebook/connector/src/test/java/co/airy/core/sources/facebook/SendMessageTest.java rename to backend/components/facebook/connector/src/test/java/co/airy/core/sources/facebook/SendMessageTest.java diff --git a/backend/sources/facebook/connector/src/test/java/co/airy/core/sources/facebook/WebhookControllerTest.java b/backend/components/facebook/connector/src/test/java/co/airy/core/sources/facebook/WebhookControllerTest.java similarity index 100% rename from backend/sources/facebook/connector/src/test/java/co/airy/core/sources/facebook/WebhookControllerTest.java rename to backend/components/facebook/connector/src/test/java/co/airy/core/sources/facebook/WebhookControllerTest.java diff --git a/backend/sources/facebook/connector/src/test/resources/test.properties b/backend/components/facebook/connector/src/test/resources/test.properties similarity index 100% rename from backend/sources/facebook/connector/src/test/resources/test.properties rename to backend/components/facebook/connector/src/test/resources/test.properties diff --git a/backend/sources/facebook/events-router/BUILD b/backend/components/facebook/events-router/BUILD similarity index 100% rename from backend/sources/facebook/events-router/BUILD rename to backend/components/facebook/events-router/BUILD diff --git a/backend/sources/facebook/events-router/src/main/java/co/airy/core/sources/facebook/EventsRouter.java b/backend/components/facebook/events-router/src/main/java/co/airy/core/sources/facebook/EventsRouter.java similarity index 100% rename from backend/sources/facebook/events-router/src/main/java/co/airy/core/sources/facebook/EventsRouter.java rename to backend/components/facebook/events-router/src/main/java/co/airy/core/sources/facebook/EventsRouter.java diff --git a/backend/sources/facebook/events-router/src/main/java/co/airy/core/sources/facebook/MessageMapper.java b/backend/components/facebook/events-router/src/main/java/co/airy/core/sources/facebook/MessageMapper.java similarity index 100% rename from backend/sources/facebook/events-router/src/main/java/co/airy/core/sources/facebook/MessageMapper.java rename to backend/components/facebook/events-router/src/main/java/co/airy/core/sources/facebook/MessageMapper.java diff --git a/backend/sources/facebook/events-router/src/main/java/co/airy/core/sources/facebook/NotAMessageException.java b/backend/components/facebook/events-router/src/main/java/co/airy/core/sources/facebook/NotAMessageException.java similarity index 100% rename from backend/sources/facebook/events-router/src/main/java/co/airy/core/sources/facebook/NotAMessageException.java rename to backend/components/facebook/events-router/src/main/java/co/airy/core/sources/facebook/NotAMessageException.java diff --git a/backend/sources/facebook/events-router/src/main/java/co/airy/core/sources/facebook/dto/Event.java b/backend/components/facebook/events-router/src/main/java/co/airy/core/sources/facebook/dto/Event.java similarity index 100% rename from backend/sources/facebook/events-router/src/main/java/co/airy/core/sources/facebook/dto/Event.java rename to backend/components/facebook/events-router/src/main/java/co/airy/core/sources/facebook/dto/Event.java diff --git a/backend/sources/facebook/events-router/src/main/java/co/airy/core/sources/facebook/model/WebhookEntry.java b/backend/components/facebook/events-router/src/main/java/co/airy/core/sources/facebook/model/WebhookEntry.java similarity index 100% rename from backend/sources/facebook/events-router/src/main/java/co/airy/core/sources/facebook/model/WebhookEntry.java rename to backend/components/facebook/events-router/src/main/java/co/airy/core/sources/facebook/model/WebhookEntry.java diff --git a/backend/sources/facebook/events-router/src/main/java/co/airy/core/sources/facebook/model/WebhookEvent.java b/backend/components/facebook/events-router/src/main/java/co/airy/core/sources/facebook/model/WebhookEvent.java similarity index 100% rename from backend/sources/facebook/events-router/src/main/java/co/airy/core/sources/facebook/model/WebhookEvent.java rename to backend/components/facebook/events-router/src/main/java/co/airy/core/sources/facebook/model/WebhookEvent.java diff --git a/backend/sources/facebook/events-router/src/main/resources/application.properties b/backend/components/facebook/events-router/src/main/resources/application.properties similarity index 100% rename from backend/sources/facebook/events-router/src/main/resources/application.properties rename to backend/components/facebook/events-router/src/main/resources/application.properties diff --git a/backend/sources/facebook/events-router/src/test/java/co/airy/core/sources/facebook/EventsRouterTest.java b/backend/components/facebook/events-router/src/test/java/co/airy/core/sources/facebook/EventsRouterTest.java similarity index 99% rename from backend/sources/facebook/events-router/src/test/java/co/airy/core/sources/facebook/EventsRouterTest.java rename to backend/components/facebook/events-router/src/test/java/co/airy/core/sources/facebook/EventsRouterTest.java index 036dbd2908..da2b8edd53 100644 --- a/backend/sources/facebook/events-router/src/test/java/co/airy/core/sources/facebook/EventsRouterTest.java +++ b/backend/components/facebook/events-router/src/test/java/co/airy/core/sources/facebook/EventsRouterTest.java @@ -89,7 +89,7 @@ void beforeEach() throws InterruptedException { // This tests simulates multiple users sending messages via multiple facebook pages // It ensures that we create the correct number of conversations and messages - //@Test + @Test void joinsAndCountsMessagesCorrectly() throws Exception { Random rand = new Random(); List pageIds = Arrays.asList("p1", "p2", "p3", "p4", "p5"); diff --git a/backend/sources/facebook/events-router/src/test/resources/test.properties b/backend/components/facebook/events-router/src/test/resources/test.properties similarity index 100% rename from backend/sources/facebook/events-router/src/test/resources/test.properties rename to backend/components/facebook/events-router/src/test/resources/test.properties diff --git a/backend/sources/api/helm/BUILD b/backend/components/facebook/helm/BUILD similarity index 100% rename from backend/sources/api/helm/BUILD rename to backend/components/facebook/helm/BUILD diff --git a/backend/sources/facebook/helm/Chart.yaml b/backend/components/facebook/helm/Chart.yaml similarity index 100% rename from backend/sources/facebook/helm/Chart.yaml rename to backend/components/facebook/helm/Chart.yaml diff --git a/backend/sources/api/helm/templates/configmap.yaml b/backend/components/facebook/helm/templates/configmap.yaml similarity index 100% rename from backend/sources/api/helm/templates/configmap.yaml rename to backend/components/facebook/helm/templates/configmap.yaml diff --git a/backend/sources/facebook/helm/templates/deployments.yaml b/backend/components/facebook/helm/templates/deployments.yaml similarity index 100% rename from backend/sources/facebook/helm/templates/deployments.yaml rename to backend/components/facebook/helm/templates/deployments.yaml diff --git a/backend/sources/facebook/helm/templates/service.yaml b/backend/components/facebook/helm/templates/service.yaml similarity index 100% rename from backend/sources/facebook/helm/templates/service.yaml rename to backend/components/facebook/helm/templates/service.yaml diff --git a/backend/sources/facebook/helm/values.yaml b/backend/components/facebook/helm/values.yaml similarity index 100% rename from backend/sources/facebook/helm/values.yaml rename to backend/components/facebook/helm/values.yaml diff --git a/backend/sources/google/connector/BUILD b/backend/components/google/connector/BUILD similarity index 100% rename from backend/sources/google/connector/BUILD rename to backend/components/google/connector/BUILD diff --git a/backend/sources/google/connector/src/main/java/co/airy/core/sources/google/ApiException.java b/backend/components/google/connector/src/main/java/co/airy/core/sources/google/ApiException.java similarity index 100% rename from backend/sources/google/connector/src/main/java/co/airy/core/sources/google/ApiException.java rename to backend/components/google/connector/src/main/java/co/airy/core/sources/google/ApiException.java diff --git a/backend/sources/google/connector/src/main/java/co/airy/core/sources/google/ChannelsController.java b/backend/components/google/connector/src/main/java/co/airy/core/sources/google/ChannelsController.java similarity index 100% rename from backend/sources/google/connector/src/main/java/co/airy/core/sources/google/ChannelsController.java rename to backend/components/google/connector/src/main/java/co/airy/core/sources/google/ChannelsController.java diff --git a/backend/sources/google/connector/src/main/java/co/airy/core/sources/google/Connector.java b/backend/components/google/connector/src/main/java/co/airy/core/sources/google/Connector.java similarity index 100% rename from backend/sources/google/connector/src/main/java/co/airy/core/sources/google/Connector.java rename to backend/components/google/connector/src/main/java/co/airy/core/sources/google/Connector.java diff --git a/backend/sources/google/connector/src/main/java/co/airy/core/sources/google/GoogleConfig.java b/backend/components/google/connector/src/main/java/co/airy/core/sources/google/GoogleConfig.java similarity index 100% rename from backend/sources/google/connector/src/main/java/co/airy/core/sources/google/GoogleConfig.java rename to backend/components/google/connector/src/main/java/co/airy/core/sources/google/GoogleConfig.java diff --git a/backend/sources/google/connector/src/main/java/co/airy/core/sources/google/Stores.java b/backend/components/google/connector/src/main/java/co/airy/core/sources/google/Stores.java similarity index 100% rename from backend/sources/google/connector/src/main/java/co/airy/core/sources/google/Stores.java rename to backend/components/google/connector/src/main/java/co/airy/core/sources/google/Stores.java diff --git a/backend/sources/google/connector/src/main/java/co/airy/core/sources/google/WebhookController.java b/backend/components/google/connector/src/main/java/co/airy/core/sources/google/WebhookController.java similarity index 100% rename from backend/sources/google/connector/src/main/java/co/airy/core/sources/google/WebhookController.java rename to backend/components/google/connector/src/main/java/co/airy/core/sources/google/WebhookController.java diff --git a/backend/sources/google/connector/src/main/java/co/airy/core/sources/google/model/GoogleServiceAccount.java b/backend/components/google/connector/src/main/java/co/airy/core/sources/google/model/GoogleServiceAccount.java similarity index 100% rename from backend/sources/google/connector/src/main/java/co/airy/core/sources/google/model/GoogleServiceAccount.java rename to backend/components/google/connector/src/main/java/co/airy/core/sources/google/model/GoogleServiceAccount.java diff --git a/backend/sources/google/connector/src/main/java/co/airy/core/sources/google/model/SendMessageRequest.java b/backend/components/google/connector/src/main/java/co/airy/core/sources/google/model/SendMessageRequest.java similarity index 100% rename from backend/sources/google/connector/src/main/java/co/airy/core/sources/google/model/SendMessageRequest.java rename to backend/components/google/connector/src/main/java/co/airy/core/sources/google/model/SendMessageRequest.java diff --git a/backend/sources/google/connector/src/main/java/co/airy/core/sources/google/services/Api.java b/backend/components/google/connector/src/main/java/co/airy/core/sources/google/services/Api.java similarity index 100% rename from backend/sources/google/connector/src/main/java/co/airy/core/sources/google/services/Api.java rename to backend/components/google/connector/src/main/java/co/airy/core/sources/google/services/Api.java diff --git a/backend/sources/google/connector/src/main/java/co/airy/core/sources/google/services/Mapper.java b/backend/components/google/connector/src/main/java/co/airy/core/sources/google/services/Mapper.java similarity index 100% rename from backend/sources/google/connector/src/main/java/co/airy/core/sources/google/services/Mapper.java rename to backend/components/google/connector/src/main/java/co/airy/core/sources/google/services/Mapper.java diff --git a/backend/sources/google/connector/src/main/resources/application.properties b/backend/components/google/connector/src/main/resources/application.properties similarity index 100% rename from backend/sources/google/connector/src/main/resources/application.properties rename to backend/components/google/connector/src/main/resources/application.properties diff --git a/backend/sources/google/connector/src/test/java/co/airy/core/sources/google/SendMessageTest.java b/backend/components/google/connector/src/test/java/co/airy/core/sources/google/SendMessageTest.java similarity index 100% rename from backend/sources/google/connector/src/test/java/co/airy/core/sources/google/SendMessageTest.java rename to backend/components/google/connector/src/test/java/co/airy/core/sources/google/SendMessageTest.java diff --git a/backend/sources/google/connector/src/test/resources/test.properties b/backend/components/google/connector/src/test/resources/test.properties similarity index 100% rename from backend/sources/google/connector/src/test/resources/test.properties rename to backend/components/google/connector/src/test/resources/test.properties diff --git a/backend/sources/google/events-router/BUILD b/backend/components/google/events-router/BUILD similarity index 100% rename from backend/sources/google/events-router/BUILD rename to backend/components/google/events-router/BUILD diff --git a/backend/sources/google/events-router/src/main/java/co/airy/core/sources/google/EventInfo.java b/backend/components/google/events-router/src/main/java/co/airy/core/sources/google/EventInfo.java similarity index 100% rename from backend/sources/google/events-router/src/main/java/co/airy/core/sources/google/EventInfo.java rename to backend/components/google/events-router/src/main/java/co/airy/core/sources/google/EventInfo.java diff --git a/backend/sources/google/events-router/src/main/java/co/airy/core/sources/google/EventsRouter.java b/backend/components/google/events-router/src/main/java/co/airy/core/sources/google/EventsRouter.java similarity index 100% rename from backend/sources/google/events-router/src/main/java/co/airy/core/sources/google/EventsRouter.java rename to backend/components/google/events-router/src/main/java/co/airy/core/sources/google/EventsRouter.java diff --git a/backend/sources/google/events-router/src/main/java/co/airy/core/sources/google/InfoExtractor.java b/backend/components/google/events-router/src/main/java/co/airy/core/sources/google/InfoExtractor.java similarity index 100% rename from backend/sources/google/events-router/src/main/java/co/airy/core/sources/google/InfoExtractor.java rename to backend/components/google/events-router/src/main/java/co/airy/core/sources/google/InfoExtractor.java diff --git a/backend/sources/google/events-router/src/main/java/co/airy/core/sources/google/ObjectMapperConfig.java b/backend/components/google/events-router/src/main/java/co/airy/core/sources/google/ObjectMapperConfig.java similarity index 91% rename from backend/sources/google/events-router/src/main/java/co/airy/core/sources/google/ObjectMapperConfig.java rename to backend/components/google/events-router/src/main/java/co/airy/core/sources/google/ObjectMapperConfig.java index adc68f94e9..f1b8cde26f 100644 --- a/backend/sources/google/events-router/src/main/java/co/airy/core/sources/google/ObjectMapperConfig.java +++ b/backend/components/google/events-router/src/main/java/co/airy/core/sources/google/ObjectMapperConfig.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -17,6 +17,6 @@ public ObjectMapper objectMapper() { .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .configure(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY, false) .setSerializationInclusion(JsonInclude.Include.NON_NULL) - .setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CAMEL_CASE); + .setPropertyNamingStrategy(PropertyNamingStrategies.LOWER_CAMEL_CASE); } } diff --git a/backend/sources/google/events-router/src/main/java/co/airy/core/sources/google/WebhookEvent.java b/backend/components/google/events-router/src/main/java/co/airy/core/sources/google/WebhookEvent.java similarity index 100% rename from backend/sources/google/events-router/src/main/java/co/airy/core/sources/google/WebhookEvent.java rename to backend/components/google/events-router/src/main/java/co/airy/core/sources/google/WebhookEvent.java diff --git a/backend/api/websocket/src/main/resources/application.properties b/backend/components/google/events-router/src/main/resources/application.properties similarity index 100% rename from backend/api/websocket/src/main/resources/application.properties rename to backend/components/google/events-router/src/main/resources/application.properties diff --git a/backend/sources/google/events-router/src/test/java/co/airy/core/sources/google/EventsRouterTest.java b/backend/components/google/events-router/src/test/java/co/airy/core/sources/google/EventsRouterTest.java similarity index 100% rename from backend/sources/google/events-router/src/test/java/co/airy/core/sources/google/EventsRouterTest.java rename to backend/components/google/events-router/src/test/java/co/airy/core/sources/google/EventsRouterTest.java diff --git a/backend/api/websocket/src/test/resources/test.properties b/backend/components/google/events-router/src/test/resources/test.properties similarity index 100% rename from backend/api/websocket/src/test/resources/test.properties rename to backend/components/google/events-router/src/test/resources/test.properties diff --git a/backend/sources/chat-plugin/helm/BUILD b/backend/components/google/helm/BUILD similarity index 100% rename from backend/sources/chat-plugin/helm/BUILD rename to backend/components/google/helm/BUILD diff --git a/backend/sources/google/helm/Chart.yaml b/backend/components/google/helm/Chart.yaml similarity index 100% rename from backend/sources/google/helm/Chart.yaml rename to backend/components/google/helm/Chart.yaml diff --git a/backend/sources/chat-plugin/helm/templates/configmap.yaml b/backend/components/google/helm/templates/configmap.yaml similarity index 100% rename from backend/sources/chat-plugin/helm/templates/configmap.yaml rename to backend/components/google/helm/templates/configmap.yaml diff --git a/backend/sources/google/helm/templates/deployments.yaml b/backend/components/google/helm/templates/deployments.yaml similarity index 100% rename from backend/sources/google/helm/templates/deployments.yaml rename to backend/components/google/helm/templates/deployments.yaml diff --git a/backend/sources/google/helm/templates/service.yaml b/backend/components/google/helm/templates/service.yaml similarity index 100% rename from backend/sources/google/helm/templates/service.yaml rename to backend/components/google/helm/templates/service.yaml diff --git a/backend/sources/google/helm/values.yaml b/backend/components/google/helm/values.yaml similarity index 100% rename from backend/sources/google/helm/values.yaml rename to backend/components/google/helm/values.yaml diff --git a/backend/media/BUILD b/backend/components/media-resolver/BUILD similarity index 100% rename from backend/media/BUILD rename to backend/components/media-resolver/BUILD diff --git a/backend/sources/facebook/helm/BUILD b/backend/components/media-resolver/helm/BUILD similarity index 100% rename from backend/sources/facebook/helm/BUILD rename to backend/components/media-resolver/helm/BUILD diff --git a/backend/media/helm/Chart.yaml b/backend/components/media-resolver/helm/Chart.yaml similarity index 100% rename from backend/media/helm/Chart.yaml rename to backend/components/media-resolver/helm/Chart.yaml diff --git a/backend/sources/facebook/helm/templates/configmap.yaml b/backend/components/media-resolver/helm/templates/configmap.yaml similarity index 100% rename from backend/sources/facebook/helm/templates/configmap.yaml rename to backend/components/media-resolver/helm/templates/configmap.yaml diff --git a/backend/media/helm/templates/deployment.yaml b/backend/components/media-resolver/helm/templates/deployment.yaml similarity index 100% rename from backend/media/helm/templates/deployment.yaml rename to backend/components/media-resolver/helm/templates/deployment.yaml diff --git a/backend/sources/api/helm/templates/service.yaml b/backend/components/media-resolver/helm/templates/service.yaml similarity index 100% rename from backend/sources/api/helm/templates/service.yaml rename to backend/components/media-resolver/helm/templates/service.yaml diff --git a/backend/media/helm/values.yaml b/backend/components/media-resolver/helm/values.yaml similarity index 100% rename from backend/media/helm/values.yaml rename to backend/components/media-resolver/helm/values.yaml diff --git a/backend/media/src/main/java/co/airy/core/media/MediaController.java b/backend/components/media-resolver/src/main/java/co/airy/core/media/MediaController.java similarity index 100% rename from backend/media/src/main/java/co/airy/core/media/MediaController.java rename to backend/components/media-resolver/src/main/java/co/airy/core/media/MediaController.java diff --git a/backend/media/src/main/java/co/airy/core/media/config/AwsConfig.java b/backend/components/media-resolver/src/main/java/co/airy/core/media/config/AwsConfig.java similarity index 100% rename from backend/media/src/main/java/co/airy/core/media/config/AwsConfig.java rename to backend/components/media-resolver/src/main/java/co/airy/core/media/config/AwsConfig.java diff --git a/backend/media/src/main/java/co/airy/core/media/services/MediaUpload.java b/backend/components/media-resolver/src/main/java/co/airy/core/media/services/MediaUpload.java similarity index 100% rename from backend/media/src/main/java/co/airy/core/media/services/MediaUpload.java rename to backend/components/media-resolver/src/main/java/co/airy/core/media/services/MediaUpload.java diff --git a/backend/media/src/main/resources/application.properties b/backend/components/media-resolver/src/main/resources/application.properties similarity index 100% rename from backend/media/src/main/resources/application.properties rename to backend/components/media-resolver/src/main/resources/application.properties diff --git a/backend/media/src/test/java/co/airy/core/media/MediaControllerTest.java b/backend/components/media-resolver/src/test/java/co/airy/core/media/MediaControllerTest.java similarity index 100% rename from backend/media/src/test/java/co/airy/core/media/MediaControllerTest.java rename to backend/components/media-resolver/src/test/java/co/airy/core/media/MediaControllerTest.java diff --git a/backend/media/src/test/java/co/airy/core/media/services/MediaUploadTest.java b/backend/components/media-resolver/src/test/java/co/airy/core/media/services/MediaUploadTest.java similarity index 100% rename from backend/media/src/test/java/co/airy/core/media/services/MediaUploadTest.java rename to backend/components/media-resolver/src/test/java/co/airy/core/media/services/MediaUploadTest.java diff --git a/backend/media/src/test/resources/giphy.gif b/backend/components/media-resolver/src/test/resources/giphy.gif similarity index 100% rename from backend/media/src/test/resources/giphy.gif rename to backend/components/media-resolver/src/test/resources/giphy.gif diff --git a/backend/media/src/test/resources/test.properties b/backend/components/media-resolver/src/test/resources/test.properties similarity index 100% rename from backend/media/src/test/resources/test.properties rename to backend/components/media-resolver/src/test/resources/test.properties diff --git a/backend/components/rasa/BUILD b/backend/components/rasa/BUILD new file mode 100644 index 0000000000..2697002c64 --- /dev/null +++ b/backend/components/rasa/BUILD @@ -0,0 +1,50 @@ +load("//tools/build:springboot.bzl", "springboot") +load("//tools/build:junit5.bzl", "junit5") +load("//tools/build:container_release.bzl", "container_release") +load("@com_github_airyhq_bazel_tools//lint:buildifier.bzl", "check_pkg") + +check_pkg(name = "buildifier") + +app_deps = [ + "//:spring", + "//:springboot", + "//:springboot_actuator", + "//:jackson", + "//:lombok", + "//backend/model/message", + "//backend/model/metadata", + "//:feign", + "//lib/java/log", + "//lib/java/spring/kafka/core:spring-kafka-core", + "//lib/java/spring/core:spring-core", + "//lib/java/spring/kafka/streams:spring-kafka-streams", + "//lib/java/spring/async:spring-async", +] + +springboot( + name = "rasa-connector", + srcs = glob(["src/main/java/**/*.java"]), + main_class = "co.airy.spring.core.AirySpringBootApplication", + deps = app_deps, +) + +[ + junit5( + size = "medium", + file = file, + resources = glob(["src/test/resources/**/*"]), + deps = [ + ":app", + "//backend:base_test", + "//lib/java/test", + "//lib/java/kafka/test:kafka-test", + "//lib/java/spring/test:spring-test", + ] + app_deps, + ) + for file in glob(["src/test/java/**/*Test.java"]) +] + +container_release( + registry = "ghcr.io/airyhq/connectors", + repository = "rasa-connector", +) diff --git a/backend/components/rasa/helm/BUILD b/backend/components/rasa/helm/BUILD new file mode 100644 index 0000000000..1f602ed4a8 --- /dev/null +++ b/backend/components/rasa/helm/BUILD @@ -0,0 +1,28 @@ +load("@rules_pkg//:pkg.bzl", "pkg_tar") +load("@com_github_airyhq_bazel_tools//helm:helm.bzl", "helm_template_test") +load("//tools/build:helm.bzl", "helm_push") + +filegroup( + name = "files", + srcs = glob( + ["**/*"], + exclude = ["BUILD"], + ), + visibility = ["//visibility:public"], +) + +pkg_tar( + name = "package", + srcs = [":files"], + extension = "tgz", + strip_prefix = "./", +) + +helm_template_test( + name = "template", + chart = ":package", +) + +helm_push( + chart = ":package", +) diff --git a/backend/components/rasa/helm/Chart.yaml b/backend/components/rasa/helm/Chart.yaml new file mode 100644 index 0000000000..eb664d831e --- /dev/null +++ b/backend/components/rasa/helm/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v2 +appVersion: "1.0" +description: A Helm chart for the Rasa connector +name: rasa-connector +version: 1.0 diff --git a/backend/components/rasa/helm/templates/configmap.yaml b/backend/components/rasa/helm/templates/configmap.yaml new file mode 100644 index 0000000000..daae16661f --- /dev/null +++ b/backend/components/rasa/helm/templates/configmap.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: "{{ .Values.component }}" + labels: + core.airy.co/managed: "true" + core.airy.co/mandatory: "{{ .Values.mandatory }}" + core.airy.co/component: "{{ .Values.component }}" + core.airy.co/enterprise: "false" + annotations: + core.airy.co/enabled: "{{ .Values.enabled }}" diff --git a/backend/components/rasa/helm/templates/deployment.yaml b/backend/components/rasa/helm/templates/deployment.yaml new file mode 100644 index 0000000000..dd392cd305 --- /dev/null +++ b/backend/components/rasa/helm/templates/deployment.yaml @@ -0,0 +1,75 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.component }} + labels: + app: {{ .Values.component }} + core.airy.co/managed: "true" + core.airy.co/mandatory: "{{ .Values.mandatory }}" + core.airy.co/component: {{ .Values.component }} +spec: + replicas: {{ if .Values.enabled }} 1 {{ else }} 0 {{ end }} + selector: + matchLabels: + app: {{ .Values.component }} + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + app: {{ .Values.component }} + spec: + containers: + - name: app + image: "{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ default .Chart.Version }}" + imagePullPolicy: Always + envFrom: + - configMapRef: + name: security + - configMapRef: + name: kafka-config + env: + - name: RASA_WEBHOOK_URL + valueFrom: + configMapKeyRef: + key: rasaWebhookUrl + name: {{ .Values.component }} + - name: SERVICE_NAME + value: {{ .Values.component }} + - name: POD_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + - name: REQUESTED_CPU + valueFrom: + resourceFieldRef: + containerName: app + resource: requests.cpu + - name: LIMIT_CPU + valueFrom: + resourceFieldRef: + containerName: app + resource: limits.cpu + - name: LIMIT_MEMORY + valueFrom: + resourceFieldRef: + containerName: app + resource: limits.memory + livenessProbe: + httpGet: + path: /actuator/health + port: 8080 + httpHeaders: + - name: Health-Check + value: health-check + initialDelaySeconds: 60 + periodSeconds: 10 + failureThreshold: 3 + volumes: + - name: {{ .Values.component }} + configMap: + name: {{ .Values.component }} diff --git a/backend/components/rasa/helm/templates/service.yaml b/backend/components/rasa/helm/templates/service.yaml new file mode 100644 index 0000000000..9ecaf9eed3 --- /dev/null +++ b/backend/components/rasa/helm/templates/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: {{ .Values.component }} + name: {{ .Values.component }} +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 8080 + selector: + app: {{ .Values.component }} + type: ClusterIP diff --git a/backend/components/rasa/helm/values.yaml b/backend/components/rasa/helm/values.yaml new file mode 100644 index 0000000000..7b8de93f1e --- /dev/null +++ b/backend/components/rasa/helm/values.yaml @@ -0,0 +1,7 @@ +component: rasa-connector +mandatory: false +enabled: false +image: connectors/rasa-connector +global: + containerRegistry: ghcr.io/airyhq +resources: {} diff --git a/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/MessageHandler.java b/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/MessageHandler.java new file mode 100644 index 0000000000..5472d57fad --- /dev/null +++ b/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/MessageHandler.java @@ -0,0 +1,89 @@ +package co.airy.core.rasa_connector; + +import co.airy.avro.communication.DeliveryState; +import co.airy.avro.communication.Message; +import co.airy.core.rasa_connector.models.MessageSendResponse; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.Map; +import java.util.UUID; + +@Service +public class MessageHandler { + private final ObjectMapper mapper = new ObjectMapper(); + + MessageHandler() { + } + + public Message getMessage(Message contactMessage, MessageSendResponse response) throws Exception { + String content = getContent(contactMessage.getSource(), response); + if (content == null) { + throw new Exception("Unable to map rasa reply to source response."); + } + + return Message.newBuilder() + .setId(UUID.randomUUID().toString()) + .setChannelId(contactMessage.getChannelId()) + .setContent(content) + .setConversationId(contactMessage.getConversationId()) + .setHeaders(Map.of()) + .setDeliveryState(DeliveryState.PENDING) + .setSource(contactMessage.getSource()) + .setSenderId("rasa-bot") + .setSentAt(Instant.now().toEpochMilli()) + .setIsFromContact(false) + .build(); + } + + public String getContent(String source, MessageSendResponse response) throws JsonProcessingException { + final String text = response.getText(); + if (text == null) { + return null; + } + + final ObjectNode node = getNode(); + switch (source) { + case "google": { + final ObjectNode representative = getNode(); + representative.put("representativeType", "BOT"); + node.set("representative", representative); + node.put("text", text); + return mapper.writeValueAsString(node); + } + case "viber": { + node.put("text", text); + node.put("type", text); + return mapper.writeValueAsString(node); + } + case "chatplugin": + case "instagram": + case "facebook": { + node.put("text", text); + return mapper.writeValueAsString(node); + } + case "twilio.sms": + case "twilio.whatsapp": { + node.put("Body", text); + return mapper.writeValueAsString(node); + } + case "whatsapp": { + node.put("Body", text); + return mapper.writeValueAsString(node); + } + + default: { + return null; + } + } + } + + private ObjectNode getNode() { + final JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance; + return jsonNodeFactory.objectNode(); + } +} diff --git a/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/RasaClient.java b/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/RasaClient.java new file mode 100644 index 0000000000..0fa5b4154a --- /dev/null +++ b/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/RasaClient.java @@ -0,0 +1,15 @@ +package co.airy.core.rasa_connector; + +import co.airy.core.rasa_connector.models.MessageSend; +import co.airy.core.rasa_connector.models.MessageSendResponse; +import feign.Headers; +import feign.RequestLine; + +import java.util.List; + +public interface RasaClient { + @RequestLine("POST /webhooks/rest/webhook") + @Headers("Content-Type: application/json") + List sendMessage(MessageSend content); + +} diff --git a/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/RasaClientConfig.java b/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/RasaClientConfig.java new file mode 100644 index 0000000000..88729dafc0 --- /dev/null +++ b/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/RasaClientConfig.java @@ -0,0 +1,24 @@ +package co.airy.core.rasa_connector; + + +import feign.Feign; +import feign.jackson.JacksonDecoder; +import feign.jackson.JacksonEncoder; +import feign.okhttp.OkHttpClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RasaClientConfig { + @Bean + public RasaClient rasaClient(@Value("${rasa.rest-webhook-url}") String rasaRestUrl) { + return Feign.builder() + .client(new OkHttpClient()) + .encoder(new JacksonEncoder()) + .decoder(new JacksonDecoder()) + .logger(new feign.Logger.ErrorLogger()) + .logLevel(feign.Logger.Level.FULL) + .target(RasaClient.class, rasaRestUrl); + } +} diff --git a/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/RasaConnectorService.java b/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/RasaConnectorService.java new file mode 100644 index 0000000000..09693f109c --- /dev/null +++ b/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/RasaConnectorService.java @@ -0,0 +1,69 @@ +package co.airy.core.rasa_connector; + +import co.airy.avro.communication.Message; +import co.airy.core.rasa_connector.models.MessageSend; +import co.airy.core.rasa_connector.models.MessageSendResponse; +import co.airy.log.AiryLoggerFactory; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.avro.specific.SpecificRecordBase; +import org.apache.kafka.streams.KeyValue; +import org.slf4j.Logger; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Service +public class RasaConnectorService { + private final RasaClient rasaClient; + private static final ObjectMapper mapper = new ObjectMapper(); + + private static final Logger log = AiryLoggerFactory.getLogger(RasaConnectorService.class); + private final MessageHandler messageHandler; + + RasaConnectorService(MessageHandler messageHandler, + RasaClient rasaClient) { + this.rasaClient = rasaClient; + this.messageHandler = messageHandler; + } + + public List> send(Message userMessage) { + final List> result = new ArrayList<>(); + try { + List rasaResponseList = this.rasaClient.sendMessage(MessageSend.builder() + .message(getTextFromContent(userMessage.getContent())) + .sender(userMessage.getId()) + .build()); + for (MessageSendResponse rasaResponse : rasaResponseList) { + try { + Message message = messageHandler.getMessage(userMessage, rasaResponse); + result.add(KeyValue.pair(message.getId(), message)); + } catch (Exception e) { + log.info(String.format("could not handle response for data type for message id %s %s. Please inspect the", rasaResponse.toString(), e)); + } + } + } catch (Exception e) { + log.error(String.format("could not call the Rasa webhook for message id %s %s", userMessage.getId(), e)); + } + return result; + } + + private String getTextFromContent(String content) { + String text = ""; + + try { + final JsonNode node = Optional.ofNullable(mapper.readTree(content)).orElseGet(mapper::createObjectNode); + + //NOTE: Tries to find the text context for text messages + text = Optional.ofNullable(node.findValue("text")).orElseGet(mapper::createObjectNode).asText(); + } catch (JsonProcessingException e) { + log.error(String.format("unable to parse text from content %s", content)); + } + + //NOTE: return default message when text is not found + return Optional.ofNullable(text).filter(s -> !s.isEmpty()).orElse("New message"); + } +} diff --git a/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/Stores.java b/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/Stores.java new file mode 100644 index 0000000000..1ecad042d9 --- /dev/null +++ b/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/Stores.java @@ -0,0 +1,75 @@ +// Store - Class that gives you access to Kafka Store(s) +package co.airy.core.rasa_connector; + +import co.airy.avro.communication.Message; +import co.airy.avro.communication.Metadata; +import co.airy.kafka.schema.application.ApplicationCommunicationMessages; +import co.airy.kafka.schema.application.ApplicationCommunicationMetadata; +import co.airy.kafka.streams.KafkaStreamsWrapper; +import org.apache.kafka.streams.KafkaStreams; +import org.apache.kafka.streams.StreamsBuilder; +import org.apache.kafka.streams.Topology; +import org.apache.kafka.streams.kstream.Consumed; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +import static co.airy.model.message.MessageRepository.isNewMessage; + +@Component +public class Stores implements HealthIndicator, ApplicationListener, DisposableBean { + private static final String appId = "rasa-connector"; + private final KafkaStreamsWrapper streams; + private RasaConnectorService rasaConnectorService; + + Stores(KafkaStreamsWrapper streams, RasaConnectorService rasaConnectorService) { + this.streams = streams; + this.rasaConnectorService = rasaConnectorService; + } + + @Override + public void onApplicationEvent(ApplicationStartedEvent event){ + final StreamsBuilder builder = new StreamsBuilder(); + + final String applicationCommunicationMetadata = new ApplicationCommunicationMetadata().name(); + final String applicationCommunicationMessages = new ApplicationCommunicationMessages().name(); + + builder.stream( + new ApplicationCommunicationMessages().name(), + Consumed.with(Topology.AutoOffsetReset.LATEST) + ).filter((messageId, message) -> message != null && isNewMessage(message) && message.getIsFromContact()) + .flatMap((messageId, message) -> rasaConnectorService.send(message)) + .to((recordId, record, context) -> { + if (record instanceof Metadata) { + return applicationCommunicationMetadata; + } + if (record instanceof Message) { + return applicationCommunicationMessages; + } + + throw new IllegalStateException("Unknown type for record " + record); + }); + + streams.start(builder.build(), appId); + } + + @Override + public void destroy(){ + if (streams != null) { + streams.close(); + } + } + + @Override + public Health health() { + return Health.up().build(); + } + + // visible for testing + KafkaStreams.State getStreamState() { + return streams.state(); + } +} diff --git a/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/models/AiryAttachment.java b/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/models/AiryAttachment.java new file mode 100644 index 0000000000..355abd6443 --- /dev/null +++ b/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/models/AiryAttachment.java @@ -0,0 +1,20 @@ +package co.airy.core.rasa_connector.models; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class AiryAttachment { + String type; + AiryPayload payload; +} diff --git a/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/models/AiryPayload.java b/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/models/AiryPayload.java new file mode 100644 index 0000000000..3b1d925231 --- /dev/null +++ b/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/models/AiryPayload.java @@ -0,0 +1,19 @@ +package co.airy.core.rasa_connector.models; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class AiryPayload { + String url; +} diff --git a/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/models/AiryResponse.java b/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/models/AiryResponse.java new file mode 100644 index 0000000000..1e53bda4c6 --- /dev/null +++ b/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/models/AiryResponse.java @@ -0,0 +1,18 @@ +package co.airy.core.rasa_connector.models; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class AiryResponse { + AiryAttachment attachment; +} diff --git a/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/models/MessageSend.java b/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/models/MessageSend.java new file mode 100644 index 0000000000..b26c359009 --- /dev/null +++ b/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/models/MessageSend.java @@ -0,0 +1,23 @@ +package co.airy.core.rasa_connector.models; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.lang.String; + + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) + +public class MessageSend { + private String message; + private String sender; +} diff --git a/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/models/MessageSendResponse.java b/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/models/MessageSendResponse.java new file mode 100644 index 0000000000..2bfd64595f --- /dev/null +++ b/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/models/MessageSendResponse.java @@ -0,0 +1,19 @@ +package co.airy.core.rasa_connector.models; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class MessageSendResponse { + private String recipientId; + private String text; + private String image; +} diff --git a/backend/components/rasa/src/main/resources/application.properties b/backend/components/rasa/src/main/resources/application.properties new file mode 100644 index 0000000000..e00cf07acc --- /dev/null +++ b/backend/components/rasa/src/main/resources/application.properties @@ -0,0 +1,5 @@ +thpool.core-pool-size=1 +thpool.max-pool-size=2 +thpool.queue-capacity=0 + +rasa.rest-webhook-url=${RASA_WEBHOOK_URL} diff --git a/backend/components/rasa/src/test/java/co/airy/core/rasa_connector/RasaConnectorServiceTest.java b/backend/components/rasa/src/test/java/co/airy/core/rasa_connector/RasaConnectorServiceTest.java new file mode 100644 index 0000000000..878e9261fc --- /dev/null +++ b/backend/components/rasa/src/test/java/co/airy/core/rasa_connector/RasaConnectorServiceTest.java @@ -0,0 +1,91 @@ +package co.airy.core.rasa_connector; + +import co.airy.avro.communication.DeliveryState; +import co.airy.avro.communication.Message; +import co.airy.core.rasa_connector.models.MessageSend; +import co.airy.kafka.schema.application.ApplicationCommunicationMessages; +import co.airy.kafka.test.KafkaTestHelper; +import co.airy.kafka.test.junit.SharedKafkaTestResource; +import co.airy.spring.core.AirySpringBootApplication; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.ArgumentCaptor; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.time.Instant; +import java.util.UUID; + +import static co.airy.test.Timing.retryOnException; +import static org.apache.kafka.streams.KafkaStreams.State.RUNNING; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.verify; + +@SpringBootTest(classes = AirySpringBootApplication.class) +@TestPropertySource(value = "classpath:test.properties") +@ExtendWith(SpringExtension.class) +@AutoConfigureMockMvc +public class RasaConnectorServiceTest { + @RegisterExtension + public static final SharedKafkaTestResource sharedKafkaTestResource = new SharedKafkaTestResource(); + public static final ApplicationCommunicationMessages applicationCommunicationMessages = new ApplicationCommunicationMessages(); + private static KafkaTestHelper kafkaTestHelper; + + @MockBean + RasaClient rasaClient; + + @Autowired + Stores stores; + + @BeforeAll + //overloads the kafkaTestHelper.beforeAll with + static void beforeAll() throws Exception { + kafkaTestHelper = new KafkaTestHelper(sharedKafkaTestResource, applicationCommunicationMessages); + kafkaTestHelper.beforeAll(); + } + + @BeforeEach + void beforeEach() throws InterruptedException { + MockitoAnnotations.openMocks(this); + retryOnException(() -> assertEquals(stores.getStreamState(), RUNNING), "Failed to reach RUNNING state."); + } + + @AfterAll + static void afterAll() throws Exception { + kafkaTestHelper.afterAll(); + } + + @Test + public void callsRasaClientSendWhenMessageIsValid() throws Exception { + //given + String textMessage = "Hello from Airy"; + sendMessage(textMessage); + //when + ArgumentCaptor messageSendArgumentCaptor = ArgumentCaptor.forClass(MessageSend.class); + //then + retryOnException(() -> { + verify(rasaClient).sendMessage(messageSendArgumentCaptor.capture()); + MessageSend sentMessage = messageSendArgumentCaptor.getValue(); + assertThat(sentMessage.getMessage(), equalTo(textMessage)); + }, "message was not created"); + } + + private Message sendMessage(String text) throws Exception { + final String Id = UUID.randomUUID().toString(); + final Message message = Message.newBuilder().setId(Id).setSource("test-source").setSentAt(Instant.now().toEpochMilli()).setSenderId("test-sender-id").setDeliveryState(DeliveryState.DELIVERED).setConversationId("test-conversation-id").setChannelId("test-channel-id").setContent(String.format("{\"text\": \"%s\"}", text)).setIsFromContact(true).build(); + kafkaTestHelper.produceRecord(new ProducerRecord<>(applicationCommunicationMessages.name(), Id, message)); + return message; + } +} diff --git a/backend/components/rasa/src/test/resources/test.properties b/backend/components/rasa/src/test/resources/test.properties new file mode 100644 index 0000000000..511fe2b7c4 --- /dev/null +++ b/backend/components/rasa/src/test/resources/test.properties @@ -0,0 +1,7 @@ +kafka.cleanup=true +kafka.commit-interval-ms=0 +kafka.cache.max.bytes=0 + +thpool.core-pool-size=1 +thpool.max-pool-size=1 +thpool.queue-capacity=1 diff --git a/backend/sources/api/BUILD b/backend/components/sources-api/BUILD similarity index 100% rename from backend/sources/api/BUILD rename to backend/components/sources-api/BUILD diff --git a/backend/sources/google/helm/BUILD b/backend/components/sources-api/helm/BUILD similarity index 100% rename from backend/sources/google/helm/BUILD rename to backend/components/sources-api/helm/BUILD diff --git a/backend/sources/api/helm/Chart.yaml b/backend/components/sources-api/helm/Chart.yaml similarity index 100% rename from backend/sources/api/helm/Chart.yaml rename to backend/components/sources-api/helm/Chart.yaml diff --git a/backend/sources/google/helm/templates/configmap.yaml b/backend/components/sources-api/helm/templates/configmap.yaml similarity index 100% rename from backend/sources/google/helm/templates/configmap.yaml rename to backend/components/sources-api/helm/templates/configmap.yaml diff --git a/backend/sources/api/helm/templates/deployment.yaml b/backend/components/sources-api/helm/templates/deployment.yaml similarity index 100% rename from backend/sources/api/helm/templates/deployment.yaml rename to backend/components/sources-api/helm/templates/deployment.yaml diff --git a/backend/sources/chat-plugin/helm/templates/backend/service.yaml b/backend/components/sources-api/helm/templates/service.yaml similarity index 100% rename from backend/sources/chat-plugin/helm/templates/backend/service.yaml rename to backend/components/sources-api/helm/templates/service.yaml diff --git a/backend/sources/api/helm/values.yaml b/backend/components/sources-api/helm/values.yaml similarity index 100% rename from backend/sources/api/helm/values.yaml rename to backend/components/sources-api/helm/values.yaml diff --git a/backend/sources/api/src/main/java/co/airy/core/sources/api/ChannelsController.java b/backend/components/sources-api/src/main/java/co/airy/core/sources/api/ChannelsController.java similarity index 100% rename from backend/sources/api/src/main/java/co/airy/core/sources/api/ChannelsController.java rename to backend/components/sources-api/src/main/java/co/airy/core/sources/api/ChannelsController.java diff --git a/backend/sources/api/src/main/java/co/airy/core/sources/api/SourcesController.java b/backend/components/sources-api/src/main/java/co/airy/core/sources/api/SourcesController.java similarity index 100% rename from backend/sources/api/src/main/java/co/airy/core/sources/api/SourcesController.java rename to backend/components/sources-api/src/main/java/co/airy/core/sources/api/SourcesController.java diff --git a/backend/sources/api/src/main/java/co/airy/core/sources/api/Stores.java b/backend/components/sources-api/src/main/java/co/airy/core/sources/api/Stores.java similarity index 100% rename from backend/sources/api/src/main/java/co/airy/core/sources/api/Stores.java rename to backend/components/sources-api/src/main/java/co/airy/core/sources/api/Stores.java diff --git a/backend/sources/api/src/main/java/co/airy/core/sources/api/WebhookController.java b/backend/components/sources-api/src/main/java/co/airy/core/sources/api/WebhookController.java similarity index 100% rename from backend/sources/api/src/main/java/co/airy/core/sources/api/WebhookController.java rename to backend/components/sources-api/src/main/java/co/airy/core/sources/api/WebhookController.java diff --git a/backend/sources/api/src/main/java/co/airy/core/sources/api/actions/Actions.java b/backend/components/sources-api/src/main/java/co/airy/core/sources/api/actions/Actions.java similarity index 100% rename from backend/sources/api/src/main/java/co/airy/core/sources/api/actions/Actions.java rename to backend/components/sources-api/src/main/java/co/airy/core/sources/api/actions/Actions.java diff --git a/backend/sources/api/src/main/java/co/airy/core/sources/api/actions/ApiException.java b/backend/components/sources-api/src/main/java/co/airy/core/sources/api/actions/ApiException.java similarity index 100% rename from backend/sources/api/src/main/java/co/airy/core/sources/api/actions/ApiException.java rename to backend/components/sources-api/src/main/java/co/airy/core/sources/api/actions/ApiException.java diff --git a/backend/sources/api/src/main/java/co/airy/core/sources/api/actions/Endpoint.java b/backend/components/sources-api/src/main/java/co/airy/core/sources/api/actions/Endpoint.java similarity index 100% rename from backend/sources/api/src/main/java/co/airy/core/sources/api/actions/Endpoint.java rename to backend/components/sources-api/src/main/java/co/airy/core/sources/api/actions/Endpoint.java diff --git a/backend/sources/api/src/main/java/co/airy/core/sources/api/actions/EndpointConfig.java b/backend/components/sources-api/src/main/java/co/airy/core/sources/api/actions/EndpointConfig.java similarity index 100% rename from backend/sources/api/src/main/java/co/airy/core/sources/api/actions/EndpointConfig.java rename to backend/components/sources-api/src/main/java/co/airy/core/sources/api/actions/EndpointConfig.java diff --git a/backend/sources/api/src/main/java/co/airy/core/sources/api/actions/dto/SendMessage.java b/backend/components/sources-api/src/main/java/co/airy/core/sources/api/actions/dto/SendMessage.java similarity index 100% rename from backend/sources/api/src/main/java/co/airy/core/sources/api/actions/dto/SendMessage.java rename to backend/components/sources-api/src/main/java/co/airy/core/sources/api/actions/dto/SendMessage.java diff --git a/backend/sources/api/src/main/java/co/airy/core/sources/api/actions/payload/ActionPayload.java b/backend/components/sources-api/src/main/java/co/airy/core/sources/api/actions/payload/ActionPayload.java similarity index 100% rename from backend/sources/api/src/main/java/co/airy/core/sources/api/actions/payload/ActionPayload.java rename to backend/components/sources-api/src/main/java/co/airy/core/sources/api/actions/payload/ActionPayload.java diff --git a/backend/sources/api/src/main/java/co/airy/core/sources/api/actions/payload/ErrorResponsePayload.java b/backend/components/sources-api/src/main/java/co/airy/core/sources/api/actions/payload/ErrorResponsePayload.java similarity index 100% rename from backend/sources/api/src/main/java/co/airy/core/sources/api/actions/payload/ErrorResponsePayload.java rename to backend/components/sources-api/src/main/java/co/airy/core/sources/api/actions/payload/ErrorResponsePayload.java diff --git a/backend/sources/api/src/main/java/co/airy/core/sources/api/actions/payload/SendMessageRequestPayload.java b/backend/components/sources-api/src/main/java/co/airy/core/sources/api/actions/payload/SendMessageRequestPayload.java similarity index 100% rename from backend/sources/api/src/main/java/co/airy/core/sources/api/actions/payload/SendMessageRequestPayload.java rename to backend/components/sources-api/src/main/java/co/airy/core/sources/api/actions/payload/SendMessageRequestPayload.java diff --git a/backend/sources/api/src/main/java/co/airy/core/sources/api/actions/payload/SendMessageResponsePayload.java b/backend/components/sources-api/src/main/java/co/airy/core/sources/api/actions/payload/SendMessageResponsePayload.java similarity index 100% rename from backend/sources/api/src/main/java/co/airy/core/sources/api/actions/payload/SendMessageResponsePayload.java rename to backend/components/sources-api/src/main/java/co/airy/core/sources/api/actions/payload/SendMessageResponsePayload.java diff --git a/backend/sources/api/src/main/java/co/airy/core/sources/api/payload/ChannelsResponsePayload.java b/backend/components/sources-api/src/main/java/co/airy/core/sources/api/payload/ChannelsResponsePayload.java similarity index 100% rename from backend/sources/api/src/main/java/co/airy/core/sources/api/payload/ChannelsResponsePayload.java rename to backend/components/sources-api/src/main/java/co/airy/core/sources/api/payload/ChannelsResponsePayload.java diff --git a/backend/sources/api/src/main/java/co/airy/core/sources/api/payload/CreateChannelRequestPayload.java b/backend/components/sources-api/src/main/java/co/airy/core/sources/api/payload/CreateChannelRequestPayload.java similarity index 100% rename from backend/sources/api/src/main/java/co/airy/core/sources/api/payload/CreateChannelRequestPayload.java rename to backend/components/sources-api/src/main/java/co/airy/core/sources/api/payload/CreateChannelRequestPayload.java diff --git a/backend/sources/api/src/main/java/co/airy/core/sources/api/payload/CreateSourceRequestPayload.java b/backend/components/sources-api/src/main/java/co/airy/core/sources/api/payload/CreateSourceRequestPayload.java similarity index 100% rename from backend/sources/api/src/main/java/co/airy/core/sources/api/payload/CreateSourceRequestPayload.java rename to backend/components/sources-api/src/main/java/co/airy/core/sources/api/payload/CreateSourceRequestPayload.java diff --git a/backend/sources/api/src/main/java/co/airy/core/sources/api/payload/DeleteSourceRequestPayload.java b/backend/components/sources-api/src/main/java/co/airy/core/sources/api/payload/DeleteSourceRequestPayload.java similarity index 100% rename from backend/sources/api/src/main/java/co/airy/core/sources/api/payload/DeleteSourceRequestPayload.java rename to backend/components/sources-api/src/main/java/co/airy/core/sources/api/payload/DeleteSourceRequestPayload.java diff --git a/backend/sources/api/src/main/java/co/airy/core/sources/api/payload/DisconnectChannelRequestPayload.java b/backend/components/sources-api/src/main/java/co/airy/core/sources/api/payload/DisconnectChannelRequestPayload.java similarity index 100% rename from backend/sources/api/src/main/java/co/airy/core/sources/api/payload/DisconnectChannelRequestPayload.java rename to backend/components/sources-api/src/main/java/co/airy/core/sources/api/payload/DisconnectChannelRequestPayload.java diff --git a/backend/sources/api/src/main/java/co/airy/core/sources/api/payload/ListSourceResponsePayload.java b/backend/components/sources-api/src/main/java/co/airy/core/sources/api/payload/ListSourceResponsePayload.java similarity index 100% rename from backend/sources/api/src/main/java/co/airy/core/sources/api/payload/ListSourceResponsePayload.java rename to backend/components/sources-api/src/main/java/co/airy/core/sources/api/payload/ListSourceResponsePayload.java diff --git a/backend/sources/api/src/main/java/co/airy/core/sources/api/payload/SourceResponsePayload.java b/backend/components/sources-api/src/main/java/co/airy/core/sources/api/payload/SourceResponsePayload.java similarity index 100% rename from backend/sources/api/src/main/java/co/airy/core/sources/api/payload/SourceResponsePayload.java rename to backend/components/sources-api/src/main/java/co/airy/core/sources/api/payload/SourceResponsePayload.java diff --git a/backend/sources/api/src/main/java/co/airy/core/sources/api/payload/WebhookRequestPayload.java b/backend/components/sources-api/src/main/java/co/airy/core/sources/api/payload/WebhookRequestPayload.java similarity index 100% rename from backend/sources/api/src/main/java/co/airy/core/sources/api/payload/WebhookRequestPayload.java rename to backend/components/sources-api/src/main/java/co/airy/core/sources/api/payload/WebhookRequestPayload.java diff --git a/backend/sources/api/src/main/java/co/airy/core/sources/api/services/SourceToken.java b/backend/components/sources-api/src/main/java/co/airy/core/sources/api/services/SourceToken.java similarity index 100% rename from backend/sources/api/src/main/java/co/airy/core/sources/api/services/SourceToken.java rename to backend/components/sources-api/src/main/java/co/airy/core/sources/api/services/SourceToken.java diff --git a/backend/sources/api/src/test/java/co/airy/core/sources/api/ChannelsControllerTest.java b/backend/components/sources-api/src/test/java/co/airy/core/sources/api/ChannelsControllerTest.java similarity index 100% rename from backend/sources/api/src/test/java/co/airy/core/sources/api/ChannelsControllerTest.java rename to backend/components/sources-api/src/test/java/co/airy/core/sources/api/ChannelsControllerTest.java diff --git a/backend/sources/api/src/test/java/co/airy/core/sources/api/SendMessageTest.java b/backend/components/sources-api/src/test/java/co/airy/core/sources/api/SendMessageTest.java similarity index 100% rename from backend/sources/api/src/test/java/co/airy/core/sources/api/SendMessageTest.java rename to backend/components/sources-api/src/test/java/co/airy/core/sources/api/SendMessageTest.java diff --git a/backend/sources/api/src/test/java/co/airy/core/sources/api/WebhookControllerTest.java b/backend/components/sources-api/src/test/java/co/airy/core/sources/api/WebhookControllerTest.java similarity index 100% rename from backend/sources/api/src/test/java/co/airy/core/sources/api/WebhookControllerTest.java rename to backend/components/sources-api/src/test/java/co/airy/core/sources/api/WebhookControllerTest.java diff --git a/backend/sources/api/src/test/java/co/airy/core/sources/api/util/TestSource.java b/backend/components/sources-api/src/test/java/co/airy/core/sources/api/util/TestSource.java similarity index 100% rename from backend/sources/api/src/test/java/co/airy/core/sources/api/util/TestSource.java rename to backend/components/sources-api/src/test/java/co/airy/core/sources/api/util/TestSource.java diff --git a/backend/sources/api/src/test/java/co/airy/core/sources/api/util/Topics.java b/backend/components/sources-api/src/test/java/co/airy/core/sources/api/util/Topics.java similarity index 100% rename from backend/sources/api/src/test/java/co/airy/core/sources/api/util/Topics.java rename to backend/components/sources-api/src/test/java/co/airy/core/sources/api/util/Topics.java diff --git a/backend/sources/api/src/test/resources/test.properties b/backend/components/sources-api/src/test/resources/test.properties similarity index 100% rename from backend/sources/api/src/test/resources/test.properties rename to backend/components/sources-api/src/test/resources/test.properties diff --git a/backend/sources/api/src/test/resources/webhook.json b/backend/components/sources-api/src/test/resources/webhook.json similarity index 100% rename from backend/sources/api/src/test/resources/webhook.json rename to backend/components/sources-api/src/test/resources/webhook.json diff --git a/backend/sources/twilio/connector/BUILD b/backend/components/twilio/connector/BUILD similarity index 100% rename from backend/sources/twilio/connector/BUILD rename to backend/components/twilio/connector/BUILD diff --git a/backend/sources/twilio/connector/src/main/java/co/airy/core/sources/twilio/ChannelsController.java b/backend/components/twilio/connector/src/main/java/co/airy/core/sources/twilio/ChannelsController.java similarity index 100% rename from backend/sources/twilio/connector/src/main/java/co/airy/core/sources/twilio/ChannelsController.java rename to backend/components/twilio/connector/src/main/java/co/airy/core/sources/twilio/ChannelsController.java diff --git a/backend/sources/twilio/connector/src/main/java/co/airy/core/sources/twilio/Connector.java b/backend/components/twilio/connector/src/main/java/co/airy/core/sources/twilio/Connector.java similarity index 100% rename from backend/sources/twilio/connector/src/main/java/co/airy/core/sources/twilio/Connector.java rename to backend/components/twilio/connector/src/main/java/co/airy/core/sources/twilio/Connector.java diff --git a/backend/sources/twilio/connector/src/main/java/co/airy/core/sources/twilio/Stores.java b/backend/components/twilio/connector/src/main/java/co/airy/core/sources/twilio/Stores.java similarity index 100% rename from backend/sources/twilio/connector/src/main/java/co/airy/core/sources/twilio/Stores.java rename to backend/components/twilio/connector/src/main/java/co/airy/core/sources/twilio/Stores.java diff --git a/backend/sources/twilio/connector/src/main/java/co/airy/core/sources/twilio/WebhookController.java b/backend/components/twilio/connector/src/main/java/co/airy/core/sources/twilio/WebhookController.java similarity index 100% rename from backend/sources/twilio/connector/src/main/java/co/airy/core/sources/twilio/WebhookController.java rename to backend/components/twilio/connector/src/main/java/co/airy/core/sources/twilio/WebhookController.java diff --git a/backend/sources/twilio/connector/src/main/java/co/airy/core/sources/twilio/dto/SendMessageRequest.java b/backend/components/twilio/connector/src/main/java/co/airy/core/sources/twilio/dto/SendMessageRequest.java similarity index 100% rename from backend/sources/twilio/connector/src/main/java/co/airy/core/sources/twilio/dto/SendMessageRequest.java rename to backend/components/twilio/connector/src/main/java/co/airy/core/sources/twilio/dto/SendMessageRequest.java diff --git a/backend/sources/twilio/connector/src/main/java/co/airy/core/sources/twilio/services/Api.java b/backend/components/twilio/connector/src/main/java/co/airy/core/sources/twilio/services/Api.java similarity index 100% rename from backend/sources/twilio/connector/src/main/java/co/airy/core/sources/twilio/services/Api.java rename to backend/components/twilio/connector/src/main/java/co/airy/core/sources/twilio/services/Api.java diff --git a/backend/sources/twilio/connector/src/main/java/co/airy/core/sources/twilio/services/ApiException.java b/backend/components/twilio/connector/src/main/java/co/airy/core/sources/twilio/services/ApiException.java similarity index 100% rename from backend/sources/twilio/connector/src/main/java/co/airy/core/sources/twilio/services/ApiException.java rename to backend/components/twilio/connector/src/main/java/co/airy/core/sources/twilio/services/ApiException.java diff --git a/backend/sources/twilio/connector/src/main/resources/application.properties b/backend/components/twilio/connector/src/main/resources/application.properties similarity index 100% rename from backend/sources/twilio/connector/src/main/resources/application.properties rename to backend/components/twilio/connector/src/main/resources/application.properties diff --git a/backend/sources/twilio/connector/src/test/java/co/airy/core/sources/twilio/CreateConversationTest.java b/backend/components/twilio/connector/src/test/java/co/airy/core/sources/twilio/CreateConversationTest.java similarity index 100% rename from backend/sources/twilio/connector/src/test/java/co/airy/core/sources/twilio/CreateConversationTest.java rename to backend/components/twilio/connector/src/test/java/co/airy/core/sources/twilio/CreateConversationTest.java diff --git a/backend/sources/twilio/connector/src/test/java/co/airy/core/sources/twilio/SendMessageTest.java b/backend/components/twilio/connector/src/test/java/co/airy/core/sources/twilio/SendMessageTest.java similarity index 100% rename from backend/sources/twilio/connector/src/test/java/co/airy/core/sources/twilio/SendMessageTest.java rename to backend/components/twilio/connector/src/test/java/co/airy/core/sources/twilio/SendMessageTest.java diff --git a/backend/sources/twilio/connector/src/test/java/co/airy/core/sources/twilio/WebhookControllerTest.java b/backend/components/twilio/connector/src/test/java/co/airy/core/sources/twilio/WebhookControllerTest.java similarity index 100% rename from backend/sources/twilio/connector/src/test/java/co/airy/core/sources/twilio/WebhookControllerTest.java rename to backend/components/twilio/connector/src/test/java/co/airy/core/sources/twilio/WebhookControllerTest.java diff --git a/backend/sources/twilio/connector/src/test/resources/test.properties b/backend/components/twilio/connector/src/test/resources/test.properties similarity index 100% rename from backend/sources/twilio/connector/src/test/resources/test.properties rename to backend/components/twilio/connector/src/test/resources/test.properties diff --git a/backend/sources/twilio/events-router/BUILD b/backend/components/twilio/events-router/BUILD similarity index 100% rename from backend/sources/twilio/events-router/BUILD rename to backend/components/twilio/events-router/BUILD diff --git a/backend/sources/twilio/events-router/src/main/java/co/airy/core/sources/twilio/EventsRouter.java b/backend/components/twilio/events-router/src/main/java/co/airy/core/sources/twilio/EventsRouter.java similarity index 100% rename from backend/sources/twilio/events-router/src/main/java/co/airy/core/sources/twilio/EventsRouter.java rename to backend/components/twilio/events-router/src/main/java/co/airy/core/sources/twilio/EventsRouter.java diff --git a/backend/sources/twilio/events-router/src/main/java/co/airy/core/sources/twilio/TimestampExtractor.java b/backend/components/twilio/events-router/src/main/java/co/airy/core/sources/twilio/TimestampExtractor.java similarity index 100% rename from backend/sources/twilio/events-router/src/main/java/co/airy/core/sources/twilio/TimestampExtractor.java rename to backend/components/twilio/events-router/src/main/java/co/airy/core/sources/twilio/TimestampExtractor.java diff --git a/backend/sources/twilio/events-router/src/main/java/co/airy/core/sources/twilio/TwilioEventInfo.java b/backend/components/twilio/events-router/src/main/java/co/airy/core/sources/twilio/TwilioEventInfo.java similarity index 100% rename from backend/sources/twilio/events-router/src/main/java/co/airy/core/sources/twilio/TwilioEventInfo.java rename to backend/components/twilio/events-router/src/main/java/co/airy/core/sources/twilio/TwilioEventInfo.java diff --git a/backend/sources/twilio/events-router/src/main/java/co/airy/core/sources/twilio/TwilioInfoExtractor.java b/backend/components/twilio/events-router/src/main/java/co/airy/core/sources/twilio/TwilioInfoExtractor.java similarity index 100% rename from backend/sources/twilio/events-router/src/main/java/co/airy/core/sources/twilio/TwilioInfoExtractor.java rename to backend/components/twilio/events-router/src/main/java/co/airy/core/sources/twilio/TwilioInfoExtractor.java diff --git a/backend/sources/google/events-router/src/main/resources/application.properties b/backend/components/twilio/events-router/src/main/resources/application.properties similarity index 100% rename from backend/sources/google/events-router/src/main/resources/application.properties rename to backend/components/twilio/events-router/src/main/resources/application.properties diff --git a/backend/sources/twilio/events-router/src/test/java/co/airy/core/sources/twilio/EventsRouterTest.java b/backend/components/twilio/events-router/src/test/java/co/airy/core/sources/twilio/EventsRouterTest.java similarity index 100% rename from backend/sources/twilio/events-router/src/test/java/co/airy/core/sources/twilio/EventsRouterTest.java rename to backend/components/twilio/events-router/src/test/java/co/airy/core/sources/twilio/EventsRouterTest.java diff --git a/backend/sources/google/events-router/src/test/resources/test.properties b/backend/components/twilio/events-router/src/test/resources/test.properties similarity index 100% rename from backend/sources/google/events-router/src/test/resources/test.properties rename to backend/components/twilio/events-router/src/test/resources/test.properties diff --git a/backend/sources/twilio/helm/BUILD b/backend/components/twilio/helm/BUILD similarity index 100% rename from backend/sources/twilio/helm/BUILD rename to backend/components/twilio/helm/BUILD diff --git a/backend/sources/twilio/helm/Chart.yaml b/backend/components/twilio/helm/Chart.yaml similarity index 100% rename from backend/sources/twilio/helm/Chart.yaml rename to backend/components/twilio/helm/Chart.yaml diff --git a/backend/sources/twilio/helm/templates/configmap.yaml b/backend/components/twilio/helm/templates/configmap.yaml similarity index 100% rename from backend/sources/twilio/helm/templates/configmap.yaml rename to backend/components/twilio/helm/templates/configmap.yaml diff --git a/backend/sources/twilio/helm/templates/deployments.yaml b/backend/components/twilio/helm/templates/deployments.yaml similarity index 100% rename from backend/sources/twilio/helm/templates/deployments.yaml rename to backend/components/twilio/helm/templates/deployments.yaml diff --git a/backend/sources/twilio/helm/templates/service.yaml b/backend/components/twilio/helm/templates/service.yaml similarity index 100% rename from backend/sources/twilio/helm/templates/service.yaml rename to backend/components/twilio/helm/templates/service.yaml diff --git a/backend/sources/twilio/helm/values.yaml b/backend/components/twilio/helm/values.yaml similarity index 100% rename from backend/sources/twilio/helm/values.yaml rename to backend/components/twilio/helm/values.yaml diff --git a/backend/sources/viber/connector/BUILD b/backend/components/viber/connector/BUILD similarity index 100% rename from backend/sources/viber/connector/BUILD rename to backend/components/viber/connector/BUILD diff --git a/backend/sources/viber/connector/src/main/java/co/airy/core/sources/viber/ChannelsController.java b/backend/components/viber/connector/src/main/java/co/airy/core/sources/viber/ChannelsController.java similarity index 100% rename from backend/sources/viber/connector/src/main/java/co/airy/core/sources/viber/ChannelsController.java rename to backend/components/viber/connector/src/main/java/co/airy/core/sources/viber/ChannelsController.java diff --git a/backend/sources/viber/connector/src/main/java/co/airy/core/sources/viber/Connector.java b/backend/components/viber/connector/src/main/java/co/airy/core/sources/viber/Connector.java similarity index 100% rename from backend/sources/viber/connector/src/main/java/co/airy/core/sources/viber/Connector.java rename to backend/components/viber/connector/src/main/java/co/airy/core/sources/viber/Connector.java diff --git a/backend/sources/viber/connector/src/main/java/co/airy/core/sources/viber/EventsRouter.java b/backend/components/viber/connector/src/main/java/co/airy/core/sources/viber/EventsRouter.java similarity index 100% rename from backend/sources/viber/connector/src/main/java/co/airy/core/sources/viber/EventsRouter.java rename to backend/components/viber/connector/src/main/java/co/airy/core/sources/viber/EventsRouter.java diff --git a/backend/sources/viber/connector/src/main/java/co/airy/core/sources/viber/Stores.java b/backend/components/viber/connector/src/main/java/co/airy/core/sources/viber/Stores.java similarity index 100% rename from backend/sources/viber/connector/src/main/java/co/airy/core/sources/viber/Stores.java rename to backend/components/viber/connector/src/main/java/co/airy/core/sources/viber/Stores.java diff --git a/backend/sources/viber/connector/src/main/java/co/airy/core/sources/viber/WebhookController.java b/backend/components/viber/connector/src/main/java/co/airy/core/sources/viber/WebhookController.java similarity index 100% rename from backend/sources/viber/connector/src/main/java/co/airy/core/sources/viber/WebhookController.java rename to backend/components/viber/connector/src/main/java/co/airy/core/sources/viber/WebhookController.java diff --git a/backend/sources/viber/connector/src/main/java/co/airy/core/sources/viber/config/Account.java b/backend/components/viber/connector/src/main/java/co/airy/core/sources/viber/config/Account.java similarity index 97% rename from backend/sources/viber/connector/src/main/java/co/airy/core/sources/viber/config/Account.java rename to backend/components/viber/connector/src/main/java/co/airy/core/sources/viber/config/Account.java index 9f2484761e..ef9a6aed3b 100644 --- a/backend/sources/viber/connector/src/main/java/co/airy/core/sources/viber/config/Account.java +++ b/backend/components/viber/connector/src/main/java/co/airy/core/sources/viber/config/Account.java @@ -5,7 +5,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.viber.bot.ViberSignatureValidator; import com.viber.bot.api.ViberBot; import com.viber.bot.message.Message; @@ -76,6 +76,6 @@ public ObjectMapper viberObjectMapper() { return new ObjectMapper() .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .configure(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY, false) - .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); } } diff --git a/backend/sources/viber/connector/src/main/java/co/airy/core/sources/viber/dto/AccountInfo.java b/backend/components/viber/connector/src/main/java/co/airy/core/sources/viber/dto/AccountInfo.java similarity index 100% rename from backend/sources/viber/connector/src/main/java/co/airy/core/sources/viber/dto/AccountInfo.java rename to backend/components/viber/connector/src/main/java/co/airy/core/sources/viber/dto/AccountInfo.java diff --git a/backend/sources/viber/connector/src/main/java/co/airy/core/sources/viber/dto/SendMessageRequest.java b/backend/components/viber/connector/src/main/java/co/airy/core/sources/viber/dto/SendMessageRequest.java similarity index 100% rename from backend/sources/viber/connector/src/main/java/co/airy/core/sources/viber/dto/SendMessageRequest.java rename to backend/components/viber/connector/src/main/java/co/airy/core/sources/viber/dto/SendMessageRequest.java diff --git a/backend/sources/viber/connector/src/main/java/co/airy/core/sources/viber/dto/SendMessageResponse.java b/backend/components/viber/connector/src/main/java/co/airy/core/sources/viber/dto/SendMessageResponse.java similarity index 100% rename from backend/sources/viber/connector/src/main/java/co/airy/core/sources/viber/dto/SendMessageResponse.java rename to backend/components/viber/connector/src/main/java/co/airy/core/sources/viber/dto/SendMessageResponse.java diff --git a/backend/sources/viber/connector/src/main/java/co/airy/core/sources/viber/services/Api.java b/backend/components/viber/connector/src/main/java/co/airy/core/sources/viber/services/Api.java similarity index 100% rename from backend/sources/viber/connector/src/main/java/co/airy/core/sources/viber/services/Api.java rename to backend/components/viber/connector/src/main/java/co/airy/core/sources/viber/services/Api.java diff --git a/backend/sources/viber/connector/src/main/java/co/airy/core/sources/viber/services/ApiException.java b/backend/components/viber/connector/src/main/java/co/airy/core/sources/viber/services/ApiException.java similarity index 100% rename from backend/sources/viber/connector/src/main/java/co/airy/core/sources/viber/services/ApiException.java rename to backend/components/viber/connector/src/main/java/co/airy/core/sources/viber/services/ApiException.java diff --git a/backend/sources/twilio/events-router/src/main/resources/application.properties b/backend/components/viber/connector/src/main/resources/application.properties similarity index 100% rename from backend/sources/twilio/events-router/src/main/resources/application.properties rename to backend/components/viber/connector/src/main/resources/application.properties diff --git a/backend/sources/viber/connector/src/test/java/co/airy/core/sources/viber/ChannelsTest.java b/backend/components/viber/connector/src/test/java/co/airy/core/sources/viber/ChannelsTest.java similarity index 100% rename from backend/sources/viber/connector/src/test/java/co/airy/core/sources/viber/ChannelsTest.java rename to backend/components/viber/connector/src/test/java/co/airy/core/sources/viber/ChannelsTest.java diff --git a/backend/sources/viber/connector/src/test/java/co/airy/core/sources/viber/EventsRouterTest.java b/backend/components/viber/connector/src/test/java/co/airy/core/sources/viber/EventsRouterTest.java similarity index 100% rename from backend/sources/viber/connector/src/test/java/co/airy/core/sources/viber/EventsRouterTest.java rename to backend/components/viber/connector/src/test/java/co/airy/core/sources/viber/EventsRouterTest.java diff --git a/backend/sources/viber/connector/src/test/java/co/airy/core/sources/viber/SendMessageTest.java b/backend/components/viber/connector/src/test/java/co/airy/core/sources/viber/SendMessageTest.java similarity index 100% rename from backend/sources/viber/connector/src/test/java/co/airy/core/sources/viber/SendMessageTest.java rename to backend/components/viber/connector/src/test/java/co/airy/core/sources/viber/SendMessageTest.java diff --git a/backend/sources/viber/connector/src/test/java/co/airy/core/sources/viber/WebhookControllerTest.java b/backend/components/viber/connector/src/test/java/co/airy/core/sources/viber/WebhookControllerTest.java similarity index 100% rename from backend/sources/viber/connector/src/test/java/co/airy/core/sources/viber/WebhookControllerTest.java rename to backend/components/viber/connector/src/test/java/co/airy/core/sources/viber/WebhookControllerTest.java diff --git a/backend/sources/viber/connector/src/test/java/co/airy/core/sources/viber/lib/MockAccountInfo.java b/backend/components/viber/connector/src/test/java/co/airy/core/sources/viber/lib/MockAccountInfo.java similarity index 100% rename from backend/sources/viber/connector/src/test/java/co/airy/core/sources/viber/lib/MockAccountInfo.java rename to backend/components/viber/connector/src/test/java/co/airy/core/sources/viber/lib/MockAccountInfo.java diff --git a/backend/sources/viber/connector/src/test/java/co/airy/core/sources/viber/lib/Topics.java b/backend/components/viber/connector/src/test/java/co/airy/core/sources/viber/lib/Topics.java similarity index 100% rename from backend/sources/viber/connector/src/test/java/co/airy/core/sources/viber/lib/Topics.java rename to backend/components/viber/connector/src/test/java/co/airy/core/sources/viber/lib/Topics.java diff --git a/backend/sources/viber/connector/src/test/resources/events/conversation_started.json b/backend/components/viber/connector/src/test/resources/events/conversation_started.json similarity index 100% rename from backend/sources/viber/connector/src/test/resources/events/conversation_started.json rename to backend/components/viber/connector/src/test/resources/events/conversation_started.json diff --git a/backend/sources/viber/connector/src/test/resources/events/message_delivered.json b/backend/components/viber/connector/src/test/resources/events/message_delivered.json similarity index 100% rename from backend/sources/viber/connector/src/test/resources/events/message_delivered.json rename to backend/components/viber/connector/src/test/resources/events/message_delivered.json diff --git a/backend/sources/viber/connector/src/test/resources/events/message_received.json b/backend/components/viber/connector/src/test/resources/events/message_received.json similarity index 100% rename from backend/sources/viber/connector/src/test/resources/events/message_received.json rename to backend/components/viber/connector/src/test/resources/events/message_received.json diff --git a/backend/sources/viber/connector/src/test/resources/events/message_seen.json b/backend/components/viber/connector/src/test/resources/events/message_seen.json similarity index 100% rename from backend/sources/viber/connector/src/test/resources/events/message_seen.json rename to backend/components/viber/connector/src/test/resources/events/message_seen.json diff --git a/backend/sources/viber/connector/src/test/resources/test.properties b/backend/components/viber/connector/src/test/resources/test.properties similarity index 100% rename from backend/sources/viber/connector/src/test/resources/test.properties rename to backend/components/viber/connector/src/test/resources/test.properties diff --git a/backend/sources/viber/helm/BUILD b/backend/components/viber/helm/BUILD similarity index 100% rename from backend/sources/viber/helm/BUILD rename to backend/components/viber/helm/BUILD diff --git a/backend/sources/viber/helm/Chart.yaml b/backend/components/viber/helm/Chart.yaml similarity index 100% rename from backend/sources/viber/helm/Chart.yaml rename to backend/components/viber/helm/Chart.yaml diff --git a/backend/sources/viber/helm/templates/configmap.yaml b/backend/components/viber/helm/templates/configmap.yaml similarity index 100% rename from backend/sources/viber/helm/templates/configmap.yaml rename to backend/components/viber/helm/templates/configmap.yaml diff --git a/backend/sources/viber/helm/templates/deployments.yaml b/backend/components/viber/helm/templates/deployments.yaml similarity index 100% rename from backend/sources/viber/helm/templates/deployments.yaml rename to backend/components/viber/helm/templates/deployments.yaml diff --git a/backend/sources/viber/helm/templates/service.yaml b/backend/components/viber/helm/templates/service.yaml similarity index 100% rename from backend/sources/viber/helm/templates/service.yaml rename to backend/components/viber/helm/templates/service.yaml diff --git a/backend/sources/viber/helm/values.yaml b/backend/components/viber/helm/values.yaml similarity index 100% rename from backend/sources/viber/helm/values.yaml rename to backend/components/viber/helm/values.yaml diff --git a/backend/webhook/consumer/BUILD b/backend/components/webhook/consumer/BUILD similarity index 96% rename from backend/webhook/consumer/BUILD rename to backend/components/webhook/consumer/BUILD index 48d4e23419..47dd780c2f 100644 --- a/backend/webhook/consumer/BUILD +++ b/backend/components/webhook/consumer/BUILD @@ -5,7 +5,7 @@ load("//tools/build:container_release.bzl", "container_release") app_deps = [ "//backend:base_app", - "//backend/webhook/lib", + "//backend/components/webhook/lib", "//backend/model/message", "//backend/model/metadata", "//backend:webhook", diff --git a/backend/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/Consumer.java b/backend/components/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/Consumer.java similarity index 93% rename from backend/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/Consumer.java rename to backend/components/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/Consumer.java index 4955507d49..d6eee4edce 100644 --- a/backend/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/Consumer.java +++ b/backend/components/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/Consumer.java @@ -6,7 +6,7 @@ import com.dinstone.beanstalkc.JobConsumer; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; import org.slf4j.Logger; import org.springframework.beans.factory.DisposableBean; import org.springframework.scheduling.annotation.Scheduled; @@ -21,7 +21,7 @@ public class Consumer implements DisposableBean { private static final Logger log = AiryLoggerFactory.getLogger(Consumer.class); private final ObjectMapper objectMapper = new ObjectMapper() - .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE) + .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE) .setSerializationInclusion(JsonInclude.Include.NON_NULL); private final JobConsumer consumer; diff --git a/backend/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/Sender.java b/backend/components/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/Sender.java similarity index 100% rename from backend/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/Sender.java rename to backend/components/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/Sender.java diff --git a/backend/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/Stores.java b/backend/components/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/Stores.java similarity index 100% rename from backend/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/Stores.java rename to backend/components/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/Stores.java index 686f40330b..c19883bbe2 100644 --- a/backend/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/Stores.java +++ b/backend/components/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/Stores.java @@ -7,9 +7,9 @@ import org.apache.kafka.streams.StreamsBuilder; import org.apache.kafka.streams.kstream.Materialized; import org.apache.kafka.streams.state.ReadOnlyKeyValueStore; +import org.springframework.beans.factory.DisposableBean; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.beans.factory.DisposableBean; import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; diff --git a/backend/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/config/BeanstalkConsumerConfig.java b/backend/components/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/config/BeanstalkConsumerConfig.java similarity index 100% rename from backend/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/config/BeanstalkConsumerConfig.java rename to backend/components/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/config/BeanstalkConsumerConfig.java diff --git a/backend/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/config/SenderConfig.java b/backend/components/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/config/SenderConfig.java similarity index 100% rename from backend/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/config/SenderConfig.java rename to backend/components/webhook/consumer/src/main/java/co/airy/core/webhook/consumer/config/SenderConfig.java diff --git a/backend/webhook/consumer/src/main/resources/application.properties b/backend/components/webhook/consumer/src/main/resources/application.properties similarity index 100% rename from backend/webhook/consumer/src/main/resources/application.properties rename to backend/components/webhook/consumer/src/main/resources/application.properties diff --git a/backend/webhook/consumer/src/test/java/co/airy/core/webhook/consumer/ConsumerTest.java b/backend/components/webhook/consumer/src/test/java/co/airy/core/webhook/consumer/ConsumerTest.java similarity index 100% rename from backend/webhook/consumer/src/test/java/co/airy/core/webhook/consumer/ConsumerTest.java rename to backend/components/webhook/consumer/src/test/java/co/airy/core/webhook/consumer/ConsumerTest.java diff --git a/backend/webhook/consumer/src/test/resources/test.properties b/backend/components/webhook/consumer/src/test/resources/test.properties similarity index 100% rename from backend/webhook/consumer/src/test/resources/test.properties rename to backend/components/webhook/consumer/src/test/resources/test.properties diff --git a/backend/sources/whatsapp/helm/BUILD b/backend/components/webhook/helm/BUILD similarity index 100% rename from backend/sources/whatsapp/helm/BUILD rename to backend/components/webhook/helm/BUILD diff --git a/backend/webhook/helm/Chart.yaml b/backend/components/webhook/helm/Chart.yaml similarity index 100% rename from backend/webhook/helm/Chart.yaml rename to backend/components/webhook/helm/Chart.yaml diff --git a/backend/sources/whatsapp/helm/templates/configmap.yaml b/backend/components/webhook/helm/templates/configmap.yaml similarity index 100% rename from backend/sources/whatsapp/helm/templates/configmap.yaml rename to backend/components/webhook/helm/templates/configmap.yaml diff --git a/backend/webhook/helm/templates/deployments.yaml b/backend/components/webhook/helm/templates/deployments.yaml similarity index 100% rename from backend/webhook/helm/templates/deployments.yaml rename to backend/components/webhook/helm/templates/deployments.yaml diff --git a/backend/webhook/helm/templates/services.yaml b/backend/components/webhook/helm/templates/services.yaml similarity index 100% rename from backend/webhook/helm/templates/services.yaml rename to backend/components/webhook/helm/templates/services.yaml diff --git a/backend/webhook/helm/values.yaml b/backend/components/webhook/helm/values.yaml similarity index 100% rename from backend/webhook/helm/values.yaml rename to backend/components/webhook/helm/values.yaml diff --git a/backend/webhook/lib/BUILD b/backend/components/webhook/lib/BUILD similarity index 100% rename from backend/webhook/lib/BUILD rename to backend/components/webhook/lib/BUILD diff --git a/backend/webhook/lib/src/main/java/co/airy/core/webhook/WebhookEvent.java b/backend/components/webhook/lib/src/main/java/co/airy/core/webhook/WebhookEvent.java similarity index 100% rename from backend/webhook/lib/src/main/java/co/airy/core/webhook/WebhookEvent.java rename to backend/components/webhook/lib/src/main/java/co/airy/core/webhook/WebhookEvent.java diff --git a/backend/webhook/publisher/BUILD b/backend/components/webhook/publisher/BUILD similarity index 97% rename from backend/webhook/publisher/BUILD rename to backend/components/webhook/publisher/BUILD index f3627a8a69..efb4a8b1a2 100644 --- a/backend/webhook/publisher/BUILD +++ b/backend/components/webhook/publisher/BUILD @@ -5,7 +5,7 @@ load("//tools/build:container_release.bzl", "container_release") app_deps = [ "//backend:base_app", - "//backend/webhook/lib", + "//backend/components/webhook/lib", "//backend/model/message", "//backend/model/metadata", "//backend/model/channel", diff --git a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/BeanstalkPublisher.java b/backend/components/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/BeanstalkPublisher.java similarity index 89% rename from backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/BeanstalkPublisher.java rename to backend/components/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/BeanstalkPublisher.java index 80766889ab..55b5729729 100644 --- a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/BeanstalkPublisher.java +++ b/backend/components/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/BeanstalkPublisher.java @@ -6,7 +6,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; import org.slf4j.Logger; import org.springframework.stereotype.Service; @@ -15,7 +15,7 @@ public class BeanstalkPublisher { private static final Logger log = AiryLoggerFactory.getLogger(BeanstalkPublisher.class); private final ObjectMapper objectMapper = new ObjectMapper() - .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE) + .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE) .setSerializationInclusion(JsonInclude.Include.NON_NULL); private final JobProducer beanstalkdJobProducer; diff --git a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/Stores.java b/backend/components/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/Stores.java similarity index 100% rename from backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/Stores.java rename to backend/components/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/Stores.java diff --git a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/config/BeanstalkProducerConfig.java b/backend/components/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/config/BeanstalkProducerConfig.java similarity index 100% rename from backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/config/BeanstalkProducerConfig.java rename to backend/components/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/config/BeanstalkProducerConfig.java diff --git a/backend/webhook/publisher/src/main/resources/application.properties b/backend/components/webhook/publisher/src/main/resources/application.properties similarity index 100% rename from backend/webhook/publisher/src/main/resources/application.properties rename to backend/components/webhook/publisher/src/main/resources/application.properties diff --git a/backend/webhook/publisher/src/test/java/co/airy/core/webhook/publisher/PublisherTest.java b/backend/components/webhook/publisher/src/test/java/co/airy/core/webhook/publisher/PublisherTest.java similarity index 100% rename from backend/webhook/publisher/src/test/java/co/airy/core/webhook/publisher/PublisherTest.java rename to backend/components/webhook/publisher/src/test/java/co/airy/core/webhook/publisher/PublisherTest.java diff --git a/backend/webhook/publisher/src/test/resources/test.properties b/backend/components/webhook/publisher/src/test/resources/test.properties similarity index 100% rename from backend/webhook/publisher/src/test/resources/test.properties rename to backend/components/webhook/publisher/src/test/resources/test.properties diff --git a/backend/api/websocket/BUILD b/backend/components/websocket/BUILD similarity index 100% rename from backend/api/websocket/BUILD rename to backend/components/websocket/BUILD diff --git a/backend/api/websocket/src/main/java/co/airy/core/api/websocket/Stores.java b/backend/components/websocket/src/main/java/co/airy/core/api/websocket/Stores.java similarity index 100% rename from backend/api/websocket/src/main/java/co/airy/core/api/websocket/Stores.java rename to backend/components/websocket/src/main/java/co/airy/core/api/websocket/Stores.java diff --git a/backend/api/websocket/src/main/java/co/airy/core/api/websocket/WebSocketConfig.java b/backend/components/websocket/src/main/java/co/airy/core/api/websocket/WebSocketConfig.java similarity index 100% rename from backend/api/websocket/src/main/java/co/airy/core/api/websocket/WebSocketConfig.java rename to backend/components/websocket/src/main/java/co/airy/core/api/websocket/WebSocketConfig.java diff --git a/backend/api/websocket/src/main/java/co/airy/core/api/websocket/WebSocketController.java b/backend/components/websocket/src/main/java/co/airy/core/api/websocket/WebSocketController.java similarity index 100% rename from backend/api/websocket/src/main/java/co/airy/core/api/websocket/WebSocketController.java rename to backend/components/websocket/src/main/java/co/airy/core/api/websocket/WebSocketController.java diff --git a/backend/sources/viber/connector/src/main/resources/application.properties b/backend/components/websocket/src/main/resources/application.properties similarity index 100% rename from backend/sources/viber/connector/src/main/resources/application.properties rename to backend/components/websocket/src/main/resources/application.properties diff --git a/backend/api/websocket/src/test/java/co/airy/core/api/websocket/WebSocketControllerTest.java b/backend/components/websocket/src/test/java/co/airy/core/api/websocket/WebSocketControllerTest.java similarity index 98% rename from backend/api/websocket/src/test/java/co/airy/core/api/websocket/WebSocketControllerTest.java rename to backend/components/websocket/src/test/java/co/airy/core/api/websocket/WebSocketControllerTest.java index e8402e582a..f2ebd5364d 100644 --- a/backend/api/websocket/src/test/java/co/airy/core/api/websocket/WebSocketControllerTest.java +++ b/backend/components/websocket/src/test/java/co/airy/core/api/websocket/WebSocketControllerTest.java @@ -20,7 +20,7 @@ import co.airy.spring.core.AirySpringBootApplication; import co.airy.spring.test.WebTestHelper; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; import org.apache.kafka.clients.producer.ProducerRecord; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -184,7 +184,7 @@ void canSendTagEvents() throws Exception { private static StompSession connectToWs(int port) throws ExecutionException, InterruptedException { final WebSocketStompClient stompClient = new WebSocketStompClient(new StandardWebSocketClient()); MappingJackson2MessageConverter messageConverter = new MappingJackson2MessageConverter(); - ObjectMapper objectMapper = new ObjectMapper().setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + ObjectMapper objectMapper = new ObjectMapper().setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); messageConverter.setObjectMapper(objectMapper); stompClient.setMessageConverter(messageConverter); diff --git a/backend/sources/twilio/events-router/src/test/resources/test.properties b/backend/components/websocket/src/test/resources/test.properties similarity index 100% rename from backend/sources/twilio/events-router/src/test/resources/test.properties rename to backend/components/websocket/src/test/resources/test.properties diff --git a/backend/sources/whatsapp/connector/BUILD b/backend/components/whatsapp/connector/BUILD similarity index 100% rename from backend/sources/whatsapp/connector/BUILD rename to backend/components/whatsapp/connector/BUILD diff --git a/backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/ChannelsController.java b/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/ChannelsController.java similarity index 98% rename from backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/ChannelsController.java rename to backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/ChannelsController.java index c102518d35..00a5343c7e 100644 --- a/backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/ChannelsController.java +++ b/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/ChannelsController.java @@ -38,7 +38,7 @@ public ChannelsController(Api api, Stores stores) { @PostMapping("/channels.whatsapp.connect") ResponseEntity connectWhatsapp(@RequestBody @Valid ConnectChannelRequestPayload payload) { final String token = payload.getUserToken(); - final String phoneNumber = payload.getPhoneNumber(); + final String phoneNumber = payload.getPhoneNumberId(); final String channelId = UUIDv5.fromNamespaceAndName("whatsapp", phoneNumber).toString(); diff --git a/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/Connector.java b/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/Connector.java new file mode 100644 index 0000000000..5d6bb8a0b2 --- /dev/null +++ b/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/Connector.java @@ -0,0 +1,104 @@ +package co.airy.core.sources.whatsapp; + +import co.airy.avro.communication.DeliveryState; +import co.airy.avro.communication.Message; +import co.airy.avro.communication.Metadata; +import co.airy.core.sources.whatsapp.api.Api; +import co.airy.core.sources.whatsapp.api.ApiException; +import co.airy.core.sources.whatsapp.api.model.SendMessageResponse; +import co.airy.core.sources.whatsapp.dto.SendMessageRequest; +import co.airy.log.AiryLoggerFactory; +import co.airy.model.metadata.MetadataKeys; +import co.airy.spring.auth.IgnoreAuthPattern; +import co.airy.spring.web.filters.RequestLoggingIgnorePatterns; +import co.airy.tracking.RouteTracking; +import org.apache.avro.specific.SpecificRecordBase; +import org.apache.kafka.streams.KeyValue; +import org.slf4j.Logger; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import static co.airy.model.message.MessageRepository.updateDeliveryState; +import static co.airy.model.metadata.MetadataRepository.getId; +import static co.airy.model.metadata.MetadataRepository.newMessageMetadata; + +@Component +public class Connector { + private static final Logger log = AiryLoggerFactory.getLogger(Connector.class); + private final long messageStaleAfterSec = 300L; // 5 minutes + private final Api api; + + Connector(Api api) { + this.api = api; + } + + public List> sendMessage(SendMessageRequest sendMessageRequest) { + final Message message = sendMessageRequest.getMessage(); + + if (isMessageStale(message)) { + updateDeliveryState(message, DeliveryState.FAILED); + return List.of(KeyValue.pair(message.getId(), message)); + } + + if (sendMessageRequest.getConversation().getSourceConversationId() == null) { + // Cannot initiate a conversation for Whatsapp as a business + updateDeliveryState(message, DeliveryState.FAILED); + return List.of(KeyValue.pair(message.getId(), message)); + } + + try { + final SendMessageResponse response = api.sendMessage(sendMessageRequest); + final Metadata metadata = newMessageMetadata(message.getId(), MetadataKeys.MessageKeys.Source.ID, response.getMessageId()); + updateDeliveryState(message, DeliveryState.DELIVERED); + + return List.of(KeyValue.pair(message.getId(), message), KeyValue.pair(getId(metadata).toString(), metadata)); + } catch (ApiException e) { + log.error(String.format("Failed to send a \n SendMessageRequest: %s \n Api Exception: %s \n", sendMessageRequest, e.getMessage()), e); + final ArrayList> results = new ArrayList<>(); + final Metadata error = newMessageMetadata(message.getId(), MetadataKeys.MessageKeys.ERROR, e.getMessage()); + results.add(KeyValue.pair(getId(error).toString(), error)); + + if (e.getErrorPayload() != null) { + final Metadata errorPayload = newMessageMetadata(message.getId(), MetadataKeys.MessageKeys.Source.ERROR, e.getErrorPayload()); + results.add(KeyValue.pair(getId(errorPayload).toString(), errorPayload)); + } + updateDeliveryState(message, DeliveryState.FAILED); + results.add(KeyValue.pair(message.getId(), message)); + return results; + } catch (Exception e) { + log.error(String.format("Failed to send a \n SendMessageRequest: %s", sendMessageRequest), e); + final Metadata metadata = newMessageMetadata(message.getId(), MetadataKeys.MessageKeys.ERROR, e.getMessage()); + updateDeliveryState(message, DeliveryState.FAILED); + return List.of(KeyValue.pair(message.getId(), message), KeyValue.pair(getId(metadata).toString(), metadata)); + } + } + + private boolean isMessageStale(Message message) { + return ChronoUnit.SECONDS.between(Instant.ofEpochMilli(message.getSentAt()), Instant.now()) > messageStaleAfterSec; + } + + @Bean + public IgnoreAuthPattern ignoreAuthPattern() { + return new IgnoreAuthPattern("/whatsapp"); + } + + @Bean + public RequestLoggingIgnorePatterns requestLoggingIgnorePatterns() { + return new RequestLoggingIgnorePatterns(List.of("/whatsapp")); + } + + @Bean + private RouteTracking routeTracking() { + Pattern urlPattern = Pattern.compile(".*whatsapp\\.connect$"); + HashMap properties = new HashMap<>(Map.of("channel", "whatsapp")); + return new RouteTracking(urlPattern, "channel_connected", properties); + } +} diff --git a/backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/Stores.java b/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/Stores.java similarity index 95% rename from backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/Stores.java rename to backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/Stores.java index 5a04642abd..c53cd337b8 100644 --- a/backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/Stores.java +++ b/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/Stores.java @@ -16,7 +16,6 @@ import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.streams.KafkaStreams; -import org.apache.kafka.streams.KeyValue; import org.apache.kafka.streams.StreamsBuilder; import org.apache.kafka.streams.kstream.KStream; import org.apache.kafka.streams.kstream.KTable; @@ -29,15 +28,9 @@ import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Service; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; import java.util.concurrent.ExecutionException; import static co.airy.model.metadata.MetadataRepository.getId; -import static co.airy.model.metadata.MetadataRepository.getSubject; -import static co.airy.model.metadata.MetadataRepository.isConversationMetadata; @Service public class Stores implements ApplicationListener, DisposableBean, HealthIndicator { diff --git a/backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/WebhookController.java b/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/WebhookController.java similarity index 100% rename from backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/WebhookController.java rename to backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/WebhookController.java diff --git a/backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/api/Api.java b/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/api/Api.java similarity index 76% rename from backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/api/Api.java rename to backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/api/Api.java index 73682af0f0..f45542183b 100644 --- a/backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/api/Api.java +++ b/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/api/Api.java @@ -1,16 +1,19 @@ package co.airy.core.sources.whatsapp.api; import co.airy.core.sources.whatsapp.api.model.LongLivingUserAccessToken; +import co.airy.core.sources.whatsapp.api.model.SendMessageResponse; +import co.airy.core.sources.whatsapp.dto.SendMessageRequest; import co.airy.log.AiryLoggerFactory; -import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.ApplicationListener; +import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -33,29 +36,29 @@ public class Api implements ApplicationListener { private static final Logger log = AiryLoggerFactory.getLogger(Api.class); private final RestTemplateBuilder restTemplateBuilder; private final ObjectMapper objectMapper; + private final Mapper mapper; private RestTemplate restTemplate; - private static final String subscribedFields = "messages,messaging_postbacks,messaging_optins,message_deliveries,message_reads,messaging_payments,messaging_pre_checkouts,messaging_checkout_updates,messaging_account_linking,messaging_referrals,message_echoes,messaging_game_plays,standby,messaging_handovers,messaging_policy_enforcement,message_reactions,inbox_labels,message_reactions"; - private static final String baseUrl = "https://graph.facebook.com/v11.0"; - private static final String requestTemplate = baseUrl + "/me/messages?access_token=%s"; - private final String pageFields = "fields=id,name_with_location_descriptor,access_token,picture,is_webhooks_subscribed"; + private static final String baseUrl = "https://graph.facebook.com/v14.0"; + private static final String requestTemplate = baseUrl + "/%s/messages?access_token=%s"; private final HttpHeaders httpHeaders = new HttpHeaders(); private final String appId; private final String appSecret; public Api(RestTemplateBuilder restTemplateBuilder, + Mapper mapper, @Value("${appId}") String appId, - @Value("${appSecret}") String appSecret) { + @Value("${appSecret}") String appSecret, + @Qualifier("metaObjectMapper") ObjectMapper objectMapper) { + this.mapper = mapper; httpHeaders.setContentType(MediaType.APPLICATION_JSON); - this.objectMapper = new ObjectMapper() - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .configure(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY, false) - .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); this.restTemplateBuilder = restTemplateBuilder; this.appId = appId; this.appSecret = appSecret; + this.objectMapper = objectMapper; } + private T apiResponse(String url, HttpMethod method, Class clazz) throws Exception { ResponseEntity responseEntity = restTemplate.exchange(url, method, null, String.class); return objectMapper.readValue(responseEntity.getBody(), clazz); @@ -66,6 +69,15 @@ public String exchangeToLongLivingUserAccessToken(String userAccessToken) throws return apiResponse(apiUrl, HttpMethod.GET, LongLivingUserAccessToken.class).getAccessToken(); } + public SendMessageResponse sendMessage(SendMessageRequest sendMessageRequest) throws JsonProcessingException { + final String token = sendMessageRequest.getConversation().getChannel().getToken(); + final String phoneNumberId = sendMessageRequest.getConversation().getChannel().getSourceChannelId(); + final JsonNode payload = mapper.fromSendMessageRequest(sendMessageRequest); + String reqUrl = String.format(requestTemplate, phoneNumberId, token); + + final ResponseEntity responseEntity = restTemplate.postForEntity(reqUrl, new HttpEntity<>(payload, httpHeaders), SendMessageResponse.class); + return responseEntity.getBody(); + } @Override public void onApplicationEvent(ApplicationReadyEvent event) { diff --git a/backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/api/ApiException.java b/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/api/ApiException.java similarity index 100% rename from backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/api/ApiException.java rename to backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/api/ApiException.java diff --git a/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/api/Mapper.java b/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/api/Mapper.java new file mode 100644 index 0000000000..955bdeac58 --- /dev/null +++ b/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/api/Mapper.java @@ -0,0 +1,31 @@ +package co.airy.core.sources.whatsapp.api; + +import co.airy.avro.communication.Message; +import co.airy.core.sources.whatsapp.dto.Conversation; +import co.airy.core.sources.whatsapp.dto.SendMessageRequest; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; + + +@Service +public class Mapper { + private final ObjectMapper objectMapper; + + public Mapper(@Qualifier("metaObjectMapper") ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + public JsonNode fromSendMessageRequest(SendMessageRequest sendMessageRequest) throws JsonProcessingException { + final Message message = sendMessageRequest.getMessage(); + final Conversation conversation = sendMessageRequest.getConversation(); + final ObjectNode payload = ((ObjectNode) objectMapper.readTree(message.getContent())); + payload.put("messaging_product", "whatsapp"); + payload.put("recipient_type", "individual"); + payload.put("to", conversation.getSourceConversationId()); + return payload; + } +} diff --git a/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/api/MetaConfig.java b/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/api/MetaConfig.java new file mode 100644 index 0000000000..ab24ce7085 --- /dev/null +++ b/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/api/MetaConfig.java @@ -0,0 +1,19 @@ +package co.airy.core.sources.whatsapp.api; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class MetaConfig { + @Bean + @Qualifier("metaObjectMapper") + public ObjectMapper metaObjectMapper() { + return new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); + } +} diff --git a/backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/api/model/LongLivingUserAccessToken.java b/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/api/model/LongLivingUserAccessToken.java similarity index 100% rename from backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/api/model/LongLivingUserAccessToken.java rename to backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/api/model/LongLivingUserAccessToken.java diff --git a/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/api/model/SendMessageResponse.java b/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/api/model/SendMessageResponse.java new file mode 100644 index 0000000000..fa5273281d --- /dev/null +++ b/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/api/model/SendMessageResponse.java @@ -0,0 +1,41 @@ +package co.airy.core.sources.whatsapp.api.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SendMessageResponse { + private String messagingProduct; + private List contacts; + private List messages; + + public String getMessageId() { + if (messages != null && messages.size() > 0) { + return messages.get(0).getId(); + } + return null; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class Contact { + private String input; + private String waId; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class Message { + private String id; + } +} + diff --git a/backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/dto/Conversation.java b/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/dto/Conversation.java similarity index 100% rename from backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/dto/Conversation.java rename to backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/dto/Conversation.java diff --git a/backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/dto/SendMessageRequest.java b/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/dto/SendMessageRequest.java similarity index 100% rename from backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/dto/SendMessageRequest.java rename to backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/dto/SendMessageRequest.java diff --git a/backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/payload/ConnectChannelRequestPayload.java b/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/payload/ConnectChannelRequestPayload.java similarity index 92% rename from backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/payload/ConnectChannelRequestPayload.java rename to backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/payload/ConnectChannelRequestPayload.java index 3c4f533d94..d1f44ace0b 100644 --- a/backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/payload/ConnectChannelRequestPayload.java +++ b/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/payload/ConnectChannelRequestPayload.java @@ -13,7 +13,7 @@ @AllArgsConstructor public class ConnectChannelRequestPayload { @NotNull - private String phoneNumber; + private String phoneNumberId; @NotNull private String userToken; @NotNull diff --git a/backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/payload/DisconnectChannelRequestPayload.java b/backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/payload/DisconnectChannelRequestPayload.java similarity index 100% rename from backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/payload/DisconnectChannelRequestPayload.java rename to backend/components/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/payload/DisconnectChannelRequestPayload.java diff --git a/backend/sources/whatsapp/connector/src/test/java/co/airy/core/sources/whatsapp/ChannelsControllerTest.java b/backend/components/whatsapp/connector/src/test/java/co/airy/core/sources/whatsapp/ChannelsControllerTest.java similarity index 99% rename from backend/sources/whatsapp/connector/src/test/java/co/airy/core/sources/whatsapp/ChannelsControllerTest.java rename to backend/components/whatsapp/connector/src/test/java/co/airy/core/sources/whatsapp/ChannelsControllerTest.java index 53db9b8ce3..b6936b4050 100644 --- a/backend/sources/whatsapp/connector/src/test/java/co/airy/core/sources/whatsapp/ChannelsControllerTest.java +++ b/backend/components/whatsapp/connector/src/test/java/co/airy/core/sources/whatsapp/ChannelsControllerTest.java @@ -98,7 +98,7 @@ void canConnectWhatsappChannel() throws Exception { final String channelName = "My customer support phone number"; final ConnectChannelRequestPayload connectPayload = ConnectChannelRequestPayload.builder() - .phoneNumber(phoneNumber) + .phoneNumberId(phoneNumber) .name(channelName) .userToken("user token") .build(); diff --git a/backend/components/whatsapp/connector/src/test/java/co/airy/core/sources/whatsapp/SendMessageTest.java b/backend/components/whatsapp/connector/src/test/java/co/airy/core/sources/whatsapp/SendMessageTest.java new file mode 100644 index 0000000000..b012d8f281 --- /dev/null +++ b/backend/components/whatsapp/connector/src/test/java/co/airy/core/sources/whatsapp/SendMessageTest.java @@ -0,0 +1,189 @@ +package co.airy.core.sources.whatsapp; + +import co.airy.avro.communication.Channel; +import co.airy.avro.communication.ChannelConnectionState; +import co.airy.avro.communication.DeliveryState; +import co.airy.avro.communication.Message; +import co.airy.avro.communication.Metadata; +import co.airy.core.sources.whatsapp.api.Api; +import co.airy.core.sources.whatsapp.api.ApiException; +import co.airy.core.sources.whatsapp.api.model.SendMessageResponse; +import co.airy.core.sources.whatsapp.dto.SendMessageRequest; +import co.airy.kafka.schema.Topic; +import co.airy.kafka.schema.application.ApplicationCommunicationChannels; +import co.airy.kafka.schema.application.ApplicationCommunicationMessages; +import co.airy.kafka.schema.application.ApplicationCommunicationMetadata; +import co.airy.kafka.test.KafkaTestHelper; +import co.airy.kafka.test.junit.SharedKafkaTestResource; +import co.airy.model.metadata.MetadataKeys; +import co.airy.spring.core.AirySpringBootApplication; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.time.Instant; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static co.airy.model.metadata.MetadataRepository.getSubject; +import static co.airy.test.Timing.retryOnException; +import static org.apache.kafka.streams.KafkaStreams.State.RUNNING; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +@SpringBootTest(classes = AirySpringBootApplication.class) +@TestPropertySource(value = "classpath:test.properties") +@ExtendWith(SpringExtension.class) +class SendMessageTest { + + @RegisterExtension + public static final SharedKafkaTestResource sharedKafkaTestResource = new SharedKafkaTestResource(); + private static KafkaTestHelper kafkaTestHelper; + + private static final Topic applicationCommunicationChannels = new ApplicationCommunicationChannels(); + private static final Topic applicationCommunicationMessages = new ApplicationCommunicationMessages(); + private static final Topic applicationCommunicationMetadata = new ApplicationCommunicationMetadata(); + + @Autowired + @InjectMocks + private Connector connector; + + @Autowired + private Stores stores; + + @MockBean + private Api api; + + @BeforeAll + static void beforeAll() throws Exception { + kafkaTestHelper = new KafkaTestHelper(sharedKafkaTestResource, + applicationCommunicationChannels, + applicationCommunicationMessages, + applicationCommunicationMetadata + ); + + kafkaTestHelper.beforeAll(); + } + + @AfterAll + static void afterAll() throws Exception { + kafkaTestHelper.afterAll(); + } + + @BeforeEach + void beforeEach() throws InterruptedException { + MockitoAnnotations.openMocks(this); + retryOnException(() -> assertEquals(stores.getStreamState(), RUNNING), "Failed to reach RUNNING state."); + } + + @Test + void canSendMessage() throws Exception { + final String conversationId = "conversationId"; + final String messageId = "message-id"; + final String failingMessageId = "message-id-failing"; + final String sourceConversationId = "source-conversation-id"; + final String channelId = "channel-id"; + final String token = "token"; + final String text = "Hello World"; + final String errorMessage = "message delivery failed"; + final String whatsappMessageId = "whatsapp message id"; + + ArgumentCaptor payloadCaptor = ArgumentCaptor.forClass(SendMessageRequest.class); + when(api.sendMessage(payloadCaptor.capture())) + .thenReturn(SendMessageResponse.builder() + .messages(List.of(new SendMessageResponse.Message(whatsappMessageId))) + .build()) + .thenThrow(new ApiException(errorMessage)); + + kafkaTestHelper.produceRecords(List.of( + new ProducerRecord<>(applicationCommunicationChannels.name(), channelId, Channel.newBuilder() + .setToken(token) + .setSourceChannelId("ps-id") + .setSource("whatsapp") + .setId(channelId) + .setConnectionState(ChannelConnectionState.CONNECTED) + .build() + ), + new ProducerRecord<>(applicationCommunicationMessages.name(), "other-message-id", + Message.newBuilder() + .setId("other-message-id") + .setSource("whatsapp") + .setSentAt(Instant.now().toEpochMilli()) + .setSenderId(sourceConversationId) + .setDeliveryState(DeliveryState.DELIVERED) + .setConversationId(conversationId) + .setChannelId(channelId) + .setContent("{\"text\":\"" + text + "\"}") + .setIsFromContact(true) + .build()) + )); + + TimeUnit.SECONDS.sleep(5); + + final ObjectMapper objectMapper = new ObjectMapper(); + final JsonNode messagePayload = objectMapper.readTree("{\"text\":\"Hello Whatsapp\"}"); + + kafkaTestHelper.produceRecords(List.of(new ProducerRecord<>(applicationCommunicationMessages.name(), messageId, + Message.newBuilder() + .setId(messageId) + .setSentAt(Instant.now().toEpochMilli()) + .setSenderId("user-id") + .setDeliveryState(DeliveryState.PENDING) + .setConversationId(conversationId) + .setChannelId(channelId) + .setSource("whatsapp") + .setContent(objectMapper.writeValueAsString(messagePayload)) + .setIsFromContact(false) + .build()), + // This message should fail + new ProducerRecord<>(applicationCommunicationMessages.name(), messageId, + Message.newBuilder() + .setId(failingMessageId) + .setSentAt(Instant.now().toEpochMilli()) + .setSenderId("user-id") + .setDeliveryState(DeliveryState.PENDING) + .setConversationId(conversationId) + .setChannelId(channelId) + .setSource("whatsapp") + .setContent(objectMapper.writeValueAsString(messagePayload)) + .setIsFromContact(false) + .build()) + )); + + + final List metadataList = kafkaTestHelper.consumeValues(2, applicationCommunicationMetadata.name()); + + assertThat(metadataList.size(), equalTo(2)); + assertThat(metadataList.stream().anyMatch((metadata) -> + metadata.getKey().equals(MetadataKeys.MessageKeys.ERROR) + && metadata.getValue().equals(errorMessage) + && getSubject(metadata).getIdentifier().equals(failingMessageId)), equalTo(true)); + + final List messageList = kafkaTestHelper.consumeValues(5, applicationCommunicationMessages.name()); + + // 1 message for the conversation + // 2 messages pending + // 1 delivered, 1 failed + assertThat(messageList.size(), equalTo(5)); + assertThat(messageList.stream().anyMatch((message) -> message.getDeliveryState().equals(DeliveryState.DELIVERED) + && message.getId().equals(messageId)), equalTo(true)); + assertThat(messageList.stream().anyMatch((message) -> message.getDeliveryState().equals(DeliveryState.FAILED) + && message.getId().equals(failingMessageId)), equalTo(true)); + } +} diff --git a/backend/components/whatsapp/connector/src/test/java/co/airy/core/sources/whatsapp/WebhookControllerTest.java b/backend/components/whatsapp/connector/src/test/java/co/airy/core/sources/whatsapp/WebhookControllerTest.java new file mode 100644 index 0000000000..8d004f3414 --- /dev/null +++ b/backend/components/whatsapp/connector/src/test/java/co/airy/core/sources/whatsapp/WebhookControllerTest.java @@ -0,0 +1,73 @@ +package co.airy.core.sources.whatsapp; + +import co.airy.kafka.schema.Topic; +import co.airy.kafka.schema.source.SourceWhatsappEvents; +import co.airy.kafka.test.KafkaTestHelper; +import co.airy.kafka.test.junit.SharedKafkaTestResource; +import co.airy.spring.core.AirySpringBootApplication; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = AirySpringBootApplication.class) +@TestPropertySource(value = "classpath:test.properties") +@AutoConfigureMockMvc +@ExtendWith(SpringExtension.class) +class WebhookControllerTest { + @RegisterExtension + public static final SharedKafkaTestResource sharedKafkaTestResource = new SharedKafkaTestResource(); + + private static KafkaTestHelper kafkaTestHelper; + + private static final Topic sourceWhatsappEvents = new SourceWhatsappEvents(); + + @Autowired + private MockMvc mvc; + + @Autowired + private Stores stores; + + @BeforeAll + static void beforeAll() throws Exception { + kafkaTestHelper = new KafkaTestHelper(sharedKafkaTestResource, sourceWhatsappEvents); + kafkaTestHelper.beforeAll(); + } + + @AfterAll + static void afterAll() throws Exception { + kafkaTestHelper.afterAll(); + } + + @BeforeEach + void beforeEach() { + MockitoAnnotations.openMocks(this); + } + + @Test + void canAcceptAnything() throws Exception { + mvc.perform(post("/whatsapp").content("whatever")).andExpect(status().isOk()); + + List records = kafkaTestHelper.consumeValues(1, sourceWhatsappEvents.name()); + + assertThat(records, hasSize(1)); + assertEquals("whatever", records.get(0)); + } +} diff --git a/backend/sources/whatsapp/connector/src/test/resources/test.properties b/backend/components/whatsapp/connector/src/test/resources/test.properties similarity index 100% rename from backend/sources/whatsapp/connector/src/test/resources/test.properties rename to backend/components/whatsapp/connector/src/test/resources/test.properties diff --git a/backend/components/whatsapp/events-router/BUILD b/backend/components/whatsapp/events-router/BUILD new file mode 100644 index 0000000000..9f6d4b50d5 --- /dev/null +++ b/backend/components/whatsapp/events-router/BUILD @@ -0,0 +1,45 @@ +load("@com_github_airyhq_bazel_tools//lint:buildifier.bzl", "check_pkg") +load("//tools/build:springboot.bzl", "springboot") +load("//tools/build:junit5.bzl", "junit5") +load("//tools/build:container_release.bzl", "container_release") + +app_deps = [ + "//backend:base_app", + "//:springboot_actuator", + "//backend/model/channel", + "//backend/model/message", + "//backend/model/metadata", + "//lib/java/uuid", + "//lib/java/log", + "//lib/java/kafka/schema:source-whatsapp-events", + "//lib/java/spring/kafka/core:spring-kafka-core", + "//lib/java/spring/kafka/streams:spring-kafka-streams", +] + +springboot( + name = "events-router", + srcs = glob(["src/main/java/**/*.java"]), + main_class = "co.airy.spring.core.AirySpringBootApplication", + deps = app_deps, +) + +[ + junit5( + size = "medium", + file = file, + resources = glob(["src/test/resources/**/*"]), + deps = [ + ":app", + "//backend:base_test", + "//lib/java/kafka/test:kafka-test", + ] + app_deps, + ) + for file in glob(["src/test/java/**/*Test.java"]) +] + +container_release( + registry = "ghcr.io/airyhq/sources", + repository = "whatsapp-events-router", +) + +check_pkg(name = "buildifier") diff --git a/backend/components/whatsapp/events-router/src/main/java/co/airy/core/sources/whatsapp/EventsRouter.java b/backend/components/whatsapp/events-router/src/main/java/co/airy/core/sources/whatsapp/EventsRouter.java new file mode 100644 index 0000000000..3e011ded19 --- /dev/null +++ b/backend/components/whatsapp/events-router/src/main/java/co/airy/core/sources/whatsapp/EventsRouter.java @@ -0,0 +1,156 @@ +package co.airy.core.sources.whatsapp; + +import co.airy.avro.communication.Channel; +import co.airy.avro.communication.ChannelConnectionState; +import co.airy.avro.communication.Message; +import co.airy.avro.communication.Metadata; +import co.airy.core.sources.whatsapp.dto.Event; +import co.airy.core.sources.whatsapp.model.WebhookEntry.Change; +import co.airy.core.sources.whatsapp.model.WebhookEvent; +import co.airy.kafka.schema.application.ApplicationCommunicationChannels; +import co.airy.kafka.schema.application.ApplicationCommunicationMessages; +import co.airy.kafka.schema.application.ApplicationCommunicationMetadata; +import co.airy.kafka.schema.source.SourceWhatsappEvents; +import co.airy.kafka.streams.KafkaStreamsWrapper; +import co.airy.log.AiryLoggerFactory; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.avro.specific.SpecificRecordBase; +import org.apache.kafka.clients.producer.KafkaProducer; +import org.apache.kafka.streams.KafkaStreams; +import org.apache.kafka.streams.KeyValue; +import org.apache.kafka.streams.StreamsBuilder; +import org.apache.kafka.streams.kstream.KTable; +import org.slf4j.Logger; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toList; + +@Component +public class EventsRouter implements HealthIndicator, DisposableBean, ApplicationListener { + private static final Logger log = AiryLoggerFactory.getLogger(EventsRouter.class); + + private final String metadataStore = "metadata-store"; + private final KafkaStreamsWrapper streams; + private final ObjectMapper objectMapper; + private final MessageMapper messageMapper; + private final KafkaProducer kafkaProducer; + + EventsRouter(KafkaStreamsWrapper streams, ObjectMapper objectMapper, MessageMapper messageMapper, KafkaProducer kafkaProducer) { + this.streams = streams; + this.objectMapper = objectMapper; + this.messageMapper = messageMapper; + this.kafkaProducer = kafkaProducer; + } + + private static final String appId = "sources.whatsapp.EventsRouter"; + + private final String applicationCommunicationMetadata = new ApplicationCommunicationMetadata().name(); + private final String applicationCommunicationMessages = new ApplicationCommunicationMessages().name(); + + + public void startStream() { + final StreamsBuilder builder = new StreamsBuilder(); + + // Channels table + KTable channelsTable = builder.stream(new ApplicationCommunicationChannels().name()) + .groupBy((k, v) -> v.getSourceChannelId()) + .reduce((aggValue, newValue) -> newValue) + .filter((sourceChannelId, channel) -> "whatsapp".equals(channel.getSource()) + && channel.getConnectionState().equals(ChannelConnectionState.CONNECTED)); + + builder.stream(new SourceWhatsappEvents().name()) + .flatMap((key, event) -> { + WebhookEvent webhookEvent; + try { + webhookEvent = objectMapper.readValue(event, WebhookEvent.class); + if (webhookEvent.getEntries() == null) { + log.warn("empty entries. key={} event={}", key, event); + return Collections.emptyList(); + } + } catch (Exception e) { + log.warn("error in record. key={} event={} e={}", key, event, e.toString()); + return Collections.emptyList(); + } + + return webhookEvent.getEntries() + .stream() + .flatMap(entry -> { + final List changes = entry.getChanges(); + + if (changes == null) { + return Stream.empty(); + } + + return changes.stream().map(change -> { + try { + final String sourceChannelId = change.getValue().getMetadata().getPhoneNumberId(); + return KeyValue.pair(sourceChannelId, Event.builder().payload(change).build() + ); + } catch (Exception e) { + log.warn("Skipping whatsapp error for record " + entry, e); + return null; + } + }); + }) + .filter(Objects::nonNull) + .collect(toList()); + }) + .join(channelsTable, (event, channel) -> event.toBuilder().channel(channel).build()) + .flatMap((sourceChannelId, event) -> { + try { + return messageMapper.getRecords(event); + } catch (Exception e) { + log.warn("skip whatsapp record for error: " + event.toString(), e); + return List.of(); + } + }) + .to((recordId, record, context) -> { + if (record instanceof Metadata) { + return applicationCommunicationMetadata; + } + if (record instanceof Message) { + return applicationCommunicationMessages; + } + + throw new IllegalStateException("Unknown type for record " + record); + }); + + + streams.start(builder.build(), appId); + } + + @Override + public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { + startStream(); + } + + @Override + public void destroy() { + if (streams != null) { + streams.close(); + } + } + + @Override + public Health health() { + if (streams == null || !streams.state().isRunningOrRebalancing()) { + return Health.down().build(); + } + return Health.up().build(); + } + + // visible for testing + KafkaStreams.State getStreamState() { + return streams.state(); + } +} diff --git a/backend/components/whatsapp/events-router/src/main/java/co/airy/core/sources/whatsapp/MessageMapper.java b/backend/components/whatsapp/events-router/src/main/java/co/airy/core/sources/whatsapp/MessageMapper.java new file mode 100644 index 0000000000..e2c643e0d7 --- /dev/null +++ b/backend/components/whatsapp/events-router/src/main/java/co/airy/core/sources/whatsapp/MessageMapper.java @@ -0,0 +1,77 @@ +package co.airy.core.sources.whatsapp; + +import co.airy.avro.communication.Channel; +import co.airy.avro.communication.DeliveryState; +import co.airy.avro.communication.Message; +import co.airy.avro.communication.Metadata; +import co.airy.core.sources.whatsapp.dto.Event; +import co.airy.core.sources.whatsapp.model.Value; +import co.airy.core.sources.whatsapp.model.WebhookEntry; +import co.airy.log.AiryLoggerFactory; +import co.airy.model.metadata.MetadataKeys; +import co.airy.uuid.UUIDv5; +import com.fasterxml.jackson.databind.JsonNode; +import org.apache.avro.specific.SpecificRecordBase; +import org.apache.kafka.streams.KeyValue; +import org.slf4j.Logger; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static co.airy.model.metadata.MetadataRepository.getId; +import static co.airy.model.metadata.MetadataRepository.newConversationMetadata; + +@Component +public class MessageMapper { + private static final Logger log = AiryLoggerFactory.getLogger(MessageMapper.class); + + public List> getRecords(Event event) { + final WebhookEntry.Change change = event.getPayload(); + if(!"messages".equals(change.getField())) { + // TODO implement remaining fields + return List.of(); + } + + final Value value = change.getValue(); + + List> results = new ArrayList<>(); + for (Value.Contact contact : value.getContacts()) { + final String conversationId = getConversationId(event.getChannel(), contact.getWaId()); + final Metadata metadata = newConversationMetadata(conversationId, MetadataKeys.ConversationKeys.Contact.DISPLAY_NAME, contact.getProfile().getName()); + results.add(KeyValue.pair(getId(metadata).toString(), metadata)); + } + + for (JsonNode message : value.getMessages()) { + try { + final String sourceConversationId = message.get("from").textValue(); + final String conversationId = getConversationId(event.getChannel(), sourceConversationId); + final String id = message.get("id").textValue(); + final long timestamp = message.get("timestamp").asLong() * 1000; + + final Message airyMessage = Message.newBuilder() + .setChannelId(event.getChannel().getId()) + .setContent(message.toString()) + .setHeaders(Map.of()) + .setId(UUIDv5.fromName(id).toString()) + .setConversationId(conversationId) + .setIsFromContact(true) + .setDeliveryState(DeliveryState.DELIVERED) + .setSenderId(sourceConversationId) + .setSource("whatsapp") + .setSentAt(timestamp) + .build(); + results.add(KeyValue.pair(airyMessage.getId(), airyMessage)); + } catch (Exception e) { + log.error("Error mapping message", e); + } + } + + return results; + } + + private String getConversationId(Channel channel, String sourceConversationId) { + return UUIDv5.fromNamespaceAndName(channel.getId(), sourceConversationId).toString(); + } +} diff --git a/backend/components/whatsapp/events-router/src/main/java/co/airy/core/sources/whatsapp/dto/Event.java b/backend/components/whatsapp/events-router/src/main/java/co/airy/core/sources/whatsapp/dto/Event.java new file mode 100644 index 0000000000..a5501bbbaf --- /dev/null +++ b/backend/components/whatsapp/events-router/src/main/java/co/airy/core/sources/whatsapp/dto/Event.java @@ -0,0 +1,19 @@ +package co.airy.core.sources.whatsapp.dto; + +import co.airy.avro.communication.Channel; +import co.airy.core.sources.whatsapp.model.WebhookEntry; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder(toBuilder = true) +public class Event implements Serializable { + private WebhookEntry.Change payload; + private Channel channel; +} diff --git a/backend/components/whatsapp/events-router/src/main/java/co/airy/core/sources/whatsapp/model/Value.java b/backend/components/whatsapp/events-router/src/main/java/co/airy/core/sources/whatsapp/model/Value.java new file mode 100644 index 0000000000..93924815fa --- /dev/null +++ b/backend/components/whatsapp/events-router/src/main/java/co/airy/core/sources/whatsapp/model/Value.java @@ -0,0 +1,44 @@ +package co.airy.core.sources.whatsapp.model; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +// See https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/payload-examples +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Value { + private String messagingProduct; + private Metadata metadata; + private List contacts; + private List messages; + + private JsonNode statuses; + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class Metadata { + private String displayPhoneNumber; + private String phoneNumberId; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class Contact { + private Profile profile; + private String waId; + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class Profile { + private String name; + } + } +} diff --git a/backend/components/whatsapp/events-router/src/main/java/co/airy/core/sources/whatsapp/model/WebhookEntry.java b/backend/components/whatsapp/events-router/src/main/java/co/airy/core/sources/whatsapp/model/WebhookEntry.java new file mode 100644 index 0000000000..b312ada4d6 --- /dev/null +++ b/backend/components/whatsapp/events-router/src/main/java/co/airy/core/sources/whatsapp/model/WebhookEntry.java @@ -0,0 +1,25 @@ +package co.airy.core.sources.whatsapp.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class WebhookEntry { + // Whatsapp business account id + private String id; + private List changes; + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class Change { + private Value value; + private String field; + } +} + diff --git a/backend/components/whatsapp/events-router/src/main/java/co/airy/core/sources/whatsapp/model/WebhookEvent.java b/backend/components/whatsapp/events-router/src/main/java/co/airy/core/sources/whatsapp/model/WebhookEvent.java new file mode 100644 index 0000000000..c9482634b8 --- /dev/null +++ b/backend/components/whatsapp/events-router/src/main/java/co/airy/core/sources/whatsapp/model/WebhookEvent.java @@ -0,0 +1,18 @@ +package co.airy.core.sources.whatsapp.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class WebhookEvent { + private String object; + + @JsonProperty("entry") + private List entries; +} diff --git a/backend/components/whatsapp/events-router/src/test/java/co/airy/core/sources/whatsapp/EventsRouterTest.java b/backend/components/whatsapp/events-router/src/test/java/co/airy/core/sources/whatsapp/EventsRouterTest.java new file mode 100644 index 0000000000..877a453b88 --- /dev/null +++ b/backend/components/whatsapp/events-router/src/test/java/co/airy/core/sources/whatsapp/EventsRouterTest.java @@ -0,0 +1,130 @@ +package co.airy.core.sources.whatsapp; + +import co.airy.avro.communication.Channel; +import co.airy.avro.communication.ChannelConnectionState; +import co.airy.avro.communication.Message; +import co.airy.avro.communication.Metadata; +import co.airy.kafka.schema.Topic; +import co.airy.kafka.schema.application.ApplicationCommunicationChannels; +import co.airy.kafka.schema.application.ApplicationCommunicationMessages; +import co.airy.kafka.schema.application.ApplicationCommunicationMetadata; +import co.airy.kafka.schema.source.SourceWhatsappEvents; +import co.airy.kafka.test.KafkaTestHelper; +import co.airy.kafka.test.junit.SharedKafkaTestResource; +import co.airy.model.metadata.MetadataKeys; +import co.airy.spring.core.AirySpringBootApplication; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.util.StreamUtils; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import static co.airy.test.Timing.retryOnException; +import static org.apache.kafka.streams.KafkaStreams.State.RUNNING; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsCollectionWithSize.hasSize; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest(classes = AirySpringBootApplication.class) +@TestPropertySource(value = "classpath:test.properties") +@ExtendWith(SpringExtension.class) +class EventsRouterTest { + + @RegisterExtension + public static final SharedKafkaTestResource sharedKafkaTestResource = new SharedKafkaTestResource(); + private static KafkaTestHelper kafkaTestHelper; + + private static final Topic sourceWhatsappEvents = new SourceWhatsappEvents(); + private static final Topic applicationCommunicationChannels = new ApplicationCommunicationChannels(); + private static final Topic applicationCommunicationMessages = new ApplicationCommunicationMessages(); + private static final Topic applicationCommunicationMetadata = new ApplicationCommunicationMetadata(); + + @Autowired + private EventsRouter worker; + + @BeforeAll + static void beforeAll() throws Exception { + kafkaTestHelper = new KafkaTestHelper(sharedKafkaTestResource, + sourceWhatsappEvents, + applicationCommunicationChannels, + applicationCommunicationMessages, + applicationCommunicationMetadata + ); + + kafkaTestHelper.beforeAll(); + } + + @AfterAll + static void afterAll() throws Exception { + kafkaTestHelper.afterAll(); + } + + final String sourceChannelId = "103874789095437"; + final String channelId = "channel-id"; + boolean channelCreated = false; + + @BeforeEach + void beforeEach() throws Exception { + + retryOnException(() -> assertEquals(worker.getStreamState(), RUNNING), "Failed to reach RUNNING state."); + + if (!channelCreated) { + kafkaTestHelper.produceRecord(new ProducerRecord<>(applicationCommunicationChannels.name(), channelId, Channel.newBuilder() + .setId(channelId) + .setConnectionState(ChannelConnectionState.CONNECTED) + .setSourceChannelId(sourceChannelId) + .setSource("whatsapp") + .build())); + channelCreated = true; + TimeUnit.SECONDS.sleep(5); + } + } + + + @Test + void routesEvents() throws Exception { + routesMessage(); + } + + void routesMessage() throws Exception { + final String messagePayload = StreamUtils.copyToString(getClass().getClassLoader().getResourceAsStream("text.json"), StandardCharsets.UTF_8); + + kafkaTestHelper.produceRecord(new ProducerRecord<>(sourceWhatsappEvents.name(), UUID.randomUUID().toString(), String.format(messagePayload))); + + retryOnException(() -> { + List messages = kafkaTestHelper.consumeValues(1, applicationCommunicationMessages.name()); + assertThat(messages, hasSize(1)); + + final JsonNode jsonNode = new ObjectMapper().readTree(messagePayload); + final JsonNode messageNode = jsonNode.get("entry").get(0).get("changes").get(0).get("value").get("messages").get(0); + + Message message = messages.get(0); + assertThat(message.getIsFromContact(), equalTo(true)); + assertThat(message.getSource(), equalTo("whatsapp")); + assertThat(message.getSenderId(), equalTo(messageNode.get("from").asText())); + }, "message was not routed"); + + List metadataList = kafkaTestHelper.consumeValues(1, applicationCommunicationMetadata.name()); + assertThat(metadataList, hasSize(1)); + assertTrue(metadataList.stream().anyMatch((metadata -> + metadata.getKey().equals(MetadataKeys.ConversationKeys.Contact.DISPLAY_NAME) && + metadata.getValue().equals("Ada Lovelace") + ))); + } +} diff --git a/backend/components/whatsapp/events-router/src/test/resources/image.json b/backend/components/whatsapp/events-router/src/test/resources/image.json new file mode 100644 index 0000000000..94a95102de --- /dev/null +++ b/backend/components/whatsapp/events-router/src/test/resources/image.json @@ -0,0 +1,41 @@ +{ + "object": "whatsapp_business_account", + "entry": [ + { + "id": "103717885778113", + "changes": [ + { + "value": { + "messaging_product": "whatsapp", + "metadata": { + "display_phone_number": "15550798077", + "phone_number_id": "103874789095437" + }, + "contacts": [ + { + "profile": { + "name": "Ada Lovelace" + }, + "wa_id": "49157123456" + } + ], + "messages": [ + { + "from": "49157123456", + "id": "wamid.HBgNNDkxNTc4NjA4MTQ0MhUCABIYFDNBRkM3QjY0OTMyM0JCNkU2MzIyAA==", + "timestamp": "1660826619", + "type": "image", + "image": { + "mime_type": "image/jpeg", + "sha256": "b18othldU8OlRIUQZrb1vriXMXiJ0oOH+tN0RPYPcC0=", + "id": "646429513571185" + } + } + ] + }, + "field": "messages" + } + ] + } + ] +} diff --git a/backend/components/whatsapp/events-router/src/test/resources/status.json b/backend/components/whatsapp/events-router/src/test/resources/status.json new file mode 100644 index 0000000000..7c6f9ac0f6 --- /dev/null +++ b/backend/components/whatsapp/events-router/src/test/resources/status.json @@ -0,0 +1,39 @@ +{ + "object": "whatsapp_business_account", + "entry": [ + { + "id": "103717885778113", + "changes": [ + { + "value": { + "messaging_product": "whatsapp", + "metadata": { + "display_phone_number": "15550798077", + "phone_number_id": "103874789095437" + }, + "statuses": [ + { + "id": "wamid.HBgNNDkxNTc4NjA4MTQ0MhUCABEYEjdERDI5QUUzODFDNDg1MTExNAA=", + "status": "delivered", + "timestamp": "1660833175", + "recipient_id": "49157123456", + "conversation": { + "id": "58f18321bfb9f5e431cee542b4b5f260", + "origin": { + "type": "business_initiated" + } + }, + "pricing": { + "billable": true, + "pricing_model": "CBP", + "category": "business_initiated" + } + } + ] + }, + "field": "messages" + } + ] + } + ] +} diff --git a/backend/components/whatsapp/events-router/src/test/resources/test.properties b/backend/components/whatsapp/events-router/src/test/resources/test.properties new file mode 100644 index 0000000000..6812f1158c --- /dev/null +++ b/backend/components/whatsapp/events-router/src/test/resources/test.properties @@ -0,0 +1,4 @@ +kafka.cleanup=true +kafka.commit-interval-ms=100 +whatsapp.app-id=12345 +logs.enabled=false diff --git a/backend/components/whatsapp/events-router/src/test/resources/text.json b/backend/components/whatsapp/events-router/src/test/resources/text.json new file mode 100644 index 0000000000..f74e5f4c12 --- /dev/null +++ b/backend/components/whatsapp/events-router/src/test/resources/text.json @@ -0,0 +1,39 @@ +{ + "object": "whatsapp_business_account", + "entry": [ + { + "id": "103717885778113", + "changes": [ + { + "value": { + "messaging_product": "whatsapp", + "metadata": { + "display_phone_number": "15550798077", + "phone_number_id": "103874789095437" + }, + "contacts": [ + { + "profile": { + "name": "Ada Lovelace" + }, + "wa_id": "49157123456" + } + ], + "messages": [ + { + "from": "49157123456", + "id": "wamid.HBgNNDkxNTc4NjA4MTQ0MhUCABIYFDNFQjBENUJBMzUyRjg5OURGMTkyAA==", + "timestamp": "1660823417", + "text": { + "body": "Hello world" + }, + "type": "text" + } + ] + }, + "field": "messages" + } + ] + } + ] +} diff --git a/backend/webhook/helm/BUILD b/backend/components/whatsapp/helm/BUILD similarity index 100% rename from backend/webhook/helm/BUILD rename to backend/components/whatsapp/helm/BUILD diff --git a/backend/sources/whatsapp/helm/Chart.yaml b/backend/components/whatsapp/helm/Chart.yaml similarity index 100% rename from backend/sources/whatsapp/helm/Chart.yaml rename to backend/components/whatsapp/helm/Chart.yaml diff --git a/backend/webhook/helm/templates/configmap.yaml b/backend/components/whatsapp/helm/templates/configmap.yaml similarity index 100% rename from backend/webhook/helm/templates/configmap.yaml rename to backend/components/whatsapp/helm/templates/configmap.yaml diff --git a/backend/sources/whatsapp/helm/templates/deployments.yaml b/backend/components/whatsapp/helm/templates/deployments.yaml similarity index 94% rename from backend/sources/whatsapp/helm/templates/deployments.yaml rename to backend/components/whatsapp/helm/templates/deployments.yaml index d49b8054d3..a6289c8869 100644 --- a/backend/sources/whatsapp/helm/templates/deployments.yaml +++ b/backend/components/whatsapp/helm/templates/deployments.yaml @@ -96,14 +96,6 @@ spec: envFrom: - configMapRef: name: kafka-config - env: - - name: WHATSAPP_APP_ID - valueFrom: - configMapKeyRef: - name: "{{ .Values.name }}" - key: appId - - name: SERVICE_NAME - value: whatsapp-events-router livenessProbe: tcpSocket: port: 6000 diff --git a/backend/sources/whatsapp/helm/templates/service.yaml b/backend/components/whatsapp/helm/templates/service.yaml similarity index 100% rename from backend/sources/whatsapp/helm/templates/service.yaml rename to backend/components/whatsapp/helm/templates/service.yaml diff --git a/backend/sources/whatsapp/helm/values.yaml b/backend/components/whatsapp/helm/values.yaml similarity index 100% rename from backend/sources/whatsapp/helm/values.yaml rename to backend/components/whatsapp/helm/values.yaml diff --git a/backend/model/metadata/src/main/java/co/airy/model/metadata/MetadataKeys.java b/backend/model/metadata/src/main/java/co/airy/model/metadata/MetadataKeys.java index ab4864179a..8b4937e7e8 100644 --- a/backend/model/metadata/src/main/java/co/airy/model/metadata/MetadataKeys.java +++ b/backend/model/metadata/src/main/java/co/airy/model/metadata/MetadataKeys.java @@ -51,6 +51,7 @@ public static class MessageKeys { public static class Source { public static final String ID = "source.id"; + // one of: delivered, seen public static final String DELIVERY_STATE = "source.delivery_state"; public static final String ERROR = "source.error"; } diff --git a/backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/Connector.java b/backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/Connector.java deleted file mode 100644 index 0edcac0c8c..0000000000 --- a/backend/sources/whatsapp/connector/src/main/java/co/airy/core/sources/whatsapp/Connector.java +++ /dev/null @@ -1,58 +0,0 @@ -package co.airy.core.sources.whatsapp; - -import co.airy.avro.communication.Message; -import co.airy.core.sources.whatsapp.api.Api; -import co.airy.core.sources.whatsapp.dto.SendMessageRequest; -import co.airy.log.AiryLoggerFactory; -import co.airy.spring.auth.IgnoreAuthPattern; -import co.airy.spring.web.filters.RequestLoggingIgnorePatterns; -import co.airy.tracking.RouteTracking; -import org.apache.avro.specific.SpecificRecordBase; -import org.apache.kafka.streams.KeyValue; -import org.slf4j.Logger; -import org.springframework.context.annotation.Bean; -import org.springframework.stereotype.Component; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Pattern; - -@Component -public class Connector { - private static final Logger log = AiryLoggerFactory.getLogger(Connector.class); - private final long messageStaleAfterSec = 300L; // 5 minutes - private final Api api; - - Connector(Api api) { - this.api = api; - } - - public List> sendMessage(SendMessageRequest sendMessageRequest) { - // TODO - return List.of(); - } - - private boolean isMessageStale(Message message) { - return ChronoUnit.SECONDS.between(Instant.ofEpochMilli(message.getSentAt()), Instant.now()) > messageStaleAfterSec; - } - - @Bean - public IgnoreAuthPattern ignoreAuthPattern() { - return new IgnoreAuthPattern("/whatsapp"); - } - - @Bean - public RequestLoggingIgnorePatterns requestLoggingIgnorePatterns() { - return new RequestLoggingIgnorePatterns(List.of("/whatsapp")); - } - - @Bean - private RouteTracking routeTracking() { - Pattern urlPattern = Pattern.compile(".*whatsapp\\.connect$"); - HashMap properties = new HashMap<>(Map.of("channel", "whatsapp")); - return new RouteTracking(urlPattern, "channel_connected", properties); - } -} diff --git a/docs/docs/api/endpoints/connect-whatsapp.mdx b/docs/docs/api/endpoints/connect-whatsapp.mdx index c385698c37..4f1eea4368 100644 --- a/docs/docs/api/endpoints/connect-whatsapp.mdx +++ b/docs/docs/api/endpoints/connect-whatsapp.mdx @@ -4,7 +4,7 @@ Connects a Whatsapp cloud phone number to Airy. POST /channels.whatsapp.connect ``` -- `phone_number` the whatsapp phone number connected to your business account +- `phone_number_id` the whatsapp phone number connected to your business account - `user_token` your user access token - `name` is the custom name for the connected page - `image_url` is a custom image url for displaying this channel on the UI @@ -13,7 +13,7 @@ POST /channels.whatsapp.connect ```json5 { - "phone_number": "1234567", + "phone_number_id": "1234567", "user_token": "user access token", "name": "Customer support hotline", "image_url": "https://example.org/custom-image.jpg" // optional diff --git a/docs/docs/changelog.md b/docs/docs/changelog.md index 381b5148d2..d027718a1e 100644 --- a/docs/docs/changelog.md +++ b/docs/docs/changelog.md @@ -3,6 +3,67 @@ title: Changelog sidebar_label: 📝 Changelog --- +## 0.49.0 + + +#### 🚀 Features + +- [[#3403](https://github.com/airyhq/airy/issues/3403)] Rasa Connector [[#3611](https://github.com/airyhq/airy/pull/3611)] +- [[#3668](https://github.com/airyhq/airy/issues/3668)] Added not healthy to connectors [[#3670](https://github.com/airyhq/airy/pull/3670)] +- [[#3535](https://github.com/airyhq/airy/issues/3535)] Send messages to Whatsapp Cloud [[#3615](https://github.com/airyhq/airy/pull/3615)] +- [[#3491](https://github.com/airyhq/airy/issues/3491)] Added Whatsapp Business Cloud [[#3647](https://github.com/airyhq/airy/pull/3647)] +- [[#3377](https://github.com/airyhq/airy/issues/3377)] Added rasa connector [[#3640](https://github.com/airyhq/airy/pull/3640)] +- [[#3638](https://github.com/airyhq/airy/issues/3638)] Added last refresh on Status page [[#3639](https://github.com/airyhq/airy/pull/3639)] +- [[#3609](https://github.com/airyhq/airy/issues/3609)] Added catch for promises [[#3636](https://github.com/airyhq/airy/pull/3636)] +- [[#3534](https://github.com/airyhq/airy/issues/3534)] Whatsapp: Receive messages [[#3614](https://github.com/airyhq/airy/pull/3614)] + +#### 🐛 Bug Fixes + +- [[#3673](https://github.com/airyhq/airy/issues/3673)] Downgrade camelcase [[#3674](https://github.com/airyhq/airy/pull/3674)] +- [[#3361](https://github.com/airyhq/airy/issues/3361)] Fixed z-Index issue on customize page chatplugin [[#3663](https://github.com/airyhq/airy/pull/3663)] +- [[#3664](https://github.com/airyhq/airy/issues/3664)] Fixed connect button and fixed notification color [[#3665](https://github.com/airyhq/airy/pull/3665)] +- [[#3631](https://github.com/airyhq/airy/issues/3631)] Control center reorganize routing for connectors [[#3660](https://github.com/airyhq/airy/pull/3660)] +- [[#3560](https://github.com/airyhq/airy/issues/3560)] Control center remove sources info component [[#3624](https://github.com/airyhq/airy/pull/3624)] +- [[#3634](https://github.com/airyhq/airy/issues/3634)] Support whatsApp source in the render library [[#3637](https://github.com/airyhq/airy/pull/3637)] +- [[#3671](https://github.com/airyhq/airy/issues/3671)] Restructure backend directories [[#3672](https://github.com/airyhq/airy/pull/3672)] +- [[#3662](https://github.com/airyhq/airy/issues/3662)] Update DynamoDB with configuration values and remove enum ComponentName in model lib [[#3666](https://github.com/airyhq/airy/pull/3666)] + +#### 📚 Documentation + +- [[#3671](https://github.com/airyhq/airy/issues/3671)] Update links in contributing docs [[#3675](https://github.com/airyhq/airy/pull/3675)] +- [[#3613](https://github.com/airyhq/airy/issues/3613)] Docs about cypress testing [[#3635](https://github.com/airyhq/airy/pull/3635)] + +#### 🧰 Maintenance + +- Bump jest-environment-jsdom from 28.1.3 to 29.0.1 [[#3656](https://github.com/airyhq/airy/pull/3656)] +- Bump typescript from 4.7.4 to 4.8.2 [[#3649](https://github.com/airyhq/airy/pull/3649)] +- Bump core-js from 3.24.1 to 3.25.0 [[#3657](https://github.com/airyhq/airy/pull/3657)] +- Bump react-i18next from 11.18.4 to 11.18.5 [[#3658](https://github.com/airyhq/airy/pull/3658)] +- Bump webpack-dev-server from 4.10.0 to 4.10.1 [[#3659](https://github.com/airyhq/airy/pull/3659)] +- Bump babel-jest from 28.1.3 to 29.0.1 [[#3650](https://github.com/airyhq/airy/pull/3650)] +- Bump camelcase-keys from 7.0.2 to 8.0.2 [[#3651](https://github.com/airyhq/airy/pull/3651)] +- Bump @types/node from 18.7.11 to 18.7.13 [[#3652](https://github.com/airyhq/airy/pull/3652)] +- Bump eslint-plugin-react from 7.30.1 to 7.31.1 [[#3653](https://github.com/airyhq/airy/pull/3653)] +- Bump @types/node from 18.7.9 to 18.7.11 [[#3632](https://github.com/airyhq/airy/pull/3632)] +- Bump dotenv-webpack from 8.0.0 to 8.0.1 [[#3617](https://github.com/airyhq/airy/pull/3617)] +- Bump terser-webpack-plugin from 5.3.4 to 5.3.5 [[#3619](https://github.com/airyhq/airy/pull/3619)] +- Bump preact from 10.10.2 to 10.10.6 [[#3620](https://github.com/airyhq/airy/pull/3620)] +- Bump @babel/core from 7.18.10 to 7.18.13 [[#3629](https://github.com/airyhq/airy/pull/3629)] +- Bump @reduxjs/toolkit from 1.8.4 to 1.8.5 [[#3630](https://github.com/airyhq/airy/pull/3630)] +- Bump cypress from 10.4.0 to 10.6.0 [[#3627](https://github.com/airyhq/airy/pull/3627)] +- Bump webpack-bundle-analyzer from 4.5.0 to 4.6.1 [[#3628](https://github.com/airyhq/airy/pull/3628)] +- Bump i18next from 21.9.0 to 21.9.1 [[#3626](https://github.com/airyhq/airy/pull/3626)] +- Bump sass from 1.54.4 to 1.54.5 [[#3621](https://github.com/airyhq/airy/pull/3621)] +- Bump @types/node from 18.7.4 to 18.7.9 [[#3618](https://github.com/airyhq/airy/pull/3618)] + +#### Airy CLI + +You can download the Airy CLI for your operating system from the following links: + +[MacOS](https://airy-core-binaries.s3.amazonaws.com/0.49.0/darwin/amd64/airy) +[Linux](https://airy-core-binaries.s3.amazonaws.com/0.49.0/linux/amd64/airy) +[Windows](https://airy-core-binaries.s3.amazonaws.com/0.49.0/windows/amd64/airy.exe) + ## 0.48.0 #### Changes @@ -1260,43 +1321,3 @@ You can download the Airy CLI for your operating system from the following links [Linux](https://airy-core-binaries.s3.amazonaws.com/0.29.0/linux/amd64/airy) [Windows](https://airy-core-binaries.s3.amazonaws.com/0.29.0/windows/amd64/airy.exe) -## 0.28.0 - -#### 🚀 Features - -- [[#1911](https://github.com/airyhq/airy/issues/1911)] Reorganize the helm charts [[#2241](https://github.com/airyhq/airy/pull/2241)] -- [[#1212](https://github.com/airyhq/airy/issues/1212)] HTTPS inside Kubernetes [[#2133](https://github.com/airyhq/airy/pull/2133)] -- [[#2197](https://github.com/airyhq/airy/issues/2197)] Add Facebook emoji reaction metadata [[#2221](https://github.com/airyhq/airy/pull/2221)] - -#### 🐛 Bug Fixes - -- [[#2260](https://github.com/airyhq/airy/issues/2260)] Fixed z-index of modal [[#2261](https://github.com/airyhq/airy/pull/2261)] -- [[#2262](https://github.com/airyhq/airy/issues/2262)] Chatplugin Interferes with Web [[#2263](https://github.com/airyhq/airy/pull/2263)] -- [[#2246](https://github.com/airyhq/airy/issues/2246)] Fix default ingress controller [[#2247](https://github.com/airyhq/airy/pull/2247)] - -#### 🧰 Maintenance - -- Bump @typescript-eslint/eslint-plugin from 4.28.5 to 4.29.1 [[#2258](https://github.com/airyhq/airy/pull/2258)] -- Bump @babel/preset-env from 7.14.9 to 7.15.0 [[#2249](https://github.com/airyhq/airy/pull/2249)] -- Bump webpack from 5.46.0 to 5.49.0 [[#2252](https://github.com/airyhq/airy/pull/2252)] -- Bump redux from 4.1.0 to 4.1.1 [[#2250](https://github.com/airyhq/airy/pull/2250)] -- Bump @typescript-eslint/parser from 4.28.5 to 4.29.0 [[#2251](https://github.com/airyhq/airy/pull/2251)] -- Bump tar from 6.1.0 to 6.1.4 in /docs [[#2240](https://github.com/airyhq/airy/pull/2240)] -- Bump cypress from 7.7.0 to 8.1.0 [[#2230](https://github.com/airyhq/airy/pull/2230)] -- Bump react-markdown from 6.0.2 to 6.0.3 [[#2234](https://github.com/airyhq/airy/pull/2234)] -- Bump @typescript-eslint/parser from 4.28.4 to 4.28.5 [[#2237](https://github.com/airyhq/airy/pull/2237)] -- Bump core-js from 3.15.2 to 3.16.0 [[#2238](https://github.com/airyhq/airy/pull/2238)] -- Bump eslint from 7.31.0 to 7.32.0 [[#2235](https://github.com/airyhq/airy/pull/2235)] -- Bump sass from 1.36.0 to 1.37.0 [[#2236](https://github.com/airyhq/airy/pull/2236)] -- Bump @types/node from 16.4.3 to 16.4.10 [[#2231](https://github.com/airyhq/airy/pull/2231)] -- Bump @babel/preset-env from 7.14.8 to 7.14.9 [[#2232](https://github.com/airyhq/airy/pull/2232)] -- Bump @typescript-eslint/eslint-plugin from 4.28.4 to 4.28.5 [[#2233](https://github.com/airyhq/airy/pull/2233)] - -#### Airy CLI - -You can download the Airy CLI for your operating system from the following links: - -[MacOS](https://airy-core-binaries.s3.amazonaws.com/0.28.0/darwin/amd64/airy) -[Linux](https://airy-core-binaries.s3.amazonaws.com/0.28.0/linux/amd64/airy) -[Windows](https://airy-core-binaries.s3.amazonaws.com/0.28.0/windows/amd64/airy.exe) - diff --git a/docs/docs/concepts/release-process.md b/docs/docs/concepts/release-process.md index 3ef28c666b..15d541eb21 100644 --- a/docs/docs/concepts/release-process.md +++ b/docs/docs/concepts/release-process.md @@ -47,13 +47,14 @@ You can check out existing releases on ## Hotfix release -In case we need to hotfix a release, we follow a different process. At the -moment, the process is completely manual and goes as follows: +In case we need to hotfix a release, we follow a different process. +At the moment, the process is completely manual and goes as follows: - Create a new branch from `main` called `hotfix/description-of-the-fix` - Test the hotfix -- Update the changelog -- Update VERSION file -- Merge to `main` _and_ `develop` (do not alter the VERSION file in `develop`) - Write a custom release draft +- Update the changelog (using `./scripts/changelog_md.sh`) +- Update `VERSION` file +- Merge to `main` _and_ `develop` (keep the VERSION file in `develop` at `*-alpha`) - Publish the draft +- Announce the hotfix diff --git a/docs/docs/guides/contributing-components.md b/docs/docs/guides/contributing-components.md index 0ffe639152..9dce29425f 100644 --- a/docs/docs/guides/contributing-components.md +++ b/docs/docs/guides/contributing-components.md @@ -5,7 +5,7 @@ sidebar_label: Contributing Components :::warning -This functionality does not yet exist and is under development. With these docs, we aim to elicit feedback from our community (you!) and focus our development efforts. Currently, we do not offer support for 3rd party components, but we are working toward supporting this. +While you can already contribute components exposing them to users via the catalog is still under development. ::: @@ -24,31 +24,33 @@ With every [installation of Airy](../getting-started/installation/introduction.m 5. frontend-inbox 6. frontend-control-center -Airy also provides a marketplace of _plug and play_ components that extend the functionality of your Airy instance. You can install them through the catalog page in the Control Center UI of your Airy Instance. +Airy also provides a marketplace of _plug and play_ components that extend the functionality of your Airy instance. +You can install them through the catalog page in the Control Center UI of your Airy Instance or by using the [install endpoint](../api/endpoints/components.md#install). In the following, we will explain how to create, update and store components. -The Helm package and information on each component (except core components) are stored in an external repository managed by Airy called [airy-components](https://github.com/airyhq/airy-/airy-components). This repository is made up of directories where each directory contains a component's Helm package its description. +The Helm package and information on each component (except core components) are stored in an external repository managed by Airy called [catalog](https://github.com/airyhq/catalog). +This repository is made up of directories where each directory contains a component's metadata. ## The Component File Structure -Below is a model of the file structure of a single component inside the [`airy-components`](https://github.com/airyhq/airy-components) repository. +Below is a model of the file structure of a single component inside the [`catalog`](https://github.com/airyhq/catalog) repository. ``` -airy-components/ +catalog/ └── [COMPONENT_NAME]/ - ├── description.yaml - └── helm/ - └── [HELM CHART] + └── description.yaml ``` -The `helm` directory contains all the files that make up the Helm package. +The `description.yaml` completely defines a component so that it can be installed and displayed in the UI catalog. -The `description.yaml` is the source-of-truth for every component. It includes a description of its functionality, pricing, availability, and version. This file is written by the component maintainer and rendered into the UI of the Control Center. +It contains a description of its functionality, availability, version, and importantly the url of the helm chart used for installation. +This file is written by the component maintainer and rendered into the UI of the Control Center. :::note -Since all components are maintained by Airy, the versioning of every component is tied to the version of Airy. However, once we support 3rd party components, we will revisit our versioning system. +Since all components are maintained by Airy, the versioning of every component is tied to the version of Airy. +However, once we support 3rd party components, we will revisit our versioning system. :::: diff --git a/docs/docs/sources/whatsapp-cloud.md b/docs/docs/sources/whatsapp-cloud.md index f7ffc9a9df..9c1b9502cc 100644 --- a/docs/docs/sources/whatsapp-cloud.md +++ b/docs/docs/sources/whatsapp-cloud.md @@ -53,7 +53,7 @@ At the end of which you should have the following: 1. A meta developer account and a business account 2. A meta app with Whatsapp connected as a product -3. A Whatsapp test phone number +3. A Whatsapp test phone number id After you also complete step 2 you have now verified that you can send messages from that test phone number to. @@ -100,7 +100,13 @@ Note down the `webhook_secret` and use it when registering your webhook. ## Step 4: Connect the webhook -TBD +In order to be able to receive messages from Whatsapp your Airy instance needs to be accessible from the internet. +Go to the Whatsapp product section of your Meta app and click on the "Configuration" section. +Here you need to set the URL to `https:///whatsapp` and the secret to the value you noted down earlier. +When clicking "Verify and Save" the connection will be tested so that you can be sure that everything is working. + +Next select the fields that you want to subscribe to. +You need to select at least `messages` and a version of `14.0` or higher. ## Step 5: Connect a phone number to Airy @@ -123,6 +129,41 @@ import ConnectWhatsapp from '../api/endpoints/connect-whatsapp.mdx' You can get a user token associated to your app using the [Facebook graph explorer](https://developers.facebook.com/tools/explorer). +To confirm that this is working you can write a message to this phone number. +The conversation should appear in your inbox. + ## Step 6: Send and receive messages with the Inbox UI -TBD +Now let's confirm that we can write messages to our Whatsapp contacts you can select the conversation we created in the previous step in the inbox and write a message. + +You can also use the [Messages API endpoint](/api/endpoints/messages#send). + + } +title="Messages endpoint" +description="Send messages from your Airy Core instance to different sources through the Messages endpoint" +link="api/endpoints/messages#send" +/> + +
+ +**Sending a text message** + +```json5 +{ + "conversation_id": "", + "message": { + "type": "text", + "text": { + "preview_url": false, + "body": "Welcome to our business" + } + } +} +``` + +
+ +import InboxMessages from './inbox-messages.mdx' + + diff --git a/docs/docs/ui/control-center/introduction.md b/docs/docs/ui/control-center/introduction.md index b13877cd0d..96ef67462b 100644 --- a/docs/docs/ui/control-center/introduction.md +++ b/docs/docs/ui/control-center/introduction.md @@ -15,6 +15,9 @@ The Control Center serves as the technical dashboard of your Airy Core app. It provides both a graphical overview and a way to manage your app's [components](/getting-started/components), [connectors](connectors), and [webhooks](/api/webhook). Its [catalog](catalog) enables you to choose and configure additional [connectors](connectors). +To run the Control Center UI locally, you can start its development server with the command:
+`./scripts/web-dev.sh //frontend/control-center:bundle_server` + } diff --git a/docs/docs/ui/inbox/introduction.md b/docs/docs/ui/inbox/introduction.md index 2685376741..70f4fc2539 100644 --- a/docs/docs/ui/inbox/introduction.md +++ b/docs/docs/ui/inbox/introduction.md @@ -15,6 +15,9 @@ import useBaseUrl from '@docusaurus/useBaseUrl'; The Inbox features a [Messenger](messenger), filled with the conversations from all of your [connectors](sources/introduction.md). You can chat, organize your conversations with features such as [Filters, Search](messenger#search-and-filter) and [Tags](tags), view and edit [contacts](contacts), in addition to adding [suggested replies](suggestedReplies) to messages to improve response time. +To run the Inbox UI locally, you can start its development server with the command:
+`./scripts/web-dev.sh //frontend/inbox:bundle_server` + } diff --git a/docs/docs/ui/testing/integration-testing.md b/docs/docs/ui/testing/integration-testing.md new file mode 100644 index 0000000000..49857ee40d --- /dev/null +++ b/docs/docs/ui/testing/integration-testing.md @@ -0,0 +1,53 @@ +--- +title: Integration Testing +sidebar_label: Integration Testing +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import SuccessBox from "@site/src/components/SuccessBox"; + +We use the end-to-end testing tool [Cypress](https://docs.cypress.io/guides/overview/why-cypress) to test +the main features of the [Airy Live Chat Plugin](/sources/chatplugin/overview), the [Inbox](/ui/inbox/introduction), and the [Control Center](/ui/control-center/introduction). + +You can find the tests in the airy repository at `/integration`. The tests are intended to be run on a local instance; a few values need to be configured before the tests can be run. + +Here is a step-by-step guide to running the tests locally: + +- First, make sure that all dependencies are up-to-date in your `airy` repository. + In doubt, delete your `nodes_modules` and run `yarn install`. + +- You need to have the [contacts feature](/ui/inbox/contacts) enabled (it is disabled by default). To enable it, set the `integration.contacts.enabled` field in your [airy.yaml config](getting-started/installation/configuration.md) to `true`. + +- Run a local instance with the command `scripts/dev_cli.sh create --provider=minikube`. [Apply the configuration](/getting-started/installation/configuration#applying-the-configuration) once the process is finished. + +- Next, start the development server for the [Control Center](/ui/control-center/introduction) with the command:
+ `./scripts/web-dev.sh //frontend/control-center:bundle_server` + +- Navigate to the `Catalog` page and install the [Airy Live Chat Plugin](/sources/chatplugin/overview). Once it is installed, navigate to the `Connectors` page, select `Airy Chat Plugin`, and add a new channel. + +- Copy the channelID of the new channel and add it to the [Cypress](https://docs.cypress.io/guides/overview/why-cypress) configuration file `/integration/cypress.config.ts` at `env.channelId`. + +- Open the demo page by passing the `channel_id` as a query parameter:
+ `http://localhost/chatplugin/ui/example?channel_id=`
+ Write a message in the chat. + +- Start the development server of the [Inbox](/ui/inbox/introduction):
+ `./scripts/web-dev.sh //frontend/inbox:bundle_server` + +- You should see the conversation of the [Airy Live Chat Plugin](/sources/chatplugin/overview) channel you just created. Copy its conversationID (it is the id that is passed in its URL) and add it to the configuration file `/integration/cypress.config.ts` at `env.conversationId`. + +- Then, copy the id of the conversation's message and add it to the configuration file `/integration/cypress.config.ts` at `env. messageId` (you can find the message's id by inspecting its DOM element, as the id is passed to the DIV as a key). + + + +You are now ready to run the tests on your local instance 🎉 + + + +- Start the development server for the [Control Center](/ui/control-center/introduction) with the command `./scripts/web-dev.sh //frontend/control-center:bundle_server` + +- Open the [Cypress dashboard](https://docs.cypress.io/guides/dashboard/introduction) with the command: `/node_modules/.bin/cypress open -C integration/cypress.config.ts` + +The dashboard lists all tests. You can run tests by clicking on each item on the list. + +Create Tags diff --git a/docs/sidebars.js b/docs/sidebars.js index cff15d63d2..a6f7d259c2 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -95,6 +95,9 @@ module.exports = { 'ui/control-center/webhooks', ], }, + { + Testing: ['ui/testing/integration-testing'], + }, ], }, { diff --git a/docs/static/img/ui/controlCenterCatalog.png b/docs/static/img/ui/controlCenterCatalog.png index b1e64a19b4..745ab19ba1 100644 Binary files a/docs/static/img/ui/controlCenterCatalog.png and b/docs/static/img/ui/controlCenterCatalog.png differ diff --git a/docs/static/img/ui/controlCenterConnectors.png b/docs/static/img/ui/controlCenterConnectors.png index 379dd09271..e53b39cf6d 100644 Binary files a/docs/static/img/ui/controlCenterConnectors.png and b/docs/static/img/ui/controlCenterConnectors.png differ diff --git a/docs/static/img/ui/controlCenterStatus.png b/docs/static/img/ui/controlCenterStatus.png index 14a4bf423a..513726bb21 100644 Binary files a/docs/static/img/ui/controlCenterStatus.png and b/docs/static/img/ui/controlCenterStatus.png differ diff --git a/docs/static/img/ui/cypressDashboard.png b/docs/static/img/ui/cypressDashboard.png new file mode 100644 index 0000000000..d7ebb14f85 Binary files /dev/null and b/docs/static/img/ui/cypressDashboard.png differ diff --git a/frontend/control-center/src/App.tsx b/frontend/control-center/src/App.tsx index b4d57a9d5c..6ef4ab0d42 100644 --- a/frontend/control-center/src/App.tsx +++ b/frontend/control-center/src/App.tsx @@ -13,9 +13,6 @@ import TwilioSmsConnect from './pages/Connectors/Providers/Twilio/SMS/TwilioSmsC import TwilioWhatsappConnect from './pages/Connectors/Providers/Twilio/WhatsApp/TwilioWhatsappConnect'; import GoogleConnect from './pages/Connectors/Providers/Google/GoogleConnect'; import InstagramConnect from './pages/Connectors/Providers/Instagram/InstagramConnect'; -import DialogflowConnect from './pages/Connectors/Providers/Dialogflow/DialogflowConnect'; -import ZendeskConnect from './pages/Connectors/Providers/Zendesk/ZendeskConnect'; -import SalesforceConnect from './pages/Connectors/Providers/Salesforce/SalesforceConnect'; import NotFound from './pages/NotFound'; import ConnectorsOutlet from './pages/Connectors/ConnectorsOutlet'; import Catalog from './pages/Catalog'; @@ -26,13 +23,7 @@ import Status from './pages/Status'; import Inbox from './pages/Inbox'; import ChannelsList from './pages/Inbox/ChannelsList'; import InboxOutlet from './pages/Inbox/InboxOutlet'; -import ConnectorChatplugin from './pages/Connectors/Providers/Airy/ChatPlugin/ConnectorChatplugin'; import ConnectorConfig from './pages/Connectors/ConnectorConfig'; -import ConnectorFacebook from './pages/Connectors/Providers/Facebook/Messenger/ConnectorFacebook'; -import ConnectorTwilioSms from './pages/Connectors/Providers/Twilio/SMS/ConnectorTwilioSms'; -import ConnectorTwilioWhatsapp from './pages/Connectors/Providers/Twilio/WhatsApp/ConnectorTwilioWhatsapp'; -import ConnectorGoogle from './pages/Connectors/Providers/Google/ConnectorGoogle'; -import ConnectorInstagram from './pages/Connectors/Providers/Instagram/ConnectorInstagram'; import CatalogProductPage from './pages/Catalog/CatalogItemDetails'; const mapDispatchToProps = { @@ -57,19 +48,12 @@ const App = (props: ConnectedProps) => { } /> - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + } /> - } /> + }> + } /> + } /> + } /> }> diff --git a/frontend/control-center/src/actions/channel/index.ts b/frontend/control-center/src/actions/channel/index.ts index 544fb9a60e..9f28ba6248 100644 --- a/frontend/control-center/src/actions/channel/index.ts +++ b/frontend/control-center/src/actions/channel/index.ts @@ -17,22 +17,17 @@ import { import {HttpClientInstance} from '../../httpClient'; -const SET_CURRENT_CONNECTORS = '@@channel/SET_CONNECTORS'; const ADD_CONNECTORS = '@@channel/ADD_CONNECTORS'; const SET_CHANNEL = '@@channel/SET_CHANNEL'; const DELETE_CHANNEL = '@@channel/DELETE_CHANNEL'; -export const setCurrentChannelsAction = createAction(SET_CURRENT_CONNECTORS, (channels: Channel[]) => channels)< - Channel[] ->(); - export const addChannelsAction = createAction(ADD_CONNECTORS, (channels: Channel[]) => channels)(); export const setChannelAction = createAction(SET_CHANNEL, (channel: Channel) => channel)(); export const deleteChannelAction = createAction(DELETE_CHANNEL, (channelId: string) => channelId)(); export const listChannels = () => async (dispatch: Dispatch) => HttpClientInstance.listChannels().then((response: Channel[]) => { - dispatch(setCurrentChannelsAction(response)); + dispatch(addChannelsAction(response)); return Promise.resolve(response); }); diff --git a/frontend/control-center/src/components/ChannelAvatar/index.tsx b/frontend/control-center/src/components/ChannelAvatar/index.tsx index f57ea9aa14..21e7371128 100644 --- a/frontend/control-center/src/components/ChannelAvatar/index.tsx +++ b/frontend/control-center/src/components/ChannelAvatar/index.tsx @@ -45,6 +45,7 @@ export const getChannelAvatar = (source: string) => { case 'Twilio SMS': return ; case Source.twilioWhatsApp: + case Source.whatsapp: case 'WhatsApp Business Cloud': return ; case Source.twilio: diff --git a/frontend/control-center/src/components/Description/index.tsx b/frontend/control-center/src/components/Description/index.tsx new file mode 100644 index 0000000000..b39398115e --- /dev/null +++ b/frontend/control-center/src/components/Description/index.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import {useTranslation} from 'react-i18next'; +import {Source} from 'model'; + +export const DescriptionComponent = (props: {description: string}) => { + const {t} = useTranslation(); + const {description} = props; + + return <>{t(description)}; +}; + +export const getDescriptionSourceName = (source: Source) => source.replaceAll('.', ''); diff --git a/frontend/control-center/src/components/InfoCard/index.module.scss b/frontend/control-center/src/components/InfoCard/index.module.scss deleted file mode 100644 index e5abed01bc..0000000000 --- a/frontend/control-center/src/components/InfoCard/index.module.scss +++ /dev/null @@ -1,156 +0,0 @@ -@import 'assets/scss/fonts.scss'; -@import 'assets/scss/colors.scss'; -@import 'assets/scss/z-index.scss'; - -.infoCard { - width: 260px; - margin-bottom: 28px; - margin-right: 36px; - padding: 12px 0 12px 20px; - display: flex; - flex-direction: column; - justify-content: center; - border: 1px solid var(--color-dark-elements-gray); - border-radius: 10px; - background-color: var(--color-background-blue); - p { - @include font-xs; - margin: 10px 0 0 0; - color: var(--color-text-gray); - max-width: 95%; - } - - button { - margin-top: 14px; - } - - &:hover { - border: 2px solid var(--color-airy-blue); - cursor: pointer; - margin-left: -1px; - width: 261px; - } -} - -.installed { - height: 98px; -} - -.notInstalled { - height: 118px; -} - -.channelLogoInstagram { - margin-right: 10px; - height: 40px; - width: 40px; - svg { - width: 35px; - height: 35px; - } -} - -.textDetails { - margin-left: 10px; - h1 { - @include font-s; - color: var(--color-text-contrast); - font-weight: 600; - letter-spacing: 0; - } -} - -.channelLogoTitleContainer { - display: flex; - align-items: center; - border-radius: 10px; - - .channelLogo { - display: flex; - align-items: center; - svg { - width: 34px; - max-height: 30px; - fill: var(--color-text-contrast); - } - } -} - -.isExpandedCard { - height: 100px; - width: 260px; - &:hover { - border: 2px solid var(--color-airy-blue); - margin-left: -1px; - width: 261px; - } -} - -.isExpandedContainer { - height: 100px; - width: 260px; - padding: 6px 0 0 0; - flex-direction: column; - align-items: start; - &:hover { - width: 261px; - } -} - -.isExpandedLogo { - min-width: 40px; - max-width: 100%; - margin: 0 18px; -} - -.isExpandedDetails { - width: 100%; - margin-left: 18px; - h1 { - @include font-base; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } -} - -.enableModalContainerWrapper { - height: 400px; - width: 550px; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - border-radius: 20px; -} - -.enableModalContainer { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - @include font-l; - - p { - max-width: 70%; - text-align: center; - margin: 4px 0 20px 0; - color: var(--color-text-contrast); - } -} - -.enableModalContainer button { - margin-top: 14px; - padding: 0 60px; - border-radius: 10px; -} - -.headerModal { - @include font-xl; -} - -.checkmarkIcon { - width: 15%; - margin-bottom: 30px; - fill: var(--color-soft-green); -} diff --git a/frontend/control-center/src/components/InfoCard/index.tsx b/frontend/control-center/src/components/InfoCard/index.tsx deleted file mode 100644 index a83ed921f7..0000000000 --- a/frontend/control-center/src/components/InfoCard/index.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import React, {useState, useEffect} from 'react'; -import {SourceInfo} from '../SourceInfo'; -import {useNavigate} from 'react-router-dom'; -import {ReactComponent as CheckmarkIcon} from 'assets/images/icons/checkmarkFilled.svg'; -import {CONNECTORS_ROUTE} from '../../routes/routes'; -import {Button, SettingsModal} from 'components'; -import {installComponent, uninstallComponent} from '../../actions/catalog'; -import {useTranslation} from 'react-i18next'; -import {connect, ConnectedProps} from 'react-redux'; -import {ConfigStatusButton} from '../../pages/Connectors/ConfigStatusButton'; -import {ComponentStatus} from '../../pages/Connectors'; -import styles from './index.module.scss'; - -export enum InfoCardStyle { - normal = 'normal', - expanded = 'expanded', -} - -type InfoCardProps = { - sourceInfo: SourceInfo; - addChannelAction: () => void; - installed: boolean; - componentStatus?: ComponentStatus; - style: InfoCardStyle; -} & ConnectedProps; - -const mapDispatchToProps = { - installComponent, - uninstallComponent, -}; - -const connector = connect(null, mapDispatchToProps); - -const InfoCard = (props: InfoCardProps) => { - const {sourceInfo, addChannelAction, installed, style, uninstallComponent, componentStatus} = props; - const [isInstalled, setIsInstalled] = useState(installed); - const [isModalVisible, setIsModalVisible] = useState(false); - const [modalTitle, setModalTitle] = useState(''); - const {t} = useTranslation(); - const navigate = useNavigate(); - const CONNECTORS_PAGE = window.location.pathname.includes(CONNECTORS_ROUTE); - - useEffect(() => { - const title = isInstalled ? t('uninstall') + ' ' + sourceInfo.title : sourceInfo.title + ' ' + t('installed'); - setModalTitle(title); - }, [isInstalled]); - - const toggleInstallation = () => { - setIsInstalled(!isInstalled); - }; - - const cancelInstallationToggle = () => { - setIsModalVisible(false); - - if (!isInstalled) toggleInstallation(); - }; - - const confirmUninstall = () => { - uninstallComponent({name: `${sourceInfo.repository}/${sourceInfo.componentName}`}); - - setIsModalVisible(false); - toggleInstallation(); - }; - - const handleCardClick = () => { - navigate(sourceInfo.newChannelRoute); - }; - - return ( -
-
-
- {sourceInfo.image} -
-
-

{sourceInfo.title}

-
-
- - {componentStatus && } - - {isModalVisible && ( - : null} - wrapperClassName={styles.enableModalContainerWrapper} - containerClassName={styles.enableModalContainer} - title={modalTitle} - close={cancelInstallationToggle} - headerClassName={styles.headerModal} - > - {isInstalled &&

{t('uninstallComponentText')}

} - {!isInstalled ? ( - - ) : ( - - )} -
- )} -
- ); -}; - -export default connector(InfoCard); diff --git a/frontend/control-center/src/components/Sidebar/index.tsx b/frontend/control-center/src/components/Sidebar/index.tsx index 1b2c45ffe8..bf6c02ff4b 100644 --- a/frontend/control-center/src/components/Sidebar/index.tsx +++ b/frontend/control-center/src/components/Sidebar/index.tsx @@ -12,13 +12,14 @@ import {ReactComponent as InboxIcon} from 'assets/images/icons/inboxIcon.svg'; import styles from './index.module.scss'; import {StateModel} from '../../reducers'; import {connect, ConnectedProps} from 'react-redux'; -import {ConfigServices} from 'model'; +import {ComponentName, ComponentRepository} from 'model'; type SideBarProps = {} & ConnectedProps; const mapStateToProps = (state: StateModel) => ({ version: state.data.config.clusterVersion, components: state.data.config.components, + catalog: state.data.catalog, }); const connector = connect(mapStateToProps); @@ -28,8 +29,9 @@ const Sidebar = (props: SideBarProps) => { return useMatch(`${route}/*`); }; - const webhooksEnabled = props.components[ConfigServices.integrationWebhook]?.enabled || false; - const inboxEnabled = props.components[ConfigServices.frontendInbox]?.enabled || false; + const webhooksEnabled = + props.catalog[`${ComponentRepository.airyCore}/${ComponentName.integrationWebhook}`]?.installed; + const inboxEnabled = props.components[ComponentName.frontendInbox]?.enabled || false; const showLine = inboxEnabled || webhooksEnabled; return ( diff --git a/frontend/control-center/src/components/SourceInfo/index.tsx b/frontend/control-center/src/components/SourceInfo/index.tsx deleted file mode 100644 index a308bf5321..0000000000 --- a/frontend/control-center/src/components/SourceInfo/index.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import React from 'react'; -import {Source} from 'model'; -import {ReactComponent as AiryAvatarIcon} from 'assets/images/icons/airyLogo.svg'; -import {ReactComponent as MessengerAvatarIcon} from 'assets/images/icons/facebookMessengerLogoBlue.svg'; -import {ReactComponent as SMSAvatarIcon} from 'assets/images/icons/phoneIcon.svg'; -import {ReactComponent as WhatsAppAvatarIcon} from 'assets/images/icons/whatsappLogoFilled.svg'; -import {ReactComponent as GoogleAvatarIcon} from 'assets/images/icons/googleLogo.svg'; -import {ReactComponent as InstagramIcon} from 'assets/images/icons/instagramLogoFilled.svg'; -import {ReactComponent as DialogflowIcon} from 'assets/images/icons/dialogflowLogo.svg'; -import {ReactComponent as ZendeskIcon} from 'assets/images/icons/zendeskLogo.svg'; -import {ReactComponent as SalesforceIcon} from 'assets/images/icons/salesforceLogo.svg'; -import {useTranslation} from 'react-i18next'; -import { - cyChannelsChatPluginAddButton, - cyChannelsFacebookAddButton, - cyChannelsGoogleAddButton, - cyChannelsTwilioSmsAddButton, - cyChannelsTwilioWhatsappAddButton, - cyChannelsInstagramAddButton, -} from 'handles'; -import { - CONNECTORS_CONNECTED_ROUTE, - CONNECTORS_FACEBOOK_ROUTE, - CONNECTORS_TWILIO_SMS_ROUTE, - CONNECTORS_TWILIO_WHATSAPP_ROUTE, - CONNECTORS_CHAT_PLUGIN_ROUTE, - CONNECTORS_GOOGLE_ROUTE, - CONNECTORS_INSTAGRAM_ROUTE, - CONNECTORS_DIALOGFLOW_ROUTE, - CONNECTORS_ZENDESK_ROUTE, - CONNECTORS_SALESFORCE_ROUTE, -} from '../../routes/routes'; - -export type SourceInfo = { - type: Source; - channel: boolean; - title: string; - description: string | JSX.Element; - image: JSX.Element; - newChannelRoute: string; - channelsListRoute: string; - configKey: string; - componentName: string; - repository: string; - itemInfoString: string; - dataCyAddChannelButton?: string; - docs: string; -}; - -interface DescriptionComponentProps { - description: string; -} - -const DescriptionComponent = (props: DescriptionComponentProps) => { - const {description} = props; - const {t} = useTranslation(); - return <>{t(description)}; -}; - -export const getSourcesInfo = (): SourceInfo[] => { - return [ - { - type: Source.chatPlugin, - channel: true, - title: 'Airy Live Chat', - description: , - image: , - newChannelRoute: CONNECTORS_CHAT_PLUGIN_ROUTE + '/new', - channelsListRoute: CONNECTORS_CONNECTED_ROUTE + '/chatplugin', - configKey: 'sources-chatplugin', - componentName: 'sources-chatplugin', - repository: 'airy-core', - itemInfoString: 'channels', - dataCyAddChannelButton: cyChannelsChatPluginAddButton, - docs: 'https://airy.co/docs/core/sources/chatplugin/overview', - }, - { - type: Source.facebook, - channel: true, - title: 'Messenger', - description: , - image: , - newChannelRoute: CONNECTORS_FACEBOOK_ROUTE + '/new', - channelsListRoute: CONNECTORS_CONNECTED_ROUTE + '/facebook', - configKey: 'sources-facebook', - componentName: 'sources-facebook', - repository: 'airy-core', - itemInfoString: 'channels', - dataCyAddChannelButton: cyChannelsFacebookAddButton, - docs: 'https://airy.co/docs/core/sources/facebook', - }, - { - type: Source.twilioSMS, - channel: true, - title: 'SMS', - description: , - image: , - newChannelRoute: CONNECTORS_TWILIO_SMS_ROUTE + '/new', - channelsListRoute: CONNECTORS_CONNECTED_ROUTE + '/twilio.sms/#', - configKey: 'sources-twilio', - componentName: 'sources-twilio', - repository: 'airy-core', - itemInfoString: 'phones', - dataCyAddChannelButton: cyChannelsTwilioSmsAddButton, - docs: 'https://airy.co/docs/core/sources/sms-twilio', - }, - { - type: Source.twilioWhatsApp, - channel: true, - title: 'WhatsApp', - description: , - image: , - newChannelRoute: CONNECTORS_TWILIO_WHATSAPP_ROUTE + '/new', - channelsListRoute: CONNECTORS_CONNECTED_ROUTE + '/twilio.whatsapp/#', - configKey: 'sources-twilio', - componentName: 'sources-twilio', - repository: 'airy-core', - itemInfoString: 'phones', - dataCyAddChannelButton: cyChannelsTwilioWhatsappAddButton, - docs: 'https://airy.co/docs/core/sources/whatsapp-twilio', - }, - { - type: Source.google, - channel: true, - title: 'Google Business Messages', - description: , - image: , - newChannelRoute: CONNECTORS_GOOGLE_ROUTE + '/new', - channelsListRoute: CONNECTORS_CONNECTED_ROUTE + '/google', - configKey: 'sources-google', - componentName: 'sources-google', - repository: 'airy-core', - itemInfoString: 'channels', - dataCyAddChannelButton: cyChannelsGoogleAddButton, - docs: 'https://airy.co/docs/core/sources/google', - }, - { - type: Source.instagram, - channel: true, - title: 'Instagram', - description: , - image: , - newChannelRoute: CONNECTORS_INSTAGRAM_ROUTE + '/new', - channelsListRoute: CONNECTORS_CONNECTED_ROUTE + '/instagram', - configKey: 'sources-facebook', - componentName: 'sources-facebook', - repository: 'airy-core', - itemInfoString: 'channels', - dataCyAddChannelButton: cyChannelsInstagramAddButton, - docs: 'https://airy.co/docs/core/sources/instagram', - }, - { - type: Source.dialogflow, - channel: false, - title: 'Dialogflow', - description: , - image: , - newChannelRoute: CONNECTORS_DIALOGFLOW_ROUTE + '/new', - channelsListRoute: CONNECTORS_CONNECTED_ROUTE + '/dialogflow', - configKey: 'enterprise-dialogflow-connector', - componentName: 'enterprise-dialogflow-connector', - repository: 'airy-enterprise', - itemInfoString: 'connectors', - docs: 'https://airy.co/docs/enterprise/apps/dialogflow/deployment', - }, - { - type: Source.zendesk, - channel: false, - title: 'Zendesk', - description: , - image: , - newChannelRoute: CONNECTORS_ZENDESK_ROUTE + '/new', - channelsListRoute: CONNECTORS_CONNECTED_ROUTE + '/zendesk', - configKey: 'enterprise-zendesk-connector', - componentName: 'enterprise-zendesk-connector', - repository: 'airy-enterprise', - itemInfoString: 'connectors', - docs: 'https://airy.co/docs/enterprise/apps/zendesk/installation', - }, - { - type: Source.salesforce, - channel: false, - title: 'Salesforce', - description: , - image: , - newChannelRoute: CONNECTORS_SALESFORCE_ROUTE + '/new', - channelsListRoute: CONNECTORS_CONNECTED_ROUTE + '/salesforce', - configKey: 'enterprise-salesforce-contacts-ingestion', - componentName: 'enterprise-salesforce-contacts-ingestion', - repository: 'airy-enterprise', - itemInfoString: 'connectors', - docs: 'https://airy.co/docs/enterprise/apps/salesforce-contacts-ingestion/deployment', - }, - ]; -}; diff --git a/frontend/control-center/src/components/TopBar/index.tsx b/frontend/control-center/src/components/TopBar/index.tsx index ee1e208f0f..a73a91653c 100644 --- a/frontend/control-center/src/components/TopBar/index.tsx +++ b/frontend/control-center/src/components/TopBar/index.tsx @@ -17,7 +17,7 @@ import {env} from '../../env'; import {useAnimation} from 'render'; import {useTranslation} from 'react-i18next'; import i18next from 'i18next'; -import {ConfigServices, Language} from 'model/Config'; +import {Language, ComponentName} from 'model'; interface TopBarProps { isAdmin: boolean; @@ -44,7 +44,7 @@ const TopBar = (props: TopBarProps & ConnectedProps) => { const [chevronLanguageAnim, setChevronLanguageAnim] = useState(false); const [currentLanguage, setCurrentLanguage] = useState(localStorage.getItem('language') || Language.english); const {t} = useTranslation(); - const inboxEnabled = props.components[ConfigServices.frontendInbox]?.enabled || false; + const inboxEnabled = props.components[ComponentName.frontendInbox]?.enabled || false; useLayoutEffect(() => { handleLanguage(localStorage.getItem('language')); diff --git a/frontend/control-center/src/components/index.ts b/frontend/control-center/src/components/index.ts new file mode 100644 index 0000000000..e89295b6b9 --- /dev/null +++ b/frontend/control-center/src/components/index.ts @@ -0,0 +1,5 @@ +export * from './ChannelAvatar'; +export * from './Description'; +export * from '../pages/Connectors/InfoCard'; +export * from './Switch'; +export * from './Wrapper'; diff --git a/frontend/control-center/src/pages/Catalog/CatalogCard/index.tsx b/frontend/control-center/src/pages/Catalog/CatalogCard/index.tsx index 7012fc8c01..18b5794eb9 100644 --- a/frontend/control-center/src/pages/Catalog/CatalogCard/index.tsx +++ b/frontend/control-center/src/pages/Catalog/CatalogCard/index.tsx @@ -1,19 +1,20 @@ import React, {useRef, useState} from 'react'; import {useNavigate} from 'react-router-dom'; -import {ComponentInfo, getSourceForComponent, NotificationModel} from 'model'; -import {ReactComponent as CheckmarkIcon} from 'assets/images/icons/checkmarkFilled.svg'; -import {Button, NotificationComponent, SettingsModal} from 'components'; -import {installComponent} from '../../../actions/catalog'; import {useTranslation} from 'react-i18next'; import {connect, ConnectedProps} from 'react-redux'; +import {StateModel} from '../../../reducers'; +import {installComponent} from '../../../actions/catalog'; +import {ComponentInfo, NotificationModel} from 'model'; +import {Button, NotificationComponent, SettingsModal, SmartButton} from 'components'; import {getChannelAvatar} from '../../../components/ChannelAvatar'; import { getConnectedRouteForComponent, getNewChannelRouteForComponent, getCatalogProductRouteForComponent, -} from '../getRouteForCard'; +} from '../../../services'; +import {DescriptionComponent, getDescriptionSourceName} from '../../../components/Description'; +import {ReactComponent as CheckmarkIcon} from 'assets/images/icons/checkmarkFilled.svg'; import styles from './index.module.scss'; -import {StateModel} from '../../../reducers'; type CatalogCardProps = { componentInfo: ComponentInfo; @@ -31,34 +32,24 @@ const connector = connect(mapStateToProps, mapDispatchToProps); export const availabilityFormatted = (availability: string) => availability.split(','); -export const DescriptionComponent = (props: {description: string}) => { - const {description} = props; - const {t} = useTranslation(); - return <>{t(description)}; -}; - -export const getDescriptionSourceName = (name: string, displayName: string) => { - if (displayName.includes('SMS')) return 'twiliosms'; - if (displayName.includes('WhatsApp')) return 'twilioWhatsapp'; - return getSourceForComponent(name)?.replace('.', ''); -}; - const CatalogCard = (props: CatalogCardProps) => { const {component, componentInfo, installComponent} = props; const isInstalled = component[componentInfo?.name]?.installed; const [isModalVisible, setIsModalVisible] = useState(false); - const [isInstalling, setIsInstalling] = useState(false); + const [isPending, setIsPending] = useState(false); const [notification, setNotification] = useState(null); const installButtonCard = useRef(null); const componentCard = useRef(null); const {t} = useTranslation(); const navigate = useNavigate(); - const CONFIG_CONNECTED_ROUTE = getConnectedRouteForComponent(componentInfo.displayName); - const NEW_CHANNEL_ROUTE = getNewChannelRouteForComponent(componentInfo.displayName); + const isChannel = componentInfo?.isChannel; + + const CONFIG_CONNECTED_ROUTE = getConnectedRouteForComponent(componentInfo.source, isChannel); + const NEW_CHANNEL_ROUTE = getNewChannelRouteForComponent(componentInfo.source); const openInstallModal = () => { - setIsInstalling(true); + setIsPending(true); installComponent({name: componentInfo.name}) .then(() => { setNotification({show: true, successful: true, text: t('successfullyInstalled')}); @@ -68,7 +59,7 @@ const CatalogCard = (props: CatalogCardProps) => { setNotification({show: true, successful: false, text: t('failedInstall')}); }) .finally(() => { - setIsInstalling(false); + setIsPending(false); }); }; @@ -100,15 +91,18 @@ const CatalogCard = (props: CatalogCardProps) => { } return ( - + /> ); }; @@ -133,9 +127,7 @@ const CatalogCard = (props: CatalogCardProps) => {
{componentInfo.name && (

- +

)} diff --git a/frontend/control-center/src/pages/Catalog/CatalogItemDetails/index.tsx b/frontend/control-center/src/pages/Catalog/CatalogItemDetails/index.tsx index ba4d29b49c..9a8c95f550 100644 --- a/frontend/control-center/src/pages/Catalog/CatalogItemDetails/index.tsx +++ b/frontend/control-center/src/pages/Catalog/CatalogItemDetails/index.tsx @@ -3,16 +3,17 @@ import {Link, useNavigate, useLocation} from 'react-router-dom'; import {useTranslation} from 'react-i18next'; import {connect, ConnectedProps} from 'react-redux'; import {installComponent, uninstallComponent} from '../../../actions/catalog'; -import {ContentWrapper, Button, LinkButton, SettingsModal, NotificationComponent} from 'components'; +import {StateModel} from '../../../reducers'; +import {ComponentInfo, Modal, ModalType, NotificationModel} from 'model'; +import {ContentWrapper, Button, LinkButton, SettingsModal, NotificationComponent, SmartButton} from 'components'; import {getChannelAvatar} from '../../../components/ChannelAvatar'; -import {availabilityFormatted, DescriptionComponent, getDescriptionSourceName} from '../CatalogCard'; +import {availabilityFormatted} from '../CatalogCard'; +import {DescriptionComponent, getDescriptionSourceName} from '../../../components/Description'; import {CATALOG_ROUTE} from '../../../routes/routes'; +import {getNewChannelRouteForComponent} from '../../../services'; import {ReactComponent as ArrowLeftIcon} from 'assets/images/icons/leftArrowCircle.svg'; import {ReactComponent as CheckmarkIcon} from 'assets/images/icons/checkmarkFilled.svg'; -import {getNewChannelRouteForComponent} from '../getRouteForCard'; -import {ComponentInfo, Modal, ModalType, NotificationModel} from 'model'; import styles from './index.module.scss'; -import {StateModel} from '../../../reducers'; const mapStateToProps = (state: StateModel) => ({ component: state.data.catalog, @@ -34,23 +35,23 @@ const CatalogItemDetails = (props: ConnectedProps) => { const location = useLocation(); const locationState = location.state as LocationState; const {componentInfo} = locationState; + const isInstalled = component[componentInfo?.name]?.installed; const [isModalVisible, setIsModalVisible] = useState(false); const [modal, setModal] = useState(null); - const [isInstalling, setIsInstalling] = useState(false); - const [isUninstalling, setIsUninstalling] = useState(false); + const [isPending, setIsPending] = useState(false); const [notification, setNotification] = useState(null); const {t} = useTranslation(); const navigate = useNavigate(); - const NEW_COMPONENT_INSTALL_ROUTE = getNewChannelRouteForComponent(componentInfo.displayName); + const NEW_COMPONENT_INSTALL_ROUTE = getNewChannelRouteForComponent(componentInfo.source); const uninstallText = t('uninstall') + ` ${componentInfo.displayName}`; const installText = `${componentInfo.displayName} ` + t('installed'); const openModalInstall = () => { if (!isInstalled) { - setIsInstalling(true); + setIsPending(true); installComponent({name: componentInfo.name}) .then(() => { setModal({type: ModalType.install, title: installText}); @@ -61,7 +62,7 @@ const CatalogItemDetails = (props: ConnectedProps) => { setNotification({show: true, successful: false, text: t('failedInstall')}); }) .finally(() => { - setIsInstalling(false); + setIsPending(false); }); } else { setModal({type: ModalType.uninstall, title: uninstallText}); @@ -74,7 +75,7 @@ const CatalogItemDetails = (props: ConnectedProps) => { }; const confirmUninstall = () => { - setIsUninstalling(true); + setIsPending(true); setIsModalVisible(false); uninstallComponent({name: `${componentInfo.name}`}) .then(() => { @@ -84,7 +85,7 @@ const CatalogItemDetails = (props: ConnectedProps) => { setNotification({show: true, successful: false, text: t('failedUninstall')}); }) .finally(() => { - setIsUninstalling(false); + setIsPending(false); }); }; @@ -93,9 +94,7 @@ const CatalogItemDetails = (props: ConnectedProps) => {

{componentInfo?.displayName}

- +

); @@ -124,20 +123,15 @@ const CatalogItemDetails = (props: ConnectedProps) => {
{getChannelAvatar(componentInfo?.displayName)}
- + className={styles.installButton} + />
diff --git a/frontend/control-center/src/pages/Catalog/getRouteForCard.ts b/frontend/control-center/src/pages/Catalog/getRouteForCard.ts deleted file mode 100644 index bff9b12686..0000000000 --- a/frontend/control-center/src/pages/Catalog/getRouteForCard.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { - CONNECTORS_CHAT_PLUGIN_CONNECTED_ROUTE, - CONNECTORS_FACEBOOK_CONNECTED_ROUTE, - CONNECTORS_TWILIO_SMS_CONNECTED_ROUTE, - CONNECTORS_TWILIO_WHATSAPP_CONNECTED_ROUTE, - CONNECTORS_WHATSAPP_BUSINESS_CLOUD_CONNECTED_ROUTE, - CONNECTORS_GOOGLE_CONNECTED_ROUTE, - CONNECTORS_INSTAGRAM_CONNECTED_ROUTE, - CONNECTORS_FACEBOOK_ROUTE, - CONNECTORS_CHAT_PLUGIN_ROUTE, - CONNECTORS_TWILIO_SMS_ROUTE, - CONNECTORS_TWILIO_WHATSAPP_ROUTE, - CONNECTORS_GOOGLE_ROUTE, - CONNECTORS_INSTAGRAM_ROUTE, - CONNECTORS_DIALOGFLOW_ROUTE, - CONNECTORS_ZENDESK_ROUTE, - CONNECTORS_SALESFORCE_ROUTE, - CONNECTORS_WHATSAPP_BUSINESS_CLOUD_ROUTE, - CATALOG_FACEBOOK_ROUTE, - CATALOG_CHAT_PLUGIN_ROUTE, - CATALOG_TWILIO_SMS_ROUTE, - CATALOG_TWILIO_WHATSAPP_ROUTE, - CATALOG_WHATSAPP_BUSINESS_CLOUD_ROUTE, - CATALOG_GOOGLE_ROUTE, - CATALOG_INSTAGRAM_ROUTE, - CATALOG_DIALOGFLOW_ROUTE, - CATALOG_ZENDESK_ROUTE, - CATALOG_SALESFORCE_ROUTE, - CATALOG_CONGNIFY_ROUTE, - CATALOG_AMELIA_ROUTE, - CATALOG_FRONTEND_INBOX_ROUTE, - CATALOG_RASA_ROUTE, - CATALOG_WEBHOOKS_ROUTE, - CATALOG_MOBILE_ROUTE, - CATALOG_VIBER_ROUTE, -} from '../../routes/routes'; - -export const getConnectedRouteForComponent = (displayName: string) => { - switch (displayName) { - case 'Airy Chat Plugin': - return CONNECTORS_CHAT_PLUGIN_CONNECTED_ROUTE; - case 'Facebook Messenger': - return CONNECTORS_FACEBOOK_CONNECTED_ROUTE; - case 'Twilio SMS': - return CONNECTORS_TWILIO_SMS_CONNECTED_ROUTE; - case 'Twilio WhatsApp': - return CONNECTORS_TWILIO_WHATSAPP_CONNECTED_ROUTE; - case 'WhatsApp Business Cloud': - return CONNECTORS_WHATSAPP_BUSINESS_CLOUD_CONNECTED_ROUTE; - case 'Google Business Messages': - return CONNECTORS_GOOGLE_CONNECTED_ROUTE; - case 'Instagram': - return CONNECTORS_INSTAGRAM_CONNECTED_ROUTE; - case 'Dialogflow': - return CONNECTORS_DIALOGFLOW_ROUTE + '/new'; - case 'Salesforce': - return CONNECTORS_SALESFORCE_ROUTE + '/new'; - case 'Zendesk': - return CONNECTORS_ZENDESK_ROUTE + '/new'; - } -}; - -export const getNewChannelRouteForComponent = (displayName: string) => { - switch (displayName) { - case 'Airy Chat Plugin': - return CONNECTORS_CHAT_PLUGIN_ROUTE + '/new'; - case 'Facebook Messenger': - return CONNECTORS_FACEBOOK_ROUTE + '/new'; - case 'Twilio SMS': - return CONNECTORS_TWILIO_SMS_ROUTE + '/new'; - case 'Twilio WhatsApp': - return CONNECTORS_TWILIO_WHATSAPP_ROUTE + '/new'; - case 'WhatsApp Business Cloud': - return CONNECTORS_WHATSAPP_BUSINESS_CLOUD_ROUTE + '/new'; - case 'Google Business Messages': - return CONNECTORS_GOOGLE_ROUTE + '/new'; - case 'Instagram': - return CONNECTORS_INSTAGRAM_ROUTE + '/new'; - case 'Dialogflow': - return CONNECTORS_DIALOGFLOW_ROUTE + '/new'; - case 'Salesforce': - return CONNECTORS_SALESFORCE_ROUTE + '/new'; - case 'Zendesk': - return CONNECTORS_ZENDESK_ROUTE + '/new'; - } -}; - -export const getCatalogProductRouteForComponent = (displayName: string) => { - switch (displayName) { - case 'Airy Chat Plugin': - return CATALOG_CHAT_PLUGIN_ROUTE; - case 'Facebook Messenger': - return CATALOG_FACEBOOK_ROUTE; - case 'Twilio SMS': - return CATALOG_TWILIO_SMS_ROUTE; - case 'Twilio WhatsApp': - return CATALOG_TWILIO_WHATSAPP_ROUTE; - case 'WhatsApp Business Cloud': - return CATALOG_WHATSAPP_BUSINESS_CLOUD_ROUTE; - case 'Google Business Messages': - return CATALOG_GOOGLE_ROUTE; - case 'Instagram': - return CATALOG_INSTAGRAM_ROUTE; - case 'Dialogflow': - return CATALOG_DIALOGFLOW_ROUTE; - case 'Salesforce': - return CATALOG_SALESFORCE_ROUTE; - case 'Zendesk': - return CATALOG_ZENDESK_ROUTE; - case 'Congnigy': - return CATALOG_CONGNIFY_ROUTE; - case 'Amelia': - return CATALOG_AMELIA_ROUTE; - case 'Inbox': - return CATALOG_FRONTEND_INBOX_ROUTE; - case 'Rasa': - return CATALOG_RASA_ROUTE; - case 'Mobile': - return CATALOG_MOBILE_ROUTE; - case 'Webhooks': - return CATALOG_WEBHOOKS_ROUTE; - case 'Viber': - return CATALOG_VIBER_ROUTE; - } -}; diff --git a/frontend/control-center/src/pages/Connectors/ChannelCard/index.tsx b/frontend/control-center/src/pages/Connectors/ChannelCard/index.tsx index fce98c91a3..47aa626df3 100644 --- a/frontend/control-center/src/pages/Connectors/ChannelCard/index.tsx +++ b/frontend/control-center/src/pages/Connectors/ChannelCard/index.tsx @@ -1,28 +1,37 @@ import React from 'react'; import styles from './index.module.scss'; -import {SourceInfo} from '../../../components/SourceInfo'; -import {Link} from 'react-router-dom'; +import {getChannelAvatar} from '../../../components/ChannelAvatar'; +import {useNavigate} from 'react-router-dom'; import {ReactComponent as ArrowRightIcon} from 'assets/images/icons/arrowRight.svg'; +import {CONNECTORS_ROUTE} from '../../../routes/routes'; import {useTranslation} from 'react-i18next'; import {ConfigStatusButton} from '../ConfigStatusButton'; -import {ComponentStatus} from '..'; +import {ComponentStatus, ConnectorCardComponentInfo} from '..'; +import {cyAddChannelButton} from 'handles'; type ChannelCardProps = { - sourceInfo: SourceInfo; + componentInfo: ConnectorCardComponentInfo; channelsToShow?: number; componentStatus?: ComponentStatus; }; export const ChannelCard = (props: ChannelCardProps) => { - const {sourceInfo, channelsToShow, componentStatus} = props; + const {componentInfo, channelsToShow, componentStatus} = props; const {t} = useTranslation(); + const navigate = useNavigate(); return ( - +
{ + event.stopPropagation(), navigate(CONNECTORS_ROUTE + '/' + componentInfo.source + '/connected'); + }} + className={styles.container} + data-cy={cyAddChannelButton} + >
- {sourceInfo.image} - {sourceInfo.title} + {getChannelAvatar(componentInfo.source)} + {componentInfo.displayName}
{componentStatus && } @@ -32,6 +41,6 @@ export const ChannelCard = (props: ChannelCardProps) => {
- +
); }; diff --git a/frontend/control-center/src/pages/Connectors/ConfigStatusButton/index.module.scss b/frontend/control-center/src/pages/Connectors/ConfigStatusButton/index.module.scss index d7b4b744b9..36c00b3b1c 100644 --- a/frontend/control-center/src/pages/Connectors/ConfigStatusButton/index.module.scss +++ b/frontend/control-center/src/pages/Connectors/ConfigStatusButton/index.module.scss @@ -20,6 +20,10 @@ .buttonNotConfigured:active, .buttonNotConfigured:visited { background: var(--color-not-configured-orange); + &:hover { + cursor: pointer; + opacity: 0.8; + } } .buttonDisabled, @@ -28,3 +32,14 @@ .buttonDisabled:visited { background: var(--color-disable-status-config); } + +.buttonNotHealthy, +.buttonNotHealthy:hover, +.buttonNotHealthy:active, +.buttonNotHealthy:visited { + background: var(--color-red-alert); + &:hover { + cursor: pointer; + background: var(--color-red-alert-pressed); + } +} diff --git a/frontend/control-center/src/pages/Connectors/ConfigStatusButton/index.tsx b/frontend/control-center/src/pages/Connectors/ConfigStatusButton/index.tsx index f62e009f9b..7bd9b17389 100644 --- a/frontend/control-center/src/pages/Connectors/ConfigStatusButton/index.tsx +++ b/frontend/control-center/src/pages/Connectors/ConfigStatusButton/index.tsx @@ -3,6 +3,8 @@ import {Button} from 'components'; import {useTranslation} from 'react-i18next'; import styles from './index.module.scss'; import {ComponentStatus} from '..'; +import {useNavigate} from 'react-router-dom'; +import {STATUS_ROUTE} from '../../../routes/routes'; interface ConfigStatusButtonProps { componentStatus: ComponentStatus; @@ -12,12 +14,33 @@ interface ConfigStatusButtonProps { export const ConfigStatusButton = (props: ConfigStatusButtonProps) => { const {componentStatus, customStyle} = props; const {t} = useTranslation(); + const navigate = useNavigate(); + + const handleNavigation = (componentStatus: ComponentStatus, event: Event) => { + switch (componentStatus) { + case ComponentStatus.notHealthy: + event.stopPropagation(); + navigate(STATUS_ROUTE); + break; + case ComponentStatus.notConfigured: + event.stopPropagation(); + console.log('navigate to configurationComponent'); + break; + default: + break; + } + }; return ( + />
{isUpdateModalVisible && ( diff --git a/frontend/control-center/src/pages/Connectors/ConnectedChannelsList/ChannelsListItem/index.tsx b/frontend/control-center/src/pages/Connectors/ConnectedChannelsList/ChannelsListItem/index.tsx index 1932f3e055..b0160ee1a6 100644 --- a/frontend/control-center/src/pages/Connectors/ConnectedChannelsList/ChannelsListItem/index.tsx +++ b/frontend/control-center/src/pages/Connectors/ConnectedChannelsList/ChannelsListItem/index.tsx @@ -40,10 +40,14 @@ const ChannelListItem = (props: ChannelListItemProps) => { }; const disconnectChannel = () => { - props.disconnectChannel({ - source: channel.source, - channelId: channel.id, - }); + props + .disconnectChannel({ + source: channel.source, + channelId: channel.id, + }) + .catch((error: Error) => { + console.error(error); + }); togglePopupVisibility(); }; diff --git a/frontend/control-center/src/pages/Connectors/ConnectedChannelsList/index.tsx b/frontend/control-center/src/pages/Connectors/ConnectedChannelsList/index.tsx index 533e35f485..fa93e475e3 100644 --- a/frontend/control-center/src/pages/Connectors/ConnectedChannelsList/index.tsx +++ b/frontend/control-center/src/pages/Connectors/ConnectedChannelsList/index.tsx @@ -20,18 +20,11 @@ import { cyChannelsTwilioWhatsappList, cyChannelsInstagramList, } from 'handles'; -import { - CONNECTORS_FACEBOOK_ROUTE, - CONNECTORS_CHAT_PLUGIN_ROUTE, - CONNECTORS_TWILIO_SMS_ROUTE, - CONNECTORS_TWILIO_WHATSAPP_ROUTE, - CONNECTORS_GOOGLE_ROUTE, - CONNECTORS_INSTAGRAM_ROUTE, -} from '../../../routes/routes'; import ChannelsListItem from './ChannelsListItem'; import {Pagination} from 'components'; import {useAnimation} from 'render/services/useAnimation'; import {useTranslation} from 'react-i18next'; +import {CONNECTORS_ROUTE} from '../../../routes/routes'; const mapDispatchToProps = { listChannels, @@ -52,11 +45,11 @@ const ConnectedChannelsList = (props: ConnectedChannelsListProps) => { return Object.values(allChannels(state)).filter((channel: Channel) => channel.source === source); }); - const [path, setPath] = useState(''); const [searchText, setSearchText] = useState(''); const [showingSearchField, setShowingSearchField] = useState(false); const [animationAction, setAnimationAction] = useState(false); const [dataCyChannelList, setDataCyChannelList] = useState(''); + const [currentPage, setCurrentPage] = useState(1); const screenDimensions: ScreenDimensions = {height: screen.height, width: screen.width}; const ITEM_LINE_HEIGHT = 64; const MARGIN_TOP = 128; @@ -64,14 +57,14 @@ const ConnectedChannelsList = (props: ConnectedChannelsListProps) => { const PAGINATION_HEIGHT = 54; const ADDITIONAL_SPACE = 60; + const path = `${CONNECTORS_ROUTE}/${source}/new`; + const filteredChannels = channels.filter((channel: Channel) => channel.metadata?.name?.toLowerCase().includes(searchText.toLowerCase()) ); const areConnectedChannels = channels.length > 0 && filteredChannels.length > 0; - const [currentPage, setCurrentPage] = useState(1); - const listPageSize = Math.floor( (screenDimensions.height - offset - MARGIN_TOP - PAGINATION_HEIGHT - PADDING_BOTTOM - ADDITIONAL_SPACE) / ITEM_LINE_HEIGHT @@ -94,36 +87,23 @@ const ConnectedChannelsList = (props: ConnectedChannelsListProps) => { }, [source]); const getInfo = () => { - let ROUTE; switch (source) { case Source.facebook: - ROUTE = CONNECTORS_FACEBOOK_ROUTE; - setPath(ROUTE + '/new'); setDataCyChannelList(cyChannelsFacebookList); break; case Source.google: - ROUTE = CONNECTORS_GOOGLE_ROUTE; - setPath(ROUTE + '/new'); setDataCyChannelList(cyChannelsGoogleList); break; case Source.twilioSMS: - ROUTE = CONNECTORS_TWILIO_SMS_ROUTE; - setPath(ROUTE + '/new'); setDataCyChannelList(cyChannelsTwilioSmsList); break; case Source.twilioWhatsApp: - ROUTE = CONNECTORS_TWILIO_WHATSAPP_ROUTE; - setPath(ROUTE + '/new'); setDataCyChannelList(cyChannelsTwilioWhatsappList); break; case Source.chatPlugin: - ROUTE = CONNECTORS_CHAT_PLUGIN_ROUTE; - setPath(ROUTE + '/new'); setDataCyChannelList(cyChannelsChatPluginList); break; case Source.instagram: - ROUTE = CONNECTORS_INSTAGRAM_ROUTE; - setPath(ROUTE + '/new'); setDataCyChannelList(cyChannelsInstagramList); break; } @@ -194,14 +174,13 @@ const ConnectedChannelsList = (props: ConnectedChannelsListProps) => { )} - {areConnectedChannels && ( = listPageSize ? listPageSize : filteredChannels.length} currentPage={currentPage} - onPageChange={page => setCurrentPage(page)} + onPageChange={setCurrentPage} onSearch={searchText !== ''} /> )} diff --git a/frontend/control-center/src/pages/Connectors/ConnectorConfig/index.module.scss b/frontend/control-center/src/pages/Connectors/ConnectorConfig/index.module.scss index cef7301382..5e75717245 100644 --- a/frontend/control-center/src/pages/Connectors/ConnectorConfig/index.module.scss +++ b/frontend/control-center/src/pages/Connectors/ConnectorConfig/index.module.scss @@ -1,220 +1,6 @@ @import 'assets/scss/fonts.scss'; @import 'assets/scss/colors.scss'; -.wrapper { - background: var(--color-background-white); - color: var(--color-text-contrast); - display: block; - border-top-left-radius: 10px; - border-top-right-radius: 10px; - width: 100%; - margin: 248px 16px 0 191px; - overflow-y: scroll; -} - -.container { - display: flex; - width: 100%; -} - -.backButtonContainer { - display: flex; - align-items: center; - flex-direction: row; - - div { - @include font-base; - font-weight: 400; - text-decoration: none; - } - - a { - @include font-base; - text-decoration: none; - } - - button { - background: transparent; - color: var(--color-airy-blue); - } -} - -.linkButtonContainer { - @include font-base; - display: flex; - align-items: center; - text-decoration: underline; - color: var(--color-text-contrast); - font-weight: bold; - - &:hover, - &:active, - &:visited { - text-decoration: underline; - color: var(--color-text-contrast); - } -} - -.headlineContainer { - display: flex; - flex-direction: column; - position: absolute; - padding: 88px 16px 0 201px; - width: 100%; - h1 { - @include font-xl; - font-weight: bold; - } - p button { - height: 20px; - background: transparent; - } - - button { - @include font-base; - font-weight: 500; - margin: 0; - } -} - -.textIconContainer { - display: flex; - width: 100%; -} - -.textContainer { - display: flex; - flex-direction: column; - width: 100%; -} - -.componentTitle { - display: flex; - align-items: center; -} - -.configStatusButton { - cursor: auto; -} - -.connectorIcon { - display: flex; - width: 75px; - margin-right: 15px; - - svg { - fill: var(--color-text-contrast); - } -} - -.textInfo { - display: flex; - justify-content: space-between; - align-items: center; -} - -.descriptionDocs { - display: flex; - align-items: center; -} - -.connectorDetails { - display: flex; - flex-direction: column; - margin-top: 25px; - - p { - display: flex; - color: var(--color-text-gray); - } -} - -.titleIconDetails { - display: flex; -} - -.infoBox { - margin: 0; - button { - padding-left: 4px; - border: none; - } - svg { - color: var(--color-text-gray); - } - h1 { - @include font-xl; - font-weight: bold; - margin-bottom: 8px; - } -} - -.backButton { - display: block; - margin-bottom: 16px; - cursor: pointer; - text-decoration: none; - &:hover { - text-decoration: underline; - } -} - -.backIcon { - width: 20px; - background: transparent; - path { - fill: var(--color-text-gray); - } - margin-right: 8px; -} - -.formWrapper { - display: flex; -} - -.headlineText { - color: var(--color-text-contrast); - margin-right: 8px; -} - -.overview { - margin-top: 32px; -} - -.listItem { - display: flex; - align-items: center; - margin-bottom: 8px; -} - -.listChannelName { - flex-grow: 1; - border-bottom: 1px solid var(--color-light-gray); - height: 50px; - display: flex; - align-items: center; - padding-left: 16px; -} - -.listButtons { - display: flex; - border-bottom: 1px solid var(--color-light-gray); - height: 50px; - align-items: center; -} - -.listButtonEdit { - margin-right: 8px; - font-weight: normal; -} - -.channelImage { - width: 40px; - height: 40px; - object-fit: cover; - border-radius: 100%; -} - .channelsLineContainer { width: 100%; display: flex; @@ -273,44 +59,3 @@ .pageContentContainer { padding: 32px; } - -.enableModalContainerWrapper { - height: 400px; - width: 550px; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - border-radius: 20px; -} - -.enableModalContainer { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - @include font-l; - - p { - max-width: 70%; - text-align: center; - margin: 4px 0 20px 0; - color: var(--color-text-contrast); - } -} - -.enableModalContainer button { - margin-top: 14px; - padding: 0 60px; - border-radius: 10px; -} - -.headerModal { - @include font-xl; -} - -.checkmarkIcon { - width: 15%; - margin-bottom: 30px; - fill: var(--color-soft-green); -} diff --git a/frontend/control-center/src/pages/Connectors/ConnectorConfig/index.tsx b/frontend/control-center/src/pages/Connectors/ConnectorConfig/index.tsx index 4f51ea11ea..0871ddc771 100644 --- a/frontend/control-center/src/pages/Connectors/ConnectorConfig/index.tsx +++ b/frontend/control-center/src/pages/Connectors/ConnectorConfig/index.tsx @@ -1,9 +1,7 @@ import React, {useState, useEffect, useRef, useLayoutEffect} from 'react'; import {connect, ConnectedProps, useSelector} from 'react-redux'; -import {Link, useParams} from 'react-router-dom'; -import {getSourcesInfo, SourceInfo} from '../../../components/SourceInfo'; -import {Button, NotificationComponent, SettingsModal} from 'components'; -import {ReactComponent as CheckmarkIcon} from 'assets/images/icons/checkmarkFilled.svg'; +import {useTranslation} from 'react-i18next'; +import {useParams} from 'react-router-dom'; import {StateModel} from '../../../reducers'; import { connectChatPlugin, @@ -12,26 +10,26 @@ import { updateConnectorConfiguration, enableDisableComponent, getConnectorsConfiguration, + listComponents, } from '../../../actions'; -import {LinkButton, InfoButton} from 'components'; -import {NotificationModel, Source} from 'model'; -import {ReactComponent as ArrowLeftIcon} from 'assets/images/icons/leftArrowCircle.svg'; -import {useTranslation} from 'react-i18next'; -import {ConnectNewDialogflow} from '../Providers/Dialogflow/ConnectNewDialogflow'; -import {ConnectNewZendesk} from '../Providers/Zendesk/ConnectNewZendesk'; -import {ConnectNewSalesforce} from '../Providers/Salesforce/ConnectNewSalesforce'; -import {ConfigStatusButton} from '../ConfigStatusButton'; import {UpdateComponentConfigurationRequestPayload} from 'httpclient/src'; -import styles from './index.module.scss'; -import ConnectedChannelsList from '../ConnectedChannelsList'; +import {Source, ComponentInfo} from 'model'; + import ChatPluginConnect from '../Providers/Airy/ChatPlugin/ChatPluginConnect'; -import {CONNECTORS_CONNECTED_ROUTE} from '../../../routes/routes'; import FacebookConnect from '../Providers/Facebook/Messenger/FacebookConnect'; import InstagramConnect from '../Providers/Instagram/InstagramConnect'; import GoogleConnect from '../Providers/Google/GoogleConnect'; import TwilioSmsConnect from '../Providers/Twilio/SMS/TwilioSmsConnect'; import TwilioWhatsappConnect from '../Providers/Twilio/WhatsApp/TwilioWhatsappConnect'; -import {getComponentStatus} from '../../../services/getComponentStatus'; +import {DialogflowConnect} from '../Providers/Dialogflow/DialogflowConnect'; +import {ConnectNewZendesk} from '../Providers/Zendesk/ConnectNewZendesk'; +import {ConnectNewSalesforce} from '../Providers/Salesforce/ConnectNewSalesforce'; +import {RasaConnect} from '../Providers/Rasa/RasaConnect'; +import {WhatsappBusinessCloudConnect} from '../Providers/WhatsappBusinessCloud/WhatsappBusinessCloudConnect'; + +import ConnectedChannelsList from '../ConnectedChannelsList'; +import {removePrefix} from '../../../services'; +import styles from './index.module.scss'; export enum Pages { createUpdate = 'create-update', @@ -46,370 +44,229 @@ const mapDispatchToProps = { updateConnectorConfiguration, enableDisableComponent, getConnectorsConfiguration, + listComponents, }; const mapStateToProps = (state: StateModel) => ({ - config: state.data.config, components: state.data.config.components, + catalog: state.data.catalog, }); const connector = connect(mapStateToProps, mapDispatchToProps); -type ConnectorConfigProps = { - connector?: Source; -} & ConnectedProps; - -const ConnectorConfig = (props: ConnectorConfigProps) => { - const { - connector, - components, - enableDisableComponent, - updateConnectorConfiguration, - getConnectorsConfiguration, - config, - } = props; - - const {channelId, source} = useParams(); - const connectorConfiguration = useSelector((state: StateModel) => state.data.connector); - const [connectorInfo, setConnectorInfo] = useState(null); +const ConnectorConfig = (props: ConnectedProps) => { + const {components, catalog, updateConnectorConfiguration, getConnectorsConfiguration, listComponents} = props; + + const connectors = useSelector((state: StateModel) => state.data.connector); + const [connectorInfo, setConnectorInfo] = useState(null); const [currentPage] = useState(Pages.createUpdate); - const [configurationModal, setConfigurationModal] = useState(false); - const [notification, setNotification] = useState(null); - const [isEnabled, setIsEnabled] = useState(components[connectorInfo?.componentName]?.enabled); - const [isEnabling, setIsEnabling] = useState(false); - const [isDisabling, setIsDisabling] = useState(false); + const [isEnabled, setIsEnabled] = useState(null); + const [isPending, setIsPending] = useState(false); const [isConfigured, setIsConfigured] = useState(false); const [lineTitle, setLineTitle] = useState(''); - const [backTitle, setBackTitle] = useState('Connectors'); - const [backRoute, setBackRoute] = useState(''); + const pageContentRef = useRef(null); const [offset, setOffset] = useState(pageContentRef?.current?.offsetTop); + const {t} = useTranslation(); - const isInstalled = true; + + const params = useParams(); + const {channelId, source} = params; + const newChannel = params['*'] === 'new'; + const connectedParams = params['*'] === 'connected'; + + const isAiryInternalConnector = source === Source.chatPlugin; + const isCatalogList = Object.entries(catalog).length > 0; useLayoutEffect(() => { setOffset(pageContentRef?.current?.offsetTop); + listComponents().catch((error: Error) => { + console.error(error); + }); }, []); useEffect(() => { - if (connectorInfo && connectorConfiguration && connectorConfiguration[connectorInfo.componentName]) { + if (connectorInfo && connectors) { if ( - Object.entries(connectorConfiguration[connectorInfo.componentName]) && - Object.entries(connectorConfiguration[connectorInfo.componentName]).length > 0 + connectors[removePrefix(connectorInfo.name)] && + Object.keys(connectors[removePrefix(connectorInfo.name)]).length > 0 ) { setIsConfigured(true); } } - }, [connectorInfo, connectorConfiguration]); + }, [connectorInfo, connectors]); useEffect(() => { - getConnectorsConfiguration(); - (source === Source.chatPlugin || connector === Source.chatPlugin) && setIsConfigured(true); - const sourceInfoArr = getSourcesInfo(); - const connectorSourceInfo = sourceInfoArr.filter(item => item.type === connector); - - channelId === 'new' - ? connector === Source.chatPlugin - ? setLineTitle(t('Create')) - : setLineTitle(t('addChannel')) - : setLineTitle(t('Configuration')); - - source - ? (setConnectorInfo(sourceInfoArr.filter(item => item.type === source)[0]), setLineTitle(t('channelsCapital'))) - : setConnectorInfo(connectorSourceInfo[0]); - - channelId - ? (setBackRoute(`${CONNECTORS_CONNECTED_ROUTE}/${connectorSourceInfo[0].type}`), setBackTitle(t('back'))) - : (setBackRoute('/connectors'), setBackTitle(t('Connectors'))); - }, [source]); + getConnectorsConfiguration().catch((error: Error) => { + console.error(error); + }); - useEffect(() => { - if (config && connectorInfo) { - setIsEnabled(config?.components[connectorInfo?.configKey]?.enabled); - } - }, [config, connectorInfo, components]); - - const createNewConnection = (...args: string[]) => { - let payload: UpdateComponentConfigurationRequestPayload; - - if (connector === Source.dialogflow) { - const [ - projectId, - appCredentials, - suggestionConfidenceLevel, - replyConfidenceLevel, - processorWaitingTime, - processorCheckPeriod, - defaultLanguage, - ] = args; - - payload = { - components: [ - { - name: connectorInfo && connectorInfo?.configKey, - enabled: true, - data: { - projectId: projectId, - dialogflowCredentials: appCredentials, - suggestionConfidenceLevel: suggestionConfidenceLevel, - replyConfidenceLevel: replyConfidenceLevel, - connectorStoreMessagesProcessorMaxWaitMillis: processorWaitingTime, - connectorStoreMessagesProcessorCheckPeriodMillis: processorCheckPeriod, - connectorDefaultLanguage: defaultLanguage, - }, - }, - ], - }; - } + if (isCatalogList) { + isAiryInternalConnector && setIsConfigured(true); - if (connector === Source.zendesk) { - const [domain, token, username] = args; - - payload = { - components: [ - { - name: connectorInfo && connectorInfo?.configKey, - enabled: true, - data: { - domain: domain, - token: token, - username: username, - }, - }, - ], - }; - } + const connectorSourceInfo = Object.entries(catalog).filter(item => item[1].source === source); - if (connector === Source.salesforce) { - const [url, username, password, securityToken] = args; - - payload = { - components: [ - { - name: connectorInfo && connectorInfo?.configKey, - enabled: true, - data: { - url: url, - username: username, - password: password, - securityToken: securityToken, - }, - }, - ], - }; - } + const connectorSourceInfoArr: [string, ComponentInfo] = connectorSourceInfo[0]; + const connectorSourceInfoFormatted = {name: connectorSourceInfoArr[0], ...connectorSourceInfoArr[1]}; - updateConnectorConfiguration(payload).then(() => { - if (!isEnabled) { - setConfigurationModal(true); - } - }); - }; + const connectorHasChannels: undefined | string = connectorSourceInfoFormatted?.isChannel; - const PageContent = () => { - if (connector === Source.dialogflow) { - return ( - - ); + determineLineTitle(connectorHasChannels); + setConnectorInfo(connectorSourceInfoFormatted); } + }, [source, isCatalogList, params]); - if (connector === Source.zendesk) { - return ( - - ); - } + useEffect(() => { + if (components && connectorInfo) setIsEnabled(components[removePrefix(connectorInfo.name)]?.enabled); + }, [connectorInfo, components]); - if (connector === Source.salesforce) { - return ( - - ); - } + const determineLineTitle = (connectorHasChannels: undefined | string) => { + const newAiryChatPluginPage = newChannel && source === Source.chatPlugin; + const newChannelPage = newChannel && connectorHasChannels; - if (connector === Source.chatPlugin) { - return ; - } - if (connector === Source.facebook) { - return ; - } - if (connector === Source.instagram) { - return ; - } - if (connector === Source.google) { - return ; + if (newAiryChatPluginPage) { + setLineTitle(t('create')); + return; } - if (connector === Source.twilioSMS) { - return ; + + if (newChannelPage) { + setLineTitle(t('addChannel')); + return; } - if (connector === Source.twilioWhatsApp) { - return ; + + if (connectedParams) { + setLineTitle(t('channelsCapital')); + return; } - return ; + setLineTitle(t('configuration')); }; - const enableDisableComponentToggle = () => { - setConfigurationModal(false); - isEnabled ? setIsDisabling(true) : setIsEnabling(true); - enableDisableComponent({components: [{name: connectorInfo && connectorInfo?.configKey, enabled: !isEnabled}]}) - .then(() => { - setNotification({ - show: true, - successful: true, - text: isEnabled ? t('successfullyDisabled') : t('successfullyEnabled'), - }); - }) - .catch(() => { - setNotification({ - show: true, - successful: false, - text: isEnabled ? t('failedDisabled') : t('failedEnabled'), - }); + const createNewConnection = (configurationValues: {[key: string]: string}) => { + setIsPending(true); + + const payload: UpdateComponentConfigurationRequestPayload = { + components: [ + { + name: connectorInfo && removePrefix(connectorInfo.name), + enabled: true, + data: configurationValues, + }, + ], + }; + + updateConnectorConfiguration(payload) + .catch((error: Error) => { + console.error(error); }) .finally(() => { - isEnabled ? setIsDisabling(false) : setIsEnabling(false); + setIsPending(false); }); }; - const closeConfigurationModal = () => { - setConfigurationModal(false); - }; + const PageContent = () => { + if (newChannel || channelId) { + if (source === Source.dialogflow) { + return ( + + ); + } + + if (source === Source.zendesk) { + return ( + + ); + } + + if (source === Source.salesforce) { + return ( + + ); + } + + if (source === Source.rasa) { + return ( + + ); + } + + if (source === Source.whatsapp) { + return ( + + ); + } + + if (source === Source.chatPlugin) { + return ; + } + if (source === Source.facebook) { + return ; + } + if (source === Source.instagram) { + return ; + } + if (source === Source.google) { + return ; + } + if (source === Source.twilioSMS) { + return ; + } + if (source === Source.twilioWhatsApp) { + return ; + } - const openConfigurationModal = () => { - setConfigurationModal(true); + if (source === Source.viber) { + return

configuration page under construction - coming soon!

; + } + } + + return ; }; return ( -
-
-
- - -
- - {backTitle} -
-
- -
- -
-
-
-
- {connectorInfo && connectorInfo?.image} -
- -
-
-

{connectorInfo && connectorInfo?.title}

- -
- -
-
- {connectorInfo &&

{connectorInfo?.description}

} - -
- - {isConfigured && ( - - )} -
-
-
+ <> + {!(source === Source.chatPlugin && (newChannel || channelId)) && ( +
+
+ + {lineTitle} +
-
-
- -
- {connector !== Source.chatPlugin && ( -
-
- - {lineTitle} - -
-
-
- )} -
- +
-
- - {notification?.show && ( - - )} - - {configurationModal && ( - : null} - wrapperClassName={styles.enableModalContainerWrapper} - containerClassName={styles.enableModalContainer} - title={ - isEnabled - ? t('disableComponent') + ' ' + connectorInfo?.title - : connectorInfo?.title + ' ' + t('enabledComponent') - } - close={closeConfigurationModal} - headerClassName={styles.headerModal} - > - {isEnabled && ( - <> -

{t('disableComponentText')}

- - - - )} -
)} -
+
+ +
+ ); }; diff --git a/frontend/control-center/src/pages/Connectors/ConnectorWrapper/index.module.scss b/frontend/control-center/src/pages/Connectors/ConnectorWrapper/index.module.scss new file mode 100644 index 0000000000..ae262da930 --- /dev/null +++ b/frontend/control-center/src/pages/Connectors/ConnectorWrapper/index.module.scss @@ -0,0 +1,184 @@ +@import 'assets/scss/fonts.scss'; +@import 'assets/scss/colors.scss'; + +.wrapper { + background: var(--color-background-white); + color: var(--color-text-contrast); + display: block; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + width: 100%; + margin: 248px 16px 0 191px; + overflow-y: scroll; +} + +.container { + display: flex; + width: 100%; +} + +.headlineContainer { + display: flex; + flex-direction: column; + position: absolute; + padding: 88px 16px 0 201px; + width: 100%; + h1 { + @include font-xl; + font-weight: bold; + } + p button { + height: 20px; + background: transparent; + } + + button { + @include font-base; + font-weight: 500; + margin: 0; + } +} + +.backButtonContainer { + display: flex; + align-items: center; + flex-direction: row; + + div { + @include font-base; + font-weight: 400; + text-decoration: none; + } + + a { + @include font-base; + text-decoration: none; + } + + button { + background: transparent; + color: var(--color-airy-blue); + } +} + +.linkButtonContainer { + @include font-base; + display: flex; + align-items: center; + text-decoration: underline; + color: var(--color-text-contrast); + font-weight: bold; + + &:hover, + &:active, + &:visited { + text-decoration: underline; + color: var(--color-text-contrast); + } +} + +.backIcon { + width: 20px; + background: transparent; + path { + fill: var(--color-text-gray); + } + margin-right: 8px; +} + +.connectorDetails { + display: flex; + flex-direction: column; + margin-top: 25px; + + p { + display: flex; + color: var(--color-text-gray); + } +} + +.titleIconDetails { + display: flex; +} + +.textIconContainer { + display: flex; + width: 100%; +} + +.textIconContainer { + display: flex; + width: 100%; +} + +.connectorIcon { + display: flex; + width: 75px; + margin-right: 15px; + + svg { + fill: var(--color-text-contrast); + } +} + +.textContainer { + display: flex; + flex-direction: column; + width: 100%; +} + +.componentTitleTextInfoContainer { + display: flex; + flex-direction: column; +} + +.componentTitle { + display: flex; + align-items: center; +} + +.headlineText { + color: var(--color-text-contrast); + margin-right: 8px; +} + +.configStatusButton { + cursor: auto; +} + +.textInfo { + display: flex; + justify-content: space-between; + align-items: center; +} + +.descriptionDocs { + display: flex; + align-items: center; +} + +.enableModalContainerWrapper { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.enableModalContainer { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + @include font-l; + + p { + max-width: 70%; + text-align: center; + margin: 4px 0 20px 0; + color: var(--color-text-contrast); + } +} + +.headerModal { + @include font-xl; +} diff --git a/frontend/control-center/src/pages/Connectors/ConnectorWrapper/index.tsx b/frontend/control-center/src/pages/Connectors/ConnectorWrapper/index.tsx new file mode 100644 index 0000000000..8b383dcea9 --- /dev/null +++ b/frontend/control-center/src/pages/Connectors/ConnectorWrapper/index.tsx @@ -0,0 +1,262 @@ +import React, {useState, useEffect} from 'react'; +import {connect, ConnectedProps, useSelector} from 'react-redux'; +import {useTranslation} from 'react-i18next'; +import {Link, useParams} from 'react-router-dom'; +import {Button, NotificationComponent, SettingsModal, SmartButton} from 'components'; +import {StateModel} from '../../../reducers'; +import { + connectChatPlugin, + updateChannel, + disconnectChannel, + enableDisableComponent, + getConnectorsConfiguration, + listComponents, +} from '../../../actions'; +import {LinkButton, InfoButton} from 'components'; +import {NotificationModel, Source, ComponentInfo} from 'model'; +import {ConfigStatusButton} from '../ConfigStatusButton'; +import {getComponentStatus, removePrefix} from '../../../services'; +import {DescriptionComponent, getDescriptionSourceName, getChannelAvatar} from '../../../components'; +import {CONNECTORS_ROUTE} from '../../../routes/routes'; +import {ReactComponent as CheckmarkIcon} from 'assets/images/icons/checkmarkFilled.svg'; +import {ReactComponent as ArrowLeftIcon} from 'assets/images/icons/leftArrowCircle.svg'; +import styles from './index.module.scss'; + +const mapDispatchToProps = { + connectChatPlugin, + updateChannel, + disconnectChannel, + enableDisableComponent, + getConnectorsConfiguration, + listComponents, +}; + +const mapStateToProps = (state: StateModel) => ({ + config: state.data.config, + components: state.data.config.components, + catalog: state.data.catalog, +}); + +const connector = connect(mapStateToProps, mapDispatchToProps); + +type ConnectorWrapperProps = { + Outlet: React.ReactElement | null; +} & ConnectedProps; + +const ConnectorWrapper = (props: ConnectorWrapperProps) => { + const {components, catalog, enableDisableComponent, getConnectorsConfiguration, listComponents, config, Outlet} = + props; + + const connectors = useSelector((state: StateModel) => state.data.connector); + const [connectorInfo, setConnectorInfo] = useState(null); + const componentName = connectorInfo && removePrefix(connectorInfo?.name); + const [configurationModal, setConfigurationModal] = useState(false); + const [notification, setNotification] = useState(null); + const [isEnabled, setIsEnabled] = useState(components[connectorInfo && componentName]?.enabled); + const [isHealthy, setIsHealthy] = useState(components[connectorInfo && componentName]?.healthy); + const [isPending, setIsPending] = useState(false); + const [isConfigured, setIsConfigured] = useState(false); + const [backTitle, setBackTitle] = useState('Connectors'); + const [backRoute, setBackRoute] = useState(''); + + const {t} = useTranslation(); + + const params = useParams(); + const {channelId, source} = params; + const newChannel = params['*'] === 'new'; + + const isInstalled = true; + const isAiryInternalConnector = source === Source.chatPlugin; + const isCatalogList = Object.entries(catalog).length > 0; + const CONNECTOR_CONNECTED_ROUTE = `${CONNECTORS_ROUTE}/${source}/connected`; + + useEffect(() => { + listComponents().catch((error: Error) => { + console.error(error); + }); + }, []); + + useEffect(() => { + if (connectorInfo && connectors && connectors[componentName]) { + if (Object.keys(connectors[componentName]).length > 0) { + setIsConfigured(true); + } + } + }, [connectorInfo, connectors]); + + useEffect(() => { + getConnectorsConfiguration().catch((error: Error) => { + console.error(error); + }); + + if (isCatalogList) { + isAiryInternalConnector && setIsConfigured(true); + + const connectorSourceInfo: [string, ComponentInfo][] = Object.entries(catalog).filter( + item => item[1].source === source + ); + + const connectorSourceInfoArr: [string, ComponentInfo] = connectorSourceInfo[0]; + const connectorSourceInfoFormatted = {name: connectorSourceInfoArr[0], ...connectorSourceInfoArr[1]}; + + const connectorHasChannels = connectorSourceInfoFormatted?.isChannel; + + determineBackRoute(channelId, newChannel, connectorHasChannels); + + setConnectorInfo(connectorSourceInfoFormatted); + } + }, [params, source, channelId, isCatalogList]); + + useEffect(() => { + if (config && connectorInfo) { + setIsEnabled(config?.components[componentName]?.enabled); + setIsHealthy(config?.components[componentName]?.healthy); + } + }, [config, connectorInfo, components]); + + const determineBackRoute = (channelId: string, newChannel: boolean, connectorHasChannels: string | undefined) => { + const channelRoute = (channelId || newChannel) && connectorHasChannels; + + if (channelRoute) { + setBackRoute(CONNECTOR_CONNECTED_ROUTE); + setBackTitle(t('back')); + } else { + setBackRoute(CONNECTORS_ROUTE); + setBackTitle(t('Connectors')); + } + }; + + const enableDisableComponentToggle = () => { + setConfigurationModal(false); + setIsPending(true); + enableDisableComponent({components: [{name: componentName, enabled: !isEnabled}]}) + .then(() => { + setNotification({ + show: true, + successful: true, + text: isEnabled ? t('successfullyDisabled') : t('successfullyEnabled'), + }); + }) + .catch(() => { + setNotification({ + show: true, + successful: false, + text: isEnabled ? t('failedDisabled') : t('failedEnabled'), + }); + }) + .finally(() => { + setIsPending(false); + }); + }; + + const closeConfigurationModal = () => setConfigurationModal(false); + + const openConfigurationModal = () => setConfigurationModal(true); + + return ( +
+
+
+ + +
+ + {backTitle} +
+
+ +
+ +
+
+
+
+ {connectorInfo && getChannelAvatar(connectorInfo?.displayName)} +
+ +
+
+

{connectorInfo && connectorInfo?.displayName}

+ +
+ +
+
+ {connectorInfo && ( +

+ +

+ )} + +
+ + {isConfigured && ( + + )} +
+
+
+
+
+
+ + {notification?.show && ( + + )} + +
{Outlet}
+ + {configurationModal && ( + : null} + wrapperClassName={styles.enableModalContainerWrapper} + containerClassName={styles.enableModalContainer} + title={ + isEnabled + ? t('disableComponent') + ' ' + connectorInfo?.displayName + : connectorInfo?.displayName + ' ' + t('enabledComponent') + } + close={closeConfigurationModal} + headerClassName={styles.headerModal} + > + {isEnabled && ( + <> +

{t('disableComponentText')}

+ + + + )} +
+ )} +
+ ); +}; + +export default connector(ConnectorWrapper); diff --git a/frontend/control-center/src/pages/Connectors/ConnectorsOutlet.tsx b/frontend/control-center/src/pages/Connectors/ConnectorsOutlet.tsx index cf6a6e7430..0a576790bc 100644 --- a/frontend/control-center/src/pages/Connectors/ConnectorsOutlet.tsx +++ b/frontend/control-center/src/pages/Connectors/ConnectorsOutlet.tsx @@ -1,8 +1,7 @@ import React from 'react'; +import ConnectorWrapper from './ConnectorWrapper'; import {Outlet} from 'react-router-dom'; -const ConnectorsOutlet = () => { - return ; -}; +const ConnectorsOutlet = () => } />; export default ConnectorsOutlet; diff --git a/frontend/control-center/src/pages/Connectors/InfoCard/index.module.scss b/frontend/control-center/src/pages/Connectors/InfoCard/index.module.scss new file mode 100644 index 0000000000..08eb62d8d0 --- /dev/null +++ b/frontend/control-center/src/pages/Connectors/InfoCard/index.module.scss @@ -0,0 +1,47 @@ +@import 'assets/scss/fonts.scss'; +@import 'assets/scss/colors.scss'; +@import 'assets/scss/z-index.scss'; + +.container { + width: 260px; + height: 100px; + margin-bottom: 28px; + margin-right: 36px; + display: flex; + flex-direction: column; + align-items: flex-start; + border: 1px solid var(--color-dark-elements-gray); + border-radius: 10px; + background-color: var(--color-background-blue); + text-decoration: none; + &:hover { + border: 2px solid var(--color-airy-blue); + margin-left: -1px; + margin-top: -1px; + width: 261px; + height: 101px; + cursor: pointer; + } +} + +.infoCard { + display: flex; + flex-direction: column; + justify-content: space-between; + margin: 14px 0 14px 16px; +} + +.channelLogoTitleContainer { + @include font-base; + font-weight: 600; + margin-bottom: 13px; + color: var(--color-text-contrast); + display: flex; + align-items: center; + + svg { + height: 30px; + width: 30px; + margin-right: 10px; + } +} diff --git a/frontend/control-center/src/pages/Connectors/InfoCard/index.tsx b/frontend/control-center/src/pages/Connectors/InfoCard/index.tsx new file mode 100644 index 0000000000..46ce40d224 --- /dev/null +++ b/frontend/control-center/src/pages/Connectors/InfoCard/index.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import {useNavigate} from 'react-router-dom'; +import {installComponent, uninstallComponent} from '../../../actions/catalog'; +import {connect, ConnectedProps} from 'react-redux'; +import {ConfigStatusButton} from '../ConfigStatusButton'; +import {ComponentStatus} from '..'; +import {getChannelAvatar} from '../../../components/ChannelAvatar'; +import {ConnectorCardComponentInfo} from '../index'; +import {getNewChannelRouteForComponent} from '../../../services'; +import styles from './index.module.scss'; + +type InfoCardProps = { + componentInfo: ConnectorCardComponentInfo; + componentStatus?: ComponentStatus; +} & ConnectedProps; + +const mapDispatchToProps = { + installComponent, + uninstallComponent, +}; + +const connector = connect(null, mapDispatchToProps); + +const InfoCard = (props: InfoCardProps) => { + const {componentInfo, componentStatus} = props; + const navigate = useNavigate(); + const CONFIGURATION_ROUTE = getNewChannelRouteForComponent(componentInfo.source); + + const handleCardClick = () => { + navigate(CONFIGURATION_ROUTE); + }; + + return ( +
+
+
+ {getChannelAvatar(componentInfo.source)} + {componentInfo.displayName} +
+ {componentStatus && } +
+
+ ); +}; + +export default connector(InfoCard); diff --git a/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/ChatPluginConnect.module.scss b/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/ChatPluginConnect.module.scss index 7fc9514c34..bf74536862 100644 --- a/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/ChatPluginConnect.module.scss +++ b/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/ChatPluginConnect.module.scss @@ -7,12 +7,13 @@ display: block; border-radius: 10px; width: 100%; - height: calc(100vh - 88px); + height: 100%; } .container { display: flex; width: 100%; + overflow-x: hidden; } .headlineContainer { @@ -165,7 +166,7 @@ } .line { - width: 100%; + width: 200%; height: 1px; background: rgb(202, 213, 219); display: flex; @@ -174,6 +175,16 @@ left: 0px; } +.customizationPageLeftOffset { + padding-top: 0; + padding-left: 32px; +} + +.defaultTopLeftOffset { + padding-top: 36px; + padding-left: 32px; +} + .enableModalContainerWrapper { display: flex; align-items: center; diff --git a/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/ChatPluginConnect.tsx b/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/ChatPluginConnect.tsx index 6404a0f48c..3a79078634 100644 --- a/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/ChatPluginConnect.tsx +++ b/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/ChatPluginConnect.tsx @@ -1,6 +1,7 @@ import React, {useState} from 'react'; import {connect, ConnectedProps} from 'react-redux'; import {Link, useNavigate, useParams} from 'react-router-dom'; +import {useTranslation} from 'react-i18next'; import {apiHostUrl} from '../../../../../httpClient'; import {StateModel} from '../../../../../reducers'; @@ -8,22 +9,20 @@ import {allChannels} from '../../../../../selectors/channels'; import {connectChatPlugin, updateChannel, disconnectChannel} from '../../../../../actions'; import {cyChannelCreatedChatPluginCloseButton} from 'handles'; -import {Button, LinkButton, SettingsModal} from 'components'; -import {Channel, Source} from 'model'; +import {Button, LinkButton, NotificationComponent, SettingsModal} from 'components'; +import {Channel, NotificationModel, Source, ChatpluginConfig, DefaultConfig} from 'model'; import {ConnectNewChatPlugin} from './sections/ConnectNewChatPlugin'; import {ReactComponent as AiryAvatarIcon} from 'assets/images/icons/airyAvatar.svg'; import {ReactComponent as CheckmarkIcon} from 'assets/images/icons/checkmarkFilled.svg'; -import styles from './ChatPluginConnect.module.scss'; +import {CONNECTORS_ROUTE} from '../../../../../routes/routes'; -import {CONNECTORS_CHAT_PLUGIN_ROUTE, CONNECTORS_CONNECTED_ROUTE} from '../../../../../routes/routes'; -import {useTranslation} from 'react-i18next'; import CreateUpdateSection from './sections/CreateUpdateSection/CreateUpdateSection'; import {CustomiseSection} from './sections/CustomiseSection/CustomiseSection'; import {InstallSection} from './sections/InstallSection/InstallSection'; -import {ChatpluginConfig, DefaultConfig} from 'model'; +import styles from './ChatPluginConnect.module.scss'; export enum Pages { createUpdate = 'create-update', @@ -45,17 +44,22 @@ const mapStateToProps = (state: StateModel) => ({ const connector = connect(mapStateToProps, mapDispatchToProps); const ChatPluginConnect = (props: ConnectedProps) => { + const params = useParams(); const {channelId} = useParams(); + const newChannel = params['*'] === 'new'; + const currentChannel = props.channels.find((channel: Channel) => channel.id === channelId); const [chatpluginConfig, setChatpluginConfig] = useState(DefaultConfig); - const [currentPage, setCurrentPage] = useState(channelId !== 'new' ? Pages.customization : Pages.createUpdate); + const [currentPage, setCurrentPage] = useState(channelId ? Pages.customization : Pages.createUpdate); const [showCreatedModal, setShowCreatedModal] = useState(false); const [currentChannelId, setCurrentChannelId] = useState(''); + const [notification, setNotification] = useState(null); const displayName = currentChannel?.metadata?.name || ''; const imageUrl = currentChannel?.metadata?.imageUrl || ''; + const navigate = useNavigate(); const {t} = useTranslation(); - const CHAT_PLUGIN_ROUTE = CONNECTORS_CHAT_PLUGIN_ROUTE; + const CHAT_PLUGIN_ROUTE = `${CONNECTORS_ROUTE}/chatplugin`; const createNewConnection = (displayName: string, imageUrl?: string) => { props @@ -68,12 +72,17 @@ const ChatPluginConnect = (props: ConnectedProps) => { .then((id: string) => { setCurrentChannelId(id); setShowCreatedModal(true); + }) + .catch((error: Error) => { + console.error(error); }); }; const disconnectChannel = (channel: Channel) => { if (window.confirm(t('deleteChannel'))) { - props.disconnectChannel({source: 'chatplugin', channelId: channel.id}); + props.disconnectChannel({source: 'chatplugin', channelId: channel.id}).catch((error: Error) => { + console.error(error); + }); } }; @@ -99,17 +108,24 @@ const ChatPluginConnect = (props: ConnectedProps) => { const handleClose = () => { setShowCreatedModal(false); - navigate(`${CONNECTORS_CONNECTED_ROUTE}/${Source.chatPlugin}`); + navigate(`${CONNECTORS_ROUTE}/${Source.chatPlugin}/connected`); }; const PageContent = () => { switch (currentPage) { case Pages.createUpdate: - if (channelId === 'new') { + if (newChannel) { return ; } if (channelId?.length > 0) { - return ; + return ( + + ); } return ; case Pages.customization: @@ -144,7 +160,7 @@ const ChatPluginConnect = (props: ConnectedProps) => {
{channel.metadata?.name}
- + {t('edit')} ) => { ); return ( -
-
-
-
- - {channelId === 'new' ? t('create') : t('update')} - - {channelId !== 'new' && ( + <> +
+
+
+
- {t('customize')} + {newChannel ? t('create') : t('update')} - )} - {channelId !== 'new' && ( - - {t('install')} - - )} + {!newChannel && ( + + {t('customize')} + + )} + {!newChannel && ( + + {t('install')} + + )} +
+
+
+
+
-
-
-
-
+ {showCreatedModal && ( + } + wrapperClassName={styles.enableModalContainerWrapper} + containerClassName={styles.enableModalContainer} + title={t('successfullyCreatedChannel')} + close={handleClose} + headerClassName={styles.headerModal} + dataCyCloseButton={cyChannelCreatedChatPluginCloseButton} + > + + + )} + {notification?.show && ( + + )}
- {showCreatedModal && ( - } - wrapperClassName={styles.enableModalContainerWrapper} - containerClassName={styles.enableModalContainer} - title={t('successfullyCreatedChannel')} - close={handleClose} - headerClassName={styles.headerModal} - dataCyCloseButton={cyChannelCreatedChatPluginCloseButton} - > - - - )} -
+ ); }; diff --git a/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/ConnectorChatplugin.tsx b/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/ConnectorChatplugin.tsx deleted file mode 100644 index eff8c449c6..0000000000 --- a/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/ConnectorChatplugin.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import {Source} from 'model'; -import React from 'react'; -import ConnectorConfig from '../../../ConnectorConfig'; - -const ConnectorChatplugin = () => { - return ; -}; - -export default ConnectorChatplugin; diff --git a/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/sections/ConnectNewChatPlugin.tsx b/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/sections/ConnectNewChatPlugin.tsx index ee9a1539c7..b30560d2ee 100644 --- a/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/sections/ConnectNewChatPlugin.tsx +++ b/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/sections/ConnectNewChatPlugin.tsx @@ -1,11 +1,8 @@ import React, {useState} from 'react'; - +import {useTranslation} from 'react-i18next'; import {Button, Input} from 'components'; - -import styles from './ConnectNewChatPlugin.module.scss'; - import {cyChannelsChatPluginFormNameInput, cyChannelsChatPluginFormSubmitButton} from 'handles'; -import {useTranslation} from 'react-i18next'; +import styles from './ConnectNewChatPlugin.module.scss'; interface ConnectNewChatPluginProps { createNewConnection: (displayName: string, imageUrl?: string) => void; diff --git a/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/sections/CreateUpdateSection/CreateUpdateSection.tsx b/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/sections/CreateUpdateSection/CreateUpdateSection.tsx index 55f637bdfd..0e8d608e20 100644 --- a/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/sections/CreateUpdateSection/CreateUpdateSection.tsx +++ b/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/sections/CreateUpdateSection/CreateUpdateSection.tsx @@ -1,14 +1,11 @@ -import React, {useState} from 'react'; -import {Button, Input} from 'components'; -import styles from './CreateUpdateSection.module.scss'; -import {cyChannelsChatPluginFormNameInput} from 'handles'; +import React, {Dispatch, SetStateAction, useState} from 'react'; +import {connect, ConnectedProps} from 'react-redux'; import {useTranslation} from 'react-i18next'; +import {Input, SmartButton} from 'components'; +import {cyChannelsChatPluginFormNameInput} from 'handles'; import {updateChannel} from '../../../../../../../actions/channel'; -import {connect, ConnectedProps} from 'react-redux'; -import {useNavigate} from 'react-router-dom'; -import {Channel} from 'model'; - -import {CONNECTORS_CONNECTED_ROUTE} from '../../../../../../../routes/routes'; +import styles from './CreateUpdateSection.module.scss'; +import {Channel, NotificationModel} from 'model'; const mapDispatchToProps = { updateChannel, @@ -20,21 +17,32 @@ type InstallUpdateSectionProps = { channel: Channel; displayName: string; imageUrl: string; + setNotification: Dispatch>; } & ConnectedProps; const CreateUpdateSection = (props: InstallUpdateSectionProps) => { - const {channel, displayName, imageUrl} = props; + const {channel, displayName, imageUrl, setNotification} = props; + const [submit, setSubmit] = useState(false); const [newDisplayName, setNewDisplayName] = useState(displayName || channel?.metadata?.name); const [newImageUrl, setNewImageUrl] = useState(imageUrl || channel?.metadata?.imageUrl); + const [isPending, setIsPending] = useState(false); const {t} = useTranslation(); - const navigate = useNavigate(); - const CONNECTED_ROUTE = CONNECTORS_CONNECTED_ROUTE; const updateConnection = (displayName: string, imageUrl?: string) => { - props.updateChannel({channelId: channel.id, name: displayName, imageUrl: imageUrl}).then(() => { - navigate(CONNECTED_ROUTE + '/chatplugin', {replace: true}); - }); + setIsPending(true); + props + .updateChannel({channelId: channel.id, name: displayName, imageUrl: imageUrl}) + .then(() => { + setNotification({show: true, text: t('updateSuccessful'), successful: true}); + }) + .catch((error: Error) => { + setNotification({show: true, text: t('updateFailed'), successful: false}); + console.error(error); + }) + .finally(() => { + setIsPending(true); + }); }; return ( @@ -79,15 +87,16 @@ const CreateUpdateSection = (props: InstallUpdateSectionProps) => { fontClass="font-base" />
- + disabled={newDisplayName === '' || newDisplayName === displayName || isPending} + />
diff --git a/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/sections/CustomiseSection/CustomiseSection.module.scss b/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/sections/CustomiseSection/CustomiseSection.module.scss index 5a04943f1a..231a492be1 100644 --- a/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/sections/CustomiseSection/CustomiseSection.module.scss +++ b/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/sections/CustomiseSection/CustomiseSection.module.scss @@ -147,7 +147,6 @@ .borderLine { height: 1px; - width: 80%; background-color: var(--color-light-gray); margin: 18px 0; } diff --git a/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/sections/CustomiseSection/InputToggleSection/index.module.scss b/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/sections/CustomiseSection/InputToggleSection/index.module.scss index d39f1b2fa7..348283ca9b 100644 --- a/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/sections/CustomiseSection/InputToggleSection/index.module.scss +++ b/frontend/control-center/src/pages/Connectors/Providers/Airy/ChatPlugin/sections/CustomiseSection/InputToggleSection/index.module.scss @@ -7,7 +7,6 @@ display: flex; flex-direction: column; flex-wrap: wrap; - margin-right: 16px; label { white-space: nowrap; margin-right: 30px; @@ -32,7 +31,6 @@ .borderLine { height: 1px; - width: 80%; background-color: var(--color-light-gray); margin: 18px 0; } diff --git a/frontend/control-center/src/pages/Connectors/Providers/Dialogflow/ConnectNewDialogflow.tsx b/frontend/control-center/src/pages/Connectors/Providers/Dialogflow/ConnectNewDialogflow.tsx deleted file mode 100644 index 86b145ea26..0000000000 --- a/frontend/control-center/src/pages/Connectors/Providers/Dialogflow/ConnectNewDialogflow.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import React, {useState} from 'react'; -import {useSelector} from 'react-redux'; -import {StateModel} from '../../../../reducers'; -import {Input} from 'components'; -import {ConnectNewForm} from '../../ConnectNewForm'; -import styles from './index.module.scss'; -import {useTranslation} from 'react-i18next'; - -type ConnectNewDialogflowProps = { - createNewConnection: ( - projectId: string, - appCredentials: string, - suggestionConfidenceLevel: string, - replyConfidenceLevel: string, - processorWaitingTime: string, - processorCheckPeriod: string, - defaultLanguage: string - ) => void; - isEnabled: boolean; - isConfigured: boolean; -}; - -export const ConnectNewDialogflow = ({createNewConnection, isEnabled, isConfigured}: ConnectNewDialogflowProps) => { - const componentInfo = useSelector((state: StateModel) => state.data.connector['dialogflow-connector']); - const [projectID, setProjectID] = useState(componentInfo?.projectId || ''); - const [appCredentials, setAppCredentials] = useState(componentInfo?.dialogflowCredentials || ''); - const [suggestionConfidenceLevel, setSuggestionConfidenceLevel] = useState( - componentInfo?.suggestionConfidenceLevel || '' - ); - const [replyConfidenceLevel, setReplyConfidenceLevel] = useState(componentInfo?.replyConfidenceLevel || ''); - const [isUpdateModalVisible, setIsUpdateModalVisible] = useState(false); - const [processorWaitingTime, setProcessorWaitingTime] = useState( - componentInfo?.connectorStoreMessagesProcessorMaxWaitMillis || '5000' - ); - const [processorCheckPeriod, setProcessorCheckPeriod] = useState( - componentInfo?.connectorStoreMessagesProcessorCheckPeriodMillis || '2500' - ); - const [defaultLanguage, setDefaultLanguage] = useState(componentInfo?.connectorDefaultLanguage || 'en'); - - const {t} = useTranslation(); - - const updateConfig = (event: React.FormEvent) => { - event.preventDefault(); - if (isEnabled) { - setIsUpdateModalVisible(true); - } else { - enableSubmitConfigData(); - } - }; - - const enableSubmitConfigData = () => { - createNewConnection( - projectID, - appCredentials, - suggestionConfidenceLevel, - replyConfidenceLevel, - processorWaitingTime, - processorCheckPeriod, - defaultLanguage - ); - }; - - return ( - -
- ) => setProjectID(e.target.value)} - label={t('projectID')} - placeholder={t('AddProjectId')} - showLabelIcon - tooltipText={t('fromCloudConsole')} - required - height={32} - fontClass="font-base" - /> -
- -
- ) => setAppCredentials(e.target.value)} - label={t('GoogleApplicationCredentials')} - placeholder={t('AddGoogleApplicationCredentials')} - showLabelIcon - tooltipText={t('fromCloudConsole')} - required - height={32} - fontClass="font-base" - /> -
-
- ) => setSuggestionConfidenceLevel(e.target.value)} - label={t('SuggestionConfidenceLevel')} - placeholder={'0.1' + ' ' + t('to') + ' ' + '0.9'} - showLabelIcon - tooltipText={t('amountSuggestions')} - required - height={32} - fontClass="font-base" - /> -
-
- ) => setReplyConfidenceLevel(e.target.value)} - label={t('ReplyConfidenceLevel')} - placeholder={'0.1' + ' ' + t('to') + ' ' + '0.9'} - showLabelIcon - tooltipText={t('amountReplies')} - required - height={32} - fontClass="font-base" - /> -
-
- ) => setProcessorWaitingTime(e.target.value)} - label={t('processorWaitingTime')} - placeholder={t('processorWaitingTime')} - showLabelIcon - tooltipText={t('waitingDefault')} - required - height={32} - fontClass="font-base" - /> -
-
- ) => setProcessorCheckPeriod(e.target.value)} - label={t('processorCheckPeriod')} - placeholder={t('processorCheckPeriod')} - showLabelIcon - tooltipText={t('checkDefault')} - required - height={32} - fontClass="font-base" - /> -
-
- ) => setDefaultLanguage(e.target.value)} - label={t('defaultLanguage')} - placeholder={t('defaultLanguage')} - showLabelIcon - tooltipText={t('defaultLanguageTooltip')} - required - height={32} - fontClass="font-base" - /> -
-
- ); -}; diff --git a/frontend/control-center/src/pages/Connectors/Providers/Dialogflow/DialogflowConnect.tsx b/frontend/control-center/src/pages/Connectors/Providers/Dialogflow/DialogflowConnect.tsx index 2b28b6ea12..fcd39c800d 100644 --- a/frontend/control-center/src/pages/Connectors/Providers/Dialogflow/DialogflowConnect.tsx +++ b/frontend/control-center/src/pages/Connectors/Providers/Dialogflow/DialogflowConnect.tsx @@ -1,9 +1,218 @@ -import {Source} from 'model'; -import React from 'react'; -import ConnectorConfig from '../../ConnectorConfig'; +import React, {useState} from 'react'; +import {useSelector} from 'react-redux'; +import {StateModel} from '../../../../reducers'; +import {Input} from 'components'; +import {ConfigureConnector} from '../../ConfigureConnector'; +import styles from './index.module.scss'; +import {useTranslation} from 'react-i18next'; +import {ComponentName} from 'model'; -const DialogflowConnector = () => { - return ; +interface ConnectParams { + [key: string]: string; +} + +type DialogflowConnectProps = { + createNewConnection: (configValues: ConnectParams) => void; + isEnabled: boolean; + isConfigured: boolean; + isPending: boolean; + componentName?: string; }; -export default DialogflowConnector; +export const DialogflowConnect = ({ + createNewConnection, + isEnabled, + isConfigured, + isPending, +}: DialogflowConnectProps) => { + const componentInfo = useSelector( + (state: StateModel) => state.data.connector[ComponentName.enterpriseDialogflowConnector] + ); + + const [projectId, setProjectID] = useState(componentInfo?.projectId); + const [dialogflowCredentials, setDialogflowCredentials] = useState(componentInfo?.dialogflowCredentials || ''); + const [suggestionConfidenceLevel, setSuggestionConfidenceLevel] = useState( + componentInfo?.suggestionConfidenceLevel || '' + ); + const [replyConfidenceLevel, setReplyConfidenceLevel] = useState(componentInfo?.replyConfidenceLevel || ''); + const [isUpdateModalVisible, setIsUpdateModalVisible] = useState(false); + const [processorWaitingTime, setProcessorWaitingTime] = useState( + componentInfo?.connectorStoreMessagesProcessorMaxWaitMillis || '5000' + ); + const [processorCheckPeriod, setProcessorCheckPeriod] = useState( + componentInfo?.connectorStoreMessagesProcessorCheckPeriodMillis || '2500' + ); + const [defaultLanguage, setDefaultLanguage] = useState(componentInfo?.connectorDefaultLanguage || 'en'); + + const {t} = useTranslation(); + + const updateConfig = (event: React.FormEvent) => { + event.preventDefault(); + if (isEnabled) { + setIsUpdateModalVisible(true); + } else { + enableSubmitConfigData(); + } + }; + + const enableSubmitConfigData = () => { + const payload = { + projectId, + dialogflowCredentials, + suggestionConfidenceLevel, + replyConfidenceLevel, + processorWaitingTime, + processorCheckPeriod, + defaultLanguage, + }; + + createNewConnection(payload); + }; + + return ( + +
+
+
+ ) => setProjectID(e.target.value)} + label={t('projectID')} + placeholder={t('AddProjectId')} + showLabelIcon + tooltipText={t('fromCloudConsole')} + required + height={32} + fontClass="font-base" + /> +
+ +
+ ) => setDialogflowCredentials(e.target.value)} + label={t('GoogleApplicationCredentials')} + placeholder={t('AddGoogleApplicationCredentials')} + showLabelIcon + tooltipText={t('fromCloudConsole')} + required + height={32} + fontClass="font-base" + /> +
+
+ ) => setSuggestionConfidenceLevel(e.target.value)} + label={t('SuggestionConfidenceLevel')} + placeholder={'0.1' + ' ' + t('to') + ' ' + '0.9'} + showLabelIcon + tooltipText={t('amountSuggestions')} + required + height={32} + fontClass="font-base" + /> +
+
+ ) => setReplyConfidenceLevel(e.target.value)} + label={t('ReplyConfidenceLevel')} + placeholder={'0.1' + ' ' + t('to') + ' ' + '0.9'} + showLabelIcon + tooltipText={t('amountReplies')} + required + height={32} + fontClass="font-base" + /> +
+
+
+
+ ) => setProcessorWaitingTime(e.target.value)} + label={t('processorWaitingTime')} + placeholder={t('processorWaitingTime')} + showLabelIcon + tooltipText={t('waitingDefault')} + required + height={32} + fontClass="font-base" + /> +
+
+ ) => setProcessorCheckPeriod(e.target.value)} + label={t('processorCheckPeriod')} + placeholder={t('processorCheckPeriod')} + showLabelIcon + tooltipText={t('checkDefault')} + required + height={32} + fontClass="font-base" + /> +
+
+ ) => setDefaultLanguage(e.target.value)} + label={t('defaultLanguage')} + placeholder={t('defaultLanguage')} + showLabelIcon + tooltipText={t('defaultLanguageTooltip')} + required + height={32} + fontClass="font-base" + /> +
+
+
+
+ ); +}; diff --git a/frontend/control-center/src/pages/Connectors/Providers/Dialogflow/index.module.scss b/frontend/control-center/src/pages/Connectors/Providers/Dialogflow/index.module.scss index fb47fdc0d9..a1bf543bd4 100644 --- a/frontend/control-center/src/pages/Connectors/Providers/Dialogflow/index.module.scss +++ b/frontend/control-center/src/pages/Connectors/Providers/Dialogflow/index.module.scss @@ -9,7 +9,24 @@ width: 29rem; } +.columnContainer { + display: flex; +} + +.firstColumnForm { + display: flex; + flex-direction: column; + margin-right: 24px; +} + +.secondColumnForm { + display: flex; + flex-direction: column; +} + .formRow { + width: 464px; + height: 80px; margin-bottom: 16px; label { diff --git a/frontend/control-center/src/pages/Connectors/Providers/Facebook/Messenger/ConnectorFacebook.tsx b/frontend/control-center/src/pages/Connectors/Providers/Facebook/Messenger/ConnectorFacebook.tsx deleted file mode 100644 index a09914b6eb..0000000000 --- a/frontend/control-center/src/pages/Connectors/Providers/Facebook/Messenger/ConnectorFacebook.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import {Source} from 'model'; -import React from 'react'; -import ConnectorConfig from '../../../ConnectorConfig'; - -const ConnectorFacebook = () => { - return ; -}; - -export default ConnectorFacebook; diff --git a/frontend/control-center/src/pages/Connectors/Providers/Facebook/Messenger/FacebookConnect.module.scss b/frontend/control-center/src/pages/Connectors/Providers/Facebook/Messenger/FacebookConnect.module.scss index 6c877ccdc1..09a67df6f1 100644 --- a/frontend/control-center/src/pages/Connectors/Providers/Facebook/Messenger/FacebookConnect.module.scss +++ b/frontend/control-center/src/pages/Connectors/Providers/Facebook/Messenger/FacebookConnect.module.scss @@ -6,7 +6,7 @@ display: block; border-radius: 10px; width: 100%; - min-height: calc(100vh - 170px); + height: 100%; } .headline { @@ -40,10 +40,9 @@ flex-direction: column; margin-bottom: 32px; width: 474px; - margin-top: 16px; label { - margin-top: 24px; + height: 82px; } input { diff --git a/frontend/control-center/src/pages/Connectors/Providers/Facebook/Messenger/FacebookConnect.tsx b/frontend/control-center/src/pages/Connectors/Providers/Facebook/Messenger/FacebookConnect.tsx index 85cc77e151..9aa81f80d3 100644 --- a/frontend/control-center/src/pages/Connectors/Providers/Facebook/Messenger/FacebookConnect.tsx +++ b/frontend/control-center/src/pages/Connectors/Providers/Facebook/Messenger/FacebookConnect.tsx @@ -1,17 +1,14 @@ import React, {useEffect, useState} from 'react'; import {useTranslation} from 'react-i18next'; import {connect, ConnectedProps} from 'react-redux'; - import {connectFacebookChannel} from '../../../../../actions/channel'; - -import {Button, Input} from 'components'; +import {Input, NotificationComponent, SmartButton} from 'components'; import {ConnectChannelFacebookRequestPayload} from 'httpclient/src'; - import styles from './FacebookConnect.module.scss'; - -import {CONNECTORS_CONNECTED_ROUTE} from '../../../../../routes/routes'; import {useCurrentChannel} from '../../../../../selectors/channels'; +import {NotificationModel} from 'model'; import {useNavigate} from 'react-router-dom'; +import {CONNECTORS_ROUTE} from '../../../../../routes/routes'; const mapDispatchToProps = { connectFacebookChannel, @@ -28,21 +25,32 @@ const FacebookConnect = (props: ConnectedProps) => { const [token, setToken] = useState(channel?.metadata?.pageToken || ''); const [name, setName] = useState(channel?.metadata?.name || ''); const [image, setImage] = useState(channel?.metadata?.imageUrl || ''); - const [buttonTitle, setButtonTitle] = useState(t('connectPage') || ''); + const buttonTitle = channel ? t('updatePage') : t('connectPage') || ''; + const [newButtonTitle, setNewButtonTitle] = useState(''); const [errorMessage, setErrorMessage] = useState(''); + const [notification, setNotification] = useState(null); + const [isPending, setIsPending] = useState(false); - const CONNECTED_ROUTE = CONNECTORS_CONNECTED_ROUTE; + useEffect(() => { + if (channel?.sourceChannelId !== id && !!channel) { + setNotification({show: true, text: t('newChannelInfo'), info: true}); + setNewButtonTitle(t('connect')); + } else { + setNewButtonTitle(buttonTitle); + } + }, [id]); const buttonStatus = () => { - return !(id.length > 5 && token != ''); + return ( + !(id.length > 5 && token != '') || + (channel?.sourceChannelId === id && + channel?.metadata?.pageToken === token && + channel?.metadata?.name === name && + channel?.metadata?.imageUrl === image) || + (!!channel?.metadata?.imageUrl && image === '') + ); }; - useEffect(() => { - if (channel) { - setButtonTitle(t('updatePage')); - } - }, []); - const connectNewChannel = () => { const connectPayload: ConnectChannelFacebookRequestPayload = { pageId: id, @@ -57,12 +65,19 @@ const FacebookConnect = (props: ConnectedProps) => { }), }; + setIsPending(true); + connectFacebookChannel(connectPayload) .then(() => { - navigate(CONNECTED_ROUTE + '/facebook', {replace: true}); + navigate(CONNECTORS_ROUTE + '/facebook/connected', {replace: true}); }) - .catch(() => { + .catch((error: Error) => { + setNotification({show: true, text: t('updateFailed'), successful: false}); setErrorMessage(t('errorMessage')); + console.error(error); + }) + .finally(() => { + setIsPending(false); }); }; @@ -113,9 +128,27 @@ const FacebookConnect = (props: ConnectedProps) => { fontClass="font-base" />
- + connectNewChannel()} + /> + {notification?.show && ( + + )}
); }; diff --git a/frontend/control-center/src/pages/Connectors/Providers/Google/ConnectorGoogle.tsx b/frontend/control-center/src/pages/Connectors/Providers/Google/ConnectorGoogle.tsx deleted file mode 100644 index 803cde3c21..0000000000 --- a/frontend/control-center/src/pages/Connectors/Providers/Google/ConnectorGoogle.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import {Source} from 'model'; -import React from 'react'; -import ConnectorConfig from '../../ConnectorConfig'; - -const ConnectorGoogle = () => { - return ; -}; - -export default ConnectorGoogle; diff --git a/frontend/control-center/src/pages/Connectors/Providers/Google/GoogleConnect.module.scss b/frontend/control-center/src/pages/Connectors/Providers/Google/GoogleConnect.module.scss index 1ce9f4705f..e5bb21c9aa 100644 --- a/frontend/control-center/src/pages/Connectors/Providers/Google/GoogleConnect.module.scss +++ b/frontend/control-center/src/pages/Connectors/Providers/Google/GoogleConnect.module.scss @@ -6,7 +6,7 @@ display: block; border-radius: 10px; width: 100%; - min-height: calc(100vh - 170px); + height: 100%; } .headline { @@ -40,10 +40,9 @@ flex-direction: column; margin-bottom: 32px; width: 474px; - margin-top: 16px; label { - margin-top: 24px; + height: 82px; } input { diff --git a/frontend/control-center/src/pages/Connectors/Providers/Google/GoogleConnect.tsx b/frontend/control-center/src/pages/Connectors/Providers/Google/GoogleConnect.tsx index 61486fe0fe..18cdcbd21e 100644 --- a/frontend/control-center/src/pages/Connectors/Providers/Google/GoogleConnect.tsx +++ b/frontend/control-center/src/pages/Connectors/Providers/Google/GoogleConnect.tsx @@ -3,15 +3,16 @@ import {connect, ConnectedProps} from 'react-redux'; import {connectGoogleChannel} from '../../../../actions'; -import {Button, Input} from 'components'; +import {Input, NotificationComponent, SmartButton} from 'components'; import {ConnectChannelGoogleRequestPayload} from 'httpclient/src'; import styles from './GoogleConnect.module.scss'; -import {CONNECTORS_CONNECTED_ROUTE} from '../../../../routes/routes'; +import {CONNECTORS_ROUTE} from '../../../../routes/routes'; import {useCurrentChannel} from '../../../../selectors/channels'; import {useNavigate} from 'react-router-dom'; import {useTranslation} from 'react-i18next'; +import {NotificationModel} from 'model'; const mapDispatchToProps = { connectGoogleChannel, @@ -27,22 +28,38 @@ const GoogleConnect = (props: ConnectedProps) => { const [id, setId] = useState(channel?.sourceChannelId || ''); const [name, setName] = useState(channel?.metadata?.name || ''); const [image, setImage] = useState(channel?.metadata?.imageUrl || ''); - const [buttonTitle, setButtonTitle] = useState(t('connectPage') || ''); + const buttonTitle = channel ? t('updatePage') : t('connectPage') || ''; const [errorMessage, setErrorMessage] = useState(''); - - const CONNECTED_ROUTE = CONNECTORS_CONNECTED_ROUTE; + const [notification, setNotification] = useState(null); + const [isPending, setIsPending] = useState(false); + const [newButtonTitle, setNewButtonTitle] = useState(''); const buttonStatus = () => { - return !(id.length > 5 && name.length > 0); + return ( + !(id.length > 5 && name.length > 0) || + (channel?.sourceChannelId === id && + channel?.metadata?.name === name && + (channel?.metadata?.imageUrl === image || image === '')) + ); }; + useEffect(() => { + if (channel?.sourceChannelId !== id && !!channel) { + setNotification({show: true, text: t('newChannelInfo'), info: true}); + setNewButtonTitle(t('connectPage')); + } else { + setNewButtonTitle(buttonTitle); + } + }, [id]); + useEffect(() => { if (channel) { - setButtonTitle(t('updatePage')); + setNewButtonTitle(t('updatePage')); } }, [channel]); const connectNewChannel = () => { + setIsPending(true); const connectPayload: ConnectChannelGoogleRequestPayload = { gmbId: id, name: name, @@ -54,10 +71,20 @@ const GoogleConnect = (props: ConnectedProps) => { connectGoogleChannel(connectPayload) .then(() => { - navigate(CONNECTED_ROUTE + '/google', {replace: true}); + navigate(CONNECTORS_ROUTE + '/google/connected', {replace: true}); }) - .catch(() => { + .catch((error: Error) => { + setNotification({ + show: true, + text: buttonTitle === t('connectPage') ? t('createFailed') : 'updateFailed', + successful: false, + info: false, + }); setErrorMessage(t('errorMessage')); + console.error(error); + }) + .finally(() => { + setIsPending(false); }); }; @@ -97,9 +124,26 @@ const GoogleConnect = (props: ConnectedProps) => { fontClass="font-base" />
- + connectNewChannel()} + /> + {notification?.show && ( + + )}
); }; diff --git a/frontend/control-center/src/pages/Connectors/Providers/Instagram/ConnectorInstagram.tsx b/frontend/control-center/src/pages/Connectors/Providers/Instagram/ConnectorInstagram.tsx deleted file mode 100644 index 29fdc9be63..0000000000 --- a/frontend/control-center/src/pages/Connectors/Providers/Instagram/ConnectorInstagram.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import {Source} from 'model'; -import React from 'react'; -import ConnectorConfig from '../../ConnectorConfig'; - -const ConnectorInstagram = () => { - return ; -}; - -export default ConnectorInstagram; diff --git a/frontend/control-center/src/pages/Connectors/Providers/Instagram/InstagramConnect.module.scss b/frontend/control-center/src/pages/Connectors/Providers/Instagram/InstagramConnect.module.scss index 1ce9f4705f..e5bb21c9aa 100644 --- a/frontend/control-center/src/pages/Connectors/Providers/Instagram/InstagramConnect.module.scss +++ b/frontend/control-center/src/pages/Connectors/Providers/Instagram/InstagramConnect.module.scss @@ -6,7 +6,7 @@ display: block; border-radius: 10px; width: 100%; - min-height: calc(100vh - 170px); + height: 100%; } .headline { @@ -40,10 +40,9 @@ flex-direction: column; margin-bottom: 32px; width: 474px; - margin-top: 16px; label { - margin-top: 24px; + height: 82px; } input { diff --git a/frontend/control-center/src/pages/Connectors/Providers/Instagram/InstagramConnect.tsx b/frontend/control-center/src/pages/Connectors/Providers/Instagram/InstagramConnect.tsx index 09c89457f9..2d2dd4a83e 100644 --- a/frontend/control-center/src/pages/Connectors/Providers/Instagram/InstagramConnect.tsx +++ b/frontend/control-center/src/pages/Connectors/Providers/Instagram/InstagramConnect.tsx @@ -1,17 +1,12 @@ import React, {useEffect, useState} from 'react'; import {connect, ConnectedProps} from 'react-redux'; - import {connectInstagramChannel} from '../../../../actions'; - -import {Button, Input} from 'components'; +import {Input, NotificationComponent, SmartButton} from 'components'; import {ConnectChannelInstagramRequestPayload} from 'httpclient/src'; - import styles from './InstagramConnect.module.scss'; - -import {CONNECTORS_CONNECTED_ROUTE} from '../../../../routes/routes'; import {useCurrentChannel} from '../../../../selectors/channels'; -import {useNavigate} from 'react-router-dom'; import {useTranslation} from 'react-i18next'; +import {NotificationModel} from 'model'; const mapDispatchToProps = { connectInstagramChannel, @@ -23,26 +18,36 @@ const InstagramConnect = (props: ConnectedProps) => { const {connectInstagramChannel} = props; const {t} = useTranslation(); const channel = useCurrentChannel(); - const navigate = useNavigate(); const [id, setId] = useState(channel?.metadata?.pageId || ''); const [token, setToken] = useState(channel?.metadata?.pageToken || ''); const [accountId, setAccountId] = useState(channel?.sourceChannelId || ''); const [name, setName] = useState(channel?.metadata?.name || ''); const [image, setImage] = useState(channel?.metadata?.imageUrl || ''); - const [buttonTitle, setButtonTitle] = useState(t('connectPage') || ''); + const buttonTitle = channel ? t('updatePage') : t('connectPage') || ''; const [errorMessage, setErrorMessage] = useState(''); - - const CONNECTED_ROUTE = CONNECTORS_CONNECTED_ROUTE; + const [notification, setNotification] = useState(null); + const [newButtonTitle, setNewButtonTitle] = useState(''); + const [isPending, setIsPending] = useState(false); const buttonStatus = () => { - return !(id.length > 5 && token != ''); + return ( + !(id.length > 5 && token != '') || + (channel?.sourceChannelId === id && + channel?.metadata?.pageToken === token && + channel?.metadata?.name === name && + channel?.metadata?.imageUrl === image) || + (!!channel?.metadata?.imageUrl && image === '') + ); }; useEffect(() => { - if (channel) { - setButtonTitle(t('updatePage')); + if (channel?.sourceChannelId !== id && !!channel) { + setNotification({show: true, text: t('newChannelInfo'), info: true}); + setNewButtonTitle(t('connectPage')); + } else { + setNewButtonTitle(buttonTitle); } - }, []); + }, [id]); const connectNewChannel = () => { const connectPayload: ConnectChannelInstagramRequestPayload = { @@ -59,12 +64,16 @@ const InstagramConnect = (props: ConnectedProps) => { }), }; + setIsPending(true); + connectInstagramChannel(connectPayload) - .then(() => { - navigate(CONNECTED_ROUTE + '/instagram', {replace: true}); - }) - .catch(() => { + .catch((error: Error) => { + setNotification({show: true, text: t('updateFailed'), successful: false}); setErrorMessage(t('errorMessage')); + console.error(error); + }) + .finally(() => { + setIsPending(false); }); }; @@ -126,9 +135,26 @@ const InstagramConnect = (props: ConnectedProps) => { fontClass="font-base" />
- + connectNewChannel()} + /> + {notification?.show && ( + + )} ); }; diff --git a/frontend/control-center/src/pages/Connectors/Providers/Rasa/RasaConnect.module.scss b/frontend/control-center/src/pages/Connectors/Providers/Rasa/RasaConnect.module.scss new file mode 100644 index 0000000000..1fdbd99211 --- /dev/null +++ b/frontend/control-center/src/pages/Connectors/Providers/Rasa/RasaConnect.module.scss @@ -0,0 +1,25 @@ +@import 'assets/scss/fonts.scss'; +@import 'assets/scss/colors.scss'; + +.formWrapper { + align-self: center; +} + +.settings { + width: 29rem; +} + +.formRow { + height: 80px; + margin-bottom: 16px; + + label { + @include font-s; + } + + &:hover { + .actionToolTip { + display: block; + } + } +} diff --git a/frontend/control-center/src/pages/Connectors/Providers/Rasa/RasaConnect.tsx b/frontend/control-center/src/pages/Connectors/Providers/Rasa/RasaConnect.tsx new file mode 100644 index 0000000000..4ce49a72ec --- /dev/null +++ b/frontend/control-center/src/pages/Connectors/Providers/Rasa/RasaConnect.tsx @@ -0,0 +1,100 @@ +import React, {useState} from 'react'; +import {useSelector} from 'react-redux'; +import {StateModel} from '../../../../reducers'; +import {Input} from 'components'; +import {ConfigureConnector} from '../../ConfigureConnector'; +import {useTranslation} from 'react-i18next'; +import styles from './RasaConnect.module.scss'; +import {ComponentName} from 'model'; + +interface ConnectParams { + [key: string]: string; +} + +type RasaConnectProps = { + createNewConnection: (configValues: ConnectParams) => void; + isEnabled: boolean; + isConfigured: boolean; + isPending: boolean; +}; + +export const RasaConnect = ({createNewConnection, isEnabled, isConfigured, isPending}: RasaConnectProps) => { + const componentInfo = useSelector((state: StateModel) => state.data.connector[ComponentName.rasaConnector]); + const [webhookUrl, setWebhookUrl] = useState(componentInfo?.webhookUrl || ''); + const [apiHost, setApiHost] = useState(componentInfo?.apiHost || ''); + const [token, setToken] = useState(componentInfo?.token || ''); + const [isUpdateModalVisible, setIsUpdateModalVisible] = useState(false); + const {t} = useTranslation(); + const isUrlValid = webhookUrl && (webhookUrl.startsWith('https') || webhookUrl.startsWith('http')); + + const updateConfig = (event: React.FormEvent) => { + event.preventDefault(); + if (isEnabled) { + setIsUpdateModalVisible(true); + } else { + enableSubmitConfigData(); + } + }; + + const enableSubmitConfigData = () => { + createNewConnection({webhookUrl, apiHost, token}); + }; + + return ( + +
+ ) => setWebhookUrl(e.target.value)} + label="Rasa Webhook" + placeholder={t('rasaWebhookPlaceholder')} + showLabelIcon + tooltipText={t('rasaWebhookTooltip')} + required + height={32} + fontClass="font-base" + /> +
+ +
+ ) => setApiHost(e.target.value)} + label="Api Host" + placeholder={t('rasaApihostPlaceholder')} + showLabelIcon + tooltipText={t('rasaApihostTooltip')} + height={32} + fontClass="font-base" + /> +
+
+ ) => setToken(e.target.value)} + label="Token" + placeholder={t('rasaTokenPlaceholder')} + showLabelIcon + tooltipText={t('rasaTokenTooltip')} + height={32} + fontClass="font-base" + /> +
+
+ ); +}; diff --git a/frontend/control-center/src/pages/Connectors/Providers/Salesforce/ConnectNewSalesforce.tsx b/frontend/control-center/src/pages/Connectors/Providers/Salesforce/ConnectNewSalesforce.tsx index 6ba4acd5f5..dff23e9562 100644 --- a/frontend/control-center/src/pages/Connectors/Providers/Salesforce/ConnectNewSalesforce.tsx +++ b/frontend/control-center/src/pages/Connectors/Providers/Salesforce/ConnectNewSalesforce.tsx @@ -2,18 +2,31 @@ import React, {useState} from 'react'; import {useSelector} from 'react-redux'; import {StateModel} from '../../../../reducers'; import {Input} from 'components'; -import {ConnectNewForm} from '../../ConnectNewForm'; +import {ConfigureConnector} from '../../ConfigureConnector'; import {useTranslation} from 'react-i18next'; import styles from './index.module.scss'; +import {ComponentName} from 'model'; + +interface ConnectParams { + [key: string]: string; +} type ConnectNewSalesforceProps = { - createNewConnection: (url: string, username: string, password: string, securityToken: string) => void; + createNewConnection: (configValues: ConnectParams) => void; isEnabled: boolean; isConfigured: boolean; + isPending: boolean; }; -export const ConnectNewSalesforce = ({createNewConnection, isEnabled, isConfigured}: ConnectNewSalesforceProps) => { - const componentInfo = useSelector((state: StateModel) => state.data.connector['salesforce-contacts-ingestion']); +export const ConnectNewSalesforce = ({ + createNewConnection, + isEnabled, + isConfigured, + isPending, +}: ConnectNewSalesforceProps) => { + const componentInfo = useSelector( + (state: StateModel) => state.data.connector[ComponentName.enterpriseSalesforceContactsIngestion] + ); const [url, setUrl] = useState(componentInfo?.url || ''); const [username, setUsername] = useState(componentInfo?.username || ''); const [password, setPassword] = useState(componentInfo?.password || ''); @@ -32,18 +45,28 @@ export const ConnectNewSalesforce = ({createNewConnection, isEnabled, isConfigur }; const enableSubmitConfigData = () => { - createNewConnection(url, username, password, securityToken); + createNewConnection({url, username, password, securityToken}); }; return ( -
-
+ ); }; diff --git a/frontend/control-center/src/pages/Connectors/Providers/Salesforce/SalesforceConnect.tsx b/frontend/control-center/src/pages/Connectors/Providers/Salesforce/SalesforceConnect.tsx deleted file mode 100644 index 470f455dbc..0000000000 --- a/frontend/control-center/src/pages/Connectors/Providers/Salesforce/SalesforceConnect.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import {Source} from 'model'; -import React from 'react'; -import ConnectorConfig from '../../ConnectorConfig'; - -const SalesforceConnector = () => { - return ; -}; - -export default SalesforceConnector; diff --git a/frontend/control-center/src/pages/Connectors/Providers/Salesforce/index.module.scss b/frontend/control-center/src/pages/Connectors/Providers/Salesforce/index.module.scss index fb47fdc0d9..1fdbd99211 100644 --- a/frontend/control-center/src/pages/Connectors/Providers/Salesforce/index.module.scss +++ b/frontend/control-center/src/pages/Connectors/Providers/Salesforce/index.module.scss @@ -10,6 +10,7 @@ } .formRow { + height: 80px; margin-bottom: 16px; label { diff --git a/frontend/control-center/src/pages/Connectors/Providers/Twilio/SMS/ConnectorTwilioSms.tsx b/frontend/control-center/src/pages/Connectors/Providers/Twilio/SMS/ConnectorTwilioSms.tsx deleted file mode 100644 index 7e177cbe43..0000000000 --- a/frontend/control-center/src/pages/Connectors/Providers/Twilio/SMS/ConnectorTwilioSms.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import {Source} from 'model'; -import React from 'react'; -import ConnectorConfig from '../../../ConnectorConfig'; - -const ConnectorTwilioSms = () => { - return ; -}; - -export default ConnectorTwilioSms; diff --git a/frontend/control-center/src/pages/Connectors/Providers/Twilio/SMS/TwilioSmsConnect.tsx b/frontend/control-center/src/pages/Connectors/Providers/Twilio/SMS/TwilioSmsConnect.tsx index e0ce76730e..8b6bdef896 100644 --- a/frontend/control-center/src/pages/Connectors/Providers/Twilio/SMS/TwilioSmsConnect.tsx +++ b/frontend/control-center/src/pages/Connectors/Providers/Twilio/SMS/TwilioSmsConnect.tsx @@ -1,4 +1,4 @@ -import React, {useState, useEffect} from 'react'; +import React, {useEffect} from 'react'; import {connect, ConnectedProps} from 'react-redux'; import {StateModel} from '../../../../../reducers'; @@ -19,14 +19,7 @@ const TwilioSmsConnect = (props: ConnectedProps) => { const {t} = useTranslation(); const {channelId} = useParams(); const channel = useCurrentChannel(); - - const [buttonTitle, setButtonTitle] = useState(t('connectSmsNumber') || ''); - - useEffect(() => { - if (channel) { - setButtonTitle(t('updateSmsNumber')); - } - }, []); + const buttonTitle = channel ? t('update') : t('connect') || ''; useEffect(() => { if (channelId !== 'new' && channelId?.length) { diff --git a/frontend/control-center/src/pages/Connectors/Providers/Twilio/TwilioConnect.tsx b/frontend/control-center/src/pages/Connectors/Providers/Twilio/TwilioConnect.tsx index f814bdb3f6..4c4048d799 100644 --- a/frontend/control-center/src/pages/Connectors/Providers/Twilio/TwilioConnect.tsx +++ b/frontend/control-center/src/pages/Connectors/Providers/Twilio/TwilioConnect.tsx @@ -1,14 +1,14 @@ -import React, {useState} from 'react'; +import React, {useEffect, useState} from 'react'; import {connect, ConnectedProps} from 'react-redux'; import {connectTwilioSms, connectTwilioWhatsapp} from '../../../../actions'; -import {Button, Input, UrlInputField} from 'components'; -import {Channel, Source} from 'model'; +import {Input, SmartButton, NotificationComponent, UrlInputField} from 'components'; +import {Channel, NotificationModel, Source} from 'model'; import styles from './TwilioConnect.module.scss'; -import {CONNECTORS_CONNECTED_ROUTE} from '../../../../routes/routes'; +import {CONNECTORS_ROUTE} from '../../../../routes/routes'; import {useNavigate} from 'react-router-dom'; import {useTranslation} from 'react-i18next'; @@ -32,8 +32,27 @@ const TwilioConnect = (props: TwilioConnectProps) => { const [numberInput, setNumberInput] = useState(channel?.sourceChannelId || ''); const [nameInput, setNameInput] = useState(channel?.metadata?.name || ''); const [imageUrlInput, setImageUrlInput] = useState(channel?.metadata?.imageUrl || ''); - - const CONNECTED_ROUTE = CONNECTORS_CONNECTED_ROUTE; + const [notification, setNotification] = useState(null); + const [newButtonText, setNewButtonText] = useState(''); + const [isPending, setIsPending] = useState(false); + + useEffect(() => { + if (channel?.sourceChannelId !== numberInput && !!channel) { + setNotification({show: true, text: t('newChannelInfo'), info: true}); + setNewButtonText(t('connect')); + } else { + setNewButtonText(buttonText); + } + }, [numberInput]); + + const buttonStatus = () => { + return ( + numberInput.trim().length === 0 || + (channel?.sourceChannelId === numberInput && + channel?.metadata?.name === nameInput && + (channel?.metadata?.imageUrl === imageUrlInput || imageUrlInput === '')) + ); + }; const handleNumberInput = (e: React.ChangeEvent): void => { setNumberInput(e.target.value); @@ -49,6 +68,7 @@ const TwilioConnect = (props: TwilioConnectProps) => { const connectTwilioChannel = (e: React.ChangeEvent) => { e.preventDefault(); + setIsPending(true); const connectPayload = { sourceChannelId: numberInput, @@ -57,17 +77,43 @@ const TwilioConnect = (props: TwilioConnectProps) => { }; if (source === Source.twilioWhatsApp) { - connectTwilioWhatsapp(connectPayload).then(() => { - navigate(CONNECTED_ROUTE + `/twilio.whatsapp/#`, { - replace: true, - state: {source: 'twilio.whatsapp'}, + connectTwilioWhatsapp(connectPayload) + .then(() => { + navigate(CONNECTORS_ROUTE + `/twilio.whatsapp/connected`, { + replace: true, + state: {source: 'twilio.whatsapp'}, + }); + }) + .catch((error: Error) => { + setNotification({ + show: true, + text: buttonText === 'connect' ? t('connectFailed') : t('updateFailed'), + successful: false, + info: false, + }); + console.error(error); + }) + .finally(() => { + setIsPending(false); }); - }); } if (source === Source.twilioSMS) { - connectTwilioSms(connectPayload).then(() => { - navigate(CONNECTED_ROUTE + `/twilio.sms/#`, {replace: true, state: {source: 'twilio.sms'}}); - }); + connectTwilioSms(connectPayload) + .then(() => { + navigate(CONNECTORS_ROUTE + `/twilio.sms/connected`, {replace: true, state: {source: 'twilio.sms'}}); + }) + .catch((error: Error) => { + setNotification({ + show: true, + text: buttonText === 'connect' ? t('connectFailed') : t('updateFailed'), + successful: false, + info: false, + }); + console.error(error); + }) + .finally(() => { + setIsPending(false); + }); } }; @@ -109,15 +155,27 @@ const TwilioConnect = (props: TwilioConnectProps) => { fontClass="font-base" /> - + /> + {notification?.show && ( + + )} ); diff --git a/frontend/control-center/src/pages/Connectors/Providers/Twilio/WhatsApp/ConnectorTwilioWhatsapp.tsx b/frontend/control-center/src/pages/Connectors/Providers/Twilio/WhatsApp/ConnectorTwilioWhatsapp.tsx deleted file mode 100644 index d853608175..0000000000 --- a/frontend/control-center/src/pages/Connectors/Providers/Twilio/WhatsApp/ConnectorTwilioWhatsapp.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import {Source} from 'model'; -import React from 'react'; -import ConnectorConfig from '../../../ConnectorConfig'; - -const ConnectorTwilioWhatsapp = () => { - return ; -}; - -export default ConnectorTwilioWhatsapp; diff --git a/frontend/control-center/src/pages/Connectors/Providers/Twilio/WhatsApp/TwilioWhatsappConnect.tsx b/frontend/control-center/src/pages/Connectors/Providers/Twilio/WhatsApp/TwilioWhatsappConnect.tsx index 5830e02fb0..5ac29eb77e 100644 --- a/frontend/control-center/src/pages/Connectors/Providers/Twilio/WhatsApp/TwilioWhatsappConnect.tsx +++ b/frontend/control-center/src/pages/Connectors/Providers/Twilio/WhatsApp/TwilioWhatsappConnect.tsx @@ -1,4 +1,4 @@ -import React, {useState, useEffect} from 'react'; +import React, {useEffect} from 'react'; import {connect, ConnectedProps} from 'react-redux'; import {allChannels, useCurrentChannel} from '../../../../../selectors/channels'; @@ -19,13 +19,7 @@ const TwilioWhatsappConnect = (props: ConnectedProps) => { const {t} = useTranslation(); const channel = useCurrentChannel(); const {channelId} = useParams(); - const [buttonTitle, setButtonTitle] = useState(t('connectWhatsappNumber') || ''); - - useEffect(() => { - if (channel) { - setButtonTitle(t('updateWhatsappNumber')); - } - }, []); + const buttonTitle = channel ? t('update') : t('connect') || ''; useEffect(() => { if (channelId !== 'new') { diff --git a/frontend/control-center/src/pages/Connectors/Providers/WhatsappBusinessCloud/WhatsappBusinessCloudConnect.module.scss b/frontend/control-center/src/pages/Connectors/Providers/WhatsappBusinessCloud/WhatsappBusinessCloudConnect.module.scss new file mode 100644 index 0000000000..1fdbd99211 --- /dev/null +++ b/frontend/control-center/src/pages/Connectors/Providers/WhatsappBusinessCloud/WhatsappBusinessCloudConnect.module.scss @@ -0,0 +1,25 @@ +@import 'assets/scss/fonts.scss'; +@import 'assets/scss/colors.scss'; + +.formWrapper { + align-self: center; +} + +.settings { + width: 29rem; +} + +.formRow { + height: 80px; + margin-bottom: 16px; + + label { + @include font-s; + } + + &:hover { + .actionToolTip { + display: block; + } + } +} diff --git a/frontend/control-center/src/pages/Connectors/Providers/WhatsappBusinessCloud/WhatsappBusinessCloudConnect.tsx b/frontend/control-center/src/pages/Connectors/Providers/WhatsappBusinessCloud/WhatsappBusinessCloudConnect.tsx new file mode 100644 index 0000000000..2d47983380 --- /dev/null +++ b/frontend/control-center/src/pages/Connectors/Providers/WhatsappBusinessCloud/WhatsappBusinessCloudConnect.tsx @@ -0,0 +1,137 @@ +import React, {useState} from 'react'; +import {useSelector} from 'react-redux'; +import {StateModel} from '../../../../reducers'; +import {Input} from 'components'; +import {ConfigureConnector} from '../../ConfigureConnector'; +import {useTranslation} from 'react-i18next'; +import styles from './WhatsappBusinessCloudConnect.module.scss'; +import {ComponentName, ConnectorName} from 'model'; + +interface ConnectParams { + [key: string]: string; +} + +type WhatsappBusinessCloudConnectProps = { + createNewConnection: (configValues: ConnectParams) => void; + isEnabled: boolean; + isConfigured: boolean; + isPending: boolean; +}; + +export const WhatsappBusinessCloudConnect = ({ + createNewConnection, + isEnabled, + isConfigured, + isPending, +}: WhatsappBusinessCloudConnectProps) => { + const componentInfo = useSelector( + (state: StateModel) => state.data.connector[ConnectorName.sourcesWhatsappBusinessCloud] + ); + const [appId, setAppId] = useState(componentInfo?.appId || ''); + const [appSecret, setAppSecret] = useState(componentInfo?.appSecret || ''); + const [phoneNumber, setPhoneNumber] = useState(componentInfo?.phoneNumber || ''); + const [name, setName] = useState(componentInfo?.name || ''); + const [avatarUrl, setAvatarUrl] = useState(componentInfo?.avatarUrl || ''); + const [isUpdateModalVisible, setIsUpdateModalVisible] = useState(false); + const {t} = useTranslation(); + + const updateConfig = (event: React.FormEvent) => { + event.preventDefault(); + if (isEnabled) { + setIsUpdateModalVisible(true); + } else { + enableSubmitConfigData(); + } + }; + + const enableSubmitConfigData = () => { + createNewConnection({appId, appSecret, phoneNumber, name, avatarUrl}); + }; + + return ( + +
+ ) => setAppId(e.target.value)} + label="App ID" + placeholder={t('whatsappBusinessCloudAppIdPlaceholder')} + showLabelIcon + tooltipText={t('whatsappBusinessCloudAppIdTooltip')} + required + height={32} + fontClass="font-base" + /> +
+
+ ) => setAppSecret(e.target.value)} + label="App Secret" + placeholder={t('whatsappBusinessCloudAppSecretPlaceholder')} + showLabelIcon + tooltipText={t('whatsappBusinessCloudAppSecretToolTip')} + required + height={32} + fontClass="font-base" + /> +
+
+ ) => setPhoneNumber(e.target.value)} + label="Phone Number" + placeholder={t('whatsappBusinessCloudPhoneNumberPlaceholder')} + showLabelIcon + tooltipText={t('whatsappBusinessCloudPhoneNumberTooltip')} + required + height={32} + fontClass="font-base" + /> +
+
+ ) => setName(e.target.value)} + label="Name" + placeholder={t('name')} + showLabelIcon + tooltipText={t('optional')} + height={32} + fontClass="font-base" + /> +
+
+ ) => setAvatarUrl(e.target.value)} + label="Avatar Url" + placeholder="Avatar Url" + showLabelIcon + tooltipText={t('optional')} + height={32} + fontClass="font-base" + /> +
+
+ ); +}; diff --git a/frontend/control-center/src/pages/Connectors/Providers/Zendesk/ConnectNewZendesk.tsx b/frontend/control-center/src/pages/Connectors/Providers/Zendesk/ConnectNewZendesk.tsx index a1ccad43eb..6d6458a00d 100644 --- a/frontend/control-center/src/pages/Connectors/Providers/Zendesk/ConnectNewZendesk.tsx +++ b/frontend/control-center/src/pages/Connectors/Providers/Zendesk/ConnectNewZendesk.tsx @@ -2,18 +2,31 @@ import React, {useState} from 'react'; import {useSelector} from 'react-redux'; import {StateModel} from '../../../../reducers'; import {Input} from 'components'; -import {ConnectNewForm} from '../../ConnectNewForm'; +import {ConfigureConnector} from '../../ConfigureConnector'; import styles from './index.module.scss'; import {useTranslation} from 'react-i18next'; +import {ComponentName} from 'model'; + +interface ConnectParams { + [key: string]: string; +} type ConnectNewDialogflowProps = { - createNewConnection: (domain: string, token: string, username: string) => void; + createNewConnection: (configValues: ConnectParams) => void; isEnabled: boolean; isConfigured: boolean; + isPending: boolean; }; -export const ConnectNewZendesk = ({createNewConnection, isEnabled, isConfigured}: ConnectNewDialogflowProps) => { - const componentInfo = useSelector((state: StateModel) => state.data.connector['zendesk-connector']); +export const ConnectNewZendesk = ({ + createNewConnection, + isEnabled, + isConfigured, + isPending, +}: ConnectNewDialogflowProps) => { + const componentInfo = useSelector( + (state: StateModel) => state.data.connector[ComponentName.enterpriseZendenkConnector] + ); const [domain, setDomain] = useState(componentInfo?.domain || ''); const [username, setUsername] = useState(componentInfo?.username || ''); const [token, setToken] = useState(componentInfo?.token || ''); @@ -30,18 +43,24 @@ export const ConnectNewZendesk = ({createNewConnection, isEnabled, isConfigured} }; const enableSubmitConfigData = () => { - createNewConnection(domain, token, username); + createNewConnection({domain, token, username}); }; return ( -
-
+ ); }; diff --git a/frontend/control-center/src/pages/Connectors/Providers/Zendesk/ZendeskConnect.tsx b/frontend/control-center/src/pages/Connectors/Providers/Zendesk/ZendeskConnect.tsx deleted file mode 100644 index 3a587f2427..0000000000 --- a/frontend/control-center/src/pages/Connectors/Providers/Zendesk/ZendeskConnect.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import {Source} from 'model'; -import React from 'react'; -import ConnectorConfig from '../../ConnectorConfig'; - -const ZendeskConnector = () => { - return ; -}; - -export default ZendeskConnector; diff --git a/frontend/control-center/src/pages/Connectors/Providers/Zendesk/index.module.scss b/frontend/control-center/src/pages/Connectors/Providers/Zendesk/index.module.scss index be5c82c3a2..422930afbf 100644 --- a/frontend/control-center/src/pages/Connectors/Providers/Zendesk/index.module.scss +++ b/frontend/control-center/src/pages/Connectors/Providers/Zendesk/index.module.scss @@ -2,6 +2,7 @@ @import 'assets/scss/colors.scss'; .formRow { + height: 80px; margin-bottom: 16px; label { diff --git a/frontend/control-center/src/pages/Connectors/RestartPopUp/index.module.scss b/frontend/control-center/src/pages/Connectors/RestartPopUp/index.module.scss index b80a4b980d..acf211e476 100644 --- a/frontend/control-center/src/pages/Connectors/RestartPopUp/index.module.scss +++ b/frontend/control-center/src/pages/Connectors/RestartPopUp/index.module.scss @@ -2,8 +2,6 @@ @import 'assets/scss/colors.scss'; .modalContainerWrapper { - width: 50%; - height: 70%; display: flex; flex-direction: column; justify-content: center; diff --git a/frontend/control-center/src/pages/Connectors/index.tsx b/frontend/control-center/src/pages/Connectors/index.tsx index 4d81a30be8..d1d96b0917 100644 --- a/frontend/control-center/src/pages/Connectors/index.tsx +++ b/frontend/control-center/src/pages/Connectors/index.tsx @@ -1,25 +1,30 @@ import React, {useEffect, useState} from 'react'; import {connect, ConnectedProps, useSelector} from 'react-redux'; -import {useNavigate} from 'react-router-dom'; -import {Channel, Source, getSourceForComponent} from 'model'; -import InfoCard, {InfoCardStyle} from '../../components/InfoCard'; +import {Channel, Source} from 'model'; +import InfoCard from './InfoCard'; import {StateModel} from '../../reducers'; import {allChannelsConnected} from '../../selectors/channels'; -import {listChannels} from '../../actions/channel'; -import {listComponents} from '../../actions/catalog'; +import {listChannels, listComponents, getConnectorsConfiguration} from '../../actions'; import {setPageTitle} from '../../services/pageTitle'; -import {getConnectorsConfiguration} from '../../actions'; -import {getSourcesInfo, SourceInfo} from '../../components/SourceInfo'; -import styles from './index.module.scss'; import {EmptyStateConnectors} from './EmptyStateConnectors'; import {ChannelCard} from './ChannelCard'; import {SimpleLoader} from 'components'; -import {getComponentStatus} from '../../services/getComponentStatus'; +import {getComponentStatus, formatComponentNameToConfigKey} from '../../services'; +import styles from './index.module.scss'; export enum ComponentStatus { enabled = 'Enabled', notConfigured = 'Not Configured', disabled = 'Disabled', + notHealthy = 'Not Healthy', +} + +export interface ConnectorCardComponentInfo { + name: string; + displayName: string; + configKey: string; + isChannel?: string; + source: Source; } const mapDispatchToProps = { @@ -32,113 +37,93 @@ const connector = connect(null, mapDispatchToProps); const Connectors = (props: ConnectedProps) => { const {listChannels, getConnectorsConfiguration, listComponents} = props; + const [connectorsPageList, setConnectorsPageList] = useState<[] | ConnectorCardComponentInfo[]>([]); const channels = useSelector((state: StateModel) => Object.values(allChannelsConnected(state))); const components = useSelector((state: StateModel) => state.data.config.components); const connectors = useSelector((state: StateModel) => state.data.connector); const catalogList = useSelector((state: StateModel) => state.data.catalog); const channelsBySource = (Source: Source) => channels.filter((channel: Channel) => channel.source === Source); - const [sourcesInfo, setSourcesInfo] = useState([]); const [hasInstalledComponents, setHasInstalledComponents] = useState(false); - const navigate = useNavigate(); const pageTitle = 'Connectors'; - const isInstalled = true; + + const catalogListArr = Object.entries(catalogList); + const emptyCatalogList = catalogListArr.length === 0; useEffect(() => { - setSourcesInfo(getSourcesInfo()); getConnectorsConfiguration(); - if (Object.entries(catalogList).length === 0) + if (emptyCatalogList) { listComponents().catch((error: Error) => { console.error(error); }); - if (Object.entries(catalogList).length > 0) - Object.entries(catalogList).map(component => { - component[1].installed === true && setHasInstalledComponents(true); + } else { + const listArr = []; + catalogListArr.map(component => { + if (component[1].installed === true && component[1].source !== 'webhooks') { + setHasInstalledComponents(true); + listArr.push({ + name: component[1].name, + displayName: component[1].displayName, + configKey: formatComponentNameToConfigKey(component[1].name), + source: component[1].source, + isChannel: component[1].isChannel, + }); + } }); + + setConnectorsPageList(listArr); + } }, [catalogList]); useEffect(() => { if (channels.length === 0) { - listChannels(); + listChannels().catch((error: Error) => { + console.error(error); + }); } setPageTitle(pageTitle); }, [channels.length]); - const isComponentInstalled = (repository: string, componentName: string) => { - const componentNameCatalog = repository + '/' + componentName; - return catalogList[componentNameCatalog] && catalogList[componentNameCatalog].installed === true; - }; - return (
- {sourcesInfo.length > 0 && ( -
-
-

Connectors

- {Object.entries(catalogList).length === 0 && } -
+
+
+

Connectors

+ {emptyCatalogList && }
- )} +
- {!hasInstalledComponents && Object.entries(catalogList).length > 0 ? ( + {!hasInstalledComponents && catalogListArr.length > 0 ? ( ) : ( <> - {sourcesInfo.map((infoItem: SourceInfo, index: number) => { - return ( - (components && - components[infoItem?.configKey] && - isInstalled && - connectors[infoItem.configKey] && - infoItem.channel && - isComponentInstalled(infoItem.repository, infoItem.componentName) && ( + {connectorsPageList.map((item: ConnectorCardComponentInfo) => { + if (components && components[item.configKey] && connectors[item.configKey] && catalogList[item.name]) { + const isConfigured = + Object.keys(connectors[item.configKey]).length > 0 || item.source === Source.chatPlugin; + const isEnabled = components[item.configKey]?.enabled; + const isHealthy = components[item.configKey]?.healthy; + const isInstalled = catalogList[item.name].installed; + + if (item.isChannel === 'true') { + return ( 0 || infoItem.type === Source.chatPlugin, - components[infoItem.configKey]?.enabled - )} - key={index} + key={item.displayName} + componentInfo={item} + componentStatus={getComponentStatus(isHealthy, isInstalled, isConfigured, isEnabled)} + channelsToShow={channelsBySource(item.source).length} + /> + ); + } + if (!item.isChannel) { + return ( + - )) || - (channelsBySource(infoItem.type).length > 0 && - !infoItem.channel && - isComponentInstalled(infoItem.repository, infoItem.componentName) && ( -
- { - navigate(infoItem.channelsListRoute); - }} - /> -
- )) || - (getSourceForComponent(infoItem.type) && - components && - components[infoItem.configKey] && - !infoItem.channel && - isComponentInstalled(infoItem.repository, infoItem.componentName) && ( -
- 0, - components[infoItem?.configKey].enabled - )} - style={InfoCardStyle.normal} - key={infoItem.type} - sourceInfo={infoItem} - addChannelAction={() => { - navigate(infoItem.channelsListRoute); - }} - /> -
- )) - ); + ); + } + } })} )} diff --git a/frontend/control-center/src/pages/Inbox/ChannelsList/ChannelListItem/index.tsx b/frontend/control-center/src/pages/Inbox/ChannelsList/ChannelListItem/index.tsx index 8a9edd3ca4..1f3a9419ad 100644 --- a/frontend/control-center/src/pages/Inbox/ChannelsList/ChannelListItem/index.tsx +++ b/frontend/control-center/src/pages/Inbox/ChannelsList/ChannelListItem/index.tsx @@ -38,10 +38,14 @@ const ChannelListItem = (props: ChannelListItemProps) => { }; const disconnectChannel = () => { - props.disconnectChannel({ - source: channel.source, - channelId: channel.id, - }); + props + .disconnectChannel({ + source: channel.source, + channelId: channel.id, + }) + .catch((error: Error) => { + console.error(error); + }); togglePopupVisibility(); }; diff --git a/frontend/control-center/src/pages/Inbox/ChannelsList/index.tsx b/frontend/control-center/src/pages/Inbox/ChannelsList/index.tsx index 71c695ff74..bb4ee971f0 100644 --- a/frontend/control-center/src/pages/Inbox/ChannelsList/index.tsx +++ b/frontend/control-center/src/pages/Inbox/ChannelsList/index.tsx @@ -16,14 +16,7 @@ import {ReactComponent as CloseIcon} from 'assets/images/icons/close.svg'; import styles from './index.module.scss'; import {cyChannelsFormBackButton} from 'handles'; -import { - CONNECTORS_FACEBOOK_ROUTE, - CONNECTORS_CHAT_PLUGIN_ROUTE, - CONNECTORS_TWILIO_SMS_ROUTE, - CONNECTORS_TWILIO_WHATSAPP_ROUTE, - CONNECTORS_GOOGLE_ROUTE, - CONNECTORS_INSTAGRAM_ROUTE, -} from '../../../routes/routes'; +import {CONNECTORS_ROUTE} from '../../../routes/routes'; import {getChannelAvatar} from '../../../components/ChannelAvatar'; import {useTranslation} from 'react-i18next'; @@ -37,7 +30,7 @@ const ChannelsList = () => { const [name, setName] = useState(''); const [description, setDescription] = useState(''); - const [path, setPath] = useState(''); + const [searchText, setSearchText] = useState(''); const [showingSearchField, setShowingSearchField] = useState(false); const connectorsRoute = location.pathname.includes('connectors'); @@ -46,48 +39,37 @@ const ChannelsList = () => { channel.metadata?.name?.toLowerCase().includes(searchText.toLowerCase()) ); + const path = `${CONNECTORS_ROUTE}/source/new`; + useLayoutEffect(() => { getInfo(); }, [source, channels]); const getInfo = () => { - let ROUTE; switch (source) { case Source.facebook: setName(t('facebookTitle')); setDescription(t('facebookDescription')); - ROUTE = CONNECTORS_FACEBOOK_ROUTE; - setPath(ROUTE + '/new'); break; case Source.google: setName(t('googleTitle')); setDescription(t('googleDescription')); - ROUTE = CONNECTORS_GOOGLE_ROUTE; - setPath(ROUTE + '/new'); break; case Source.twilioSMS: setName(t('twilioSmsTitle')); setDescription(t('twilioSmsDescription')); - ROUTE = CONNECTORS_TWILIO_SMS_ROUTE; - setPath(ROUTE + '/new'); break; case Source.twilioWhatsApp: setName(t('twilioWhatsappTitle')); setDescription(t('twilioWhatsappDescription')); - ROUTE = CONNECTORS_TWILIO_WHATSAPP_ROUTE; - setPath(ROUTE + '/new'); break; case Source.chatPlugin: setName(t('chatpluginTitle')); setDescription(t('chatpluginDescription')); - ROUTE = CONNECTORS_CHAT_PLUGIN_ROUTE; - setPath(ROUTE + '/new'); break; case Source.instagram: setName(t('instagramTitle')); setDescription(t('instagramDescription')); - ROUTE = CONNECTORS_INSTAGRAM_ROUTE; - setPath(ROUTE + '/new'); break; } }; diff --git a/frontend/control-center/src/pages/Inbox/index.module.scss b/frontend/control-center/src/pages/Inbox/index.module.scss index a7e89997dd..3f462a4680 100644 --- a/frontend/control-center/src/pages/Inbox/index.module.scss +++ b/frontend/control-center/src/pages/Inbox/index.module.scss @@ -41,8 +41,35 @@ .channelsLine { width: 100%; - span { - @include font-base; - color: var(--color-airy-blue); - } +} + +.channelsLineWrapper { + display: flex; + flex-direction: column; + margin-bottom: 36px; +} + +.channelsTitle { + margin-bottom: 16px; + margin-top: 16px; + margin-left: 36px; + @include font-base; + color: var(--color-airy-blue); +} + +.lineContainer { + display: flex; + align-items: center; +} + +.lineBlue { + width: 150px; + height: 4px; + background: var(--color-airy-blue); +} + +.lineGrey { + width: 100%; + height: 1px; + background: var(--color-light-gray); } diff --git a/frontend/control-center/src/pages/Inbox/index.tsx b/frontend/control-center/src/pages/Inbox/index.tsx index 6b1035ef78..c408eebfaf 100644 --- a/frontend/control-center/src/pages/Inbox/index.tsx +++ b/frontend/control-center/src/pages/Inbox/index.tsx @@ -1,40 +1,58 @@ -import {Source} from 'model'; -import {Channel} from 'model/Channel'; import React, {useEffect, useState} from 'react'; import {connect, ConnectedProps, useSelector} from 'react-redux'; -import {listChannels} from '../../actions/channel'; -import {getSourcesInfo, SourceInfo} from '../../components/SourceInfo'; +import {listChannels, listComponents} from '../../actions'; import {StateModel} from '../../reducers'; import {allChannelsConnected} from '../../selectors/channels'; import {setPageTitle} from '../../services/pageTitle'; import {ChannelCard} from '../Connectors/ChannelCard'; import {EmptyStateInbox} from './EmptyStateInbox'; +import {Channel, Source} from 'model'; +import {formatComponentNameToConfigKey} from '../../services'; import styles from './index.module.scss'; const mapDispatchToProps = { listChannels, + listComponents, }; -const mapStateToProps = (state: StateModel) => ({ - channels: Object.values(allChannelsConnected(state)), -}); - -const connector = connect(mapStateToProps, mapDispatchToProps); +const connector = connect(null, mapDispatchToProps); const Inbox = (props: ConnectedProps) => { - const [sourcesInfo, setSourcesInfo] = useState([]); const channels = useSelector((state: StateModel) => Object.values(allChannelsConnected(state))); + const catalogList = useSelector((state: StateModel) => state.data.catalog); const channelsBySource = (Source: Source) => channels.filter((channel: Channel) => channel.source === Source); + const [inboxList, setInboxList] = useState([]); + + const catalogListArr = Object.entries(catalogList); + const emptyCatalogList = catalogListArr.length === 0; useEffect(() => { - setSourcesInfo(getSourcesInfo()); + props.listComponents(); }, []); useEffect(() => { - if (props.channels.length === 0) { + if (Object.entries(catalogList).length > 0) { + const list = []; + catalogListArr.map(component => { + if (component[1]?.displayName) { + const configKey = formatComponentNameToConfigKey(component[1].displayName); + list.push({ + name: component[1].name, + displayName: component[1].displayName, + configKey: configKey, + source: component[1].source, + }); + } + }); + setInboxList(list); + } + }, [catalogList]); + + useEffect(() => { + if (channels.length === 0) { props.listChannels(); } - }, [props.channels.length]); + }, [channels.length]); useEffect(() => { setPageTitle('Inbox'); @@ -42,7 +60,7 @@ const Inbox = (props: ConnectedProps) => { return (
- {sourcesInfo.length > 0 && ( + {!emptyCatalogList && (

Inbox

@@ -50,21 +68,21 @@ const Inbox = (props: ConnectedProps) => {
)}
- {sourcesInfo.length === 0 ? ( + {emptyCatalogList ? ( ) : (
-
- Channels -
-
-
+
+

Channels

+
+
+
- {sourcesInfo.map((infoItem: SourceInfo, index: number) => { - if (channelsBySource(infoItem.type).length > 0) { - return ; + {inboxList.map((item: {name: string; displayName: string; configKey: string; source: Source}) => { + if (channelsBySource(item.source).length > 0) { + return ; } })}
diff --git a/frontend/control-center/src/pages/Status/ComponentListItem/ItemInfo.tsx b/frontend/control-center/src/pages/Status/ComponentListItem/ItemInfo.tsx index e70dc5d463..c115d88f20 100644 --- a/frontend/control-center/src/pages/Status/ComponentListItem/ItemInfo.tsx +++ b/frontend/control-center/src/pages/Status/ComponentListItem/ItemInfo.tsx @@ -5,13 +5,11 @@ import {ReactComponent as CheckmarkIcon} from 'assets/images/icons/checkmarkFill import {ReactComponent as UncheckedIcon} from 'assets/images/icons/uncheckIcon.svg'; import {ReactComponent as ArrowRight} from 'assets/images/icons/arrowRight.svg'; import {getChannelAvatar} from '../../../components/ChannelAvatar'; -import {getSourcesInfo} from '../../../components/SourceInfo'; -import {getComponentName} from '../../../services'; -import {ConfigServices, getSourceForComponent} from 'model'; +import {ComponentName, Source} from 'model'; import {SettingsModal, Button, Toggle, Tooltip} from 'components'; -import styles from './index.module.scss'; import {connect, ConnectedProps, useSelector} from 'react-redux'; import {useTranslation} from 'react-i18next'; +import styles from './index.module.scss'; type ComponentInfoProps = { healthy: boolean; @@ -20,6 +18,7 @@ type ComponentInfoProps = { isExpanded: boolean; enabled?: boolean; setIsPopUpOpen: (value: boolean) => void; + source?: Source; } & ConnectedProps; const mapDispatchToProps = { @@ -28,37 +27,40 @@ const mapDispatchToProps = { const connector = connect(null, mapDispatchToProps); -const isConfigurableConnector = (name: string) => { - let isConfigurable = false; - - getSourcesInfo().forEach(elem => { - if (elem.configKey === name) isConfigurable = true; - }); - - return isConfigurable; -}; - const ItemInfo = (props: ComponentInfoProps) => { - const {healthy, itemName, isComponent, isExpanded, enabled, setIsPopUpOpen, enableDisableComponent} = props; + const {source, healthy, itemName, isComponent, isExpanded, enabled, setIsPopUpOpen, enableDisableComponent} = props; + const catalogList = useSelector((state: StateModel) => state.data.catalog); const connectors = useSelector((state: StateModel) => state.data.connector); - const [channelSource] = useState(itemName && getSourceForComponent(itemName)); - const [componentName] = useState(itemName && getComponentName(itemName)); + const [channelSource] = useState(source); + const [componentName] = useState(itemName); const [componentEnabled, setComponentEnabled] = useState(enabled); const [enablePopupVisible, setEnablePopupVisible] = useState(false); const isVisible = isExpanded || isComponent; const {t} = useTranslation(); + const isConfigurableConnector = () => { + let isConfigurable = false; + + Object.entries(catalogList).forEach(elem => { + if (elem[1] && elem[1].source && elem[1].source === source) isConfigurable = true; + }); + + return isConfigurable; + }; + const isComponentConfigured = - connectors[itemName] && isConfigurableConnector(itemName) && Object.keys(connectors[itemName]).length > 0; + connectors[itemName] && isConfigurableConnector() && Object.keys(connectors[itemName]).length > 0; //status const needsConfig = + connector && + connectors[itemName] && isComponent && enabled && healthy && - isConfigurableConnector(itemName) && + isConfigurableConnector() && !isComponentConfigured && - itemName !== ConfigServices.sourcesChatPlugin; + itemName !== ComponentName.sourcesChatPlugin; const isRunning = healthy && enabled; const isNotHealthy = !healthy && enabled; const isDisabled = !enabled; @@ -118,7 +120,7 @@ const ItemInfo = (props: ComponentInfoProps) => { hoverElement={} hoverElementHeight={20} hoverElementWidth={20} - tooltipContent={t('healthy')} + tooltipContent={t('enabled')} /> ) : isNotHealthy ? ( { - const {healthy, componentName, enabled, services} = props; + const {healthy, componentName, source, enabled, services} = props; const [isExpanded, setIsExpanded] = useState(false); const [isPopUpOpen, setIsPopUpOpen] = useState(false); @@ -41,6 +43,7 @@ export const ComponentListItem = (props: ComponentsListProps) => { isExpanded={isExpanded} enabled={enabled} setIsPopUpOpen={setIsPopUpOpen} + source={source} /> {services.map((service, index) => ( diff --git a/frontend/control-center/src/pages/Status/index.module.scss b/frontend/control-center/src/pages/Status/index.module.scss index f40b30d09d..c820ad609b 100644 --- a/frontend/control-center/src/pages/Status/index.module.scss +++ b/frontend/control-center/src/pages/Status/index.module.scss @@ -24,6 +24,18 @@ margin-bottom: 14px; } +.statusLastRefreshContainer { + display: flex; + justify-content: space-between; + align-items: center; + margin-right: 16px; + + span { + @include font-s; + color: var(--color-text-contrast); + } +} + .listHeader { display: flex; flex-direction: row; diff --git a/frontend/control-center/src/pages/Status/index.tsx b/frontend/control-center/src/pages/Status/index.tsx index 908702d749..179115e26a 100644 --- a/frontend/control-center/src/pages/Status/index.tsx +++ b/frontend/control-center/src/pages/Status/index.tsx @@ -1,45 +1,79 @@ import React, {useEffect, useState} from 'react'; import {connect, ConnectedProps, useSelector} from 'react-redux'; -import {getClientConfig, getConnectorsConfiguration} from '../../actions'; +import {getClientConfig, getConnectorsConfiguration, listComponents} from '../../actions'; import {StateModel} from '../../reducers'; import {ComponentListItem} from './ComponentListItem'; import {ReactComponent as RefreshIcon} from 'assets/images/icons/refreshIcon.svg'; -import styles from './index.module.scss'; import {setPageTitle} from '../../services/pageTitle'; import {useTranslation} from 'react-i18next'; +import {ComponentRepository} from 'model'; +import styles from './index.module.scss'; const mapDispatchToProps = { getClientConfig, getConnectorsConfiguration, + listComponents, }; const connector = connect(null, mapDispatchToProps); const Status = (props: ConnectedProps) => { - const {getClientConfig, getConnectorsConfiguration} = props; + const {getClientConfig, getConnectorsConfiguration, listComponents} = props; const components = useSelector((state: StateModel) => Object.entries(state.data.config.components)); + const catalogList = useSelector((state: StateModel) => state.data.catalog); const [spinAnim, setSpinAnim] = useState(true); + const [lastRefresh, setLastRefresh] = useState(new Date().toLocaleString()); const {t} = useTranslation(); useEffect(() => { setPageTitle('Status'); - getClientConfig(); - getConnectorsConfiguration(); + getClientConfig() + .then(() => { + setLastRefresh(new Date().toLocaleString()); + }) + .catch((error: Error) => { + console.error(error); + }); + getConnectorsConfiguration().catch((error: Error) => { + console.error(error); + }); + listComponents().catch((error: Error) => { + console.error(error); + }); }, []); - setInterval(() => { - props.getClientConfig(); - setSpinAnim(!spinAnim); - }, 300000); - const handleRefresh = () => { - props.getClientConfig(); + props + .getClientConfig() + .then(() => { + setLastRefresh(new Date().toLocaleString()); + }) + .catch((error: Error) => { + console.error(error); + }); setSpinAnim(!spinAnim); }; + const formatToComponentName = (name: string) => { + let formattedName; + if (name.includes('enterprise')) { + formattedName = `${ComponentRepository.airyEnterprise}/${name}`; + } else { + formattedName = `${ComponentRepository.airyCore}/${name}`; + } + + return formattedName; + }; + return (
-

{t('status')}

+
+

{t('status')}

+ + Last Refresh:
+ {lastRefresh} +
+

{t('componentName')}

{t('healthStatus')}

@@ -52,15 +86,21 @@ const Status = (props: ConnectedProps) => {
- {components.map((component, index) => ( - - ))} + {Object.entries(catalogList).length > 0 && + components.map((component, index) => { + const formattedName = formatToComponentName(component[0]); + const catalogItem = catalogList[formattedName]; + return ( + + ); + })}
); diff --git a/frontend/control-center/src/pages/Webhooks/index.tsx b/frontend/control-center/src/pages/Webhooks/index.tsx index 74391b0714..fbda3cee18 100644 --- a/frontend/control-center/src/pages/Webhooks/index.tsx +++ b/frontend/control-center/src/pages/Webhooks/index.tsx @@ -41,7 +41,10 @@ const Webhooks = (props: WebhooksProps) => { }, []); useEffect(() => { - webhooks.length === 0 && listWebhooks(); + webhooks.length === 0 && + listWebhooks().catch((error: Error) => { + console.error(error); + }); }, [webhooks]); const handleNotification = (show: boolean, error: boolean) => { diff --git a/frontend/control-center/src/reducers/data/channels/index.ts b/frontend/control-center/src/reducers/data/channels/index.ts index 405db1bcbb..8f8bfef120 100644 --- a/frontend/control-center/src/reducers/data/channels/index.ts +++ b/frontend/control-center/src/reducers/data/channels/index.ts @@ -49,8 +49,6 @@ const channelsReducer = (state = {}, action: Action): ChannelsState => { metadata: merge({}, state[action.payload.identifier]?.metadata, action.payload.metadata), }, }; - case getType(actions.setCurrentChannelsAction): - return action.payload.reduce(setChannel, {}); case getType(actions.addChannelsAction): return action.payload.reduce(setChannel, state); case getType(actions.setChannelAction): diff --git a/frontend/control-center/src/reducers/data/connector/index.ts b/frontend/control-center/src/reducers/data/connector/index.ts index e0aaabc7bf..e000060887 100644 --- a/frontend/control-center/src/reducers/data/connector/index.ts +++ b/frontend/control-center/src/reducers/data/connector/index.ts @@ -17,10 +17,7 @@ export default function connectorsReducer(state = defaultState, action: Action): ...action.payload.components, }; case getType(actions.updateConnectorConfigurationAction): { - let name = action.payload.components[0].name; - if (name.includes('enterprise')) { - name = name.replace('enterprise-', ''); - } + const name = action.payload.components[0].name; return { ...state, [name]: { diff --git a/frontend/control-center/src/routes/routes.ts b/frontend/control-center/src/routes/routes.ts index c90969f8e1..ba07d6c2d7 100644 --- a/frontend/control-center/src/routes/routes.ts +++ b/frontend/control-center/src/routes/routes.ts @@ -1,36 +1,13 @@ export const ROOT_ROUTE = '/'; export const CONNECTORS_ROUTE = '/connectors'; -export const CONNECTORS_FACEBOOK_ROUTE = '/connectors/facebook'; -export const CONNECTORS_CHAT_PLUGIN_ROUTE = '/connectors/chatplugin'; -export const CONNECTORS_TWILIO_SMS_ROUTE = '/connectors/twilio.sms'; -export const CONNECTORS_TWILIO_WHATSAPP_ROUTE = '/connectors/twilio.whatsapp'; -//change this when adding WhatsApp Cloud -export const CONNECTORS_WHATSAPP_BUSINESS_CLOUD_ROUTE = '/connectors'; -// -export const CONNECTORS_GOOGLE_ROUTE = '/connectors/google'; -export const CONNECTORS_INSTAGRAM_ROUTE = '/connectors/instagram'; -export const CONNECTORS_DIALOGFLOW_ROUTE = '/connectors/dialogflow'; -export const CONNECTORS_ZENDESK_ROUTE = '/connectors/zendesk'; -export const CONNECTORS_SALESFORCE_ROUTE = '/connectors/salesforce'; - -export const CONNECTORS_CONNECTED_ROUTE = '/connectors/connected'; -export const CONNECTORS_FACEBOOK_CONNECTED_ROUTE = '/connectors/connected/facebook'; -export const CONNECTORS_CHAT_PLUGIN_CONNECTED_ROUTE = '/connectors/connected/chatplugin'; -export const CONNECTORS_TWILIO_SMS_CONNECTED_ROUTE = '/connectors/connected/twilio.sms'; -export const CONNECTORS_TWILIO_WHATSAPP_CONNECTED_ROUTE = '/connectors/connected/twilio.whatsapp'; -//change this when adding WhatsApp Cloud -export const CONNECTORS_WHATSAPP_BUSINESS_CLOUD_CONNECTED_ROUTE = '/connectors'; -// -export const CONNECTORS_GOOGLE_CONNECTED_ROUTE = '/connectors/connected/google'; -export const CONNECTORS_INSTAGRAM_CONNECTED_ROUTE = '/connectors/connected/instagram'; export const CATALOG_ROUTE = '/catalog'; export const CATALOG_FACEBOOK_ROUTE = '/catalog/facebook'; export const CATALOG_CHAT_PLUGIN_ROUTE = '/catalog/chatplugin'; export const CATALOG_TWILIO_SMS_ROUTE = '/catalog/twilio.sms'; export const CATALOG_TWILIO_WHATSAPP_ROUTE = '/catalog/twilio.whatsapp'; -export const CATALOG_WHATSAPP_BUSINESS_CLOUD_ROUTE = '/catalog/whatsappBusinessCloud'; +export const CATALOG_WHATSAPP_BUSINESS_CLOUD_ROUTE = '/catalog/whatsapp-cloud'; export const CATALOG_GOOGLE_ROUTE = '/catalog/google'; export const CATALOG_INSTAGRAM_ROUTE = '/catalog/instagram'; export const CATALOG_DIALOGFLOW_ROUTE = '/catalog/dialogflow'; diff --git a/frontend/control-center/src/services/format.ts b/frontend/control-center/src/services/format.ts index 127412b05c..8bc74f8d48 100644 --- a/frontend/control-center/src/services/format.ts +++ b/frontend/control-center/src/services/format.ts @@ -1,5 +1,3 @@ -import {getSourceForComponent} from 'model'; - export const capitalizeTitle = (str: string) => { return str.split(' ').map(capitalize).join(' '); }; @@ -8,29 +6,6 @@ const capitalize = (str: string) => { return str.charAt(0).toUpperCase() + str.slice(1); }; -const formatName = (str: string) => { - const name = str - .split('-') - .filter( - element => element !== 'enterprise' && element !== 'sources' && element !== 'connector' && element !== 'frontend' - ) - .join(' '); - return capitalizeTitle(name); -}; - -export const getComponentName = (itemName: string) => { - if (itemName.includes('frontend-ui')) { - return 'Airy Inbox'; - } - if (getSourceForComponent(itemName) && !itemName.includes('enterprise')) { - return formatName(itemName); - } - - const formattedComponentName = itemName - .split('-') - .filter(element => element !== 'sources' && element !== 'connector' && element !== 'frontend') - .join(' '); - return capitalizeTitle(formattedComponentName); -}; - export const removePrefix = (name: string) => name.split('/').pop(); + +export const formatComponentNameToConfigKey = (componentName: string) => componentName.split('/')[1]; diff --git a/frontend/control-center/src/services/getComponentStatus.ts b/frontend/control-center/src/services/getComponentStatus.ts index 8898cf07e9..e597b50aa9 100644 --- a/frontend/control-center/src/services/getComponentStatus.ts +++ b/frontend/control-center/src/services/getComponentStatus.ts @@ -1,7 +1,13 @@ import {ComponentStatus} from '../pages/Connectors'; -export const getComponentStatus = (isInstalled: boolean, isConfigured: boolean, isEnabled: boolean) => { +export const getComponentStatus = ( + isHealthy: boolean, + isInstalled: boolean, + isConfigured: boolean, + isEnabled: boolean +) => { + if (isInstalled && !isEnabled) return ComponentStatus.disabled; + if (!isHealthy) return ComponentStatus.notHealthy; if (isInstalled && !isConfigured) return ComponentStatus.notConfigured; if (isInstalled && isConfigured && isEnabled) return ComponentStatus.enabled; - if (isInstalled && !isEnabled) return ComponentStatus.disabled; }; diff --git a/frontend/control-center/src/services/getRouteForCard.ts b/frontend/control-center/src/services/getRouteForCard.ts new file mode 100644 index 0000000000..8c0e420d9b --- /dev/null +++ b/frontend/control-center/src/services/getRouteForCard.ts @@ -0,0 +1,73 @@ +import { + CONNECTORS_ROUTE, + CATALOG_FACEBOOK_ROUTE, + CATALOG_CHAT_PLUGIN_ROUTE, + CATALOG_TWILIO_SMS_ROUTE, + CATALOG_TWILIO_WHATSAPP_ROUTE, + CATALOG_WHATSAPP_BUSINESS_CLOUD_ROUTE, + CATALOG_GOOGLE_ROUTE, + CATALOG_INSTAGRAM_ROUTE, + CATALOG_DIALOGFLOW_ROUTE, + CATALOG_ZENDESK_ROUTE, + CATALOG_SALESFORCE_ROUTE, + CATALOG_CONGNIFY_ROUTE, + CATALOG_AMELIA_ROUTE, + CATALOG_FRONTEND_INBOX_ROUTE, + CATALOG_RASA_ROUTE, + CATALOG_WEBHOOKS_ROUTE, + CATALOG_MOBILE_ROUTE, + CATALOG_VIBER_ROUTE, + WEBHOOKS_ROUTE, +} from '../routes/routes'; +import {Source} from 'model'; + +export const getConnectedRouteForComponent = (source: Source, isChannel: string) => { + if (source === Source.webhooks) return WEBHOOKS_ROUTE; + + if (isChannel) return `${CONNECTORS_ROUTE}/${source}/connected`; + + return `${CONNECTORS_ROUTE}/${source}/new`; +}; + +export const getNewChannelRouteForComponent = (source: Source) => { + return source === Source.webhooks ? WEBHOOKS_ROUTE : `${CONNECTORS_ROUTE}/${source}/new`; +}; + +export const getCatalogProductRouteForComponent = (displayName: string) => { + switch (displayName) { + case 'Airy Chat Plugin': + return CATALOG_CHAT_PLUGIN_ROUTE; + case 'Facebook Messenger': + return CATALOG_FACEBOOK_ROUTE; + case 'Twilio SMS': + return CATALOG_TWILIO_SMS_ROUTE; + case 'Twilio WhatsApp': + return CATALOG_TWILIO_WHATSAPP_ROUTE; + case 'WhatsApp Business Cloud': + return CATALOG_WHATSAPP_BUSINESS_CLOUD_ROUTE; + case 'Google Business Messages': + return CATALOG_GOOGLE_ROUTE; + case 'Instagram': + return CATALOG_INSTAGRAM_ROUTE; + case 'Dialogflow': + return CATALOG_DIALOGFLOW_ROUTE; + case 'Salesforce': + return CATALOG_SALESFORCE_ROUTE; + case 'Zendesk': + return CATALOG_ZENDESK_ROUTE; + case 'Congnigy': + return CATALOG_CONGNIFY_ROUTE; + case 'Amelia': + return CATALOG_AMELIA_ROUTE; + case 'Inbox': + return CATALOG_FRONTEND_INBOX_ROUTE; + case 'Rasa': + return CATALOG_RASA_ROUTE; + case 'Mobile': + return CATALOG_MOBILE_ROUTE; + case 'Webhooks': + return CATALOG_WEBHOOKS_ROUTE; + case 'Viber': + return CATALOG_VIBER_ROUTE; + } +}; diff --git a/frontend/control-center/src/services/index.ts b/frontend/control-center/src/services/index.ts index 0f471f7f9d..5a7dc2fd3d 100644 --- a/frontend/control-center/src/services/index.ts +++ b/frontend/control-center/src/services/index.ts @@ -1,3 +1,5 @@ export * from './hooks'; export * from './format'; export * from './pageTitle'; +export * from './getComponentStatus'; +export * from './getRouteForCard'; diff --git a/frontend/inbox/handles/index.ts b/frontend/inbox/handles/index.ts index 2ca4b24653..482cace97a 100644 --- a/frontend/inbox/handles/index.ts +++ b/frontend/inbox/handles/index.ts @@ -20,6 +20,7 @@ export const cyTagsTableRowDisplayDeleteModalInput = 'tagsTableRowDisplayDeleteM export const cyTagsTableRowDisplayDeleteModalButton = 'tagsTableRowDisplayDeleteModalButton'; export const cyConnectorAddButton = 'connectorAddButton'; +export const cyAddChannelButton = 'addChannelButton'; export const cyChannelsChatPluginAddButton = 'channelsChatPluginAddButton'; export const cyChannelsFacebookAddButton = 'channelsFacebookAddButton'; diff --git a/frontend/inbox/src/App.tsx b/frontend/inbox/src/App.tsx index bb52760893..dae1f2a5a2 100644 --- a/frontend/inbox/src/App.tsx +++ b/frontend/inbox/src/App.tsx @@ -23,7 +23,9 @@ const connector = connect(null, mapDispatchToProps); const App = (props: ConnectedProps) => { useEffect(() => { - props.getClientConfig(); + props.getClientConfig().catch((error: Error) => { + console.error(error); + }); if (localStorage.getItem('theme') === 'dark') { document.documentElement.setAttribute('data-theme', 'dark'); } diff --git a/frontend/inbox/src/components/IconChannel/index.tsx b/frontend/inbox/src/components/IconChannel/index.tsx index edf173b8a1..509bfc8501 100644 --- a/frontend/inbox/src/components/IconChannel/index.tsx +++ b/frontend/inbox/src/components/IconChannel/index.tsx @@ -72,6 +72,11 @@ const SOURCE_INFO = { icon: () => , avatar: () => , }, + whatsapp: { + text: 'WhatsApp', + icon: () => , + avatar: () => , + }, unknown: { text: 'Unknown Source', icon: () => , diff --git a/frontend/inbox/src/components/Sidebar/index.tsx b/frontend/inbox/src/components/Sidebar/index.tsx index a14f51158d..5515356b8d 100644 --- a/frontend/inbox/src/components/Sidebar/index.tsx +++ b/frontend/inbox/src/components/Sidebar/index.tsx @@ -10,7 +10,7 @@ import {CONTACTS_ROUTE, INBOX_ROUTE, TAGS_ROUTE} from '../../routes/routes'; import styles from './index.module.scss'; import {connect, ConnectedProps} from 'react-redux'; import {StateModel} from '../../reducers'; -import {ConfigServices} from 'model'; +import {ComponentName} from 'model'; type SideBarProps = {} & ConnectedProps; @@ -28,12 +28,10 @@ const Sidebar = (props: SideBarProps) => { useEffect(() => { Object.entries(props.components).length > 0 && - setContactsEnabled(props.components[ConfigServices.apiContacts]?.enabled || false); + setContactsEnabled(props.components[ComponentName.apiContacts]?.enabled || false); }, [props.components]); - const [contactsEnabled, setContactsEnabled] = useState( - props.components[ConfigServices.apiContacts]?.enabled || false - ); + const [contactsEnabled, setContactsEnabled] = useState(props.components[ComponentName.apiContacts]?.enabled || false); return (