diff --git a/VERSION b/VERSION index 5a03fb737b..885415662f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.20.0 +0.21.0 diff --git a/WORKSPACE b/WORKSPACE index 271e190dc1..19fc5a30b0 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -9,9 +9,9 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") # Airy Bazel tools git_repository( name = "com_github_airyhq_bazel_tools", - commit = "d18b5a4418a8b69c0a7177f2831f8aa62da038c1", + commit = "bbfbc0844c30b52e146690412030cfe9c6b475e3", remote = "https://github.com/airyhq/bazel-tools.git", - shallow_since = "1618558833 +0200", + shallow_since = "1620236403 +0200", ) load("@com_github_airyhq_bazel_tools//:repositories.bzl", "airy_bazel_tools_dependencies", "airy_jvm_deps") @@ -35,10 +35,10 @@ maven_install( "com.jayway.jsonpath:json-path:2.4.0", "com.dinstone:beanstalkc:2.3.0", "com.twilio.sdk:twilio:7.51.0", - "io.confluent:kafka-avro-serializer:5.5.1", - "io.confluent:kafka-schema-registry-client:5.5.1", - "io.confluent:kafka-schema-registry:5.5.1", - "io.confluent:kafka-streams-avro-serde:5.5.1", + "io.confluent:kafka-avro-serializer:6.1.1", + "io.confluent:kafka-schema-registry-client:6.1.1", + "io.confluent:kafka-schema-registry:6.1.1", + "io.confluent:kafka-streams-avro-serde:6.1.1", "io.jsonwebtoken:jjwt-api:0.10.5", "io.jsonwebtoken:jjwt-impl:0.10.5", "io.jsonwebtoken:jjwt-jackson:0.10.5", @@ -52,12 +52,12 @@ maven_install( "org.apache.avro:avro-tools:1.10.0", "org.apache.avro:avro:1.10.0", "org.apache.curator:curator-test:4.2.0", - "org.apache.kafka:connect-api:2.5.1", - "org.apache.kafka:connect-transforms:2.5.1", - "org.apache.kafka:kafka-clients:2.5.1", - "org.apache.kafka:kafka-clients:jar:test:2.5.1", - "org.apache.kafka:kafka-streams:2.5.1", - "org.apache.kafka:kafka_2.12:2.5.1", + "org.apache.kafka:connect-api:2.7.0", + "org.apache.kafka:connect-transforms:2.7.0", + "org.apache.kafka:kafka-clients:2.7.0", + "org.apache.kafka:kafka-clients:jar:test:2.7.0", + "org.apache.kafka:kafka-streams:2.7.0", + "org.apache.kafka:kafka_2.13:2.7.0", "org.apache.lucene:lucene-queryparser:8.7.0", "org.apache.lucene:lucene-analyzers-common:8.7.0", "org.apache.lucene:lucene-core:8.7.0", @@ -149,9 +149,9 @@ protobuf_deps() http_archive( name = "io_bazel_rules_docker", - sha256 = "4521794f0fba2e20f3bf15846ab5e01d5332e587e9ce81629c7f96c793bb7036", - strip_prefix = "rules_docker-0.14.4", - urls = ["https://github.com/bazelbuild/rules_docker/releases/download/v0.14.4/rules_docker-v0.14.4.tar.gz"], + sha256 = "59d5b42ac315e7eadffa944e86e90c2990110a1c8075f1cd145f487e999d22b3", + strip_prefix = "rules_docker-0.17.0", + urls = ["https://github.com/bazelbuild/rules_docker/releases/download/v0.17.0/rules_docker-v0.17.0.tar.gz"], ) load( @@ -165,10 +165,6 @@ load("@io_bazel_rules_docker//repositories:deps.bzl", container_deps = "deps") container_deps() -load("@io_bazel_rules_docker//repositories:pip_repositories.bzl", "pip_deps") - -pip_deps() - load( "@io_bazel_rules_docker//container:container.bzl", "container_pull", @@ -237,8 +233,11 @@ go_repositories() http_archive( name = "rules_pkg", - sha256 = "352c090cc3d3f9a6b4e676cf42a6047c16824959b438895a76c2989c6d7c246a", - url = "https://github.com/bazelbuild/rules_pkg/releases/download/0.2.5/rules_pkg-0.2.5.tar.gz", + sha256 = "038f1caa773a7e35b3663865ffb003169c6a71dc995e39bf4815792f385d837d", + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/rules_pkg/releases/download/0.4.0/rules_pkg-0.4.0.tar.gz", + "https://github.com/bazelbuild/rules_pkg/releases/download/0.4.0/rules_pkg-0.4.0.tar.gz", + ], ) load("@rules_pkg//:deps.bzl", "rules_pkg_dependencies") diff --git a/backend/api/admin/src/test/java/co/airy/core/api/admin/WebhooksControllerTest.java b/backend/api/admin/src/test/java/co/airy/core/api/admin/WebhooksControllerTest.java index 8f40b27cd1..67d62ec387 100644 --- a/backend/api/admin/src/test/java/co/airy/core/api/admin/WebhooksControllerTest.java +++ b/backend/api/admin/src/test/java/co/airy/core/api/admin/WebhooksControllerTest.java @@ -27,9 +27,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = AirySpringBootApplication.class) -@TestPropertySource(value = "classpath:test.properties", properties = { - "allowedOrigins=origin1,origin2", -}) +@TestPropertySource(value = "classpath:test.properties") @AutoConfigureMockMvc @ExtendWith(SpringExtension.class) public class WebhooksControllerTest { diff --git a/backend/avro/http-log.avsc b/backend/avro/http-log.avsc index 56c2f81d49..8ce1a69e47 100644 --- a/backend/avro/http-log.avsc +++ b/backend/avro/http-log.avsc @@ -5,6 +5,9 @@ "fields": [ {"name": "uri", "type": "string"}, {"name": "body", "type": ["null", "string"], "default": null}, + {"name": "userId", "type": ["null", "string"], "default": null}, + {"name": "userName", "type": ["null", "string"], "default": null}, + {"name": "userAvatar", "type": ["null", "string"], "default": null}, { "name": "headers", "type": { diff --git a/backend/media/src/test/java/co/airy/core/media/MessagesTest.java b/backend/media/src/test/java/co/airy/core/media/MessagesTest.java index ec9229572c..c6a9c00534 100644 --- a/backend/media/src/test/java/co/airy/core/media/MessagesTest.java +++ b/backend/media/src/test/java/co/airy/core/media/MessagesTest.java @@ -92,7 +92,7 @@ static void afterAll() throws Exception { @BeforeEach void beforeEach() throws Exception { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); mediaUpload = new MediaUpload(amazonS3, bucket, path); retryOnException(() -> assertEquals(stores.getStreamState(), RUNNING), "Failed to reach RUNNING state."); } diff --git a/backend/media/src/test/java/co/airy/core/media/MetadataTest.java b/backend/media/src/test/java/co/airy/core/media/MetadataTest.java index aeef4a2751..60bc60a316 100644 --- a/backend/media/src/test/java/co/airy/core/media/MetadataTest.java +++ b/backend/media/src/test/java/co/airy/core/media/MetadataTest.java @@ -86,7 +86,7 @@ static void afterAll() throws Exception { @BeforeEach void beforeEach() throws Exception { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); mediaUpload = new MediaUpload(amazonS3, bucket, path); retryOnException(() -> assertEquals(stores.getStreamState(), RUNNING), "Failed to reach RUNNING state."); } diff --git a/backend/sources/chat-plugin/BUILD b/backend/sources/chat-plugin/BUILD index 493a811573..e8439530f3 100644 --- a/backend/sources/chat-plugin/BUILD +++ b/backend/sources/chat-plugin/BUILD @@ -13,7 +13,7 @@ app_deps = [ "//backend/model/message", "//lib/java/uuid", "//lib/java/date", - "//lib/java/spring/web:spring-web", + # "//lib/java/spring/web:spring-web", "//lib/java/spring/kafka/core:spring-kafka-core", "//lib/java/spring/kafka/streams:spring-kafka-streams", "@maven//:javax_xml_bind_jaxb_api", diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/ChatController.java b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/ChatController.java index f401e1d3ef..2dfe02db57 100644 --- a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/ChatController.java +++ b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/ChatController.java @@ -6,11 +6,11 @@ import co.airy.core.chat_plugin.config.Jwt; import co.airy.core.chat_plugin.payload.AuthenticationRequestPayload; import co.airy.core.chat_plugin.payload.AuthenticationResponsePayload; +import co.airy.core.chat_plugin.payload.RequestErrorResponsePayload; import co.airy.core.chat_plugin.payload.ResumeTokenRequestPayload; import co.airy.core.chat_plugin.payload.ResumeTokenResponsePayload; import co.airy.core.chat_plugin.payload.SendMessageRequestPayload; import co.airy.model.message.dto.MessageResponsePayload; -import co.airy.spring.web.payload.RequestErrorResponsePayload; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Value; @@ -30,7 +30,7 @@ import java.util.UUID; import java.util.stream.Collectors; -import static co.airy.spring.web.Headers.getAuthToken; +import static co.airy.core.chat_plugin.Headers.getAuthToken; import static org.springframework.http.HttpStatus.NOT_FOUND; @RestController diff --git a/lib/java/spring/web/src/main/java/co/airy/spring/web/Headers.java b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/Headers.java similarity index 93% rename from lib/java/spring/web/src/main/java/co/airy/spring/web/Headers.java rename to backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/Headers.java index 24410a3f93..fa9714883c 100644 --- a/lib/java/spring/web/src/main/java/co/airy/spring/web/Headers.java +++ b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/Headers.java @@ -1,4 +1,4 @@ -package co.airy.spring.web; +package co.airy.core.chat_plugin; import org.springframework.http.HttpHeaders; diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/AuthConfig.java b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/AuthConfig.java index e1e3c865b6..2448557e5f 100644 --- a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/AuthConfig.java +++ b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/AuthConfig.java @@ -13,6 +13,7 @@ import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import java.util.Arrays; import java.util.List; @Configuration @@ -49,7 +50,7 @@ CorsConfigurationSource corsConfigurationSource(final Environment environment) { final String allowed = environment.getProperty("allowedOrigins", ""); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); - config.addAllowedOriginPattern(allowed); + config.setAllowedOriginPatterns(Arrays.asList(allowed.split(","))); config.addAllowedHeader("*"); config.setAllowedMethods(List.of("GET", "POST")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/JwtAuthenticationFilter.java b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/JwtAuthenticationFilter.java index fac6b7b66b..e227165ae1 100644 --- a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/JwtAuthenticationFilter.java +++ b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/JwtAuthenticationFilter.java @@ -13,7 +13,7 @@ import java.io.IOException; import java.util.List; -import static co.airy.spring.web.Headers.getAuthToken; +import static co.airy.core.chat_plugin.Headers.getAuthToken; public class JwtAuthenticationFilter extends BasicAuthenticationFilter { private final Jwt jwt; diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/RequestErrorResponsePayload.java b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/RequestErrorResponsePayload.java new file mode 100644 index 0000000000..200f931ba5 --- /dev/null +++ b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/RequestErrorResponsePayload.java @@ -0,0 +1,12 @@ +package co.airy.core.chat_plugin.payload; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.io.Serializable; + +@Data +@AllArgsConstructor +public class RequestErrorResponsePayload implements Serializable { + private String message; +} diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/Connector.java b/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/Connector.java index 2617c7b6ba..dddeb32042 100644 --- a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/Connector.java +++ b/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/Connector.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import static co.airy.model.message.MessageRepository.updateDeliveryState; import static co.airy.model.metadata.MetadataKeys.ConversationKeys; @@ -72,7 +73,7 @@ public boolean needsMetadataFetched(Conversation conversation) { } public List> fetchMetadata(String conversationId, Conversation conversation) { - final UserProfile profile = getProfile(conversation); + final UserProfile profile = Optional.ofNullable(getProfile(conversation)).orElse(new UserProfile()); final List> recordList = new ArrayList<>(); diff --git a/backend/sources/facebook/connector/src/test/java/co/airy/core/sources/facebook/FetchMetadataTest.java b/backend/sources/facebook/connector/src/test/java/co/airy/core/sources/facebook/FetchMetadataTest.java index 52a4a63247..b39da7f3e2 100644 --- a/backend/sources/facebook/connector/src/test/java/co/airy/core/sources/facebook/FetchMetadataTest.java +++ b/backend/sources/facebook/connector/src/test/java/co/airy/core/sources/facebook/FetchMetadataTest.java @@ -78,7 +78,7 @@ static void afterAll() throws Exception { @BeforeEach void beforeEach() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); } @Test diff --git a/backend/sources/facebook/connector/src/test/java/co/airy/core/sources/facebook/SendMessageTest.java b/backend/sources/facebook/connector/src/test/java/co/airy/core/sources/facebook/SendMessageTest.java index 8f48e3d1d2..d9419fe145 100644 --- a/backend/sources/facebook/connector/src/test/java/co/airy/core/sources/facebook/SendMessageTest.java +++ b/backend/sources/facebook/connector/src/test/java/co/airy/core/sources/facebook/SendMessageTest.java @@ -83,7 +83,7 @@ static void afterAll() throws Exception { @BeforeEach void beforeEach() throws InterruptedException { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); retryOnException(() -> assertEquals(stores.getStreamState(), RUNNING), "Failed to reach RUNNING state."); } @@ -141,7 +141,7 @@ void canSendMessageViaTheFacebookApi() throws Exception { .setIsFromContact(false) .build()) ); - + retryOnException(() -> { final SendMessagePayload sendMessagePayload = payloadCaptor.getValue(); assertThat(sendMessagePayload.getRecipient().getId(), equalTo(sourceConversationId)); diff --git a/backend/sources/google/connector/src/test/java/co/airy/core/sources/google/SendMessageTest.java b/backend/sources/google/connector/src/test/java/co/airy/core/sources/google/SendMessageTest.java index 9048aa530e..3b3868033a 100644 --- a/backend/sources/google/connector/src/test/java/co/airy/core/sources/google/SendMessageTest.java +++ b/backend/sources/google/connector/src/test/java/co/airy/core/sources/google/SendMessageTest.java @@ -74,7 +74,7 @@ static void afterAll() throws Exception { @BeforeEach void beforeEach() throws InterruptedException { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); retryOnException(() -> assertEquals(stores.getStreamState(), RUNNING), "Failed to reach RUNNING state."); } diff --git a/backend/sources/twilio/connector/src/test/java/co/airy/core/sources/twilio/SendMessageTest.java b/backend/sources/twilio/connector/src/test/java/co/airy/core/sources/twilio/SendMessageTest.java index 074d7f67fa..d26f1d5355 100644 --- a/backend/sources/twilio/connector/src/test/java/co/airy/core/sources/twilio/SendMessageTest.java +++ b/backend/sources/twilio/connector/src/test/java/co/airy/core/sources/twilio/SendMessageTest.java @@ -76,7 +76,7 @@ static void afterAll() throws Exception { @BeforeEach void beforeEach() throws InterruptedException { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); retryOnException(() -> assertEquals(stores.getStreamState(), RUNNING), "Failed to reach RUNNING state."); } diff --git a/backend/webhook/consumer/main.go b/backend/webhook/consumer/main.go index d0c65203a5..b0e63996e7 100644 --- a/backend/webhook/consumer/main.go +++ b/backend/webhook/consumer/main.go @@ -27,7 +27,8 @@ func main() { Brokers: os.Getenv("KAFKA_BROKERS"), SchemaRegistryURL: os.Getenv("KAFKA_SCHEMA_REGISTRY_URL"), Group: "WebhookConsumer", - Topics: "application.communication.webhooks", + Topic: "application.communication.webhooks", + Partitions: 10, } wg.Add(1) diff --git a/backend/webhook/consumer/pkg/worker/consumer.go b/backend/webhook/consumer/pkg/worker/consumer.go index bc4e4be13f..a790e18ab7 100644 --- a/backend/webhook/consumer/pkg/worker/consumer.go +++ b/backend/webhook/consumer/pkg/worker/consumer.go @@ -16,10 +16,12 @@ type Consumer struct { ready chan bool webhookConfigStream chan string schemaRegistryClient *srclient.SchemaRegistryClient + kafkaConsumerConfig KafkaConsumerConfig } type KafkaConsumerConfig struct { - Brokers, SchemaRegistryURL, Topics, Group string + Brokers, SchemaRegistryURL, Topic, Group string + Partitions int } func StartKafkaConsumer( @@ -38,6 +40,7 @@ func StartKafkaConsumer( ready: make(chan bool), webhookConfigStream: webhookConfigStream, schemaRegistryClient: schemaRegistryClient, + kafkaConsumerConfig: kafkaConsumerConfig, } client, err := sarama.NewConsumerGroup(strings.Split(kafkaConsumerConfig.Brokers, ","), kafkaConsumerConfig.Group, config) @@ -47,7 +50,7 @@ func StartKafkaConsumer( go func() { for { - if err := client.Consume(ctx, strings.Split(kafkaConsumerConfig.Topics, ","), &consumer); err != nil { + if err := client.Consume(ctx, strings.Split(kafkaConsumerConfig.Topic, ","), &consumer); err != nil { log.Panicf("Error from consumer: %v", err) } if ctx.Err() != nil { @@ -67,7 +70,10 @@ func StartKafkaConsumer( } } -func (consumer *Consumer) Setup(sarama.ConsumerGroupSession) error { +func (consumer *Consumer) Setup(session sarama.ConsumerGroupSession) error { + for p := 0; p < consumer.kafkaConsumerConfig.Partitions; p++ { + session.ResetOffset(consumer.kafkaConsumerConfig.Topic, int32(p), 0, "") + } close(consumer.ready) return nil } diff --git a/backend/webhook/consumer/pkg/worker/worker.go b/backend/webhook/consumer/pkg/worker/worker.go index 2cea9585db..ecd8be1f72 100644 --- a/backend/webhook/consumer/pkg/worker/worker.go +++ b/backend/webhook/consumer/pkg/worker/worker.go @@ -79,27 +79,31 @@ func (w *Worker) Run(ctx context.Context, wg *sync.WaitGroup) { } continue } + w.processJob(ctx, id, event) + } + } +} - for { - select { - case <-ctx.Done(): - log.Println("terminating worker: context cancelled") - return - default: - err = w.HandleEvent(event) - if err != nil { - w.logError(err) - time.Sleep(w.backoff.Duration()) - continue - } - err = w.beanstalk.Delete(id) - if err != nil { - w.logError(err) - } - w.backoff.Reset() - break - } +func (w *Worker) processJob(ctx context.Context, jobId uint64, event []byte) { + for { + select { + case <-ctx.Done(): + log.Println("terminating worker: context cancelled") + return + default: + err := w.HandleEvent(event) + if err != nil { + w.logError(err) + time.Sleep(w.backoff.Duration()) + continue } + + err = w.beanstalk.Delete(jobId) + if err != nil { + w.logError(err) + } + w.backoff.Reset() + return } } } @@ -107,16 +111,19 @@ func (w *Worker) Run(ctx context.Context, wg *sync.WaitGroup) { func (w *Worker) updateWebhookConfig(ctx context.Context, wg *sync.WaitGroup, webhookConfigStream chan string) { defer wg.Done() log.Println("Started updateWebhookConfig routine") - select { - case <-ctx.Done(): - log.Println("terminating updateWebhookConfig: context cancelled") - case config := <-webhookConfigStream: - var webhookConfig = webhookConfig{} - if err := json.Unmarshal([]byte(config), &webhookConfig); err != nil { - log.Fatal(err) + for { + select { + case <-ctx.Done(): + log.Println("terminating updateWebhookConfig: context cancelled") + return + case config := <-webhookConfigStream: + var webhookConfig = webhookConfig{} + if err := json.Unmarshal([]byte(config), &webhookConfig); err != nil { + log.Fatal(err) + } + w.endpoint = webhookConfig.Endpoint + w.customHeader = webhookConfig.Headers["map"] } - w.endpoint = webhookConfig.Endpoint - w.customHeader = webhookConfig.Headers["map"] } } diff --git a/backend/webhook/publisher/src/test/java/co/airy/core/webhook/publisher/PublisherTest.java b/backend/webhook/publisher/src/test/java/co/airy/core/webhook/publisher/PublisherTest.java index 4119ddbb68..a43f77ac66 100644 --- a/backend/webhook/publisher/src/test/java/co/airy/core/webhook/publisher/PublisherTest.java +++ b/backend/webhook/publisher/src/test/java/co/airy/core/webhook/publisher/PublisherTest.java @@ -75,7 +75,7 @@ static void afterAll() throws Exception { @BeforeEach void beforeEach() throws InterruptedException { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); retryOnException(() -> assertEquals(publisher.getStreamState(), RUNNING), "Failed to reach RUNNING state."); } diff --git a/bazel.tsconfig.json b/bazel.tsconfig.json index 940fdf242b..d2ebbe67af 100644 --- a/bazel.tsconfig.json +++ b/bazel.tsconfig.json @@ -12,7 +12,8 @@ "allowSyntheticDefaultImports": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, - "isolatedModules": true, + "preserveConstEnums": true, + "isolatedModules": false, "sourceMap": true, "jsx": "react", "paths": { diff --git a/cli/pkg/providers/aws/aws.go b/cli/pkg/providers/aws/aws.go index 9859bc2ea7..3ea7fd9e54 100644 --- a/cli/pkg/providers/aws/aws.go +++ b/cli/pkg/providers/aws/aws.go @@ -43,7 +43,7 @@ func New(w io.Writer) *provider { } func (p *provider) GetHelmOverrides() []string { - return []string{"--set", "global.ngrokEnabled=false"} + return []string{"--set", "global.ngrokEnabled=false", "--set", "global.loadbalancer.annotations={service.beta.kubernetes.io/aws-load-balancer-type: nlb}"} } func (p *provider) PostInstallation(namespace string) error { diff --git a/docs/docs/changelog.md b/docs/docs/changelog.md index 8a81ffaff0..086bd72977 100644 --- a/docs/docs/changelog.md +++ b/docs/docs/changelog.md @@ -3,6 +3,54 @@ title: Changelog sidebar_label: πŸ“ Changelog --- +## 0.21.0 + +#### πŸš€ Features + +- [[#1519](https://github.com/airyhq/airy/issues/1519)] Implement auth UI behavior [[#1737](https://github.com/airyhq/airy/pull/1737)] +- [[#1721](https://github.com/airyhq/airy/issues/1721)] Webhook consumer start consuming from beginning[[#1722](https://github.com/airyhq/airy/pull/1722)] +- [[#1713](https://github.com/airyhq/airy/issues/1713)] Fix crash conversation from search [[#1720](https://github.com/airyhq/airy/pull/1720)] +- [[#1707](https://github.com/airyhq/airy/issues/1707)] Attach user profile to application logs [[#1718](https://github.com/airyhq/airy/pull/1718)] +- [[#1714](https://github.com/airyhq/airy/issues/1714)] Webhook config updates only once [[#1715](https://github.com/airyhq/airy/pull/1715)] + +#### πŸ› Bug Fixes + +- [[#1763](https://github.com/airyhq/airy/issues/1763)] Fixed tag string length in contactInfo [[#1766](https://github.com/airyhq/airy/pull/1766)] +- [[#1736](https://github.com/airyhq/airy/issues/1736)] improve render library for facebook [[#1756](https://github.com/airyhq/airy/pull/1756)] +- [[#1748](https://github.com/airyhq/airy/issues/1748)] Add missing default redirect uri [[#1749](https://github.com/airyhq/airy/pull/1749)] +- [[#1729](https://github.com/airyhq/airy/issues/1729)] Fix lastMessageIcon [[#1735](https://github.com/airyhq/airy/pull/1735)] +- [[#1726](https://github.com/airyhq/airy/issues/1726)] Customize Start a New Conversation button [[#1731](https://github.com/airyhq/airy/pull/1731)] +- [[#1696](https://github.com/airyhq/airy/issues/1696)] Fix backgroundColor accountName [[#1728](https://github.com/airyhq/airy/pull/1728)] +- [[#1694](https://github.com/airyhq/airy/issues/1694)] Adding icons for lastMessage [[#1704](https://github.com/airyhq/airy/pull/1704)] +- [[#1706](https://github.com/airyhq/airy/issues/1706)] Fixed gap between conversationList and conversationListHeader [[#1717](https://github.com/airyhq/airy/pull/1717)] +- [[#1558](https://github.com/airyhq/airy/issues/1558)] Enable loadbalancer annotations [[#1683](https://github.com/airyhq/airy/pull/1683)] +- [[#1695](https://github.com/airyhq/airy/issues/1695)] Twilio SMS does not display the channelSourceId correctly [[#1703](https://github.com/airyhq/airy/pull/1703)] + +#### πŸ“š Documentation + +- [[#1745](https://github.com/airyhq/airy/issues/1745)] Update chat plugin customization docs [[#1754](https://github.com/airyhq/airy/pull/1754)] + +#### 🧰 Maintenance + +- [[#1486](https://github.com/airyhq/airy/issues/1486)] upgrade react [[#1757](https://github.com/airyhq/airy/pull/1757)] +- [[#1486](https://github.com/airyhq/airy/issues/1486)] Upgrade rules\_pkg and rules\_docker [[#1755](https://github.com/airyhq/airy/pull/1755)] +- Bump react-markdown from 6.0.1 to 6.0.2 [[#1742](https://github.com/airyhq/airy/pull/1742)] +- Bump typescript from 3.7.4 to 4.2.3 [[#1189](https://github.com/airyhq/airy/pull/1189)] +- [[#1486](https://github.com/airyhq/airy/issues/1486)] Upgrade kafka images [[#1691](https://github.com/airyhq/airy/pull/1691)] +- Bump core-js from 3.11.1 to 3.12.0 [[#1739](https://github.com/airyhq/airy/pull/1739)] +- Bump @bazel/bazelisk from 1.8.0 to 1.8.1 [[#1740](https://github.com/airyhq/airy/pull/1740)] +- Bump webpack-cli from 4.6.0 to 4.7.0 [[#1741](https://github.com/airyhq/airy/pull/1741)] +- Bump @types/node from 15.0.1 to 15.0.2 [[#1723](https://github.com/airyhq/airy/pull/1723)] +- Bump @typescript-eslint/parser from 4.22.0 to 4.22.1 [[#1724](https://github.com/airyhq/airy/pull/1724)] + +#### 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.21.0/darwin/amd64/airy) +[Linux](https://airy-core-binaries.s3.amazonaws.com/0.21.0/linux/amd64/airy) +[Windows](https://airy-core-binaries.s3.amazonaws.com/0.21.0/windows/amd64/airy.exe) + ## 0.20.0 #### Changes @@ -11,15 +59,17 @@ sidebar_label: πŸ“ Changelog #### πŸš€ Features +- [[#1642](https://github.com/airyhq/airy/issues/1642)] Add dev cli script [[#1690](https://github.com/airyhq/airy/pull/1690)] - [[#1518](https://github.com/airyhq/airy/issues/1518)] Add OIDC authentication backend [[#1623](https://github.com/airyhq/airy/pull/1623)] - [[#1505](https://github.com/airyhq/airy/issues/1505)] Rewrite Webhook queue with Beanstalkd [[#1536](https://github.com/airyhq/airy/pull/1536)] -- [[#1687](https://github.com/airyhq/airy/issues/1687)] Kafka Prometheus exporter error on… [[#1688](https://github.com/airyhq/airy/pull/1688)] +- [[#1687](https://github.com/airyhq/airy/issues/1687)] Kafka Prometheus exporter error [[#1688](https://github.com/airyhq/airy/pull/1688)] - [[#1615](https://github.com/airyhq/airy/issues/1615)] Expose tag events via websocket [[#1624](https://github.com/airyhq/airy/pull/1624)] - [[#1495](https://github.com/airyhq/airy/issues/1495)] Add kafka Prometheus exporter [[#1668](https://github.com/airyhq/airy/pull/1668)] - [[#1525](https://github.com/airyhq/airy/issues/1525)] Update quick filter [[#1660](https://github.com/airyhq/airy/pull/1660)] #### πŸ› Bug Fixes +- [[#1642](https://github.com/airyhq/airy/issues/1642)] Fix flag [[#1712](https://github.com/airyhq/airy/pull/1712)] - [[#1698](https://github.com/airyhq/airy/issues/1698)] Webhook config error [[#1699](https://github.com/airyhq/airy/pull/1699)] - [[#1689](https://github.com/airyhq/airy/issues/1689)] Bug: Conversation Counter: Optimize Filter Use [[#1697](https://github.com/airyhq/airy/pull/1697)] - [[#1665](https://github.com/airyhq/airy/issues/1665)] Fix chatplugin reconnection problem [[#1682](https://github.com/airyhq/airy/pull/1682)] @@ -50,9 +100,9 @@ sidebar_label: πŸ“ Changelog You can download the Airy CLI for your operating system from the following links: -[MacOS](https://airy-core-binaries.s3.amazonaws.com/0.19.1/darwin/amd64/airy) -[Linux](https://airy-core-binaries.s3.amazonaws.com/0.19.1/linux/amd64/airy) -[Windows](https://airy-core-binaries.s3.amazonaws.com/0.19.1/windows/amd64/airy.exe) +[MacOS](https://airy-core-binaries.s3.amazonaws.com/0.20.0/darwin/amd64/airy) +[Linux](https://airy-core-binaries.s3.amazonaws.com/0.20.0/linux/amd64/airy) +[Windows](https://airy-core-binaries.s3.amazonaws.com/0.20.0/windows/amd64/airy.exe) ## 0.19.0 diff --git a/docs/docs/getting-started/installation/aws.md b/docs/docs/getting-started/installation/aws.md index e73573da57..6a487327a5 100644 --- a/docs/docs/getting-started/installation/aws.md +++ b/docs/docs/getting-started/installation/aws.md @@ -176,13 +176,19 @@ Locate and set your KUBECONFIG file: export KUBECONFIG="PATH/TO/DIR/kube.conf" ``` -Modify the existing ingress service to reconfigure the AWS LoadBalancer. +Modify the existing ingress service to reconfigure the AWS LoadBalancer: ```sh -kubectl --kubeconfig ${KUBECONFIG} -n kube-system annotate service traefik "service.beta.kubernetes.io/aws-load-balancer-backend-protocol=http" -ARN="Your-unique-ACM-ARN" kubectl --kubeconfig ${KUBECONFIG} -n kube-system annotate service traefik "service.beta.kubernetes.io/aws-load-balancer-ssl-cert=${ARN}" -kubectl --kubeconfig ${KUBECONFIG} -n kube-system annotate service traefik "service.beta.kubernetes.io/aws-load-balancer-ssl-ports=https" -kubectl --kubeconfig ${KUBECONFIG} -n kube-system patch service traefik --patch '{"spec": { "type": "LoadBalancer", "ports": [ { "name": "https", "port": 443, "protocol": "TCP", "targetPort": 80 } ] } }' +export ARN="Your-unique-ACM-ARN" +kubectl -n kube-system annotate service traefik "service.beta.kubernetes.io/aws-load-balancer-ssl-ports=443" "service.beta.kubernetes.io/aws-load-balancer-ssl-cert=${ARN}" +kubectl -n kube-system patch service traefik --patch '{"spec": { "ports": [ { "name": "https", "port": 443, "protocol": "TCP", "targetPort": 80 } ] } }' +``` + +Update the `hostnames` configMap with the new https endpoint: + +```sh +export ELB=$(kubectl -n kube-system get service traefik -o jsonpath='{.status.loadBalancer.ingress[0].hostname}') +kubectl patch configmap hostnames --patch "{\"data\": { \"HOST\": \"https://${ELB}\"} }" ``` ### Print HTTPS endpoint diff --git a/docs/docs/sources/chatplugin/customization.md b/docs/docs/sources/chatplugin/customization.md index c8bff72d71..980b82d21b 100644 --- a/docs/docs/sources/chatplugin/customization.md +++ b/docs/docs/sources/chatplugin/customization.md @@ -15,7 +15,7 @@ We support two ways of customizing your Airy Chat Plugin. For most use cases the After setting up your [first source](/sources/chatplugin/quickstart#step-1-set-up-your-first-source) you can customize your Airy Chat Plugin to your needs. -On your instance's [Airy Core UI](http://airy.core/ui/channels), click on the button displaying a cross icon next to the Airy Live Chat channel. +On your instance's [Airy Core UI](http://airy.core/ui/channels), click on the button displaying a + icon next to the Airy Live Chat channel. Basic Customization Example
@@ -27,21 +27,22 @@ Then click on the **edit** of your source:

-Switch to the **Install & Customize** register and start customizing your Airy Chat Plugin to your needs. +Switch to the **Install & Customize** tab and start customizing your Airy Chat Plugin to your needs. Basic Customization Example
If you are happy with your customization, copy it and add this code inside the tag ``. -| Option name | Option description | -| ---------------- | -------------------------------------------------------------------------------------------- | -| Header text | Set the header text of your Airy Chat Plugin | -| Chat Plugin Icon | Set your company icon which appears on the button that opens and closes the Airy Chat Plugin | -| Input Icon | Set your icon as `sendButton` and replace the default paperplane | -| Primary Color | Set your primary color as the topbar, border of `textArea` or text color of `buttons` | -| Accent Color | Set your accent color as the `sendButton` | -| Background Color | Set the background color of the entire Airy Chat Plugin | +| Option name | Option description | +| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| Header text | Set the header text of your Airy Chat Plugin | +| Start new Conversation text | Set the Start new Conversation text button of your Airy Chat Plugin | +| Chat Plugin Icon | Set your company icon which appears on the button that opens and closes the Airy Chat Plugin | +| Input Icon | Set your icon as `sendButton` and replace the default paperplane | +| Primary Color | Set your primary color as the topbar, border of `textArea`, Start new Conversation button text and border color, or text color of `buttons` | +| Accent Color | Set your accent color as the `sendButton` | +| Background Color | Set the background color of the entire Airy Chat Plugin | **Sample** @@ -51,7 +52,7 @@ If you are happy with your customization, copy it and add this code inside the t ### Advanced customization -Installing the Airy Chat Plugin as a library allows you to customize the interface as much as you want using **["Render Props"](https://reactjs.org/docs/render-props.html)**. +Installing the Airy Chat Plugin as a library allows you to customize the interface as much as you want using **[Render Props](https://reactjs.org/docs/render-props.html)**. The Airy Chat Plugin provides you with four Render Props. All Render Props parameters are optional. If you omit all of them, the Airy Chat Plugin will render the default styling and behavior. Each Render Prop accepts a set of parameters that allows you to customize the interface or the behavior of the Airy Chat Plugin. diff --git a/frontend/chat-plugin/src/components/chat/index.tsx b/frontend/chat-plugin/src/components/chat/index.tsx index 8e91cbc561..1398627f2e 100644 --- a/frontend/chat-plugin/src/components/chat/index.tsx +++ b/frontend/chat-plugin/src/components/chat/index.tsx @@ -188,7 +188,10 @@ const Chat = (props: Props) => { setNewConversation={setNewConversation} /> ) : ( - + ); const bubble = props.bubbleProp diff --git a/frontend/chat-plugin/src/components/newConversation/index.module.scss b/frontend/chat-plugin/src/components/newConversation/index.module.scss index 0c51690daf..206a576b56 100644 --- a/frontend/chat-plugin/src/components/newConversation/index.module.scss +++ b/frontend/chat-plugin/src/components/newConversation/index.module.scss @@ -5,7 +5,6 @@ .newConversation { display: flex; justify-content: center; - padding-left: 20px; margin: 0; font-family: 'Lato', sans-serif; color: var(--color-airy-blue); @@ -19,12 +18,32 @@ margin: 0; } -.newConversationLink { +.startConversationContainer { display: flex; justify-content: center; + width: 100%; margin-bottom: 20px; +} + +.newConversationLink, +.newConversationLink:hover, +.newConversationLink:active { + text-decoration: none; + color: var(--color-airy-blue); font-family: 'Lato', sans-serif; + font-size: 16px; +} + +.newConversationButton { font-weight: 600; - font-size: 20px; - color: #1578d4; + line-height: 16px; + height: 40px; + background-color: white; + border-radius: 8px; + border: 1px solid var(--color-airy-blue); + cursor: pointer; + padding: 8px 10px; + &:hover { + background-color: rgba(128, 128, 128, 0.173); + } } diff --git a/frontend/chat-plugin/src/components/newConversation/index.tsx b/frontend/chat-plugin/src/components/newConversation/index.tsx index 52d25883e2..3b4072876a 100644 --- a/frontend/chat-plugin/src/components/newConversation/index.tsx +++ b/frontend/chat-plugin/src/components/newConversation/index.tsx @@ -4,24 +4,26 @@ import {cyChatPluginStartNewConversation} from 'chat-plugin-handles'; type newConversationProps = { reAuthenticate: () => void; + startNewConversationText: string; }; const NewConversation = (props: newConversationProps) => { return (
-

Your conversation has ended. Thank you for

{' '} -

chatting with us today.

+

Your conversation has ended.

- ); diff --git a/frontend/chat-plugin/src/config.ts b/frontend/chat-plugin/src/config.ts index 535eb97c0f..bf34323400 100644 --- a/frontend/chat-plugin/src/config.ts +++ b/frontend/chat-plugin/src/config.ts @@ -7,6 +7,7 @@ export type RenderProp = (ctrl?: RenderCtrl) => JSX.Element; export type Config = { welcomeMessage?: {}; headerText?: string; + startNewConversationText?: string; headerTextColor?: string; backgroundColor?: string; primaryColor?: string; diff --git a/frontend/ui/src/App.tsx b/frontend/ui/src/App.tsx index 4b4c51850a..1db0540090 100644 --- a/frontend/ui/src/App.tsx +++ b/frontend/ui/src/App.tsx @@ -15,6 +15,7 @@ import {StateModel} from './reducers'; import {INBOX_ROUTE, CHANNELS_ROUTE, ROOT_ROUTE, TAGS_ROUTE} from './routes/routes'; import styles from './App.module.scss'; +import {getClientConfig} from './actions/config'; const mapStateToProps = (state: StateModel, ownProps: RouteComponentProps) => { return { @@ -25,6 +26,7 @@ const mapStateToProps = (state: StateModel, ownProps: RouteComponentProps) => { const mapDispatchToProps = { fakeSettingsAPICall, + getClientConfig, }; const connector = connect(mapStateToProps, mapDispatchToProps); @@ -36,14 +38,10 @@ class App extends Component & RouteComponentPro componentDidMount() { this.props.fakeSettingsAPICall(); + this.props.getClientConfig(); } render() { - /* TODO Add this logic back in: https://github.com/airyhq/airy/issues/1519 - if (!this.props.user.id) { - return ; - }*/ - return (
diff --git a/frontend/ui/src/InitializeAiryApi.ts b/frontend/ui/src/InitializeAiryApi.ts index c236bf9c69..8205db12f1 100644 --- a/frontend/ui/src/InitializeAiryApi.ts +++ b/frontend/ui/src/InitializeAiryApi.ts @@ -1,6 +1,9 @@ import {HttpClient} from 'httpclient'; import {env} from './env'; -export const HttpClientInstance = new HttpClient(env.API_HOST, error => { +export const HttpClientInstance = new HttpClient(env.API_HOST, (error, loginUrl) => { console.error(error); + if (location.href != loginUrl) { + location.replace(loginUrl); + } }); diff --git a/frontend/ui/src/actions/conversationsFilter/index.ts b/frontend/ui/src/actions/conversationsFilter/index.ts index c244b7cd1e..29a7688ecd 100644 --- a/frontend/ui/src/actions/conversationsFilter/index.ts +++ b/frontend/ui/src/actions/conversationsFilter/index.ts @@ -99,7 +99,7 @@ const filterToLuceneSyntax = (filter: ConversationFilter): string | null => { filterQuery.push('unread_count:0'); } if (filter.displayName) { - filterQuery.push('display_name:*' + filter.displayName + '*'); + filterQuery.push('display_name=*' + filter.displayName + '*'); } if (filter.byTags && filter.byTags.length > 0) { filterQuery.push('tag_ids:(' + filter.byTags.join(' AND ') + ')'); diff --git a/frontend/ui/src/components/IconChannel/index.tsx b/frontend/ui/src/components/IconChannel/index.tsx index 09a62639fe..db9cdec006 100644 --- a/frontend/ui/src/components/IconChannel/index.tsx +++ b/frontend/ui/src/components/IconChannel/index.tsx @@ -74,12 +74,17 @@ const IconChannel: React.FC = ({ const channelInfo = SOURCE_INFO[channel.source]; const fbFallback = SOURCE_INFO['facebook']; + const isFromTwilioSource = channel.source === 'twilio.sms' || channel.source === 'twilio.whatsapp'; + + const ChannelName = () => { + return

{channel.metadata?.name || (isFromTwilioSource ? channel.sourceChannelId : channel.source)}

; + }; if (icon && showName) { return (
{channelInfo.icon()} -

{channel.metadata?.name}

+
); } @@ -88,7 +93,7 @@ const IconChannel: React.FC = ({ return (
{channelInfo.avatar()} -

{channel.metadata?.name}

+
); } diff --git a/frontend/ui/src/components/TopBar/index.module.scss b/frontend/ui/src/components/TopBar/index.module.scss index 0ab8464b66..1721b68be4 100644 --- a/frontend/ui/src/components/TopBar/index.module.scss +++ b/frontend/ui/src/components/TopBar/index.module.scss @@ -47,7 +47,6 @@ .dropHint { width: 16px; - margin-bottom: 6px; svg { path { fill: var(--color-text-gray); @@ -80,7 +79,7 @@ align-items: center; } -.dropdown { +.dropdownContainer { position: absolute; background-color: white; border: 1px solid var(--color-light-gray); diff --git a/frontend/ui/src/components/TopBar/index.tsx b/frontend/ui/src/components/TopBar/index.tsx index a4b5464168..624951fb0c 100644 --- a/frontend/ui/src/components/TopBar/index.tsx +++ b/frontend/ui/src/components/TopBar/index.tsx @@ -4,21 +4,21 @@ import {withRouter, RouteComponentProps} from 'react-router-dom'; import {ListenOutsideClick} from 'components'; import {StateModel} from '../../reducers'; import {ReactComponent as ShortcutIcon} from 'assets/images/icons/shortcut.svg'; +import {ReactComponent as LogoutIcon} from 'assets/images/icons/sign-out.svg'; import {ReactComponent as AiryLogo} from 'assets/images/logo/airy_primary_rgb.svg'; import {ReactComponent as ChevronDownIcon} from 'assets/images/icons/chevron-down.svg'; import styles from './index.module.scss'; +import {env} from '../../env'; interface TopBarProps { isAdmin: boolean; } -const mapStateToProps = (state: StateModel) => { - return { - user: state.data.user, - firstName: state.data.user.firstName, - lastName: state.data.user.lastName, - }; -}; +const mapStateToProps = (state: StateModel) => ({ + user: state.data.user, +}); + +const logoutUrl = `${env.API_HOST}/logout`; const connector = connect(mapStateToProps); @@ -55,7 +55,7 @@ const TopBar = (props: TopBarProps & ConnectedProps & RouteCom {isFaqDropdownOn && ( - - -
-
-
-
{props.firstName + ' ' + props.lastName}
-
-
- - - -
-
- - {isAccountDropdownOn && ( - -
); diff --git a/frontend/ui/src/pages/Channels/ConnectedChannelsBySourceCard/index.tsx b/frontend/ui/src/pages/Channels/ConnectedChannelsBySourceCard/index.tsx index 76b7cb646e..74e84128de 100644 --- a/frontend/ui/src/pages/Channels/ConnectedChannelsBySourceCard/index.tsx +++ b/frontend/ui/src/pages/Channels/ConnectedChannelsBySourceCard/index.tsx @@ -36,7 +36,12 @@ const ConnectedChannelsBySourceCard = (props: ConnectedChannelsBySourceCardProps