diff --git a/.eslintrc b/.eslintrc index be1e58e484..6cba1cc7f8 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,30 +1,56 @@ { - "extends": ["eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/recommended"], - "plugins": ["react", "@typescript-eslint"], + "extends": [ + "eslint:recommended", + "plugin:react/recommended", + "plugin:@typescript-eslint/recommended" + ], + "plugins": [ + "react", + "@typescript-eslint" + ], "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaFeatures": { "jsx": true } }, - "env": { "browser": true, "es2021": true, "node": true }, - "rules": { + "camelcase": [ + "warn", + { + "properties": "never" + } + ], "react/prop-types": 0, - "@typescript-eslint/no-unused-vars": ["error", {"varsIgnorePattern": "_"}], + "@typescript-eslint/no-unused-vars": [ + "error", + { + "ignoreRestSiblings": true, + "varsIgnorePattern": "_" + } + ], "@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-empty-interface": 0, - "@typescript-eslint/ban-types": ["error", {"extendDefaults": true, "types": {"{}": false, "Object": false}}] + "@typescript-eslint/ban-types": [ + "error", + { + "extendDefaults": true, + "types": { + "{}": false, + "Object": false + } + } + ] }, "settings": { "react": { "version": "16.12" } } -} +} \ No newline at end of file diff --git a/BUILD b/BUILD index 74ee504d57..7e9e6434c6 100644 --- a/BUILD +++ b/BUILD @@ -173,6 +173,7 @@ exports_files( ], ) +# gazelle:ignore # gazelle:proto disable_global # gazelle:build_file_name BUILD # gazelle:prefix diff --git a/VERSION b/VERSION index ac39a106c4..78bc1abd14 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.0 +0.10.0 diff --git a/WORKSPACE b/WORKSPACE index f76e1f5099..d5f8dfbe44 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -7,12 +7,11 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") # Airy Bazel tools - git_repository( name = "com_github_airyhq_bazel_tools", - commit = "5f3c835f4320292e76ad1b23308ea3704a35bd64", + commit = "701e4bd1503efe7308bf057ce031a165346b4580", remote = "https://github.com/airyhq/bazel-tools.git", - shallow_since = "1612440527 +0100", + shallow_since = "1614011000 +0100", ) load("@com_github_airyhq_bazel_tools//:repositories.bzl", "airy_bazel_tools_dependencies", "airy_jvm_deps") @@ -212,14 +211,6 @@ yarn_install( yarn_lock = "//:yarn.lock", ) -load("@npm//:install_bazel_dependencies.bzl", "install_bazel_dependencies") - -install_bazel_dependencies() - -load("@npm_bazel_typescript//:index.bzl", "ts_setup_workspace") - -ts_setup_workspace() - ### Bazel tooling git_repository( diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/ChannelsController.java b/backend/api/admin/src/main/java/co/airy/core/api/admin/ChannelsController.java index 47a105c3c1..75efed3a34 100644 --- a/backend/api/admin/src/main/java/co/airy/core/api/admin/ChannelsController.java +++ b/backend/api/admin/src/main/java/co/airy/core/api/admin/ChannelsController.java @@ -62,16 +62,16 @@ ResponseEntity updateChannel(@RequestBody @Valid UpdateChannelRequestPayload return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new EmptyResponsePayload()); } - container.getMetadataMap(); + final MetadataMap metadataMap = container.getMetadataMap(); if (requestPayload.getName() != null) { - container.getMetadataMap().put(MetadataKeys.ChannelKeys.NAME, newChannelMetadata(channelId, MetadataKeys.ChannelKeys.NAME, requestPayload.getName())); + metadataMap.put(MetadataKeys.ChannelKeys.NAME, newChannelMetadata(channelId, MetadataKeys.ChannelKeys.NAME, requestPayload.getName())); } if (requestPayload.getImageUrl() != null) { - container.getMetadataMap().put(MetadataKeys.ChannelKeys.IMAGE_URL, newChannelMetadata(channelId, MetadataKeys.ChannelKeys.IMAGE_URL, requestPayload.getName())); + metadataMap.put(MetadataKeys.ChannelKeys.IMAGE_URL, newChannelMetadata(channelId, MetadataKeys.ChannelKeys.IMAGE_URL, requestPayload.getName())); } try { - stores.storeMetadataMap(container.getMetadataMap()); + stores.storeMetadataMap(metadataMap); return ResponseEntity.ok(fromChannelContainer(container)); } catch (Exception e) { return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build(); diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/MessagesController.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/MessagesController.java index 3b3274e383..3c5a038200 100644 --- a/backend/api/communication/src/main/java/co/airy/core/api/communication/MessagesController.java +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/MessagesController.java @@ -14,9 +14,7 @@ import javax.validation.Valid; import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; import static java.util.stream.Collectors.toList; diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/Stores.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/Stores.java index 50253128a5..efb19d2cd2 100644 --- a/backend/api/communication/src/main/java/co/airy/core/api/communication/Stores.java +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/Stores.java @@ -4,7 +4,6 @@ import co.airy.avro.communication.Message; import co.airy.avro.communication.Metadata; import co.airy.avro.communication.ReadReceipt; -import co.airy.avro.communication.SenderType; import co.airy.core.api.communication.dto.Conversation; import co.airy.core.api.communication.dto.CountAction; import co.airy.core.api.communication.dto.MessagesTreeSet; @@ -50,6 +49,7 @@ import java.util.Optional; import java.util.concurrent.ExecutionException; +import static co.airy.model.message.MessageRepository.isFromContact; import static co.airy.model.metadata.MetadataRepository.getId; import static co.airy.model.metadata.MetadataRepository.getSubject; import static co.airy.model.metadata.MetadataRepository.isChannelMetadata; @@ -61,11 +61,10 @@ @Component @RestController public class Stores implements HealthIndicator, ApplicationListener, DisposableBean { - private static final String appId = "api.CommunicationStoresTEST"; + private static final String appId = "api.CommunicationStores"; private final KafkaStreamsWrapper streams; private final KafkaProducer producer; - private final WebSocketController webSocketController; private final LuceneProvider luceneProvider; private final String messagesStore = "messages-store"; @@ -77,12 +76,10 @@ public class Stores implements HealthIndicator, ApplicationListener producer, - WebSocketController webSocketController, LuceneProvider luceneProvider ) { this.streams = streams; this.producer = producer; - this.webSocketController = webSocketController; this.luceneProvider = luceneProvider; } @@ -93,9 +90,7 @@ private void startStream() { final KStream messageStream = builder.stream(new ApplicationCommunicationMessages().name()); - final KTable channelTable = builder.stream(new ApplicationCommunicationChannels().name()) - .peek((channelId, channel) -> webSocketController.onChannelUpdate(channel)) - .toTable(); + final KTable channelTable = builder.table(new ApplicationCommunicationChannels().name()); // conversation/message/channel metadata keyed by conversation/message/channel id final KTable metadataTable = builder.table(applicationCommunicationMetadata) @@ -109,7 +104,7 @@ private void startStream() { // produce unread count metadata messageStream.selectKey((messageId, message) -> message.getConversationId()) - .peek((conversationId, message) -> webSocketController.onNewMessage(message)) + .filter((conversationId, message) -> isFromContact(message)) .mapValues(message -> CountAction.increment(message.getSentAt())) .merge(resetStream) .groupByKey() @@ -124,7 +119,7 @@ private void startStream() { } return unreadCountState; - }).toStream().peek(webSocketController::onUnreadCount) + }).toStream() .map((conversationId, unreadCountState) -> { final Metadata metadata = newConversationMetadata(conversationId, MetadataKeys.ConversationKeys.UNREAD_COUNT, unreadCountState.getUnreadCount().toString()); @@ -165,7 +160,7 @@ private void startStream() { aggregate.setLastMessageContainer(container); } - if (SenderType.SOURCE_CONTACT.equals(container.getMessage().getSenderType())) { + if (isFromContact(container.getMessage())) { aggregate.setSourceConversationId(container.getMessage().getSenderId()); } diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/WebSocketConfig.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/WebSocketConfig.java deleted file mode 100644 index 0797a9c9c9..0000000000 --- a/backend/api/communication/src/main/java/co/airy/core/api/communication/WebSocketConfig.java +++ /dev/null @@ -1,90 +0,0 @@ -package co.airy.core.api.communication; - -import co.airy.log.AiryLoggerFactory; -import co.airy.spring.jwt.Jwt; -import org.slf4j.Logger; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.simp.config.ChannelRegistration; -import org.springframework.messaging.simp.config.MessageBrokerRegistry; -import org.springframework.messaging.simp.stomp.StompCommand; -import org.springframework.messaging.simp.stomp.StompHeaderAccessor; -import org.springframework.messaging.support.ChannelInterceptor; -import org.springframework.messaging.support.MessageHeaderAccessor; -import org.springframework.scheduling.TaskScheduler; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; -import org.springframework.web.socket.config.annotation.StompEndpointRegistry; -import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; - -import java.util.List; - -@Configuration -@EnableWebSocketMessageBroker -public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { - private static final Logger log = AiryLoggerFactory.getLogger(WebSocketConfig.class); - private final Jwt jwt; - - public WebSocketConfig(Jwt jwt) { - this.jwt = jwt; - } - - @Override - public void configureMessageBroker(MessageBrokerRegistry config) { - config.enableSimpleBroker("/topic", "/queue") - .setHeartbeatValue(new long[]{30_000, 30_000}) - .setTaskScheduler(heartbeatScheduler()); - config.setApplicationDestinationPrefixes("/app"); - } - - @Override - public void registerStompEndpoints(StompEndpointRegistry registry) { - registry.addEndpoint("/ws.communication").setAllowedOrigins("*"); - } - - @Bean - TaskScheduler heartbeatScheduler() { - final ThreadPoolTaskScheduler heartbeatScheduler = new ThreadPoolTaskScheduler(); - - heartbeatScheduler.setPoolSize(1); - heartbeatScheduler.setThreadNamePrefix("wss-heartbeat-scheduler-thread-"); - - return heartbeatScheduler; - } - - @Override - public void configureClientInboundChannel(ChannelRegistration registration) { - registration.interceptors(new ChannelInterceptor() { - @Override - public Message preSend(Message message, MessageChannel channel) { - final StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); - - if (accessor != null && StompCommand.CONNECT.equals(accessor.getCommand())) { - String authToken = accessor.getFirstNativeHeader(HttpHeaders.AUTHORIZATION); - if (authToken != null && authToken.startsWith("Bearer")) { - authToken = authToken.substring(7); - } - - try { - final String userId = jwt.authenticate(authToken); - accessor.setUser(new UsernamePasswordAuthenticationToken(userId, null, List.of())); - } catch (Exception e) { - log.error(String.format("STOMP Command: %s, token: %s \n Failed to authenticate", accessor.getCommand(), authToken)); - } - } - - if (accessor == null || accessor.getUser() == null) { - throw new HttpClientErrorException(HttpStatus.UNAUTHORIZED, "Unauthorized"); - } - - return message; - } - }); - } -} diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/WebSocketController.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/WebSocketController.java deleted file mode 100644 index 083bb3068a..0000000000 --- a/backend/api/communication/src/main/java/co/airy/core/api/communication/WebSocketController.java +++ /dev/null @@ -1,62 +0,0 @@ -package co.airy.core.api.communication; - -import co.airy.avro.communication.Channel; -import co.airy.avro.communication.Message; -import co.airy.core.api.communication.dto.UnreadCountState; -import co.airy.core.api.communication.payload.MessageUpsertPayload; -import co.airy.core.api.communication.payload.UnreadCountPayload; -import co.airy.model.channel.ChannelPayload; -import co.airy.model.message.dto.MessageContainer; -import co.airy.model.message.dto.MessageResponsePayload; -import co.airy.model.metadata.dto.MetadataMap; -import org.springframework.messaging.simp.SimpMessagingTemplate; -import org.springframework.stereotype.Service; - -import java.time.Instant; -import java.util.Map; - -import static co.airy.avro.communication.ChannelConnectionState.CONNECTED; -import static co.airy.date.format.DateFormat.isoFromMillis; -import static co.airy.model.channel.ChannelPayload.fromChannel; - -@Service -public class WebSocketController { - public static final String QUEUE_MESSAGE = "/queue/message"; - public static final String QUEUE_CHANNEL_CONNECTED = "/queue/channel/connected"; - public static final String QUEUE_CHANNEL_DISCONNECTED = "/queue/channel/disconnected"; - public static final String QUEUE_UNREAD_COUNT = "/queue/unread-count"; - - private final SimpMessagingTemplate messagingTemplate; - - WebSocketController(SimpMessagingTemplate messagingTemplate) { - this.messagingTemplate = messagingTemplate; - } - - public void onNewMessage(Message message) { - final MessageUpsertPayload messageUpsertPayload = MessageUpsertPayload.builder() - .channelId(message.getChannelId()) - .conversationId(message.getConversationId()) - .message(MessageResponsePayload.fromMessageContainer(new MessageContainer(message, new MetadataMap()))) - .build(); - messagingTemplate.convertAndSend(QUEUE_MESSAGE, messageUpsertPayload); - } - - public void onUnreadCount(String conversationId, UnreadCountState unreadCountState) { - final UnreadCountPayload unreadCountPayload = UnreadCountPayload.builder() - .conversationId(conversationId) - .unreadMessageCount(unreadCountState.getUnreadCount()) - .timestamp(isoFromMillis(Instant.now().toEpochMilli())) - .build(); - - messagingTemplate.convertAndSend(QUEUE_UNREAD_COUNT, unreadCountPayload); - } - - public void onChannelUpdate(Channel channel) { - final ChannelPayload channelPayload = fromChannel(channel); - final String queue = CONNECTED.equals(channel.getConnectionState()) ? - QUEUE_CHANNEL_CONNECTED : - QUEUE_CHANNEL_DISCONNECTED; - - messagingTemplate.convertAndSend(queue, channelPayload); - } -} diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ContactResponsePayload.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ContactResponsePayload.java deleted file mode 100644 index 93b4d37a67..0000000000 --- a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ContactResponsePayload.java +++ /dev/null @@ -1,19 +0,0 @@ -package co.airy.core.api.communication.payload; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.Map; - -@Builder -@NoArgsConstructor -@AllArgsConstructor -@Data -public class ContactResponsePayload { - private String avatarUrl; - private String displayName; - private Map info; -} - diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationResponsePayload.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationResponsePayload.java index d32b52e84b..b7a7e60248 100644 --- a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationResponsePayload.java +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationResponsePayload.java @@ -12,7 +12,7 @@ import lombok.NoArgsConstructor; import static co.airy.date.format.DateFormat.isoFromMillis; -import static co.airy.model.metadata.MetadataKeys.*; +import static co.airy.model.metadata.MetadataKeys.ConversationKeys; import static co.airy.model.metadata.MetadataObjectMapper.getMetadataPayload; @Data diff --git a/backend/api/communication/src/test/java/co/airy/core/api/communication/ConversationsTagTest.java b/backend/api/communication/src/test/java/co/airy/core/api/communication/ConversationsTagTest.java index d9cc981880..9c5f75d24a 100644 --- a/backend/api/communication/src/test/java/co/airy/core/api/communication/ConversationsTagTest.java +++ b/backend/api/communication/src/test/java/co/airy/core/api/communication/ConversationsTagTest.java @@ -27,7 +27,6 @@ import static co.airy.test.Timing.retryOnException; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; -import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; import static org.hamcrest.core.Is.is; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; diff --git a/backend/api/communication/src/test/java/co/airy/core/api/communication/MessagesTest.java b/backend/api/communication/src/test/java/co/airy/core/api/communication/MessagesTest.java index d627c60090..a949540670 100644 --- a/backend/api/communication/src/test/java/co/airy/core/api/communication/MessagesTest.java +++ b/backend/api/communication/src/test/java/co/airy/core/api/communication/MessagesTest.java @@ -39,6 +39,7 @@ import static java.util.Comparator.reverseOrder; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; import static org.hamcrest.core.StringContains.containsString; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -163,8 +164,55 @@ void canReplaceMessageContentUrl() throws Exception { () -> webTestHelper.post("/messages.list", payload, "user-id") .andExpect(status().isOk()) .andExpect(jsonPath("$.data", hasSize(1))) - .andExpect(jsonPath("$.data[0].content", containsString(persistentUrl))), + .andExpect(jsonPath("$.data[0].content.url", containsString(persistentUrl))), "/messages.list content url was not replaced by metadata"); } + @Test + void canReturnTwilioMessagesUnparsed() throws Exception { + + final String conversationId = UUID.randomUUID().toString(); + final String messageId = UUID.randomUUID().toString(); + final String sourceConversationId = "+491234567"; + final String text = "Hello World"; + final String sourceChannelId = "+497654321"; + final String token = "token"; + + final String content = "ApiVersion=2010-04-01&SmsSid=SMbc31b6419de618d65076200c54676476&SmsStatus=received&SmsMessageSid=SMbc31b6419de618d65076200c54676476&NumSegments=1&To=whatsapp%3A%2B" + + sourceChannelId + + "&From=whatsapp%3A%2B" + + sourceConversationId + + "&MessageSid=SMbc31b6419de618d65076200c54676476&Body=Hi&AccountSid=AC64c9ab479b849275b7b50bd19540c602&NumMedia=0"; + + kafkaTestHelper.produceRecords(List.of( + new ProducerRecord<>(applicationCommunicationChannels.name(), channelId, Channel.newBuilder() + .setToken(token) + .setSourceChannelId(sourceChannelId) + .setSource("twilio.sms") + .setId(channelId) + .setConnectionState(ChannelConnectionState.CONNECTED) + .build() + ), + new ProducerRecord<>(applicationCommunicationMessages.name(), messageId, + Message.newBuilder() + .setId(messageId) + .setSource("twilio.sms") + .setSentAt(Instant.now().toEpochMilli()) + .setSenderId(sourceConversationId) + .setSenderType(SenderType.SOURCE_CONTACT) + .setDeliveryState(DeliveryState.DELIVERED) + .setConversationId(conversationId) + .setChannelId(channelId) + .setContent(content) + .build()) + )); + final String payload = "{\"conversation_id\":\"" + conversationId + "\"}"; + retryOnException( + () -> webTestHelper.post("/messages.list", payload, "user-id") + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data", hasSize(1))) + .andExpect(jsonPath("$.data[0].content", is(content))), + "/messages.list content url was not replaced by metadata"); + + } } diff --git a/backend/api/communication/src/test/java/co/airy/core/api/communication/SendMessageControllerTest.java b/backend/api/communication/src/test/java/co/airy/core/api/communication/SendMessageControllerTest.java index 3d7bc79161..e38bb9b5aa 100644 --- a/backend/api/communication/src/test/java/co/airy/core/api/communication/SendMessageControllerTest.java +++ b/backend/api/communication/src/test/java/co/airy/core/api/communication/SendMessageControllerTest.java @@ -3,7 +3,6 @@ import co.airy.avro.communication.Channel; import co.airy.avro.communication.ChannelConnectionState; import co.airy.avro.communication.Message; -import co.airy.avro.communication.SenderType; import co.airy.core.api.communication.util.TestConversation; import co.airy.kafka.test.KafkaTestHelper; import co.airy.kafka.test.junit.SharedKafkaTestResource; @@ -32,6 +31,7 @@ import static co.airy.core.api.communication.util.Topics.applicationCommunicationChannels; import static co.airy.core.api.communication.util.Topics.applicationCommunicationMessages; import static co.airy.core.api.communication.util.Topics.getTopics; +import static co.airy.model.message.MessageRepository.isFromAiry; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.collection.IsCollectionWithSize.hasSize; @@ -99,7 +99,7 @@ void canSendTextMessages() throws Exception { final Optional maybeMessage = records.stream() .map(ConsumerRecord::value) - .filter(m -> m.getSenderType().equals(SenderType.APP_USER) && m.getId().equals(messageId)) + .filter(message -> isFromAiry(message) && message.getId().equals(messageId)) .findFirst(); if (maybeMessage.isEmpty()) { diff --git a/backend/api/communication/src/test/java/co/airy/core/api/communication/UnreadCountTest.java b/backend/api/communication/src/test/java/co/airy/core/api/communication/UnreadCountTest.java index e30b596d51..d5cbea36b4 100644 --- a/backend/api/communication/src/test/java/co/airy/core/api/communication/UnreadCountTest.java +++ b/backend/api/communication/src/test/java/co/airy/core/api/communication/UnreadCountTest.java @@ -2,6 +2,9 @@ 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.SenderType; import co.airy.core.api.communication.util.TestConversation; import co.airy.kafka.test.KafkaTestHelper; import co.airy.kafka.test.junit.SharedKafkaTestResource; @@ -20,9 +23,12 @@ 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.UUID; import static co.airy.core.api.communication.util.Topics.applicationCommunicationChannels; +import static co.airy.core.api.communication.util.Topics.applicationCommunicationMessages; import static co.airy.core.api.communication.util.Topics.getTopics; import static co.airy.test.Timing.retryOnException; import static org.hamcrest.core.IsEqual.equalTo; @@ -75,6 +81,21 @@ void canResetUnreadCount() throws Exception { kafkaTestHelper.produceRecords(TestConversation.generateRecords(conversationId, channel, unreadMessages)); + // Messages from Airy should not increase the unread count + kafkaTestHelper.produceRecords(List.of( + new ProducerRecord<>(applicationCommunicationMessages.name(), "message-id", Message.newBuilder() + .setId("message-id") + .setSentAt(Instant.now().toEpochMilli()) + .setSenderId("source-conversation-id") + .setDeliveryState(DeliveryState.DELIVERED) + .setSource("facebook") + .setSenderType(SenderType.APP_USER) + .setConversationId(conversationId) + .setChannelId(channel.getId()) + .setContent("from airy") + .build()) + )); + final String payload = "{\"conversation_id\":\"" + conversationId + "\"}"; retryOnException(() -> webTestHelper.post("/conversations.info", payload, userId) diff --git a/backend/api/communication/src/test/java/co/airy/core/api/communication/WebSocketControllerTest.java b/backend/api/communication/src/test/java/co/airy/core/api/communication/WebSocketControllerTest.java deleted file mode 100644 index 2ac8dd48a9..0000000000 --- a/backend/api/communication/src/test/java/co/airy/core/api/communication/WebSocketControllerTest.java +++ /dev/null @@ -1,171 +0,0 @@ -package co.airy.core.api.communication; - -import co.airy.avro.communication.Channel; -import co.airy.avro.communication.ChannelConnectionState; -import co.airy.core.api.communication.payload.MessageUpsertPayload; -import co.airy.core.api.communication.payload.UnreadCountPayload; -import co.airy.core.api.communication.util.TestConversation; -import co.airy.kafka.test.KafkaTestHelper; -import co.airy.kafka.test.junit.SharedKafkaTestResource; -import co.airy.model.channel.ChannelPayload; -import co.airy.spring.core.AirySpringBootApplication; -import co.airy.spring.jwt.Jwt; -import co.airy.spring.test.WebTestHelper; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; -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.beans.factory.annotation.Value; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.messaging.converter.MappingJackson2MessageConverter; -import org.springframework.messaging.simp.stomp.StompHeaders; -import org.springframework.messaging.simp.stomp.StompSession; -import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.socket.WebSocketHttpHeaders; -import org.springframework.web.socket.client.standard.StandardWebSocketClient; -import org.springframework.web.socket.messaging.WebSocketStompClient; - -import java.lang.reflect.Type; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; - -import static co.airy.core.api.communication.WebSocketController.QUEUE_CHANNEL_CONNECTED; -import static co.airy.core.api.communication.WebSocketController.QUEUE_MESSAGE; -import static co.airy.core.api.communication.WebSocketController.QUEUE_UNREAD_COUNT; -import static co.airy.core.api.communication.util.Topics.applicationCommunicationChannels; -import static co.airy.core.api.communication.util.Topics.getTopics; -import static co.airy.test.Timing.retryOnException; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.springframework.http.HttpHeaders.AUTHORIZATION; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@ExtendWith(SpringExtension.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {AirySpringBootApplication.class}) -@TestPropertySource(value = "classpath:test.properties") -@AutoConfigureMockMvc -public class WebSocketControllerTest { - @RegisterExtension - public static final SharedKafkaTestResource sharedKafkaTestResource = new SharedKafkaTestResource(); - private static KafkaTestHelper kafkaTestHelper; - - private static final String conversationId = "conversation-id"; - - final Channel channel = Channel.newBuilder() - .setConnectionState(ChannelConnectionState.CONNECTED) - .setId(UUID.randomUUID().toString()) - .setSource("facebook") - .setSourceChannelId("ps-id") - .setToken("AWESOME TOKEN") - .build(); - - @Value("${local.server.port}") - private int port; - - @Autowired - private MockMvc mvc; - - @Autowired - private Jwt jwt; - - @Autowired - private WebTestHelper webTestHelper; - - @BeforeAll - static void beforeAll() throws Exception { - kafkaTestHelper = new KafkaTestHelper(sharedKafkaTestResource, getTopics()); - kafkaTestHelper.beforeAll(); - } - - @AfterAll - static void afterAll() throws Exception { - kafkaTestHelper.afterAll(); - } - - @BeforeEach - void beforeEach() throws Exception { - webTestHelper.waitUntilHealthy(); - } - - @Test - void canSendMessagesViaWebSocket() throws Exception { - final CompletableFuture messageFuture = subscribe(port, MessageUpsertPayload.class, QUEUE_MESSAGE, jwt); - final CompletableFuture channelFuture = subscribe(port, ChannelPayload.class, QUEUE_CHANNEL_CONNECTED, jwt); - final CompletableFuture unreadFuture = subscribe(port, UnreadCountPayload.class, QUEUE_UNREAD_COUNT, jwt); - - kafkaTestHelper.produceRecord(new ProducerRecord<>(applicationCommunicationChannels.name(), channel.getId(), channel)); - - TestConversation testConversation = TestConversation.from( - conversationId, - channel, - 1); - - kafkaTestHelper.produceRecords(testConversation.getRecords()); - - final MessageUpsertPayload recMessage = messageFuture.get(30, TimeUnit.SECONDS); - - assertNotNull(recMessage); - assertThat(recMessage.getConversationId(), is(conversationId)); - assertThat(recMessage.getMessage().getSource(), is("facebook")); - - final ChannelPayload receivedChannel = channelFuture.get(30, TimeUnit.SECONDS); - - assertNotNull(receivedChannel); - - assertThat(receivedChannel.getId(), is(channel.getId())); - - final UnreadCountPayload receivedUnreadCount = unreadFuture.get(30, TimeUnit.SECONDS); - - assertNotNull(receivedUnreadCount); - assertThat(receivedUnreadCount.getUnreadMessageCount(), is(1)); - } - - private static StompSession connectToWs(int port, Jwt jwt) throws ExecutionException, InterruptedException { - final WebSocketStompClient stompClient = new WebSocketStompClient(new StandardWebSocketClient()); - MappingJackson2MessageConverter messageConverter = new MappingJackson2MessageConverter(); - ObjectMapper objectMapper = new ObjectMapper().setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); - messageConverter.setObjectMapper(objectMapper); - stompClient.setMessageConverter(messageConverter); - - StompHeaders connectHeaders = new StompHeaders(); - WebSocketHttpHeaders httpHeaders = new WebSocketHttpHeaders(); - connectHeaders.add(AUTHORIZATION, "Bearer " + jwt.tokenFor("userId")); - - return stompClient.connect("ws://localhost:" + port + "/ws.communication", httpHeaders, connectHeaders, new StompSessionHandlerAdapter() { - }).get(); - } - - public static CompletableFuture subscribe(int port, Class payloadType, String topic, Jwt jwt) throws ExecutionException, InterruptedException { - final StompSession stompSession = connectToWs(port, jwt); - - final CompletableFuture completableFuture = new CompletableFuture<>(); - - stompSession.subscribe(topic, new StompSessionHandlerAdapter() { - @Override - public Type getPayloadType(StompHeaders headers) { - return payloadType; - } - - @Override - public void handleFrame(StompHeaders headers, Object payload) { - completableFuture.complete((T) payload); - } - }); - - return completableFuture; - } -} diff --git a/backend/api/websocket/BUILD b/backend/api/websocket/BUILD index 2f47493eaa..8110638bbd 100644 --- a/backend/api/websocket/BUILD +++ b/backend/api/websocket/BUILD @@ -6,8 +6,9 @@ app_deps = [ "//backend:base_app", "//:springboot_actuator", "//:springboot_websocket", - "//backend/model/message", "//backend/model/channel", + "//backend/model/event", + "//backend/model/message", "//backend/model/metadata", "//lib/java/date", "//lib/java/spring/auth:spring-auth", diff --git a/backend/api/websocket/src/main/java/co/airy/core/api/websocket/Stores.java b/backend/api/websocket/src/main/java/co/airy/core/api/websocket/Stores.java index 59a156be01..65a973f692 100644 --- a/backend/api/websocket/src/main/java/co/airy/core/api/websocket/Stores.java +++ b/backend/api/websocket/src/main/java/co/airy/core/api/websocket/Stores.java @@ -8,11 +8,8 @@ import co.airy.kafka.schema.application.ApplicationCommunicationMetadata; import co.airy.kafka.streams.KafkaStreamsWrapper; import co.airy.model.metadata.dto.MetadataMap; -import org.apache.avro.specific.SpecificRecordBase; -import org.apache.kafka.clients.consumer.KafkaConsumer; import org.apache.kafka.streams.KeyValue; import org.apache.kafka.streams.StreamsBuilder; -import org.apache.kafka.streams.kstream.KTable; import org.springframework.beans.factory.DisposableBean; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; @@ -22,7 +19,6 @@ import org.springframework.stereotype.Component; import static co.airy.model.metadata.MetadataRepository.getSubject; -import static co.airy.model.metadata.MetadataRepository.isChannelMetadata; @Component public class Stores implements HealthIndicator, ApplicationListener, DisposableBean { diff --git a/backend/api/websocket/src/main/java/co/airy/core/api/websocket/WebSocketConfig.java b/backend/api/websocket/src/main/java/co/airy/core/api/websocket/WebSocketConfig.java index 0c2e150d23..c0729823e0 100644 --- a/backend/api/websocket/src/main/java/co/airy/core/api/websocket/WebSocketConfig.java +++ b/backend/api/websocket/src/main/java/co/airy/core/api/websocket/WebSocketConfig.java @@ -46,7 +46,7 @@ public void configureMessageBroker(MessageBrokerRegistry config) { public void registerStompEndpoints(StompEndpointRegistry registry) { // TODO this is a temporary name. We can change it back to // /ws.communication in https://github.com/airyhq/airy/issues/886 - registry.addEndpoint("/ws.events").setAllowedOrigins("*"); + registry.addEndpoint("/ws.communication").setAllowedOrigins("*"); } @Bean diff --git a/backend/api/websocket/src/main/java/co/airy/core/api/websocket/WebSocketController.java b/backend/api/websocket/src/main/java/co/airy/core/api/websocket/WebSocketController.java index 8439a52a28..0f189c4826 100644 --- a/backend/api/websocket/src/main/java/co/airy/core/api/websocket/WebSocketController.java +++ b/backend/api/websocket/src/main/java/co/airy/core/api/websocket/WebSocketController.java @@ -2,10 +2,10 @@ import co.airy.avro.communication.Channel; import co.airy.avro.communication.Message; -import co.airy.core.api.websocket.payload.ChannelEvent; -import co.airy.core.api.websocket.payload.MessageEvent; -import co.airy.core.api.websocket.payload.MetadataEvent; import co.airy.model.channel.ChannelPayload; +import co.airy.model.event.payload.ChannelEvent; +import co.airy.model.event.payload.MessageEvent; +import co.airy.model.event.payload.MetadataEvent; import co.airy.model.metadata.dto.MetadataMap; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Service; @@ -15,6 +15,7 @@ public class WebSocketController { public static final String QUEUE_EVENTS = "/events"; private final SimpMessagingTemplate messagingTemplate; + WebSocketController(SimpMessagingTemplate messagingTemplate) { this.messagingTemplate = messagingTemplate; } @@ -22,14 +23,16 @@ public class WebSocketController { public void onMessage(Message message) { messagingTemplate.convertAndSend(QUEUE_EVENTS, MessageEvent.fromMessage(message)); } + public void onChannel(Channel channel) { messagingTemplate.convertAndSend(QUEUE_EVENTS, ChannelEvent.builder() .payload(ChannelPayload.fromChannel(channel)) .build() ); } + public void onMetadata(MetadataMap metadataMap) { - if(metadataMap.isEmpty()) { + if (metadataMap.isEmpty()) { return; } diff --git a/backend/api/websocket/src/test/java/co/airy/core/api/websocket/WebSocketControllerTest.java b/backend/api/websocket/src/test/java/co/airy/core/api/websocket/WebSocketControllerTest.java index 2499582ae1..2ad2ffeb17 100644 --- a/backend/api/websocket/src/test/java/co/airy/core/api/websocket/WebSocketControllerTest.java +++ b/backend/api/websocket/src/test/java/co/airy/core/api/websocket/WebSocketControllerTest.java @@ -6,14 +6,14 @@ import co.airy.avro.communication.Message; import co.airy.avro.communication.Metadata; import co.airy.avro.communication.SenderType; -import co.airy.core.api.websocket.payload.ChannelEvent; -import co.airy.core.api.websocket.payload.MessageEvent; -import co.airy.core.api.websocket.payload.MetadataEvent; 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.event.payload.ChannelEvent; +import co.airy.model.event.payload.MessageEvent; +import co.airy.model.event.payload.MetadataEvent; import co.airy.spring.core.AirySpringBootApplication; import co.airy.spring.jwt.Jwt; import co.airy.spring.test.WebTestHelper; @@ -36,26 +36,24 @@ import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.socket.WebSocketHttpHeaders; import org.springframework.web.socket.client.standard.StandardWebSocketClient; import org.springframework.web.socket.messaging.WebSocketStompClient; import java.lang.reflect.Type; import java.time.Instant; +import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import static co.airy.core.api.websocket.WebSocketController.QUEUE_EVENTS; -import static co.airy.test.Timing.retryOnException; +import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.springframework.http.HttpHeaders.AUTHORIZATION; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @ExtendWith(SpringExtension.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {AirySpringBootApplication.class}) @@ -70,12 +68,9 @@ public class WebSocketControllerTest { private static final ApplicationCommunicationChannels applicationCommunicationChannels = new ApplicationCommunicationChannels(); private static final ApplicationCommunicationMetadata applicationCommunicationMetadata = new ApplicationCommunicationMetadata(); - @Value("${local.server.port}") private int port; - @Autowired - private MockMvc mvc; @Autowired private WebTestHelper webTestHelper; @@ -125,7 +120,8 @@ void canReceiveMessageEvents() throws Exception { assertNotNull(recMessage); assertThat(recMessage.getPayload().getChannelId(), equalTo(message.getChannelId())); assertThat(recMessage.getPayload().getMessage().getId(), equalTo(message.getId())); - assertThat(recMessage.getPayload().getMessage().getContent(), equalTo(message.getContent())); + Map node = (Map) recMessage.getPayload().getMessage().getContent(); + assertThat(node.get("text").toString(), containsString("hello world")); } @Test @@ -177,7 +173,7 @@ private static StompSession connectToWs(int port, Jwt jwt) throws ExecutionExcep WebSocketHttpHeaders httpHeaders = new WebSocketHttpHeaders(); connectHeaders.add(AUTHORIZATION, "Bearer " + jwt.tokenFor("userId")); - return stompClient.connect("ws://localhost:" + port + "/ws.events", httpHeaders, connectHeaders, new StompSessionHandlerAdapter() { + return stompClient.connect("ws://localhost:" + port + "/ws.communication", httpHeaders, connectHeaders, new StompSessionHandlerAdapter() { }).get(); } diff --git a/backend/model/channel/src/main/java/co/airy/model/channel/ChannelPayload.java b/backend/model/channel/src/main/java/co/airy/model/channel/ChannelPayload.java index 7a1331223a..aac4330666 100644 --- a/backend/model/channel/src/main/java/co/airy/model/channel/ChannelPayload.java +++ b/backend/model/channel/src/main/java/co/airy/model/channel/ChannelPayload.java @@ -1,9 +1,9 @@ package co.airy.model.channel; import co.airy.avro.communication.Channel; +import co.airy.avro.communication.ChannelConnectionState; import co.airy.model.channel.dto.ChannelContainer; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.AllArgsConstructor; import lombok.Builder; @@ -20,10 +20,11 @@ public class ChannelPayload { private String id; private String source; private String sourceChannelId; + private boolean connected; private JsonNode metadata; public static ChannelPayload fromChannelContainer(ChannelContainer container) { - if(container.getMetadataMap() == null) { + if (container.getMetadataMap() == null) { return fromChannel(container.getChannel()); } @@ -33,15 +34,16 @@ public static ChannelPayload fromChannelContainer(ChannelContainer container) { .metadata(defaultMetadata(getMetadataPayload(container.getMetadataMap()), channel)) .source(channel.getSource()) .sourceChannelId(channel.getSourceChannelId()) + .connected(channel.getConnectionState().equals(ChannelConnectionState.CONNECTED)) .build(); } public static ChannelPayload fromChannel(Channel channel) { return ChannelPayload.builder() .id(channel.getId()) - .metadata(defaultMetadata(JsonNodeFactory.instance.objectNode(), channel)) .source(channel.getSource()) .sourceChannelId(channel.getSourceChannelId()) + .connected(channel.getConnectionState().equals(ChannelConnectionState.CONNECTED)) .build(); } diff --git a/backend/model/event/BUILD b/backend/model/event/BUILD new file mode 100644 index 0000000000..34d4a952a3 --- /dev/null +++ b/backend/model/event/BUILD @@ -0,0 +1,14 @@ +load("//tools/build:java_library.bzl", "custom_java_library") + +custom_java_library( + name = "event", + srcs = glob(["src/main/java/co/airy/model/event/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//:jackson", + "//:lombok", + "//backend/model/channel", + "//backend/model/message", + "//backend/model/metadata", + ], +) diff --git a/backend/api/websocket/src/main/java/co/airy/core/api/websocket/payload/ChannelEvent.java b/backend/model/event/src/main/java/co/airy/model/event/payload/ChannelEvent.java similarity index 64% rename from backend/api/websocket/src/main/java/co/airy/core/api/websocket/payload/ChannelEvent.java rename to backend/model/event/src/main/java/co/airy/model/event/payload/ChannelEvent.java index dd83b8a00e..bd9db13522 100644 --- a/backend/api/websocket/src/main/java/co/airy/core/api/websocket/payload/ChannelEvent.java +++ b/backend/model/event/src/main/java/co/airy/model/event/payload/ChannelEvent.java @@ -1,7 +1,7 @@ -package co.airy.core.api.websocket.payload; +package co.airy.model.event.payload; +import co.airy.avro.communication.Channel; import co.airy.model.channel.ChannelPayload; -import co.airy.model.message.dto.MessageResponsePayload; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -17,4 +17,8 @@ @EqualsAndHashCode(callSuper = false) public class ChannelEvent extends Event implements Serializable { private ChannelPayload payload; + + public static ChannelEvent fromChannel(Channel channel) { + return builder().payload(ChannelPayload.fromChannel(channel)).build(); + } } diff --git a/backend/api/websocket/src/main/java/co/airy/core/api/websocket/payload/Event.java b/backend/model/event/src/main/java/co/airy/model/event/payload/Event.java similarity index 91% rename from backend/api/websocket/src/main/java/co/airy/core/api/websocket/payload/Event.java rename to backend/model/event/src/main/java/co/airy/model/event/payload/Event.java index 9bee2ac1c9..aae28c1c68 100644 --- a/backend/api/websocket/src/main/java/co/airy/core/api/websocket/payload/Event.java +++ b/backend/model/event/src/main/java/co/airy/model/event/payload/Event.java @@ -1,4 +1,4 @@ -package co.airy.core.api.websocket.payload; +package co.airy.model.event.payload; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; diff --git a/backend/api/websocket/src/main/java/co/airy/core/api/websocket/payload/MessageEvent.java b/backend/model/event/src/main/java/co/airy/model/event/payload/MessageEvent.java similarity index 96% rename from backend/api/websocket/src/main/java/co/airy/core/api/websocket/payload/MessageEvent.java rename to backend/model/event/src/main/java/co/airy/model/event/payload/MessageEvent.java index 27245c0e01..79e2b376af 100644 --- a/backend/api/websocket/src/main/java/co/airy/core/api/websocket/payload/MessageEvent.java +++ b/backend/model/event/src/main/java/co/airy/model/event/payload/MessageEvent.java @@ -1,4 +1,4 @@ -package co.airy.core.api.websocket.payload; +package co.airy.model.event.payload; import co.airy.avro.communication.Message; import co.airy.model.message.dto.MessageResponsePayload; diff --git a/backend/api/websocket/src/main/java/co/airy/core/api/websocket/payload/MetadataEvent.java b/backend/model/event/src/main/java/co/airy/model/event/payload/MetadataEvent.java similarity index 95% rename from backend/api/websocket/src/main/java/co/airy/core/api/websocket/payload/MetadataEvent.java rename to backend/model/event/src/main/java/co/airy/model/event/payload/MetadataEvent.java index 25acb79b20..ea73bbe743 100644 --- a/backend/api/websocket/src/main/java/co/airy/core/api/websocket/payload/MetadataEvent.java +++ b/backend/model/event/src/main/java/co/airy/model/event/payload/MetadataEvent.java @@ -1,8 +1,8 @@ -package co.airy.core.api.websocket.payload; +package co.airy.model.event.payload; import co.airy.avro.communication.Metadata; -import co.airy.model.metadata.dto.MetadataMap; import co.airy.model.metadata.Subject; +import co.airy.model.metadata.dto.MetadataMap; import com.fasterxml.jackson.databind.JsonNode; import lombok.AllArgsConstructor; import lombok.Builder; @@ -11,7 +11,6 @@ import lombok.NoArgsConstructor; import java.io.Serializable; -import java.util.Map; import static co.airy.model.metadata.MetadataObjectMapper.getMetadataPayload; import static co.airy.model.metadata.MetadataRepository.getSubject; diff --git a/backend/model/message/src/main/java/co/airy/model/message/MessageRepository.java b/backend/model/message/src/main/java/co/airy/model/message/MessageRepository.java index 1ce81cd1e5..e671ea3348 100644 --- a/backend/model/message/src/main/java/co/airy/model/message/MessageRepository.java +++ b/backend/model/message/src/main/java/co/airy/model/message/MessageRepository.java @@ -2,10 +2,13 @@ import co.airy.avro.communication.DeliveryState; import co.airy.avro.communication.Message; +import co.airy.avro.communication.SenderType; import co.airy.model.metadata.dto.MetadataMap; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import java.time.Instant; -import java.util.Map; public class MessageRepository { public static Message updateDeliveryState(Message message, DeliveryState state) { @@ -18,10 +21,24 @@ public static boolean isNewMessage(Message message) { return message.getUpdatedAt() == null; } - public static String resolveContent(Message message, MetadataMap metadata) { + // In preparation for https://github.com/airyhq/airy/issues/572 + public static boolean isFromContact(Message message) { + return message.getSenderType().equals(SenderType.SOURCE_CONTACT); + } + + public static boolean isFromAiry(Message message) { + return message.getSenderType().equals(SenderType.APP_USER); + } + + public static Object resolveContent(Message message) { + return resolveContent(message, new MetadataMap()); + } + + public static Object resolveContent(Message message, MetadataMap metadata) { final String content = message.getContent(); + JsonNode jsonNode; - return metadata.entrySet() + final String resolvedContent = metadata.entrySet() .stream() .filter((entry) -> entry.getKey().startsWith("data_")) .reduce(content, (updatedContent, entry) -> { @@ -30,5 +47,13 @@ public static String resolveContent(Message message, MetadataMap metadata) { return updatedContent.replace(urlToReplace, entry.getValue().getValue()); }, (oldValue, newValue) -> newValue); + + try { + jsonNode = new ObjectMapper().readTree(resolvedContent); + } catch (JsonProcessingException e) { + return resolvedContent; + } + + return jsonNode; } } diff --git a/backend/model/message/src/main/java/co/airy/model/message/dto/MessageContainer.java b/backend/model/message/src/main/java/co/airy/model/message/dto/MessageContainer.java index f9f114f82c..4387eb7884 100644 --- a/backend/model/message/src/main/java/co/airy/model/message/dto/MessageContainer.java +++ b/backend/model/message/src/main/java/co/airy/model/message/dto/MessageContainer.java @@ -15,5 +15,6 @@ @AllArgsConstructor public class MessageContainer implements Serializable { private Message message; - private MetadataMap metadataMap; + @Builder.Default + private MetadataMap metadataMap = new MetadataMap(); } diff --git a/backend/model/message/src/main/java/co/airy/model/message/dto/MessageResponsePayload.java b/backend/model/message/src/main/java/co/airy/model/message/dto/MessageResponsePayload.java index 32879207d4..b93c106c9b 100644 --- a/backend/model/message/src/main/java/co/airy/model/message/dto/MessageResponsePayload.java +++ b/backend/model/message/src/main/java/co/airy/model/message/dto/MessageResponsePayload.java @@ -2,14 +2,11 @@ import co.airy.avro.communication.Message; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import java.util.Map; - import static co.airy.date.format.DateFormat.isoFromMillis; import static co.airy.model.message.MessageRepository.resolveContent; import static co.airy.model.metadata.MetadataObjectMapper.getMetadataPayload; @@ -20,7 +17,7 @@ @AllArgsConstructor public class MessageResponsePayload { private String id; - private String content; + private Object content; private String senderType; private String sentAt; private String deliveryState; @@ -42,13 +39,12 @@ public static MessageResponsePayload fromMessageContainer(MessageContainer messa public static MessageResponsePayload fromMessage(Message message) { return MessageResponsePayload.builder() - .content(message.getContent()) + .content(resolveContent(message)) .senderType(message.getSenderType().toString().toLowerCase()) .deliveryState(message.getDeliveryState().toString().toLowerCase()) .id(message.getId()) .sentAt(isoFromMillis(message.getSentAt())) .source(message.getSource()) - .metadata(JsonNodeFactory.instance.objectNode()) .build(); } diff --git a/backend/model/metadata/src/main/java/co/airy/model/metadata/MetadataRepository.java b/backend/model/metadata/src/main/java/co/airy/model/metadata/MetadataRepository.java index 4c1e9b3a03..24b8e458e4 100644 --- a/backend/model/metadata/src/main/java/co/airy/model/metadata/MetadataRepository.java +++ b/backend/model/metadata/src/main/java/co/airy/model/metadata/MetadataRepository.java @@ -1,16 +1,11 @@ package co.airy.model.metadata; import co.airy.avro.communication.Metadata; -import co.airy.model.metadata.dto.MetadataMap; import co.airy.uuid.UUIDv5; import java.time.Instant; -import java.util.Map; import java.util.UUID; -import static co.airy.model.metadata.MetadataKeys.USER_DATA; -import static java.util.stream.Collectors.toMap; - public class MetadataRepository { public static Metadata newConversationMetadata(String conversationId, String key, String value) { return Metadata.newBuilder() diff --git a/backend/model/metadata/src/main/java/co/airy/model/metadata/dto/MetadataMap.java b/backend/model/metadata/src/main/java/co/airy/model/metadata/dto/MetadataMap.java index 6600b9351f..4df38e910b 100644 --- a/backend/model/metadata/src/main/java/co/airy/model/metadata/dto/MetadataMap.java +++ b/backend/model/metadata/src/main/java/co/airy/model/metadata/dto/MetadataMap.java @@ -2,7 +2,6 @@ import co.airy.avro.communication.Metadata; import co.airy.log.AiryLoggerFactory; -import co.airy.model.metadata.MetadataKeys; import org.slf4j.Logger; import java.io.Serializable; diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/Stores.java b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/Stores.java index 16dec8a576..9021d36adb 100644 --- a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/Stores.java +++ b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/Stores.java @@ -4,7 +4,6 @@ import co.airy.avro.communication.ChannelConnectionState; import co.airy.avro.communication.DeliveryState; import co.airy.avro.communication.Message; -import co.airy.avro.communication.SenderType; import co.airy.core.chat_plugin.dto.MessagesTreeSet; import co.airy.kafka.schema.application.ApplicationCommunicationChannels; import co.airy.kafka.schema.application.ApplicationCommunicationMessages; @@ -29,6 +28,7 @@ import java.util.List; import java.util.concurrent.ExecutionException; +import static co.airy.model.message.MessageRepository.isFromContact; import static co.airy.model.message.MessageRepository.updateDeliveryState; @Component @@ -66,11 +66,11 @@ private void startStream() { }), Materialized.as(messagesStore)); // Client Echoes - messageStream.filter((messageId, message) -> message.getSenderType().equals(SenderType.SOURCE_CONTACT)) + messageStream.filter((messageId, message) -> isFromContact(message)) .peek((messageId, message) -> webSocketController.onNewMessage(message)); // Runtime Outbound - messageStream.filter((messageId, message) -> !message.getSenderType().equals(SenderType.SOURCE_CONTACT) + messageStream.filter((messageId, message) -> !isFromContact(message) && message.getDeliveryState().equals(DeliveryState.PENDING) ) .mapValues((messageId, message) -> { diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/SendMessageRequestPayload.java b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/SendMessageRequestPayload.java index e076b0ffc2..138fb92e9a 100644 --- a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/SendMessageRequestPayload.java +++ b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/payload/SendMessageRequestPayload.java @@ -4,8 +4,6 @@ import lombok.Data; import lombok.NoArgsConstructor; -import javax.validation.constraints.NotNull; - @Data @NoArgsConstructor public class SendMessageRequestPayload { diff --git a/backend/sources/chat-plugin/src/test/java/co/airy/core/chat_plugin/ChatControllerTest.java b/backend/sources/chat-plugin/src/test/java/co/airy/core/chat_plugin/ChatControllerTest.java index 305e62c415..80a861e6dd 100644 --- a/backend/sources/chat-plugin/src/test/java/co/airy/core/chat_plugin/ChatControllerTest.java +++ b/backend/sources/chat-plugin/src/test/java/co/airy/core/chat_plugin/ChatControllerTest.java @@ -36,6 +36,7 @@ import org.springframework.web.socket.messaging.WebSocketStompClient; import java.lang.reflect.Type; +import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -119,17 +120,17 @@ void authenticateSendAndReceive() throws Exception { final String messageText = "answer is 42"; String sendMessagePayload = "{\"message\": { \"text\": \"" + messageText + "\", \"type\":\"text\" }}"; - retryOnException(() -> mvc.perform(post("/chatplugin.send") - .headers(buildHeaders(token)) - .content(sendMessagePayload)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.content", containsString(messageText))), - "Message was not sent"); + mvc.perform(post("/chatplugin.send") + .headers(buildHeaders(token)) + .content(sendMessagePayload)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.content.text", is(messageText))); final MessageUpsertPayload messageUpsertPayload = messageFuture.get(); assertNotNull(messageUpsertPayload); - assertThat(messageUpsertPayload.getMessage().getContent(), containsString(messageText)); + Map node = (Map) messageUpsertPayload.getMessage().getContent(); + assertThat(node.get("text").toString(), containsString(messageText)); } @Test @@ -152,7 +153,7 @@ void canResumeConversation() throws Exception { .content(sendMessagePayload)) .andExpect(status().isOk()) .andExpect(jsonPath("$.delivery_state", is("delivered"))) - .andExpect(jsonPath("$.content", containsString(messageText))); + .andExpect(jsonPath("$.content.text", containsString(messageText))); response = mvc.perform(post("/chatplugin.resumeToken") .headers(buildHeaders(authToken)) @@ -172,7 +173,7 @@ void canResumeConversation() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.token", is(not(nullValue())))) .andExpect(jsonPath("$.messages", hasSize(1))) - .andExpect(jsonPath("$.messages[0].content", containsString(messageText))); + .andExpect(jsonPath("$.messages[0].content.text", containsString(messageText))); }, "Did not resume conversation"); } diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/ChannelsController.java b/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/ChannelsController.java index a417f9f506..a365db9f26 100644 --- a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/ChannelsController.java +++ b/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/ChannelsController.java @@ -96,6 +96,7 @@ ResponseEntity connect(@RequestBody @Valid ConnectRequestPayload requestPaylo .setConnectionState(ChannelConnectionState.CONNECTED) .setSource("facebook") .setSourceChannelId(pageId) + .setToken(longLivingUserToken) .build() ) .metadataMap(MetadataMap.from(List.of( 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 1c9711bbab..2617c7b6ba 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 @@ -8,10 +8,9 @@ import co.airy.core.sources.facebook.api.Mapper; import co.airy.core.sources.facebook.api.model.SendMessagePayload; import co.airy.core.sources.facebook.api.model.UserProfile; -import co.airy.core.sources.facebook.dto.SendMessageRequest; import co.airy.core.sources.facebook.dto.Conversation; +import co.airy.core.sources.facebook.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 org.apache.kafka.streams.KeyValue; @@ -25,7 +24,7 @@ import java.util.Objects; import static co.airy.model.message.MessageRepository.updateDeliveryState; -import static co.airy.model.metadata.MetadataKeys.*; +import static co.airy.model.metadata.MetadataKeys.ConversationKeys; import static co.airy.model.metadata.MetadataKeys.ConversationKeys.ContactFetchState.failed; import static co.airy.model.metadata.MetadataKeys.ConversationKeys.ContactFetchState.ok; import static co.airy.model.metadata.MetadataRepository.getId; diff --git a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/Stores.java b/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/Stores.java index 9093d5f953..7379b83269 100644 --- a/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/Stores.java +++ b/backend/sources/facebook/connector/src/main/java/co/airy/core/sources/facebook/Stores.java @@ -5,7 +5,6 @@ import co.airy.avro.communication.DeliveryState; import co.airy.avro.communication.Message; import co.airy.avro.communication.Metadata; -import co.airy.avro.communication.SenderType; import co.airy.core.sources.facebook.dto.Conversation; import co.airy.core.sources.facebook.dto.SendMessageRequest; import co.airy.kafka.schema.application.ApplicationCommunicationChannels; @@ -35,6 +34,7 @@ import java.util.Optional; import java.util.concurrent.ExecutionException; +import static co.airy.model.message.MessageRepository.isFromContact; import static co.airy.model.metadata.MetadataRepository.getId; import static co.airy.model.metadata.MetadataRepository.getSubject; import static co.airy.model.metadata.MetadataRepository.isConversationMetadata; @@ -92,20 +92,18 @@ public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) .aggregate(Conversation::new, (conversationId, message, conversation) -> { final Conversation.ConversationBuilder conversationBuilder = conversation.toBuilder(); - if (SenderType.SOURCE_CONTACT.equals(message.getSenderType())) { + if (isFromContact(message)) { conversationBuilder.sourceConversationId(message.getSenderId()); } conversationBuilder.channelId(message.getChannelId()); return conversationBuilder.build(); }) - .join(channelsTable, Conversation::getChannelId, (conversation, channel) -> { - return conversation.toBuilder() - .channelId(conversation.getChannelId()) - .channel(channel) - .sourceConversationId(conversation.getSourceConversationId()) - .build(); - }); + .join(channelsTable, Conversation::getChannelId, (conversation, channel) -> conversation.toBuilder() + .channelId(conversation.getChannelId()) + .channel(channel) + .sourceConversationId(conversation.getSourceConversationId()) + .build()); // Send outbound messages messageStream.filter((messageId, message) -> DeliveryState.PENDING.equals(message.getDeliveryState())) 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 1440f0c30d..3bc636bddb 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 @@ -14,7 +14,6 @@ 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 org.apache.kafka.clients.producer.ProducerRecord; import org.junit.jupiter.api.AfterAll; @@ -36,7 +35,7 @@ import java.time.Instant; import java.util.List; -import static co.airy.model.metadata.MetadataKeys.*; +import static co.airy.model.metadata.MetadataKeys.ConversationKeys; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.collection.IsCollectionWithSize.hasSize; import static org.hamcrest.core.IsEqual.equalTo; diff --git a/backend/sources/facebook/events-router/src/main/java/co/airy/core/sources/facebook/MessageParser.java b/backend/sources/facebook/events-router/src/main/java/co/airy/core/sources/facebook/MessageParser.java index 4cec5ff0ba..a3dd3740b4 100644 --- a/backend/sources/facebook/events-router/src/main/java/co/airy/core/sources/facebook/MessageParser.java +++ b/backend/sources/facebook/events-router/src/main/java/co/airy/core/sources/facebook/MessageParser.java @@ -53,7 +53,7 @@ public Message.Builder parse(final String payload) throws Exception { } else if (appId != null && !appId.equals(this.facebookAppId)) { senderType = SenderType.SOURCE_USER; senderId = appId; - } else if(isEcho && appId == null) { + } else if(appId == null) { senderType = SenderType.SOURCE_USER; senderId = getSourceConversationId(webhookMessaging); } else { diff --git a/backend/sources/google/connector/src/main/java/co/airy/core/sources/google/Stores.java b/backend/sources/google/connector/src/main/java/co/airy/core/sources/google/Stores.java index 243c51b462..b542718934 100644 --- a/backend/sources/google/connector/src/main/java/co/airy/core/sources/google/Stores.java +++ b/backend/sources/google/connector/src/main/java/co/airy/core/sources/google/Stores.java @@ -4,7 +4,6 @@ import co.airy.avro.communication.DeliveryState; import co.airy.avro.communication.Message; import co.airy.avro.communication.Metadata; -import co.airy.avro.communication.SenderType; import co.airy.core.sources.google.model.SendMessageRequest; import co.airy.kafka.schema.application.ApplicationCommunicationChannels; import co.airy.kafka.schema.application.ApplicationCommunicationMessages; @@ -29,6 +28,7 @@ import java.util.concurrent.ExecutionException; +import static co.airy.model.message.MessageRepository.isFromContact; import static co.airy.model.metadata.MetadataRepository.getId; @Component @@ -61,10 +61,11 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { .groupByKey() .aggregate(SendMessageRequest::new, (conversationId, message, aggregate) -> { - if (SenderType.SOURCE_CONTACT.equals(message.getSenderType())) { - aggregate.setSourceConversationId(message.getSenderId()); + final SendMessageRequest.SendMessageRequestBuilder requestBuilder = aggregate.toBuilder(); + if (isFromContact(message)) { + requestBuilder.sourceConversationId(message.getSenderId()); } - return aggregate; + return requestBuilder.build(); }); messageStream.filter((messageId, message) -> DeliveryState.PENDING.equals(message.getDeliveryState())) diff --git a/backend/sources/twilio/connector/src/main/java/co/airy/core/sources/twilio/Stores.java b/backend/sources/twilio/connector/src/main/java/co/airy/core/sources/twilio/Stores.java index 7477967561..10b61572ce 100644 --- a/backend/sources/twilio/connector/src/main/java/co/airy/core/sources/twilio/Stores.java +++ b/backend/sources/twilio/connector/src/main/java/co/airy/core/sources/twilio/Stores.java @@ -5,7 +5,6 @@ import co.airy.avro.communication.DeliveryState; import co.airy.avro.communication.Message; import co.airy.avro.communication.Metadata; -import co.airy.avro.communication.SenderType; import co.airy.core.sources.twilio.dto.SendMessageRequest; import co.airy.kafka.schema.application.ApplicationCommunicationChannels; import co.airy.kafka.schema.application.ApplicationCommunicationMessages; @@ -30,6 +29,7 @@ import java.util.concurrent.ExecutionException; +import static co.airy.model.message.MessageRepository.isFromContact; import static co.airy.model.metadata.MetadataRepository.getId; @Component @@ -72,7 +72,7 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { .aggregate(SendMessageRequest::new, (conversationId, message, aggregate) -> { SendMessageRequest.SendMessageRequestBuilder sendMessageRequestBuilder = aggregate.toBuilder(); - if (SenderType.SOURCE_CONTACT.equals(message.getSenderType())) { + if (isFromContact(message)) { sendMessageRequestBuilder.sourceConversationId(message.getSenderId()); } diff --git a/backend/webhook/publisher/BUILD b/backend/webhook/publisher/BUILD index 641e101902..436c1befd0 100644 --- a/backend/webhook/publisher/BUILD +++ b/backend/webhook/publisher/BUILD @@ -4,7 +4,9 @@ load("//tools/build:container_push.bzl", "container_push") app_deps = [ "//backend:base_app", + "//backend/model/event", "//backend/model/message", + "//backend/model/metadata", "//backend:webhook", "//lib/java/uuid", "//lib/java/date", diff --git a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/Mapper.java b/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/Mapper.java deleted file mode 100644 index 56ad2ecb1e..0000000000 --- a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/Mapper.java +++ /dev/null @@ -1,42 +0,0 @@ -package co.airy.core.webhook.publisher; - -import co.airy.avro.communication.Message; -import co.airy.core.webhook.publisher.model.Postback; -import co.airy.core.webhook.publisher.model.WebhookBody; -import org.springframework.stereotype.Component; - -import java.util.Map; - -import static co.airy.date.format.DateFormat.isoFromMillis; - -@Component -public class Mapper { - public WebhookBody fromMessage(Message message) throws Exception { - return WebhookBody.builder() - .conversationId(message.getConversationId()) - .id(message.getId()) - .content(message.getContent()) - .source(message.getSource()) - .postback(buildPostback(message)) - .sentAt(isoFromMillis(message.getSentAt())) - .sender(WebhookBody.Sender.builder() - .id(message.getSenderId()) - .type(message.getSenderType().toString().toLowerCase()) - .build()) - .build(); - } - - private Postback buildPostback(Message message) { - final Map headers = message.getHeaders(); - - if (headers == null || headers.isEmpty()) { - return null; - } - - return Postback.builder() - .payload(headers.get("postback.payload")) - .referral(headers.get("postback.referral")) - .type(headers.get("postback.type")) - .build(); - } -} diff --git a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/NotATextMessage.java b/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/NotATextMessage.java deleted file mode 100644 index 0cdddea4ec..0000000000 --- a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/NotATextMessage.java +++ /dev/null @@ -1,4 +0,0 @@ -package co.airy.core.webhook.publisher; - -public class NotATextMessage extends Exception { -} diff --git a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/Publisher.java b/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/Publisher.java index 60dcf0172d..17a4a5083e 100644 --- a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/Publisher.java +++ b/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/Publisher.java @@ -2,14 +2,21 @@ import co.airy.avro.communication.DeliveryState; import co.airy.avro.communication.Message; +import co.airy.avro.communication.Metadata; import co.airy.avro.communication.Status; import co.airy.avro.communication.Webhook; -import co.airy.core.webhook.publisher.model.QueueMessage; +import co.airy.core.webhook.publisher.payload.QueueMessage; import co.airy.kafka.schema.application.ApplicationCommunicationMessages; +import co.airy.kafka.schema.application.ApplicationCommunicationMetadata; import co.airy.kafka.schema.application.ApplicationCommunicationWebhooks; import co.airy.kafka.streams.KafkaStreamsWrapper; import co.airy.log.AiryLoggerFactory; +import co.airy.model.event.payload.Event; +import co.airy.model.event.payload.MessageEvent; +import co.airy.model.event.payload.MetadataEvent; +import co.airy.model.metadata.dto.MetadataMap; import org.apache.kafka.streams.KafkaStreams; +import org.apache.kafka.streams.KeyValue; import org.apache.kafka.streams.StreamsBuilder; import org.apache.kafka.streams.kstream.Materialized; import org.apache.kafka.streams.state.ReadOnlyKeyValueStore; @@ -19,6 +26,10 @@ import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; +import java.io.Serializable; + +import static co.airy.model.metadata.MetadataRepository.getSubject; + @Component public class Publisher implements ApplicationListener, DisposableBean { private final Logger log = AiryLoggerFactory.getLogger(Publisher.class); @@ -28,12 +39,10 @@ public class Publisher implements ApplicationListener, private final String allWebhooksKey = "339ab777-92aa-43a5-b452-82e73c50fc59"; private final KafkaStreamsWrapper streams; private final RedisQueue redisQueuePublisher; - private final Mapper mapper; - public Publisher(KafkaStreamsWrapper streams, RedisQueue redisQueuePublisher, Mapper mapper) { + public Publisher(KafkaStreamsWrapper streams, RedisQueue redisQueuePublisher) { this.streams = streams; this.redisQueuePublisher = redisQueuePublisher; - this.mapper = mapper; } private void startStream() { @@ -44,30 +53,46 @@ private void startStream() { .reduce((oldValue, newValue) -> newValue, Materialized.as(webhooksStore)); builder.stream(new ApplicationCommunicationMessages().name()) - .filter(((messageId, message) -> - DeliveryState.DELIVERED.equals(message.getDeliveryState()) && message.getUpdatedAt() == null)) - .foreach((messageId, message) -> { - try { - final ReadOnlyKeyValueStore webhookStore = streams.acquireLocalStore(webhooksStore); - final Webhook webhook = webhookStore.get(allWebhooksKey); - - if (webhook != null && webhook.getStatus().equals(Status.Subscribed)) { - redisQueuePublisher.publishMessage(webhook.getId(), - QueueMessage.builder() - .endpoint(webhook.getEndpoint()) - .headers(webhook.getHeaders()) - .body(mapper.fromMessage(message)) - .build()); - } - } catch (NotATextMessage expected) { - } catch (Exception e) { - log.error("failed to publish webhook", e); - } - }); + .filter((messageId, message) -> message.getUpdatedAt() == null) + .foreach((messageId, message) -> publishRecord(message)); + + builder.table(new ApplicationCommunicationMetadata().name()) + .groupBy((metadataId, metadata) -> KeyValue.pair(getSubject(metadata).getIdentifier(), metadata)) + .aggregate(MetadataMap::new, MetadataMap::adder, MetadataMap::subtractor) + .toStream() + .peek((identifier, metadataMap) -> publishRecord(metadataMap)); streams.start(builder.build(), appId); } + private void publishRecord(Serializable record) { + try { + final ReadOnlyKeyValueStore webhookStore = streams.acquireLocalStore(webhooksStore); + final Webhook webhook = webhookStore.get(allWebhooksKey); + + if (webhook != null && webhook.getStatus().equals(Status.Subscribed)) { + redisQueuePublisher.publishMessage(webhook.getId(), QueueMessage.builder() + .body(fromRecord(record)) + .endpoint(webhook.getEndpoint()) + .headers(webhook.getHeaders()) + .build() + ); + } + } catch (Exception e) { + log.error("failed to publish record", e); + } + } + + private Event fromRecord(Serializable record) throws Exception { + if (record instanceof Message) { + return MessageEvent.fromMessage((Message) record); + } else if (record instanceof MetadataMap) { + return MetadataEvent.fromMetadataMap((MetadataMap) record); + } + + throw new Exception("unknown type for record " + record); + } + @Override public void destroy() { if (streams != null) { diff --git a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/RedisQueue.java b/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/RedisQueue.java index 7c2f2c4b21..4f04a4e524 100644 --- a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/RedisQueue.java +++ b/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/RedisQueue.java @@ -1,7 +1,8 @@ package co.airy.core.webhook.publisher; -import co.airy.core.webhook.publisher.model.QueueMessage; +import co.airy.core.webhook.publisher.payload.QueueMessage; import co.airy.log.AiryLoggerFactory; +import co.airy.model.event.payload.Event; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/model/Postback.java b/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/model/Postback.java deleted file mode 100644 index 2298b7fff8..0000000000 --- a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/model/Postback.java +++ /dev/null @@ -1,18 +0,0 @@ -package co.airy.core.webhook.publisher.model; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class Postback implements Serializable { - public String payload; - public String type; - public String referral; -} diff --git a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/model/WebhookBody.java b/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/model/WebhookBody.java deleted file mode 100644 index 558c237c05..0000000000 --- a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/model/WebhookBody.java +++ /dev/null @@ -1,29 +0,0 @@ -package co.airy.core.webhook.publisher.model; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class WebhookBody implements Serializable { - private String conversationId; - private String id; - private String content; - private Sender sender; - private String sentAt; - private String source; - private Postback postback; - - @Data - @Builder - public static class Sender { - private String id; - private String type; - } -} diff --git a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/model/QueueMessage.java b/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/payload/QueueMessage.java similarity index 75% rename from backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/model/QueueMessage.java rename to backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/payload/QueueMessage.java index 5705344279..613495fb3d 100644 --- a/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/model/QueueMessage.java +++ b/backend/webhook/publisher/src/main/java/co/airy/core/webhook/publisher/payload/QueueMessage.java @@ -1,5 +1,6 @@ -package co.airy.core.webhook.publisher.model; +package co.airy.core.webhook.publisher.payload; +import co.airy.model.event.payload.Event; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -8,12 +9,12 @@ import java.io.Serializable; import java.util.Map; +@Data @Builder @NoArgsConstructor @AllArgsConstructor -@Data public class QueueMessage implements Serializable { private String endpoint; private Map headers; - private WebhookBody body; + private Event body; } 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 23760c34ba..8db0021317 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 @@ -5,8 +5,9 @@ import co.airy.avro.communication.SenderType; import co.airy.avro.communication.Status; import co.airy.avro.communication.Webhook; -import co.airy.core.webhook.publisher.model.QueueMessage; +import co.airy.core.webhook.publisher.payload.QueueMessage; import co.airy.kafka.schema.application.ApplicationCommunicationMessages; +import co.airy.kafka.schema.application.ApplicationCommunicationMetadata; import co.airy.kafka.schema.application.ApplicationCommunicationWebhooks; import co.airy.kafka.test.KafkaTestHelper; import co.airy.kafka.test.junit.SharedKafkaTestResource; @@ -48,11 +49,13 @@ public class PublisherTest { private static KafkaTestHelper kafkaTestHelper; private static final ApplicationCommunicationWebhooks applicationCommunicationWebhooks = new ApplicationCommunicationWebhooks(); private static final ApplicationCommunicationMessages applicationCommunicationMessages = new ApplicationCommunicationMessages(); + private static final ApplicationCommunicationMetadata applicationCommunicationMetadata = new ApplicationCommunicationMetadata(); @BeforeAll static void beforeAll() throws Exception { kafkaTestHelper = new KafkaTestHelper(sharedKafkaTestResource, applicationCommunicationWebhooks, + applicationCommunicationMetadata, applicationCommunicationMessages ); diff --git a/backend/webhook/redis-worker/go.mod b/backend/webhook/redis-worker/go.mod index 867b8bbfd5..4603f85322 100644 --- a/backend/webhook/redis-worker/go.mod +++ b/backend/webhook/redis-worker/go.mod @@ -1,4 +1,4 @@ -module redis-worker +module github.com/airyhq/airy/backend/webhook/redis-worker go 1.13 diff --git a/docs/docs/api/endpoints/channels.md b/docs/docs/api/endpoints/channels.md index 4aa46e4e9a..7f443a3421 100644 --- a/docs/docs/api/endpoints/channels.md +++ b/docs/docs/api/endpoints/channels.md @@ -31,7 +31,8 @@ for more information. "name": "my page 1", // optional "image_url": "http://example.org/avatar.jpeg" - } + }, + "connected": true }, { "id": "channel-uuid-2", @@ -39,7 +40,8 @@ for more information. "source_channel_id": "fb-page-id-2", "metadata": { "name": "my page 2" - } + }, + "connected": true } ] } @@ -68,7 +70,8 @@ for more information. "name": "my page 1", // optional "image_url": "http://example.org/avatar.jpeg" - } + }, + "connected": true } ``` @@ -99,7 +102,8 @@ Update a channel's name or image url. "name": "new name for this channel", // optional "image_url": "http://example.org/avatar_redesign.jpeg" - } + }, + "connected": true } ``` @@ -128,79 +132,22 @@ POST /channels.chatplugin.connect "id": "1F679227-76C2-4302-BB12-703B2ADB0F66", "source": "chat_plugin", "source_channel_id": "website-identifier-42", - "metadata": {"name": "website-identifier-42"} + "metadata": {"name": "website-identifier-42"}, + "connected": true } ``` ### Facebook -Connects a Facebook page to Airy Core. - -``` -POST /channels.facebook.connect -``` - -- `page_id` is the Facebook page id -- `page_token` is the page Access Token -- `name` Custom name for the connected page -- `image_url` Custom image URL - -**Sample request** - -```json5 -{ - "page_id": "fb-page-id-1", - "page_token": "authentication token", - "name": "My custom name for this page", - "image_url": "https://example.org/custom-image.jpg" // optional -} -``` +import ConnectFacebook from './connect-facebook.mdx' -**Sample response** - -```json5 -{ - "id": "channel-uuid-1", - "source": "facebook", - "source_channel_id": "fb-page-id-1", - "metadata": { - "name": "My custom name for this page", - // optional - "image_url": "https://example.org/custom-image.jpg" - } -} -``` + ### Google -Connects a Google Business Account to Airy Core. - -``` -POST /channels.google.connect -``` +import ConnectGoogle from './connect-google.mdx' -- `gbm_id` The id of your Google Business Message [agent](https://developers.google.com/business-communications/business-messages/reference/business-communications/rest/v1/brands.agents#Agent). -- `name` Custom name for the connected business -- `image_url` Custom image URL - -```json5 -{ - "gbm_id": "gbm-id", - "name": "My custom name for this location", - "image_url": "https://example.com/custom-image.jpg" // optional -} -``` - -**Sample response** - -```json5 -{ - "id": "channel-uuid-1", - "metadata": {"name": "My custom name for this location", "image_url": "https://example.com/custom-image.jpg"}, - "source": "google", - "source_channel_id": "gbm-id" -} -``` + ### SMS - Twilio @@ -237,7 +184,8 @@ POST /channels.twilio.sms.connect "id": "channel-uuid-1", "metadata": {"name": "SMS for receipts", "image_url": "https://example.com/custom-image.jpg"}, "source": "twilio.sms", - "source_channel_id": "+491234567" + "source_channel_id": "+491234567", + "connected": true } ``` @@ -277,7 +225,8 @@ POST /channels.twilio.whatsapp.connect "id": "channel-uuid-1", "metadata": {"name": "WhatsApp Marketing", "image_url": "https://example.com/custom-image.jpg"}, "source": "twilio.whatsapp", - "source_channel_id": "whatsapp:+491234567" + "source_channel_id": "whatsapp:+491234567", + "connected": true } ``` diff --git a/docs/docs/api/endpoints/connect-facebook.mdx b/docs/docs/api/endpoints/connect-facebook.mdx new file mode 100644 index 0000000000..39429a459a --- /dev/null +++ b/docs/docs/api/endpoints/connect-facebook.mdx @@ -0,0 +1,36 @@ +Connects a Facebook page to Airy Core. + +``` +POST /channels.facebook.connect +``` + +- `page_id` is the Facebook page id +- `page_token` is the page Access Token +- `name` Custom name for the connected page +- `image_url` Custom image URL + +**Sample request** + +```json5 +{ + "page_id": "fb-page-id-1", + "page_token": "authentication token", + "name": "My custom name for this page", + "image_url": "https://example.org/custom-image.jpg" // optional +} +``` + +**Sample response** + +```json5 +{ + "id": "channel-uuid-1", + "source": "facebook", + "source_channel_id": "fb-page-id-1", + "metadata": { + "name": "My custom name for this page", + // optional + "image_url": "https://example.org/custom-image.jpg" + } +} +``` \ No newline at end of file diff --git a/docs/docs/api/endpoints/connect-google.mdx b/docs/docs/api/endpoints/connect-google.mdx new file mode 100644 index 0000000000..f54d2c4f2e --- /dev/null +++ b/docs/docs/api/endpoints/connect-google.mdx @@ -0,0 +1,28 @@ +Connects a Google Business Account to Airy Core. + +``` +POST /channels.google.connect +``` + +- `gbm_id` The id of your Google Business Message [agent](https://developers.google.com/business-communications/business-messages/reference/business-communications/rest/v1/brands.agents#Agent). +- `name` Custom name for the connected business +- `image_url` Custom image URL + +```json5 +{ + "gbm_id": "gbm-id", + "name": "My custom name for this location", + "image_url": "https://example.com/custom-image.jpg" // optional +} +``` + +**Sample response** + +```json5 +{ + "id": "channel-uuid-1", + "metadata": {"name": "My custom name for this location", "image_url": "https://example.com/custom-image.jpg"}, + "source": "google", + "source_channel_id": "gbm-id" +} +``` \ No newline at end of file diff --git a/docs/docs/api/endpoints/conversations.md b/docs/docs/api/endpoints/conversations.md index 6fc7efb1d8..91b6a6c879 100644 --- a/docs/docs/api/endpoints/conversations.md +++ b/docs/docs/api/endpoints/conversations.md @@ -67,7 +67,7 @@ Find users whose name ends with "Lovelace": }, "last_message": { id: "{UUID}", - "content": '{"text":"Hello World"}', + "content": {"text": "Hello World"}, // source message payload // typed source message model state: "{String}", @@ -128,7 +128,7 @@ Find users whose name ends with "Lovelace": }, "last_message": { "id": "{UUID}", - "content": '{"text":"Hello World"}', + "content": {"text": "Hello World"}, // source message payload // typed source message model "delivery_state": "{String}", diff --git a/docs/docs/api/endpoints/google-messages-send.mdx b/docs/docs/api/endpoints/google-messages-send.mdx new file mode 100644 index 0000000000..f398b07c58 --- /dev/null +++ b/docs/docs/api/endpoints/google-messages-send.mdx @@ -0,0 +1,44 @@ +`POST /messages.send` + +Sends a message to a conversation from Google's Business Messages source and returns a payload. + +Note that when you send a message from this source, you must specify a representative, the individual or automation that composed the message. +Google's Business Messages supports "BOT" and "HUMAN" as the `representativeType`. Find more information on [Google's Business Messages guide](https://developers.google.com/business-communications/business-messages/guides/build/send). + +Whatever is put on the `message` field will be forwarded "as-is" to the source's message endpoint. + +**Sending a text message** + +```json5 +{ + conversation_id: 'a688d36c-a85e-44af-bc02-4248c2c97622', + message: { + text: 'Hello World Agent!', + representative: { + displayName: 'Hello World from Agent', + avatarImage: 'REPRESENTATIVE_AVATAR_URL', + representativeType: 'HUMAN', + }, + }, +} +``` + +**Sample response** + +```json5 +{ + id: '{UUID}', + content: '{"text":"Hello"}', + state: 'pending|failed|delivered', + sender_type: '{string/enum}', + // See glossary + sent_at: '{string}', + //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients + source: '{String}', + // one of the possible sources + metadata: { + sentFrom: 'iPhone', + }, + // metadata object of the message +} +``` diff --git a/docs/docs/api/endpoints/messages-send.mdx b/docs/docs/api/endpoints/messages-send.mdx new file mode 100644 index 0000000000..8caa442c4f --- /dev/null +++ b/docs/docs/api/endpoints/messages-send.mdx @@ -0,0 +1,35 @@ +`POST /messages.send` + +Sends a message to a conversation and returns a payload. Whatever is put on the +`message` field will be forwarded "as-is" to the source's message endpoint. + +**Sending a text message** + +```json5 +{ + "conversation_id": "a688d36c-a85e-44af-bc02-4248c2c97622", + "message": { + "text": "Hello World" + } +} +``` + +**Sample response** + +```json5 +{ + "id": "{UUID}", + "content": '{"text":"Hello"}', + "state": "pending|failed|delivered", + "sender_type": "{string/enum}", + // See glossary + "sent_at": "{string}", + //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients + "source": "{String}", + // one of the possible sources + "metadata": { + "sentFrom": "iPhone" + } + // metadata object of the message +} +``` \ No newline at end of file diff --git a/docs/docs/api/endpoints/messages.md b/docs/docs/api/endpoints/messages.md index 05bf8b6edd..34c9fa1f3e 100644 --- a/docs/docs/api/endpoints/messages.md +++ b/docs/docs/api/endpoints/messages.md @@ -30,7 +30,7 @@ latest. "data": [ { "id": "{UUID}", - "content": '{"text":"Hello World"}', + "content": {"text": "Hello World"}, // source message payload // typed source message model "state": "{String}", @@ -58,38 +58,12 @@ latest. ## Send -`POST /messages.send` +import MessagesSend from './messages-send.mdx' -Sends a message to a conversation and returns a payload. Whatever is put on the -`message` field will be forwarded "as-is" to the source's message endpoint. + -**Sending a text message** +## Send from Google's Business Messages source -```json5 -{ - "conversation_id": "a688d36c-a85e-44af-bc02-4248c2c97622", - "message": { - "text": "Hello World" - } -} -``` - -**Sample response** +import GoogleMessagesSend from './google-messages-send.mdx' -```json5 -{ - "id": "{UUID}", - "content": '{"text":"Hello"}', - "state": "pending|failed|delivered", - "sender_type": "{string/enum}", - // See glossary - "sent_at": "{string}", - //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients - "source": "{String}", - // one of the possible sources - "metadata": { - "sentFrom": "iPhone" - } - // metadata object of the message -} -``` + diff --git a/docs/docs/api/event-payloads.mdx b/docs/docs/api/event-payloads.mdx new file mode 100644 index 0000000000..d7faed2e45 --- /dev/null +++ b/docs/docs/api/event-payloads.mdx @@ -0,0 +1,62 @@ +### Message + +```json5 +{ + "type": "message", + "payload": { + "conversation_id": "{UUID}", + "channel_id": "{UUID}", + "message": { + "id": "{UUID}", + "content": {"text":"Hello World"}, + // source message payload + "delivery_state": "pending|failed|delivered", + // delivery state of message, one of pending, failed, delivered + "sender_type": "{string/enum}", + // See glossary + "sent_at": "{string}", + //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients + "source": "{String}", + // one of the possible sources + "metadata": {} // empty + } + } +} +``` + +### Metadata + +**Sample payload** + +```json5 +{ + "type": "metadata", + + "payload": { + "subject": "conversation|channel|message", + "identifier": "conversation/channel/message id", + "metadata": { + // nested metadata object. I.e. for a conversation: + "contact": { + "displayName": "Grace" + }, + "isUserTyping": true + } + } +} +``` + +### Channel + +```json5 +{ + "type": "channel", + + "payload": { + "id": "{UUID}", + "source": "facebook", + "source_channel_id": "fb-page-id-1", + "connected": true // or false + } +} +``` diff --git a/docs/docs/api/webhook.md b/docs/docs/api/webhook.md index 7a11de9af0..9a7a9b2b0e 100644 --- a/docs/docs/api/webhook.md +++ b/docs/docs/api/webhook.md @@ -86,25 +86,55 @@ Subscribes the webhook for the first time or update its parameters. } ``` -## Event Payload +## Event Payloads After [subscribing](#subscribing) to an Airy webhook, you will start receiving events on your URL of choice. The event will _always_ be a POST -request with the following structure: +request with one the following payloads: + +### Message ```json5 { - "conversation_id": "4242424242", - "id": "7560bf66-d9c4-48f8-b7f1-27ab6c40a40a", - "sender": { - "id": "adac9220-fe7b-40a8-98e5-2fcfaf4a53b5", - "type": "source_contact" - }, - "source": "facebook", - "sent_at": "2020-07-20T14:18:08.584Z", - "content": '{"text":"Hello World"}' + "type": "message", + "payload": { + "conversation_id": "{UUID}", + "channel_id": "{UUID}", + "message": { + "id": "{UUID}", + "content": {"text": "Hello World"}, + // source message payload + "delivery_state": "pending|failed|delivered", + // delivery state of message, one of pending, failed, delivered + "sender_type": "{string/enum}", + // See glossary + "sent_at": "{string}", + //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients + "source": "{String}" + // one of the possible sources + } + } } ``` -For possible values of `sender.type` see the [Message model -documentation](/getting-started/glossary.md#fields) +### Metadata + +**Sample payload** + +```json5 +{ + "type": "metadata", + + "payload": { + "subject": "conversation|channel|message", + "identifier": "conversation/channel/message id", + "metadata": { + // nested metadata object. I.e. for a conversation: + "contact": { + "displayName": "Grace" + }, + "isUserTyping": true + } + } +} +``` diff --git a/docs/docs/api/websocket.md b/docs/docs/api/websocket.md index dff6bc1d3f..006a661579 100644 --- a/docs/docs/api/websocket.md +++ b/docs/docs/api/websocket.md @@ -14,13 +14,13 @@ near real-time updates**. The WebSocket server uses the [STOMP](https://en.wikipedia.org/wiki/Streaming_Text_Oriented_Messaging_Protocol) -protocol endpoint at `/ws.events`. +protocol endpoint at `/ws.communication`. -To execute the handshake with `/ws.events` you must set an +To execute the handshake with `/ws.communication` you must set an `Authorization` header where the value is the authorization token obtained [from the API](/api/introduction#authentication). -## Event Types +## Event Payloads All event updates are sent to the `/events` queue as JSON encoded payloads. The `type` field informs the client of the kind of update that is encoded in the payload. @@ -35,7 +35,7 @@ field informs the client of the kind of update that is encoded in the payload. "channel_id": "{UUID}", "message": { "id": "{UUID}", - "content": '{"text":"Hello World"}', + "content": {"text": "Hello World"}, // source message payload "delivery_state": "pending|failed|delivered", // delivery state of message, one of pending, failed, delivered @@ -43,12 +43,8 @@ field informs the client of the kind of update that is encoded in the payload. // See glossary "sent_at": "{string}", //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients - "source": "{String}", + "source": "{String}" // one of the possible sources - "metadata": { - "sentFrom": "iPhone" - } - // metadata object of the message } } } @@ -84,7 +80,6 @@ field informs the client of the kind of update that is encoded in the payload. "payload": { "id": "{UUID}", - "name": "my page 1", "source": "facebook", "source_channel_id": "fb-page-id-1", "connected": true // or false diff --git a/docs/docs/apps/ui/introduction.md b/docs/docs/apps/ui/introduction.md index 9cb00a7385..f92e0ee9f2 100644 --- a/docs/docs/apps/ui/introduction.md +++ b/docs/docs/apps/ui/introduction.md @@ -22,9 +22,9 @@ Additional features like [Filters, Search](inbox) and [Tags](tags) help you. } - title='UI Quick Start' + title='UI Quickstart' description='Step by Step Guide on getting up and running with the UI' - link='apps/ui/ui-quick-start' + link='apps/ui/quickstart' /> } diff --git a/docs/docs/apps/ui/quickstart.md b/docs/docs/apps/ui/quickstart.md new file mode 100644 index 0000000000..95b3956a56 --- /dev/null +++ b/docs/docs/apps/ui/quickstart.md @@ -0,0 +1,79 @@ +--- +title: Quickstart +sidebar_label: Quickstart +--- + +import SuccessBox from "@site/src/components/SuccessBox"; +import useBaseUrl from '@docusaurus/useBaseUrl'; +import ButtonBox from "@site/src/components/ButtonBox"; +import TLDR from "@site/src/components/TLDR"; +import PrototypeSVG from "@site/static/icons/prototype.svg"; + + + +In this Quickstart we are **creating a user** and using its credentials to **log +in to the UI** + + + +- [Introduction](#introduction) +- [Step 1: Registration](#step-1-registration) +- [Step 2: Open the UI](#step-2-open-the-ui) +- [Step 3: Login](#step-3-login) + +## Introduction + +The easiest way to see the Airy Core UI in action is to launch the +[Vagrant Box](getting-started/deployment/vagrant.md) and then follow these three +simple steps. + +## Step 1: Registration + +To register a new user in the Airy Core you can use the **users.signup** +[endpoint](api/endpoints/users.md#signup). + +Alternatively you can run the +following [CLI command](cli/reference.md#api-signup) to create a default user. +You can also pass your own email and password as parameters. Otherwise these default +credentials will be used: + +``` +username: grace@example.com +password: the_answer_is_42 +``` + +```bash +airy api signup +``` + +## Step 2: Open the UI + +Either go to [ui.airy/login](http://ui.airy/login) in your browser or execute +this [CLI command](cli/reference.md#api-login): + +```bash +airy ui +``` + +## Step 3: Login + +Now enter your credentials on the login page and click **Login**. + +login + + + +:tada: **SUCCESS!** + +**You are now in the UI!** + + + +### + + } +title='Next Step: Discover the Inbox' +description='Now that you can access the UI it is time to discover the Inbox and its features ' +link='apps/ui/inbox' +/> diff --git a/docs/docs/apps/ui/ui-quick-start.md b/docs/docs/apps/ui/ui-quick-start.md deleted file mode 100644 index 910eec97b0..0000000000 --- a/docs/docs/apps/ui/ui-quick-start.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: UI Quick Start -sidebar_label: UI Quick Start ---- - -**Step 1: Create a user** - -Now that Airy is installed you first need to create a user. A user enables you to then login to the UI and access the inbox and other features. - -**Step 2: Open UI** - -Now that you have a user you can use it to simply login into the UI. For that just open http://ui.airy. - -**Step 3: Login** - -Using the credentials you created in Step 1, login to your Airy UI. - -:tada: Success! -You are now in the UI! diff --git a/docs/docs/cli/reference.md b/docs/docs/cli/reference.md index a62dbb146c..40b3700832 100644 --- a/docs/docs/cli/reference.md +++ b/docs/docs/cli/reference.md @@ -81,6 +81,31 @@ airy config apply [flags] ``` +*** + +## Create + +Creates an instance of Airy Core + +``` +airy create [flags] +``` + +#### Options + +``` + -h, --help help for create + --provider string One of the supported providers (aws|local). Default is aws +``` + +#### Options inherited from parent commands + +``` + --apihost string Airy Core HTTP API host (default "http://api.airy") + --cli-config string config file (default is $HOME/.airy/cli.yaml) +``` + + *** ## Init diff --git a/docs/docs/getting-started/deployment/vagrant.md b/docs/docs/getting-started/deployment/vagrant.md index 56e437cb0e..280cfc1419 100644 --- a/docs/docs/getting-started/deployment/vagrant.md +++ b/docs/docs/getting-started/deployment/vagrant.md @@ -164,14 +164,7 @@ You can ssh inside the Airy Core box for testing and debugging purposes with ### Status -To view the status of the Vagrant box run: - -```sh -cd infrastructure -vagrant status -``` - -or +Run the following `status command` to print the information on how to access Airy Core. ```sh cd infrastructure @@ -197,6 +190,13 @@ ${TWILIO_WEBHOOK_PUBLIC_URL}/twilio "curl -X POST -H 'Content-Type: application/json' -d '{\"first_name\": \"Grace\",\"last_name\": \"Hopper\",\"password\": \"the_answer_is_42\",\"email\": \"grace@example.com\"}' ``` +To inspect the status of the Vagrant box run: + +```sh +cd infrastructure +vagrant status +``` + ### Overwrite default CPUs and memory You can specify number of CPU and memory (in MB) you want to use for your Airy Core box with the following ENV variables: diff --git a/docs/docs/sources/chat-plugin.md b/docs/docs/sources/chat-plugin.md index 032ada16d4..9eb9a36b91 100644 --- a/docs/docs/sources/chat-plugin.md +++ b/docs/docs/sources/chat-plugin.md @@ -98,7 +98,7 @@ previous conversation using the [resume endpoint](#get-a-resume-token). "messages": [ { "id": "{UUID}", - "content": '{"text":"Hello World"}', + "content": {"text": "Hello World"}, // source message payload "state": "{String}", // delivery state of message, one of PENDING, FAILED, DELIVERED @@ -158,7 +158,7 @@ header. ```json5 { id: "{UUID}", - "content": '{"text":"Hello World"}', + "content": {"text": "Hello World"}, // source message payload state: "{String}", // delivery state of message, one of PENDING, FAILED, DELIVERED @@ -191,7 +191,7 @@ The WebSocket connection endpoint is at `/ws.chatplugin`. { message: { id: "{UUID}", - "content": '{"text":"Hello World"}', + "content": {"text": "Hello World"}, // source message payload state: "{String}", // delivery state of message, one of PENDING, FAILED, DELIVERED diff --git a/docs/docs/sources/facebook.md b/docs/docs/sources/facebook.md index bba34dc1f8..70fb07ae65 100644 --- a/docs/docs/sources/facebook.md +++ b/docs/docs/sources/facebook.md @@ -5,6 +5,10 @@ sidebar_label: Facebook Messenger import useBaseUrl from '@docusaurus/useBaseUrl'; import TLDR from "@site/src/components/TLDR"; +import ButtonBox from "@site/src/components/ButtonBox"; +import BoltSVG from "@site/static/icons/bolt.svg"; +import InboxSVG from "@site/static/icons/prototype.svg"; +import SuccessBox from "@site/src/components/SuccessBox"; @@ -27,48 +31,91 @@ Core Platform instance. The Facebook source requires the following configuration: -- An app id and an app secret so that the platform can send messages back via - your Facebook application -- A webhook integration so that the platform can ingest messages from your - Facebook pages -- A page token for each facebook page you intend to integrate - -Refer to the [Configuration Docs](/getting-started/deployment/configuration.md#components) on how to input these values. +- [Step 1: Find the App ID and Secret](#step-1-find-the-app-id-and-secret) +- [Step 2: Configure the webhook integration](#step-2-configure-the-webhook-integration) +- [Step 3: Obtain the page token](#step-3-obtain-the-page-token) Let's proceed step by step. -### Find the app id and secret +### Step 1: Find the App ID and Secret To connect a page, you must have an approved Facebook app. If you don't have -one, you must to create it before proceeding. Once you are done with the -configuration, you should see something like this: +one, you must register and create a Business app on [Facebook for Developers](https://developers.facebook.com/). + +All your registered apps are listed on [developers.facebook.com/apps](https://developers.facebook.com/apps/). Facebook apps page -Note down the `App ID` of your Facebook application and then head to the basic -settings page. Here you will find your `App Secret`: +The dashboard of each registered apps can be found on: -Facebook apps page +``` +https://developers.facebook.com/apps/INSERT_YOUR_APP_ID_HERE/dashboard/ +``` -Now you can use the app id and the app secret for the following environment variables: +On your application's dashboard, note down the `App ID` of your application and then head to the Basic Settings page. + +``` +https://developers.facebook.com/apps/INSERT_YOUR_APP_ID_HERE/settings/basic/ +``` + +You will find your `App Secret` on this page: + +Facebook apps page -- `FACEBOOK_APP_ID` -- `FACEBOOK_APP_SECRET` +Copy and paste your App ID and App Secret as strings next to `appId` and `appSecret` below `sources/facebook` in `infrastructure/airy.yaml`. :::note +Refer to the [Configuration Docs](/getting-started/deployment/configuration.md#components) on how to input these values. + Refer to the [test](getting-started/deployment/vagrant.md#connect-sources) guide or the [production](getting-started/deployment/production.md#connect-sources) one to set these variables in your Airy Core instance. ::: -### Configure the webhook integration +### Step 2: Configure the webhook integration + +Facebook must first verify your integration with a challenge to start sending events to your running instance. To verify your Facebook webhook integration, set the environment variable `webhookSecret` in `infrastructure/airy.yaml` to a value of your choice. + +You are now ready to configure the webhook integration. Click on the + icon next to "Products" on the left sidebar of your app's dashboard: scroll down, a list of products will appear. + +``` +https://developers.facebook.com/apps/INSERT_YOUR_APP_ID_HERE/dashboard/#addProduct +``` + +Click on the button "Set Up" on the Webhooks product card. + +Facebook webhook add product + +This will add the Webhooks as one of your app's products and will lead you to the Webhooks product page. -For Facebook to start sending events to your running instance, it must first -verify your integration with a challenge. To verify your Facebook webhook -integration, you must set the environment variable `FACEBOOK_WEBHOOK_SECRET` to -a value of your choice. +``` +https://developers.facebook.com/apps/INSERT_YOUR_APP_ID_HERE/webhooks/ +``` + +Facebook webhook + +Select "Page" from the dropdown (the default is "User") and click on the button "Subscribe to this object". + +This will open a modal box: add your Callback URL (your instance's Facebook Webhook URL) and Verify Token (the webhookSecret you added in `infrastructure/airy.yaml` in the previous step). + +Facebook webhook + +
+ +:::note + +Your Facebook Webhook URL should have the following format: + +``` +`https://fb-RANDOM_STRING.tunnel.airy.co/facebook` +``` + +Refer to [our section on the Vagrant box status](/getting-started/deployment/vagrant#status) for information about how to find your Facebook Webhook URL. +::: + +If you encounter errors, please make sure that the Verify Token matches the `webhookSecret` in `infrastructure/airy.yaml` and that your variables have been successfully set to your Airy Core instance. :::note @@ -79,24 +126,95 @@ set these variables in your Airy Core instance. ::: -Then you are ready to configure the webhook integration. Head to the dashboard -of your Facebook application, find the "Webhooks" link on the left menu and then -click on "Edit subscription". You will see something like this: - -Facebook edit subscription - Once the verification process has been completed, Facebook will immediately start sending events to your Airy Core instance. -### Obtain a page token +### Step 3: Obtain the page token + +Go to the Products page (click on the + icon next to Products on the left sidebar). + +Click on the button "Set Up" on the Messenger product card. + +``` +https://developers.facebook.com/apps/INSERT_YOUR_APP_ID_HERE/dashboard/#addProduct +``` + +Facebook messenger product + +This will add Messenger as one of your app's products and will lead you to the Messenger product page. + +Notice that at the bottom of the page, the Webhooks product has been added with the variables you gave at the previous step. + +Facebook messenger product + +Click on the blue button "Add or Remove Pages" and select your page. + +Once your page has been added, scroll down and click on the button "Add Subscriptions". + +Facebook page subscriptions + +This opens a modal box: tick "messages" and "messaging_postbacks" from the Subscription Fields list. + +Facebook page subscriptions + +Next, scroll up, and click on the button "Generate Token". + +Facebook page token + +This will open a pop-up revealing your page Access Token. Copy it, you will need it to connect the Facebook page to your instance. + +Facebook page token + +
+
+ + + +Success! You are now ready to connect a Facebook page to your Airy Core instance 🎉 + + + +## Connect a Facebook page to your instance + +The next step is to send a request to the [Channels endpoint](/api/endpoints/channels#facebook) to connect a Facebook page to your instance. + + } +title='Channels endpoint' +description='Connect a Facebook source to your Airy Core instance through the Channels endpoint' +link='api/endpoints/channels#facebook' +/> + +
+ +import ConnectFacebook from '../api/endpoints/connect-facebook.mdx' + + + +## Send messages from a Facebook source + +After connecting the source to your instance, you will be able to send messages through the [Messages endpoint](/api/endpoints/messages#send). + + } +title='Messages endpoint' +description='Send messages to your Airy Core instance from a Facebook source through the Messages endpoint' +link='api/endpoints/messages#send' +/> + +
+ +import MessagesSend from '../api/endpoints/messages-send.mdx' -The next step is to obtain a page token, so that Airy Core can send messages on -behalf of your page. The fastest way to get one is to use the graph explorer -that Facebook provides [Graph -Explorer](https://developers.facebook.com/tools/explorer/). + -On the `User or Page` option, select `Get Page Token` and click on `Generate Access Token`: +## Send and receive messages with the Inbox UI -Facebook token page +Now that you connected Facebook Messenger to your instance and started a conversation, you can see the conversations, messages, and templates in the [Airy Inbox](/apps/ui/inbox), and use it to respond to the messages. -You're now ready to connect a Facebook page to your Airy Core instace 🎉. + } +title='Inbox' +description='Receive messages from a Facebook source and send messages using the Inbox UI' +link='apps/ui/inbox' +/> diff --git a/docs/docs/sources/google.md b/docs/docs/sources/google.md index 387f71f569..cedc85ec1d 100644 --- a/docs/docs/sources/google.md +++ b/docs/docs/sources/google.md @@ -3,7 +3,12 @@ title: Google’s Business Messages sidebar_label: Google’s Business Messages --- +import useBaseUrl from '@docusaurus/useBaseUrl'; import TLDR from "@site/src/components/TLDR"; +import ButtonBox from "@site/src/components/ButtonBox"; +import BoltSVG from "@site/static/icons/bolt.svg"; +import InboxSVG from "@site/static/icons/prototype.svg"; +import SuccessBox from "@site/src/components/SuccessBox"; @@ -11,28 +16,98 @@ Start receiving and sending messages from **Google Maps & Google Search**. -The Google source provides a channel of communication between your Google +Google's Business Messages source provides a channel of communication between your Google Business Location and your running instance of Airy Core. :::tip What you will learn -- The required steps to configure the Google source +- The required steps to configure Google's Business Messages source - How to connect a Google Business Location to Airy Core ::: ## Configuration -The first step is to copy the Google Service Account file provided by Google to -`infrastructure/airy.yaml` as a one line string +Google's Business Messages requires the following configuration: + +- [Step 1: Registration](#step-1-registration) +- [Step 2: Editing of the yaml file in Airy Core](#step-2-editing-of-the-yaml-file-in-airy-core) +- [Step 3: Verification by Google](#step-3-verification-by-google) + +Let's proceed step by step. + +## Step 1: Registration + +You first need to be registered with Google's Business Messages before configuring this source. Refer to [Google's Business Messages' guides](https://developers.google.com/business-communications/business-messages/guides) to find more information about this source and the registration process. + +## Step 2: Editing of the yaml file in Airy Core + +Once you are registered, head over to your Google Service Account and create a key in json format. + +Facebook token page + +Copy the Google Service Account key file provided by Google to +`infrastructure/airy.yaml` as a one line string, below `sources/google`: ``` GOOGLE_SA_FILE= ``` +## Step 3: Verification by Google + As a security measure, every request sent by Google is signed and verified against your partner key. You must also set the environment variable -`GOOGLE_PARTNER_KEY` to your partner key +`GOOGLE_PARTNER_KEY` to your partner key. + + + +Once the verification process has been completed, Google's Business Messages will immediately start sending events to your Airy Core instance 🎉 + + + +## Connecting Google's Business Messages to your Airy Core instance + +After the configuration, you can connect Google's Business Messages source to your instance by sending a request to the [Channels endpoint](/api/endpoints/channels#google). + + } +title='Channels endpoint' +description="Connect Google's Business Messages source to your Airy Core instance through the Channels endpoint" +link='api/endpoints/channels#google' +/> + +
+ +import ConnectGoogle from '../api/endpoints/connect-google.mdx' + + + +## Send messages from Google's Business Messages source + +After connecting the source to your instance, you will be able to send messages through the [Messages endpoint](/api/endpoints/messages#send). + + } +title='Messages endpoint' +description="Send messages to your Airy Core instance from Google's Business Messages through the Messages endpoint" +link='api/endpoints/messages#send-from-googles-business-messages-source' +/> + +
+ +import GoogleMessagesSend from '../api/endpoints/google-messages-send.mdx' + + + +## Send and receive messages with the Inbox UI + +Now that you connected Google's Business Messages to your instance and started a conversation, you can see the conversations, messages in the [Airy Inbox](/apps/ui/inbox), and use it to respond to the messages. + +The [Inbox's UI](/apps/ui/inbox) is able render the messages types you can send from Google's Business Messages, such as Rich Cards or Suggestions. -Once the verification process has been completed, Google will immediately start -sending events to your Airy Core instance. + } +title='Inbox' +description="Receive messages from Google's Business Messages and send messages using the Inbox UI" +link='apps/ui/inbox' +/> diff --git a/docs/sidebars.js b/docs/sidebars.js index 8e85675162..0fb9bc606e 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -52,7 +52,7 @@ module.exports = { { '✨ Apps': [ { - UI: ['apps/ui/introduction', 'apps/ui/ui-quick-start', 'apps/ui/inbox', 'apps/ui/tags', 'apps/ui/components'], + UI: ['apps/ui/introduction', 'apps/ui/quickstart', 'apps/ui/inbox', 'apps/ui/tags', 'apps/ui/components'], }, ], }, diff --git a/docs/src/components/ButtonBox/index.js b/docs/src/components/ButtonBox/index.js index 7d8c14293b..e78d7638f0 100644 --- a/docs/src/components/ButtonBox/index.js +++ b/docs/src/components/ButtonBox/index.js @@ -29,23 +29,12 @@ const ButtonBox = ({children, icon, title, description, link, customizedBackgrou return ( {icon && icon()}
-

- {title} -

-

+

{title}

+

{description}

diff --git a/docs/src/components/ButtonBox/styles.module.css b/docs/src/components/ButtonBox/styles.module.css index 39d8ca2b68..92eb8007bd 100644 --- a/docs/src/components/ButtonBox/styles.module.css +++ b/docs/src/components/ButtonBox/styles.module.css @@ -1,35 +1,69 @@ -.containerDark { +.containerLight { display: flex; align-items: center; padding: 1em; border-radius: 8px; cursor: pointer; transition: all 0.2s ease-in-out; - background-color: #4bb3fd; + background-color: white; + border: 3px solid #4bb3fd; box-shadow: none; } -.containerLight { +.containerLightTitle { + color: black; + margin: 0px; +} + +.containerLightDescription { + color: #303030; + font-size: 14px; + margin: 0px; +} + +.containerLight:hover { + box-shadow: 0 0 8px 0px #4bb3fd; + text-decoration: none; +} + +.containerDark { display: flex; align-items: center; padding: 1em; border-radius: 8px; cursor: pointer; transition: all 0.2s ease-in-out; - background-color: #4bb3fd; + background-color: #20201d; + border: 3px solid #4bb3fd; box-shadow: none; } -.containerLight:hover, +.containerDarkTitle { + color: white; + margin: 0px; +} + +.containerDarkDescription { + color: white; + font-size: 14px; + margin: 0px; +} + .containerDark:hover { - box-shadow: 0px 0px 0px 4px #3678e5; + box-shadow: 0 0 8px 0px #4bb3fd; text-decoration: none; } -.containerLight svg, +.containerLight svg { + height: 32px; + width: 32px; + margin-right: 12px; + fill: black; +} + .containerDark svg { - color: white; height: 32px; width: 32px; margin-right: 12px; + fill: white; } diff --git a/docs/static/icons/bolt.svg b/docs/static/icons/bolt.svg index 44431de7d8..94ca91b944 100644 --- a/docs/static/icons/bolt.svg +++ b/docs/static/icons/bolt.svg @@ -1,3 +1,3 @@ - + diff --git a/docs/static/icons/cloud.svg b/docs/static/icons/cloud.svg index 691e506b0a..00c333f289 100644 --- a/docs/static/icons/cloud.svg +++ b/docs/static/icons/cloud.svg @@ -2,7 +2,7 @@ - + diff --git a/docs/static/icons/deploy.svg b/docs/static/icons/deploy.svg index f78c42b9c3..801b63473b 100644 --- a/docs/static/icons/deploy.svg +++ b/docs/static/icons/deploy.svg @@ -1,3 +1,3 @@ - + diff --git a/docs/static/icons/drafting.svg b/docs/static/icons/drafting.svg index b29de6c7e0..04a3a11ce5 100644 --- a/docs/static/icons/drafting.svg +++ b/docs/static/icons/drafting.svg @@ -1,3 +1,3 @@ - + \ No newline at end of file diff --git a/docs/static/icons/information-architecture.svg b/docs/static/icons/information-architecture.svg index 2f69241178..4bacfd2559 100644 --- a/docs/static/icons/information-architecture.svg +++ b/docs/static/icons/information-architecture.svg @@ -1,3 +1,3 @@ - + diff --git a/docs/static/icons/prototype.svg b/docs/static/icons/prototype.svg index 5e71631da9..f9f7cb0911 100644 --- a/docs/static/icons/prototype.svg +++ b/docs/static/icons/prototype.svg @@ -1,3 +1,3 @@ - + diff --git a/docs/static/icons/server-stack.svg b/docs/static/icons/server-stack.svg index 285f9aa5c9..6a1d469b82 100644 --- a/docs/static/icons/server-stack.svg +++ b/docs/static/icons/server-stack.svg @@ -1,3 +1,3 @@ - + diff --git a/docs/static/img/apps/ui/login.gif b/docs/static/img/apps/ui/login.gif new file mode 100644 index 0000000000..5ac41431bd Binary files /dev/null and b/docs/static/img/apps/ui/login.gif differ diff --git a/docs/static/img/sources/facebook/add_subscriptions.png b/docs/static/img/sources/facebook/add_subscriptions.png new file mode 100644 index 0000000000..8d7cfd1fb2 Binary files /dev/null and b/docs/static/img/sources/facebook/add_subscriptions.png differ diff --git a/docs/static/img/sources/facebook/apps.jpg b/docs/static/img/sources/facebook/apps.jpg index ba7457d40e..20647bceeb 100644 Binary files a/docs/static/img/sources/facebook/apps.jpg and b/docs/static/img/sources/facebook/apps.jpg differ diff --git a/docs/static/img/sources/facebook/edit_page_subs.png b/docs/static/img/sources/facebook/edit_page_subs.png new file mode 100644 index 0000000000..a8f9fcf5b4 Binary files /dev/null and b/docs/static/img/sources/facebook/edit_page_subs.png differ diff --git a/docs/static/img/sources/facebook/messenger.png b/docs/static/img/sources/facebook/messenger.png new file mode 100644 index 0000000000..ad719a8597 Binary files /dev/null and b/docs/static/img/sources/facebook/messenger.png differ diff --git a/docs/static/img/sources/facebook/messenger_product.png b/docs/static/img/sources/facebook/messenger_product.png new file mode 100644 index 0000000000..1822dd0c34 Binary files /dev/null and b/docs/static/img/sources/facebook/messenger_product.png differ diff --git a/docs/static/img/sources/facebook/secret.png b/docs/static/img/sources/facebook/secret.png index 3523278573..a0aea41344 100644 Binary files a/docs/static/img/sources/facebook/secret.png and b/docs/static/img/sources/facebook/secret.png differ diff --git a/docs/static/img/sources/facebook/token.jpg b/docs/static/img/sources/facebook/token.jpg deleted file mode 100644 index ee549167b9..0000000000 Binary files a/docs/static/img/sources/facebook/token.jpg and /dev/null differ diff --git a/docs/static/img/sources/facebook/tokenMessenger_popUp.png b/docs/static/img/sources/facebook/tokenMessenger_popUp.png new file mode 100644 index 0000000000..d31a87d872 Binary files /dev/null and b/docs/static/img/sources/facebook/tokenMessenger_popUp.png differ diff --git a/docs/static/img/sources/facebook/token_messenger.png b/docs/static/img/sources/facebook/token_messenger.png new file mode 100644 index 0000000000..c921750d4c Binary files /dev/null and b/docs/static/img/sources/facebook/token_messenger.png differ diff --git a/docs/static/img/sources/facebook/webhook.png b/docs/static/img/sources/facebook/webhook.png deleted file mode 100644 index b11ae780c5..0000000000 Binary files a/docs/static/img/sources/facebook/webhook.png and /dev/null differ diff --git a/docs/static/img/sources/facebook/webhookProduct.png b/docs/static/img/sources/facebook/webhookProduct.png new file mode 100644 index 0000000000..2b7a76e469 Binary files /dev/null and b/docs/static/img/sources/facebook/webhookProduct.png differ diff --git a/docs/static/img/sources/facebook/webhook_1.png b/docs/static/img/sources/facebook/webhook_1.png new file mode 100644 index 0000000000..e862ede4f6 Binary files /dev/null and b/docs/static/img/sources/facebook/webhook_1.png differ diff --git a/docs/static/img/sources/facebook/webhook_2.png b/docs/static/img/sources/facebook/webhook_2.png new file mode 100644 index 0000000000..e35bf8deef Binary files /dev/null and b/docs/static/img/sources/facebook/webhook_2.png differ diff --git a/docs/static/img/sources/google/key.png b/docs/static/img/sources/google/key.png new file mode 100644 index 0000000000..1230826465 Binary files /dev/null and b/docs/static/img/sources/google/key.png differ diff --git a/frontend/ui/src/assets/images/empty-state/inbox-empty-state.svg b/frontend/assets/images/empty-state/inbox-empty-state.svg similarity index 100% rename from frontend/ui/src/assets/images/empty-state/inbox-empty-state.svg rename to frontend/assets/images/empty-state/inbox-empty-state.svg diff --git a/frontend/ui/src/assets/images/empty-state/tags-empty-state.svg b/frontend/assets/images/empty-state/tags-empty-state.svg similarity index 100% rename from frontend/ui/src/assets/images/empty-state/tags-empty-state.svg rename to frontend/assets/images/empty-state/tags-empty-state.svg diff --git a/frontend/ui/src/assets/images/icons/airy-icon.svg b/frontend/assets/images/icons/airy-icon.svg similarity index 100% rename from frontend/ui/src/assets/images/icons/airy-icon.svg rename to frontend/assets/images/icons/airy-icon.svg diff --git a/frontend/ui/src/assets/images/icons/airy_avatar.svg b/frontend/assets/images/icons/airy_avatar.svg similarity index 92% rename from frontend/ui/src/assets/images/icons/airy_avatar.svg rename to frontend/assets/images/icons/airy_avatar.svg index 6ccf9258a9..d37de0cf38 100644 --- a/frontend/ui/src/assets/images/icons/airy_avatar.svg +++ b/frontend/assets/images/icons/airy_avatar.svg @@ -1,5 +1,5 @@ - + avatar/Airy Live Agent diff --git a/frontend/ui/src/assets/images/icons/arrow-left-2.svg b/frontend/assets/images/icons/arrow-left-2.svg similarity index 100% rename from frontend/ui/src/assets/images/icons/arrow-left-2.svg rename to frontend/assets/images/icons/arrow-left-2.svg diff --git a/frontend/ui/src/assets/images/icons/checkmark-circle.svg b/frontend/assets/images/icons/checkmark-circle.svg similarity index 100% rename from frontend/ui/src/assets/images/icons/checkmark-circle.svg rename to frontend/assets/images/icons/checkmark-circle.svg diff --git a/frontend/ui/src/assets/images/icons/checkmark.svg b/frontend/assets/images/icons/checkmark.svg similarity index 100% rename from frontend/ui/src/assets/images/icons/checkmark.svg rename to frontend/assets/images/icons/checkmark.svg diff --git a/frontend/ui/src/assets/images/icons/chevron-down.svg b/frontend/assets/images/icons/chevron-down.svg similarity index 100% rename from frontend/ui/src/assets/images/icons/chevron-down.svg rename to frontend/assets/images/icons/chevron-down.svg diff --git a/frontend/ui/src/assets/images/icons/chevron_left.svg b/frontend/assets/images/icons/chevron_left.svg similarity index 100% rename from frontend/ui/src/assets/images/icons/chevron_left.svg rename to frontend/assets/images/icons/chevron_left.svg diff --git a/frontend/ui/src/assets/images/icons/close.svg b/frontend/assets/images/icons/close.svg similarity index 100% rename from frontend/ui/src/assets/images/icons/close.svg rename to frontend/assets/images/icons/close.svg diff --git a/frontend/ui/src/assets/images/icons/cog.svg b/frontend/assets/images/icons/cog.svg similarity index 100% rename from frontend/ui/src/assets/images/icons/cog.svg rename to frontend/assets/images/icons/cog.svg diff --git a/frontend/ui/src/assets/images/icons/edit.svg b/frontend/assets/images/icons/edit.svg similarity index 100% rename from frontend/ui/src/assets/images/icons/edit.svg rename to frontend/assets/images/icons/edit.svg diff --git a/frontend/ui/src/assets/images/icons/facebook_rounded.svg b/frontend/assets/images/icons/facebook_rounded.svg similarity index 100% rename from frontend/ui/src/assets/images/icons/facebook_rounded.svg rename to frontend/assets/images/icons/facebook_rounded.svg diff --git a/frontend/assets/images/icons/filter-alt.svg b/frontend/assets/images/icons/filter-alt.svg new file mode 100644 index 0000000000..fa9d2f50c2 --- /dev/null +++ b/frontend/assets/images/icons/filter-alt.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/ui/src/assets/images/icons/git-merge.svg b/frontend/assets/images/icons/git-merge.svg similarity index 100% rename from frontend/ui/src/assets/images/icons/git-merge.svg rename to frontend/assets/images/icons/git-merge.svg diff --git a/frontend/ui/src/assets/images/icons/google-messages.svg b/frontend/assets/images/icons/google-messages.svg similarity index 100% rename from frontend/ui/src/assets/images/icons/google-messages.svg rename to frontend/assets/images/icons/google-messages.svg diff --git a/frontend/assets/images/icons/google_avatar.svg b/frontend/assets/images/icons/google_avatar.svg new file mode 100644 index 0000000000..350d80ab43 --- /dev/null +++ b/frontend/assets/images/icons/google_avatar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/ui/src/assets/images/icons/inbox.svg b/frontend/assets/images/icons/inbox.svg similarity index 100% rename from frontend/ui/src/assets/images/icons/inbox.svg rename to frontend/assets/images/icons/inbox.svg diff --git a/frontend/assets/images/icons/link.svg b/frontend/assets/images/icons/link.svg new file mode 100644 index 0000000000..2466c20301 --- /dev/null +++ b/frontend/assets/images/icons/link.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/ui/src/assets/images/icons/messenger_avatar.svg b/frontend/assets/images/icons/messenger_avatar.svg similarity index 96% rename from frontend/ui/src/assets/images/icons/messenger_avatar.svg rename to frontend/assets/images/icons/messenger_avatar.svg index 7276cb745c..4211607473 100644 --- a/frontend/ui/src/assets/images/icons/messenger_avatar.svg +++ b/frontend/assets/images/icons/messenger_avatar.svg @@ -1,5 +1,5 @@ - + avatar/Messenger diff --git a/frontend/ui/src/assets/images/icons/paperplane.svg b/frontend/assets/images/icons/paperplane.svg similarity index 100% rename from frontend/ui/src/assets/images/icons/paperplane.svg rename to frontend/assets/images/icons/paperplane.svg diff --git a/frontend/assets/images/icons/phone.svg b/frontend/assets/images/icons/phone.svg new file mode 100644 index 0000000000..f5f5c8bc8e --- /dev/null +++ b/frontend/assets/images/icons/phone.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/assets/images/icons/placeholder.svg b/frontend/assets/images/icons/placeholder.svg new file mode 100644 index 0000000000..b0f75b8df2 --- /dev/null +++ b/frontend/assets/images/icons/placeholder.svg @@ -0,0 +1 @@ +man-5-handdrawn \ No newline at end of file diff --git a/frontend/assets/images/icons/plus-circle.svg b/frontend/assets/images/icons/plus-circle.svg new file mode 100644 index 0000000000..2067cd0bbd --- /dev/null +++ b/frontend/assets/images/icons/plus-circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/ui/src/assets/images/icons/plus.svg b/frontend/assets/images/icons/plus.svg similarity index 100% rename from frontend/ui/src/assets/images/icons/plus.svg rename to frontend/assets/images/icons/plus.svg diff --git a/frontend/ui/src/assets/images/icons/price-tag.svg b/frontend/assets/images/icons/price-tag.svg similarity index 100% rename from frontend/ui/src/assets/images/icons/price-tag.svg rename to frontend/assets/images/icons/price-tag.svg diff --git a/frontend/ui/src/assets/images/icons/search.svg b/frontend/assets/images/icons/search.svg similarity index 52% rename from frontend/ui/src/assets/images/icons/search.svg rename to frontend/assets/images/icons/search.svg index 2ae349b29b..adad89f30e 100644 --- a/frontend/ui/src/assets/images/icons/search.svg +++ b/frontend/assets/images/icons/search.svg @@ -6,7 +6,7 @@ - + diff --git a/frontend/ui/src/assets/images/icons/shortcut.svg b/frontend/assets/images/icons/shortcut.svg similarity index 100% rename from frontend/ui/src/assets/images/icons/shortcut.svg rename to frontend/assets/images/icons/shortcut.svg diff --git a/frontend/ui/src/assets/images/icons/sign-out.svg b/frontend/assets/images/icons/sign-out.svg similarity index 100% rename from frontend/ui/src/assets/images/icons/sign-out.svg rename to frontend/assets/images/icons/sign-out.svg diff --git a/frontend/assets/images/icons/sms-channel.svg b/frontend/assets/images/icons/sms-channel.svg new file mode 100644 index 0000000000..ad2318ec5e --- /dev/null +++ b/frontend/assets/images/icons/sms-channel.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/frontend/ui/src/assets/images/icons/sms-icon.svg b/frontend/assets/images/icons/sms-icon.svg similarity index 100% rename from frontend/ui/src/assets/images/icons/sms-icon.svg rename to frontend/assets/images/icons/sms-icon.svg diff --git a/frontend/assets/images/icons/sms.svg b/frontend/assets/images/icons/sms.svg new file mode 100644 index 0000000000..b8455faf02 --- /dev/null +++ b/frontend/assets/images/icons/sms.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/frontend/ui/src/assets/images/icons/sms_avatar.svg b/frontend/assets/images/icons/sms_avatar.svg similarity index 97% rename from frontend/ui/src/assets/images/icons/sms_avatar.svg rename to frontend/assets/images/icons/sms_avatar.svg index c7de6dc3f8..aadff40f69 100644 --- a/frontend/ui/src/assets/images/icons/sms_avatar.svg +++ b/frontend/assets/images/icons/sms_avatar.svg @@ -1,5 +1,5 @@ - + Group 16 diff --git a/frontend/ui/src/assets/images/icons/speak-bubble.svg b/frontend/assets/images/icons/speak-bubble.svg similarity index 100% rename from frontend/ui/src/assets/images/icons/speak-bubble.svg rename to frontend/assets/images/icons/speak-bubble.svg diff --git a/frontend/ui/src/assets/images/icons/trash.svg b/frontend/assets/images/icons/trash.svg similarity index 100% rename from frontend/ui/src/assets/images/icons/trash.svg rename to frontend/assets/images/icons/trash.svg diff --git a/frontend/ui/src/assets/images/icons/whatsapp-icon.svg b/frontend/assets/images/icons/whatsapp-icon.svg similarity index 100% rename from frontend/ui/src/assets/images/icons/whatsapp-icon.svg rename to frontend/assets/images/icons/whatsapp-icon.svg diff --git a/frontend/ui/src/assets/images/icons/whatsapp_avatar.svg b/frontend/assets/images/icons/whatsapp_avatar.svg similarity index 95% rename from frontend/ui/src/assets/images/icons/whatsapp_avatar.svg rename to frontend/assets/images/icons/whatsapp_avatar.svg index 95773d4399..3045b5809a 100644 --- a/frontend/ui/src/assets/images/icons/whatsapp_avatar.svg +++ b/frontend/assets/images/icons/whatsapp_avatar.svg @@ -1,5 +1,5 @@ - + Group 14 diff --git a/frontend/ui/src/assets/images/logo/airy_primary_rgb.png b/frontend/assets/images/logo/airy_primary_rgb.png similarity index 100% rename from frontend/ui/src/assets/images/logo/airy_primary_rgb.png rename to frontend/assets/images/logo/airy_primary_rgb.png diff --git a/frontend/ui/src/assets/images/logo/airy_primary_rgb.svg b/frontend/assets/images/logo/airy_primary_rgb.svg similarity index 100% rename from frontend/ui/src/assets/images/logo/airy_primary_rgb.svg rename to frontend/assets/images/logo/airy_primary_rgb.svg diff --git a/frontend/chat-plugin/BUILD b/frontend/chat-plugin/BUILD index c49197a5a9..f625eaddd0 100644 --- a/frontend/chat-plugin/BUILD +++ b/frontend/chat-plugin/BUILD @@ -1,4 +1,4 @@ -load("@npm_bazel_typescript//:index.bzl", "ts_config") +load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@com_github_airyhq_bazel_tools//web:typescript.bzl", "ts_library") load("@com_github_airyhq_bazel_tools//web:web_app.bzl", "web_app") load("@com_github_airyhq_bazel_tools//web:web_library.bzl", "web_library") diff --git a/frontend/chat-plugin/example.html b/frontend/chat-plugin/example.html index 7df3bc0366..b2ab941778 100644 --- a/frontend/chat-plugin/example.html +++ b/frontend/chat-plugin/example.html @@ -1,28 +1,28 @@ - + Chat Plugin Souce Demo Page - - + + - - - -
- + + + +
+ diff --git a/frontend/chat-plugin/src/App.module.scss b/frontend/chat-plugin/src/App.module.scss index cde035f82f..b20d437713 100644 --- a/frontend/chat-plugin/src/App.module.scss +++ b/frontend/chat-plugin/src/App.module.scss @@ -4,8 +4,10 @@ width: -moz-available; right: 0; bottom: 0; - max-height: 500px; - max-width: 320px; + height: 100vh; + max-height: 700px; + min-height: 340px; + max-width: 380px; padding: 0; margin: 0; background: transparent; diff --git a/frontend/chat-plugin/src/App.tsx b/frontend/chat-plugin/src/App.tsx index 849620131c..020bbad3af 100644 --- a/frontend/chat-plugin/src/App.tsx +++ b/frontend/chat-plugin/src/App.tsx @@ -11,7 +11,7 @@ export default class App extends Component { return (
{channelId ? ( - + ) : ( Widget authorization failed. Please check your installation. )} diff --git a/frontend/chat-plugin/src/components/api/index.tsx b/frontend/chat-plugin/src/api/index.tsx similarity index 78% rename from frontend/chat-plugin/src/components/api/index.tsx rename to frontend/chat-plugin/src/api/index.tsx index 78639f4ddd..0b0bbbe8c9 100644 --- a/frontend/chat-plugin/src/components/api/index.tsx +++ b/frontend/chat-plugin/src/api/index.tsx @@ -1,4 +1,5 @@ import {SuggestionResponse, TextContent} from 'render/providers/chatplugin/chatPluginModel'; +import {setResumeTokenInStorage} from '../storage'; declare const window: { airy: { @@ -38,27 +39,27 @@ const convertToBody = (message: TextContent | SuggestionResponse) => { }; }; -export const getResumeToken = async (token: string) => { +export const getResumeToken = async (channelId: string, authToken: string) => { const resumeChat = await fetch(`//${API_HOST}/chatplugin.resumeToken`, { method: 'POST', body: JSON.stringify({}), headers: { 'Content-Type': 'application/json', - Authorization: `Bearer ${token}`, + Authorization: `Bearer ${authToken}`, }, }); const jsonResumeToken = await resumeChat.json(); - localStorage.setItem('resume_token', jsonResumeToken.resume_token); + setResumeTokenInStorage(channelId, jsonResumeToken.resume_token); }; -export const start = async (channel_id: string, resume_token: string) => { +export const start = async (channelId: string, resumeToken: string) => { try { const response = await fetch(`//${API_HOST}/chatplugin.authenticate`, { method: 'POST', body: JSON.stringify({ - channel_id: channel_id, - ...(resume_token && { - resume_token, + channel_id: channelId, + ...(resumeToken && { + resume_token: resumeToken, }), }), headers: { diff --git a/frontend/chat-plugin/src/components/chat/index.module.scss b/frontend/chat-plugin/src/components/chat/index.module.scss index c33acad896..3917d6e71d 100644 --- a/frontend/chat-plugin/src/components/chat/index.module.scss +++ b/frontend/chat-plugin/src/components/chat/index.module.scss @@ -2,27 +2,30 @@ height: 100%; padding: 0; background-color: transparent; + display: flex; + flex-direction: column; + justify-content: flex-end; } @keyframes chatAnimationOpen { 0% { - height: 0; opacity: 0; + max-height: 0vh; } 100% { - height: 400px; + max-height: 100vh; opacity: 1; } } @keyframes chatAnimationClose { 0% { - height: 400px; opacity: 1; + max-height: 100vh; } 100% { opacity: 0; - height: 0; + max-height: 0vh; } } @@ -32,6 +35,9 @@ background-color: white; box-shadow: 0 2px 6px #98a4ab; overflow: hidden; + display: flex; + flex-direction: column; + flex-grow: 1; } .containerAnimationOpen { @@ -53,6 +59,8 @@ flex-direction: column; height: 352px; justify-content: space-between; + flex-grow: 1; + overflow: auto; } .messages { diff --git a/frontend/chat-plugin/src/components/chat/index.tsx b/frontend/chat-plugin/src/components/chat/index.tsx index da768831ad..f1c2023fb3 100644 --- a/frontend/chat-plugin/src/components/chat/index.tsx +++ b/frontend/chat-plugin/src/components/chat/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import {useState, useEffect} from 'react'; import {IMessage} from '@stomp/stompjs'; -import WebSocket from '../../components/websocket'; +import WebSocket from '../../websocket'; import MessageProp from '../../components/message'; import InputBarProp from '../../components/inputBar'; import AiryInputBar from '../../airyRenderProps/AiryInputBar'; @@ -15,12 +15,14 @@ import BubbleProp from '../bubble'; import AiryBubble from '../../airyRenderProps/AiryBubble'; import {MessagePayload, SenderType, MessageState, isFromContact, Message, messageMapper} from 'httpclient'; import {SourceMessage, CommandUnion} from 'render'; +import {MessageInfoWrapper} from 'render/components/MessageInfoWrapper'; +import {getResumeTokenFromStorage} from '../../storage'; let ws: WebSocket; const welcomeMessage: Message = { id: '19527d24-9b47-4e18-9f79-fd1998b95059', - content: JSON.stringify({text: 'Hello! How can we help you?'}), + content: {text: 'Hello! How can we help you?'}, deliveryState: MessageState.delivered, senderType: SenderType.appUser, sentAt: new Date(), @@ -35,7 +37,7 @@ const Chat = (props: Props) => { const [messages, setMessages] = useState([welcomeMessage]); useEffect(() => { - ws = new WebSocket(props.channel_id, onReceive, setInitialMessages, getResumeToken()); + ws = new WebSocket(props.channelId, onReceive, setInitialMessages, getResumeTokenFromStorage(props.channelId)); ws.start().catch(error => { console.error(error); setInstallError(error.message); @@ -46,14 +48,6 @@ const Chat = (props: Props) => { updateScroll(); }, [messages]); - const getResumeToken = () => { - const queryParams = new URLSearchParams(window.location.search); - if (queryParams.has('resume_token')) { - localStorage.setItem('resume_token', queryParams.get('resume_token')); - } - return queryParams.get('resume_token') || localStorage.getItem('resume_token'); - }; - const setInitialMessages = (initialMessages: Array) => { setMessages([...messages, ...initialMessages]); }; @@ -87,7 +81,7 @@ const Chat = (props: Props) => { }; const onReceive = (data: IMessage) => { - const newMessage = messageMapper(JSON.parse(data.body).message as MessagePayload); + const newMessage = messageMapper((JSON.parse(data.body) as any).message as MessagePayload); setMessages((messages: Message[]) => [...messages, newMessage]); }; @@ -149,13 +143,15 @@ const Chat = (props: Props) => { props.airyMessageProp ? () => props.airyMessageProp(ctrl) : () => ( - + + + ) } /> diff --git a/frontend/chat-plugin/src/config.ts b/frontend/chat-plugin/src/config.ts index 9e4cc741b6..6d0f4381b6 100644 --- a/frontend/chat-plugin/src/config.ts +++ b/frontend/chat-plugin/src/config.ts @@ -5,7 +5,7 @@ export type RenderCtrl = { export type RenderProp = (ctrl?: RenderCtrl) => JSX.Element; export type AuthConfiguration = { - channel_id: string; + channelId: string; }; export type AiryWidgetConfiguration = AuthConfiguration & { diff --git a/frontend/chat-plugin/src/defaultScript.tsx b/frontend/chat-plugin/src/defaultScript.tsx index 2fcf678e2a..842f338b6d 100644 --- a/frontend/chat-plugin/src/defaultScript.tsx +++ b/frontend/chat-plugin/src/defaultScript.tsx @@ -32,7 +32,7 @@ declare const window: { if (window.airy.cid.length) { new AiryWidget({ - channel_id: window.airy.cid, + channelId: window.airy.cid, }).render(anchor); } else { console.log( diff --git a/frontend/chat-plugin/src/storage.ts b/frontend/chat-plugin/src/storage.ts new file mode 100644 index 0000000000..2607fa7362 --- /dev/null +++ b/frontend/chat-plugin/src/storage.ts @@ -0,0 +1,17 @@ +export const getResumeTokenFromStorage = (channelId: string): string => { + const queryParams = new URLSearchParams(window.location.search); + if (queryParams.has('resume_token')) { + setResumeTokenInStorage(channelId, queryParams.get('resume_token')); + } + return localStorage.getItem(getResumeTokenKey(channelId)); +}; + +export const setResumeTokenInStorage = (channelId: string, resumeToken: string) => { + localStorage.setItem(getResumeTokenKey(channelId), resumeToken); +}; + +const getResumeTokenKey = (channelId: string) => `resume_token_${channelId}`; + +export const resetStorage = (channelId: string) => { + localStorage.removeItem(getResumeTokenKey(channelId)); +}; diff --git a/frontend/chat-plugin/src/components/websocket/index.ts b/frontend/chat-plugin/src/websocket/index.ts similarity index 77% rename from frontend/chat-plugin/src/components/websocket/index.ts rename to frontend/chat-plugin/src/websocket/index.ts index 21daf9cee8..bfc145a52b 100644 --- a/frontend/chat-plugin/src/components/websocket/index.ts +++ b/frontend/chat-plugin/src/websocket/index.ts @@ -3,6 +3,7 @@ import 'regenerator-runtime/runtime'; import {start, getResumeToken, sendMessage} from '../api'; import {SuggestionResponse, TextContent} from 'render/providers/chatplugin/chatPluginModel'; import {Message, messageMapper} from 'httpclient'; +import {resetStorage} from '../storage'; declare const window: { airy: { @@ -13,25 +14,26 @@ declare const window: { }; const API_HOST = window.airy ? window.airy.h : 'chatplugin.airy'; -const TLS_PREFIX = window.airy ? (window.airy.no_tls === true ? '' : 's') : ''; +// https: -> wss: and http: -> ws: +const protocol = location.protocol.replace('http', 'ws'); class WebSocket { client: Client; - channel_id: string; + channelId: string; token: string; - resume_token: string; + resumeToken: string; setInitialMessages: (messages: Array) => void; onReceive: messageCallbackType; constructor( - channel_id: string, + channelId: string, onReceive: messageCallbackType, setInitialMessages: (messages: Array) => void, - resume_token?: string + resumeToken?: string ) { - this.channel_id = channel_id; + this.channelId = channelId; this.onReceive = onReceive; - this.resume_token = resume_token; + this.resumeToken = resumeToken; this.setInitialMessages = setInitialMessages; } @@ -39,7 +41,7 @@ class WebSocket { this.token = token; this.client = new Client({ - brokerURL: `ws${TLS_PREFIX}://${API_HOST}/ws.chatplugin`, + brokerURL: `${protocol}//${API_HOST}/ws.chatplugin`, connectHeaders: { Authorization: `Bearer ${token}`, }, @@ -64,15 +66,15 @@ class WebSocket { onSend = (message: TextContent | SuggestionResponse) => sendMessage(message, this.token); start = async () => { - const response = await start(this.channel_id, this.resume_token); + const response = await start(this.channelId, this.resumeToken); if (response.token && response.messages) { this.connect(response.token); this.setInitialMessages(response.messages.map(messageMapper)); - if (!this.resume_token) { - await getResumeToken(this.token); + if (!this.resumeToken) { + await getResumeToken(this.channelId, this.token); } } else { - localStorage.clear(); + resetStorage(this.channelId); } }; diff --git a/frontend/ui/BUILD b/frontend/ui/BUILD index 24ced5faf5..a1829abb0a 100644 --- a/frontend/ui/BUILD +++ b/frontend/ui/BUILD @@ -19,7 +19,6 @@ ts_library( "@npm//@airyhq/components", "@npm//@types/facebook-js-sdk", "@npm//@types/node", - "@npm//@types/prop-types", "@npm//@types/react", "@npm//@types/react-dom", "@npm//@types/react-redux", diff --git a/frontend/ui/src/actions/channel/index.ts b/frontend/ui/src/actions/channel/index.ts index 1cf799dedc..860a622c27 100644 --- a/frontend/ui/src/actions/channel/index.ts +++ b/frontend/ui/src/actions/channel/index.ts @@ -11,66 +11,38 @@ import {HttpClientInstance} from '../../InitializeAiryApi'; const SET_CURRENT_CHANNELS = '@@channel/SET_CHANNELS'; const ADD_CHANNELS = '@@channel/ADD_CHANNELS'; -const ADD_CHANNEL = '@@channel/ADD_CHANNEL'; -const REMOVE_CHANNEL = '@@channel/REMOVE_CHANNEL'; +const SET_CHANNEL = '@@channel/SET_CHANNEL'; export const setCurrentChannelsAction = createAction(SET_CURRENT_CHANNELS, resolve => (channels: Channel[]) => resolve(channels) ); export const addChannelsAction = createAction(ADD_CHANNELS, resolve => (channels: Channel[]) => resolve(channels)); - -export const addChannelAction = createAction(ADD_CHANNEL, resolve => (channel: Channel) => resolve(channel)); -export const removeChannelAction = createAction(REMOVE_CHANNEL, resolve => (channel: Channel) => resolve(channel)); - -export function listChannels() { - return async (dispatch: Dispatch) => { - return HttpClientInstance.listChannels() - .then((response: Channel[]) => { - dispatch(setCurrentChannelsAction(response)); - return Promise.resolve(response); - }) - .catch((error: Error) => { - return Promise.reject(error); - }); - }; -} - -export function exploreChannels(requestPayload: ExploreChannelRequestPayload) { - return async (dispatch: Dispatch) => { - return HttpClientInstance.exploreFacebookChannels(requestPayload) - .then((response: Channel[]) => { - dispatch(addChannelsAction(response)); - return Promise.resolve(response); - }) - .catch((error: Error) => { - return Promise.reject(error); - }); - }; -} - -export function connectChannel(requestPayload: ConnectChannelRequestPayload) { - return async (dispatch: Dispatch) => { - return HttpClientInstance.connectFacebookChannel(requestPayload) - .then((response: Channel) => { - dispatch(addChannelsAction([response])); - return Promise.resolve(response); - }) - .catch((error: Error) => { - return Promise.reject(error); - }); - }; -} - -export function disconnectChannel(source: string, requestPayload: DisconnectChannelRequestPayload) { - return async (dispatch: Dispatch) => { - return HttpClientInstance.disconnectChannel(source, requestPayload) - .then((response: Channel[]) => { - dispatch(setCurrentChannelsAction(response)); - return Promise.resolve(response); - }) - .catch((error: Error) => { - return Promise.reject(error); - }); - }; -} +export const setChannelAction = createAction(SET_CHANNEL, resolve => (channel: Channel) => resolve(channel)); + +export const listChannels = () => async (dispatch: Dispatch) => + HttpClientInstance.listChannels().then((response: Channel[]) => { + dispatch(setCurrentChannelsAction(response)); + return Promise.resolve(response); + }); + +export const exploreChannels = (requestPayload: ExploreChannelRequestPayload) => async (dispatch: Dispatch) => { + return HttpClientInstance.exploreFacebookChannels(requestPayload).then((response: Channel[]) => { + dispatch(addChannelsAction(response)); + return Promise.resolve(response); + }); +}; + +export const connectChannel = (requestPayload: ConnectChannelRequestPayload) => async (dispatch: Dispatch) => + HttpClientInstance.connectFacebookChannel(requestPayload).then((response: Channel) => { + dispatch(addChannelsAction([response])); + return Promise.resolve(response); + }); + +export const disconnectChannel = (source: string, requestPayload: DisconnectChannelRequestPayload) => async ( + dispatch: Dispatch +) => + HttpClientInstance.disconnectChannel(source, requestPayload).then((response: Channel[]) => { + dispatch(setCurrentChannelsAction(response)); + return Promise.resolve(response); + }); diff --git a/frontend/ui/src/actions/conversations/index.ts b/frontend/ui/src/actions/conversations/index.ts index 332a08cc28..54646fcc34 100644 --- a/frontend/ui/src/actions/conversations/index.ts +++ b/frontend/ui/src/actions/conversations/index.ts @@ -3,7 +3,7 @@ import {createAction} from 'typesafe-actions'; import {Conversation, PaginatedResponse} from 'httpclient'; import {HttpClientInstance} from '../../InitializeAiryApi'; import {StateModel} from '../../reducers'; -import {setMetadataAction} from '../metadata'; +import {mergeMetadataAction, setMetadataAction} from '../metadata'; const CONVERSATION_LOADING = '@@conversation/LOADING'; const CONVERSATIONS_LOADING = '@@conversations/LOADING'; @@ -38,11 +38,6 @@ export const removeErrorFromConversationAction = createAction( resolve => (conversationId: string) => resolve({conversationId}) ); -export const addTagToConversationAction = createAction( - CONVERSATION_ADD_TAG, - resolve => (conversationId: string, tagId: string) => resolve({conversationId, tagId}) -); - export const removeTagFromConversationAction = createAction( CONVERSATION_REMOVE_TAG, resolve => (conversationId: string, tagId: string) => resolve({conversationId, tagId}) @@ -54,86 +49,75 @@ export const updateMessagesPaginationDataAction = createAction( resolve({conversationId, paginationData}) ); -export function listConversations() { - return async (dispatch: Dispatch) => { - dispatch(loadingConversationsAction()); - return HttpClientInstance.listConversations({page_size: 10}) - .then((response: PaginatedResponse) => { - dispatch(mergeConversationsAction(response.data, response.paginationData)); - return Promise.resolve(true); - }) - .catch((error: Error) => { - return Promise.reject(error); - }); - }; -} +export const listConversations = () => async (dispatch: Dispatch) => { + dispatch(loadingConversationsAction()); + return HttpClientInstance.listConversations({page_size: 10}).then((response: PaginatedResponse) => { + dispatch(mergeConversationsAction(response.data, response.paginationData)); + return Promise.resolve(true); + }); +}; -export function listNextConversations() { - return async (dispatch: Dispatch, state: () => StateModel) => { - const cursor = state().data.conversations.all.paginationData.nextCursor; +export const listNextConversations = () => async (dispatch: Dispatch, state: () => StateModel) => { + const cursor = state().data.conversations.all.paginationData.nextCursor; - dispatch(loadingConversationsAction()); - return HttpClientInstance.listConversations({cursor: cursor}) - .then((response: PaginatedResponse) => { - dispatch(mergeConversationsAction(response.data, response.paginationData)); - return Promise.resolve(true); - }) - .catch((error: Error) => { - return Promise.reject(error); - }); - }; -} + dispatch(loadingConversationsAction()); + return HttpClientInstance.listConversations({cursor: cursor}).then((response: PaginatedResponse) => { + dispatch(mergeConversationsAction(response.data, response.paginationData)); + return Promise.resolve(true); + }); +}; function sleep(time) { return new Promise(resolve => setTimeout(resolve, time)); } -export function getConversationInfo(conversationId: string, retries?: number) { - return async (dispatch: Dispatch) => { - return HttpClientInstance.getConversationInfo(conversationId) - .then(response => { - dispatch(mergeConversationsAction([response])); - return Promise.resolve(true); +export const getConversationInfo = (conversationId: string, retries?: number) => async (dispatch: Dispatch) => + HttpClientInstance.getConversationInfo(conversationId) + .then(response => { + dispatch(mergeConversationsAction([response])); + return Promise.resolve(true); + }) + .catch(async (error: Error) => { + if (retries > 5) { + return Promise.reject(error); + } else { + await sleep(1000); + return getConversationInfo(conversationId, retries ? retries + 1 : 1)(dispatch); + } + }); + +export const readConversations = (conversationId: string) => (dispatch: Dispatch) => { + HttpClientInstance.readConversations(conversationId).then(() => + dispatch( + setMetadataAction({ + subject: 'conversation', + identifier: conversationId, + metadata: { + unreadCount: 0, + }, }) - .catch(async (error: Error) => { - if (retries > 5) { - return Promise.reject(error); - } else { - await sleep(1000); - return getConversationInfo(conversationId, retries ? retries + 1 : 1)(dispatch); - } - }); - }; -} - -export function readConversations(conversationId: string) { - return function(dispatch: Dispatch) { - HttpClientInstance.readConversations(conversationId).then(() => - dispatch( - setMetadataAction({ - subject: 'conversation', - identifier: conversationId, - metadata: { - unreadCount: 0, + ) + ); +}; + +export const addTagToConversation = (conversationId: string, tagId: string) => (dispatch: Dispatch) => { + HttpClientInstance.tagConversation({conversationId, tagId}).then(() => + dispatch( + mergeMetadataAction({ + subject: 'conversation', + identifier: conversationId, + metadata: { + tags: { + [tagId]: '', }, - }) - ) - ); - }; -} - -export function addTagToConversation(conversationId: string, tagId: string) { - return function(dispatch: Dispatch) { - HttpClientInstance.tagConversation({conversationId, tagId}).then(() => - dispatch(addTagToConversationAction(conversationId, tagId)) - ); - }; -} - -export function removeTagFromConversation(conversationId: string, tagId: string) { - return function(dispatch: Dispatch) { - HttpClientInstance.untagConversation({conversationId, tagId}).then(() => - dispatch(removeTagFromConversationAction(conversationId, tagId)) - ); - }; -} + }, + }) + ) + ); +}; + +export const removeTagFromConversation = (conversationId: string, tagId: string) => (dispatch: Dispatch) => { + HttpClientInstance.untagConversation({conversationId, tagId}).then(() => + dispatch(removeTagFromConversationAction(conversationId, tagId)) + ); +}; diff --git a/frontend/ui/src/actions/messages/index.ts b/frontend/ui/src/actions/messages/index.ts index c371c72903..701ae19d54 100644 --- a/frontend/ui/src/actions/messages/index.ts +++ b/frontend/ui/src/actions/messages/index.ts @@ -5,8 +5,8 @@ import {HttpClientInstance} from '../../InitializeAiryApi'; import {StateModel} from '../../reducers'; import {updateMessagesPaginationDataAction, loadingConversationAction} from '../conversations'; -export const MESSAGES_LOADING = '@@messages/LOADING'; -export const MESSAGES_ADDED = '@@messages/ADDED'; +const MESSAGES_LOADING = '@@messages/LOADING'; +const MESSAGES_ADDED = '@@messages/ADDED'; export const loadingMessagesAction = createAction( MESSAGES_LOADING, @@ -22,24 +22,20 @@ export function listMessages(conversationId: string) { return HttpClientInstance.listMessages({ conversationId, pageSize: 10, - }) - .then((response: PaginatedResponse) => { - dispatch( - loadingMessagesAction({ - conversationId, - messages: response.data, - }) - ); + }).then((response: PaginatedResponse) => { + dispatch( + loadingMessagesAction({ + conversationId, + messages: response.data, + }) + ); - if (response.paginationData) { - dispatch(updateMessagesPaginationDataAction(conversationId, response.paginationData)); - } + if (response.paginationData) { + dispatch(updateMessagesPaginationDataAction(conversationId, response.paginationData)); + } - return Promise.resolve(true); - }) - .catch((error: Error) => { - return Promise.reject(error); - }); + return Promise.resolve(true); + }); }; } @@ -69,24 +65,20 @@ export function listPreviousMessages(conversationId: string) { conversationId, pageSize: 10, cursor: cursor, - }) - .then((response: PaginatedResponse) => { - dispatch( - loadingMessagesAction({ - conversationId, - messages: response.data, - }) - ); + }).then((response: PaginatedResponse) => { + dispatch( + loadingMessagesAction({ + conversationId, + messages: response.data, + }) + ); - if (response.paginationData) { - dispatch(updateMessagesPaginationDataAction(conversationId, response.paginationData)); - } + if (response.paginationData) { + dispatch(updateMessagesPaginationDataAction(conversationId, response.paginationData)); + } - return Promise.resolve(true); - }) - .catch((error: Error) => { - return Promise.reject(error); - }); + return Promise.resolve(true); + }); } }; } diff --git a/frontend/ui/src/actions/metadata/index.ts b/frontend/ui/src/actions/metadata/index.ts index a978921b52..22fa5866a9 100644 --- a/frontend/ui/src/actions/metadata/index.ts +++ b/frontend/ui/src/actions/metadata/index.ts @@ -1,8 +1,13 @@ import {createAction} from 'typesafe-actions'; import {MetadataEvent} from 'httpclient'; +const MERGE_METADATA = '@@metadata/MERGE_METADATA'; const SET_METADATA = '@@metadata/SET_METADATA'; export const setMetadataAction = createAction(SET_METADATA, resolve => (metadataEvent: MetadataEvent) => resolve(metadataEvent) ); + +export const mergeMetadataAction = createAction(MERGE_METADATA, resolve => (metadataEvent: MetadataEvent) => + resolve(metadataEvent) +); diff --git a/frontend/ui/src/actions/settings/index.tsx b/frontend/ui/src/actions/settings/index.tsx index 79a0468dd7..a6d9b1d8c7 100644 --- a/frontend/ui/src/actions/settings/index.tsx +++ b/frontend/ui/src/actions/settings/index.tsx @@ -1,17 +1,9 @@ import _, {Dispatch} from 'redux'; import {fakeData} from '../../pages/Tags/FAKESETTINGS'; +import {createAction} from 'typesafe-actions'; -export const ADD_SETTINGS_TO_STORE = 'ADD_SETTINGS_TO_STORE'; +const ADD_SETTINGS_TO_STORE = 'ADD_SETTINGS_TO_STORE'; -export function fetchSettings() { - return { - type: ADD_SETTINGS_TO_STORE, - colors: fakeData(), - }; -} +export const fetchSettings = createAction(ADD_SETTINGS_TO_STORE, resolve => () => resolve(fakeData())); -export function fakeSettingsAPICall() { - return function(dispatch: Dispatch) { - dispatch(fetchSettings()); - }; -} +export const fakeSettingsAPICall = () => (dispatch: Dispatch) => dispatch(fetchSettings()); diff --git a/frontend/ui/src/assets/images/icons/add-icon.svg b/frontend/ui/src/assets/images/icons/add-icon.svg new file mode 100644 index 0000000000..43127a8927 --- /dev/null +++ b/frontend/ui/src/assets/images/icons/add-icon.svg @@ -0,0 +1 @@ +plus-other \ No newline at end of file diff --git a/frontend/ui/src/assets/images/icons/filter.svg b/frontend/ui/src/assets/images/icons/filter.svg new file mode 100644 index 0000000000..f3b4f60041 --- /dev/null +++ b/frontend/ui/src/assets/images/icons/filter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/ui/src/assets/images/icons/google_avatar.svg b/frontend/ui/src/assets/images/icons/google_avatar.svg deleted file mode 100644 index 68b33c0898..0000000000 --- a/frontend/ui/src/assets/images/icons/google_avatar.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/ui/src/assets/images/icons/search-channel-icon.svg b/frontend/ui/src/assets/images/icons/search-channel-icon.svg new file mode 100644 index 0000000000..88f1bb4dbe --- /dev/null +++ b/frontend/ui/src/assets/images/icons/search-channel-icon.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/ui/src/components/AiryWebsocket/index.tsx b/frontend/ui/src/components/AiryWebsocket/index.tsx index 3f9bd6226e..3229243e5c 100644 --- a/frontend/ui/src/components/AiryWebsocket/index.tsx +++ b/frontend/ui/src/components/AiryWebsocket/index.tsx @@ -1,14 +1,15 @@ import React, {useEffect, useState} from 'react'; import _, {connect, ConnectedProps} from 'react-redux'; import {WebSocketClient} from 'websocketclient'; -import {Message, Channel} from 'httpclient'; +import {Message, Channel, MetadataEvent} from 'httpclient'; import {env} from '../../env'; import {StateModel} from '../../reducers'; import {addMessagesAction} from '../../actions/messages'; import {getConversationInfo} from '../../actions/conversations'; -import {addChannelAction, removeChannelAction} from '../../actions/channel'; +import {setChannelAction} from '../../actions/channel'; import {setMetadataAction} from '../../actions/metadata'; +import {allConversations} from '../../selectors/conversations'; type AiryWebSocketProps = {} & ConnectedProps; @@ -18,47 +19,25 @@ export const AiryWebSocketContext = React.createContext({ const mapStateToProps = (state: StateModel) => { return { - conversations: state.data.conversations.all.items, + conversations: allConversations(state), user: state.data.user, }; }; -const mapDispatchToProps = dispatch => { - return { - addMessages: (conversationId: string, messages: Message[]) => - dispatch(addMessagesAction({conversationId, messages})), - addChannel: (channel: Channel) => dispatch(addChannelAction(channel)), - removeChannel: (channel: Channel) => dispatch(removeChannelAction(channel)), - getConversationInfo: (conversationId: string) => dispatch(getConversationInfo(conversationId)), - setConversationUnreadCount: (conversationId: string, unreadCount: number) => - dispatch( - setMetadataAction({ - subject: 'conversation', - identifier: conversationId, - metadata: { - unreadCount, - }, - }) - ), - }; -}; +const mapDispatchToProps = dispatch => ({ + addMessages: (conversationId: string, messages: Message[]) => dispatch(addMessagesAction({conversationId, messages})), + onChannel: (channel: Channel) => dispatch(setChannelAction(channel)), + getConversationInfo: (conversationId: string) => dispatch(getConversationInfo(conversationId)), + onMetadata: (metadataEvent: MetadataEvent) => dispatch(setMetadataAction(metadataEvent)), +}); const connector = connect(mapStateToProps, mapDispatchToProps); const AiryWebSocket: React.FC = props => { - const { - children, - conversations, - getConversationInfo, - user, - addMessages, - addChannel, - removeChannel, - setConversationUnreadCount, - } = props; + const {children, conversations, getConversationInfo, user, addMessages, onChannel, onMetadata} = props; const [webSocketClient, setWebSocketClient] = useState(null); - const addMessage = (conversationId: string, message: Message) => { + const onMessage = (conversationId: string, message: Message) => { if (conversations[conversationId]) { addMessages(conversationId, [message]); } else { @@ -78,11 +57,10 @@ const AiryWebSocket: React.FC = props => { user.token, { onMessage: (conversationId: string, _channelId: string, message: Message) => { - addMessage(conversationId, message); + onMessage(conversationId, message); }, - onUnreadCountUpdated: setConversationUnreadCount, - onChannelConnected: addChannel, - onChannelDisconnected: removeChannel, + onChannel, + onMetadata, }, env.API_HOST ) diff --git a/frontend/ui/src/components/IconChannel/index.tsx b/frontend/ui/src/components/IconChannel/index.tsx index 1fb86501aa..b69c530787 100644 --- a/frontend/ui/src/components/IconChannel/index.tsx +++ b/frontend/ui/src/components/IconChannel/index.tsx @@ -2,16 +2,16 @@ import React from 'react'; import {Channel} from 'httpclient'; -import {ReactComponent as FacebookIcon} from '../../assets/images/icons/facebook_rounded.svg'; -import {ReactComponent as GoogleIcon} from '../../assets/images/icons/google-messages.svg'; -import {ReactComponent as SmsIcon} from '../../assets/images/icons/sms-icon.svg'; -import {ReactComponent as WhatsappIcon} from '../../assets/images/icons/whatsapp-icon.svg'; -import {ReactComponent as MessengerAvatar} from '../../assets/images/icons/messenger_avatar.svg'; -import {ReactComponent as GoogleAvatar} from '../../assets/images/icons/google_avatar.svg'; -import {ReactComponent as SmsAvatar} from '../../assets/images/icons/sms_avatar.svg'; -import {ReactComponent as WhatsappAvatar} from '../../assets/images/icons/whatsapp_avatar.svg'; -import {ReactComponent as AiryAvatar} from '../../assets/images/icons/airy_avatar.svg'; -import {ReactComponent as AiryIcon} from '../../assets/images/icons/airy-icon.svg'; +import {ReactComponent as FacebookIcon} from 'assets/images/icons/facebook_rounded.svg'; +import {ReactComponent as GoogleIcon} from 'assets/images/icons/google-messages.svg'; +import {ReactComponent as SmsIcon} from 'assets/images/icons/sms-icon.svg'; +import {ReactComponent as WhatsappIcon} from 'assets/images/icons/whatsapp-icon.svg'; +import {ReactComponent as MessengerAvatar} from 'assets/images/icons/messenger_avatar.svg'; +import {ReactComponent as GoogleAvatar} from 'assets/images/icons/google_avatar.svg'; +import {ReactComponent as SmsAvatar} from 'assets/images/icons/sms_avatar.svg'; +import {ReactComponent as WhatsappAvatar} from 'assets/images/icons/whatsapp_avatar.svg'; +import {ReactComponent as AiryAvatar} from 'assets/images/icons/airy_avatar.svg'; +import {ReactComponent as AiryIcon} from 'assets/images/icons/airy-icon.svg'; import styles from './index.module.scss'; diff --git a/frontend/ui/src/components/IconChannelFilter/index.tsx b/frontend/ui/src/components/IconChannelFilter/index.tsx index 3742ca16d4..2c8ebe0a5c 100644 --- a/frontend/ui/src/components/IconChannelFilter/index.tsx +++ b/frontend/ui/src/components/IconChannelFilter/index.tsx @@ -2,10 +2,10 @@ import React from 'react'; import {Channel} from 'httpclient'; -import {ReactComponent as GoogleIcon} from '../../assets/images/icons/google_avatar.svg'; -import {ReactComponent as WhatsappIcon} from '../../assets/images/icons/whatsapp_avatar.svg'; -import {ReactComponent as SmsIcon} from '../../assets/images/icons/sms_avatar.svg'; -import {ReactComponent as FacebookIcon} from '../../assets/images/icons/messenger_avatar.svg'; +import {ReactComponent as GoogleIcon} from 'assets/images/icons/google_avatar.svg'; +import {ReactComponent as WhatsappIcon} from 'assets/images/icons/whatsapp_avatar.svg'; +import {ReactComponent as SmsIcon} from 'assets/images/icons/sms_avatar.svg'; +import {ReactComponent as FacebookIcon} from 'assets/images/icons/messenger_avatar.svg'; const sourceIconsMap = { google: GoogleIcon, diff --git a/frontend/ui/src/components/SearchField.tsx b/frontend/ui/src/components/SearchField.tsx index d10a8a664f..56d2389c4e 100644 --- a/frontend/ui/src/components/SearchField.tsx +++ b/frontend/ui/src/components/SearchField.tsx @@ -1,7 +1,7 @@ import React, {createRef, useCallback} from 'react'; -import closeIcon from '../assets/images/icons/close.svg'; -import searchIcon from '../assets/images/icons/search.svg'; +import closeIcon from 'assets/images/icons/close.svg'; +import searchIcon from 'assets/images/icons/search.svg'; import styles from './index.module.scss'; type SearchFieldProps = { diff --git a/frontend/ui/src/components/Sidebar/index.tsx b/frontend/ui/src/components/Sidebar/index.tsx index a3e3935ad5..7b96866f65 100644 --- a/frontend/ui/src/components/Sidebar/index.tsx +++ b/frontend/ui/src/components/Sidebar/index.tsx @@ -1,9 +1,9 @@ import React from 'react'; import {withRouter, Link, matchPath, RouteProps} from 'react-router-dom'; -import {ReactComponent as PlugIcon} from '../../assets/images/icons/git-merge.svg'; -import {ReactComponent as InboxIcon} from '../../assets/images/icons/inbox.svg'; -import {ReactComponent as TagIcon} from '../../assets/images/icons/price-tag.svg'; +import {ReactComponent as PlugIcon} from 'assets/images/icons/git-merge.svg'; +import {ReactComponent as InboxIcon} from 'assets/images/icons/inbox.svg'; +import {ReactComponent as TagIcon} from 'assets/images/icons/price-tag.svg'; import {INBOX_ROUTE, CHANNELS_ROUTE, TAGS_ROUTE} from '../../routes/routes'; diff --git a/frontend/ui/src/components/Tag/index.tsx b/frontend/ui/src/components/Tag/index.tsx index c0134e5047..ae0d8cbb9f 100644 --- a/frontend/ui/src/components/Tag/index.tsx +++ b/frontend/ui/src/components/Tag/index.tsx @@ -4,7 +4,7 @@ import _, {connect, ConnectedProps} from 'react-redux'; import {Tag as TagModel} from 'httpclient'; import {Settings} from '../../reducers/data/settings'; -import {ReactComponent as Close} from '../../assets/images/icons/close.svg'; +import {ReactComponent as Close} from 'assets/images/icons/close.svg'; import styles from './index.module.scss'; import {StateModel} from '../../reducers'; diff --git a/frontend/ui/src/components/TopBar/index.tsx b/frontend/ui/src/components/TopBar/index.tsx index cfce604a15..43ea5b1f0b 100644 --- a/frontend/ui/src/components/TopBar/index.tsx +++ b/frontend/ui/src/components/TopBar/index.tsx @@ -3,10 +3,10 @@ import _, {connect, ConnectedProps} from 'react-redux'; import {withRouter, Link, RouteComponentProps} from 'react-router-dom'; import {StateModel} from '../../reducers'; import ListenOutsideClick from '../ListenOutsideClick'; -import {ReactComponent as LogoutIcon} from '../../assets/images/icons/sign-out.svg'; -import {ReactComponent as ShortcutIcon} from '../../assets/images/icons/shortcut.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 {ReactComponent as LogoutIcon} from 'assets/images/icons/sign-out.svg'; +import {ReactComponent as ShortcutIcon} from 'assets/images/icons/shortcut.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 {LOGOUT_ROUTE} from '../../routes/routes'; import styles from './index.module.scss'; diff --git a/frontend/ui/src/cookies/webStore.ts b/frontend/ui/src/cookies/webStore.ts index 012f7d6e59..cb5d4833af 100644 --- a/frontend/ui/src/cookies/webStore.ts +++ b/frontend/ui/src/cookies/webStore.ts @@ -11,14 +11,16 @@ export const getUserId = () => getCookie('userId'); export const getAuthToken = () => getCookie('authToken'); export function storeUserData(data: User) { - if (data.token) { - localStorage.setItem('id', data.id); - localStorage.setItem('firstName', data.firstName); - localStorage.setItem('lastName', data.lastName); - localStorage.setItem('isAuthSuccess', JSON.stringify(true)); - setAuthToken(data.token); - setUserId(data.id); + if (!data.token) { + return; } + + localStorage.setItem('id', data.id); + localStorage.setItem('firstName', data.firstName); + localStorage.setItem('lastName', data.lastName); + localStorage.setItem('isAuthSuccess', JSON.stringify(true)); + setAuthToken(data.token); + setUserId(data.id); } export function clearUserData() { diff --git a/frontend/ui/src/pages/Channels/ChannelsSources/ChannelDetails.module.scss b/frontend/ui/src/pages/Channels/ChannelsSources/ChannelDetails.module.scss new file mode 100644 index 0000000000..8690bfe9fc --- /dev/null +++ b/frontend/ui/src/pages/Channels/ChannelsSources/ChannelDetails.module.scss @@ -0,0 +1,99 @@ +@import '../../../assets/scss/fonts'; +@import '../../../assets/scss/colors'; + +.flexWrap { + display: flex; + flex-grow: 1; +} + +.channelCard { + display: flex; + box-sizing: border-box; + height: 100px; + width: 300px; + border: 1px solid var(--color-light-gray); + border-radius: 8px; + background-color: #ffffff; + margin-top: 22px; +} + +.channelLogo { + display: inline-flex; + height: 40px; + width: 40px; + margin: 16px 8px 42px 16px; + svg { + width: 30px; + height: 30px; + } +} + +.channelTitle { + display: inline-flex; + height: 24px; + width: 190px; + color: #212428; + font-size: 16px; + font-weight: bold; + letter-spacing: 0; + line-height: 24px; + margin: 16px 20px 2px 2px; +} + +.channelText { + display: inline-flex; + height: 48px; + width: 190px; + color: var(--color-text-gray); + font-size: 16px; + letter-spacing: 0; + line-height: 24px; +} + +.channelButton { + display: flex; + padding-top: 8px; + width: 40px; + height: 40px; + margin: 20px 0px 8px 0px; +} + +.addChannelButton { + position: relative; + background: white; + width: 40px; + height: 40px; + margin-left: 7px; + margin-top: 32px; + transition: 0.2s ease-in-out all; + border: none; + padding: 0; + outline: none; + cursor: pointer; +} + +.channelButtonIcon { + @include font-m; + margin-right: 16px; + width: 24px; + height: 24px; + display: flex; + justify-content: center; + align-items: center; + color: var(--color-text-gray); + cursor: pointer; + + padding: 0px; + + svg { + margin: 0; + path { + fill: var(--color-text-gray); + } + } + &:hover { + color: var(--color-airy-blue); + background-color: var(--color-background-blue); + box-shadow: 0px 0px 0px 3px var(--color-background-blue); + } +} diff --git a/frontend/ui/src/pages/Channels/ChannelsSources/ChannelDetails.tsx b/frontend/ui/src/pages/Channels/ChannelsSources/ChannelDetails.tsx new file mode 100644 index 0000000000..10a3a2e78d --- /dev/null +++ b/frontend/ui/src/pages/Channels/ChannelsSources/ChannelDetails.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import styles from './ChannelDetails.module.scss'; + +type ChannelDetailsProps = { + image: JSX.Element; + title: string; + text: string; + buttonIcon: JSX.Element; + displayButton: boolean; +}; + +const ChannelDetails = (props: ChannelDetailsProps) => { + return ( + <> +
+
{props.image}
+
+

{props.title}

+

{props.text}

+
+
+ + {props.displayButton && ( +
+ +
+ )} + + ); +}; + +export default ChannelDetails; diff --git a/frontend/ui/src/pages/Channels/ChannelsSources/ChannelsConnected.module.scss b/frontend/ui/src/pages/Channels/ChannelsSources/ChannelsConnected.module.scss new file mode 100644 index 0000000000..61f80093a2 --- /dev/null +++ b/frontend/ui/src/pages/Channels/ChannelsSources/ChannelsConnected.module.scss @@ -0,0 +1,134 @@ +@import '../../../assets/scss/fonts'; +@import '../../../assets/scss/colors'; + +.connectedContainer { + display: flex; + flex-direction: column; + min-width: 514px; + height: 120px; + margin-bottom: 10px; +} + +.connectedSum { + margin-left: 16px; + color: var(--color-text-gray); +} + +.connectedChannelBox { + display: flex; + box-sizing: border-box; + min-height: 100px; + min-width: 450px; + border: 1px solid var(--color-light-gray); + border-radius: 8px; + background-color: #ffffff; + margin-left: 12px; +} + +.connectedChannel { + display: flex; + flex-wrap: wrap; + flex-direction: column; + min-width: 398px; + min-height: 94px; +} + +.extraChannel { + display: flex; + min-width: 88px; + min-height: 20px; + border: none; + padding: 0; + outline: none; + cursor: pointer; + background: white; + color: var(--color-airy-blue); + text-decoration: underline; + margin: 2px -1px 0px 0px; +} + +.connectedChannelData { + display: flex; + align-items: flex-start; + margin: 6px 0px 0px 6px; +} + +.channelListEntry { + display: inline-flex; +} + +.placeholderLogo { + display: inline-flex; + height: 20px; + width: 20px; + margin: 14px 2px 0px 6px; + svg { + height: 18px; + width: 18px; + } +} + +.facebookImage { + display: inline-flex; + height: 16px; + width: 16px; + border-radius: 50%; + margin: 14px 6px 0px 8px; +} + +.connectedChannelName { + min-width: 168px; + height: 24px; + margin-right: 7px; + padding-top: 10px; +} + +.channelId { + padding-top: 10px; +} + +.channelButton { + display: flex; + width: 40px; + height: 40px; + padding-top: 8px; + margin: 20px 0px 8px 0px; +} + +.addChannelButton { + position: relative; + width: 40px; + height: 40px; + background: white; + margin: 32px 0px 0px 7px; + padding: 0; + transition: 0.2s ease-in-out all; + border: none; + outline: none; + cursor: pointer; +} + +.channelButtonIcon { + @include font-m; + margin: 0px 16px 24px 0px; + width: 24px; + height: 24px; + display: flex; + justify-content: center; + align-items: center; + color: var(--color-text-gray); + cursor: pointer; + padding: 0; + + svg { + margin: 0; + path { + fill: var(--color-text-gray); + } + } + &:hover { + color: var(--color-airy-blue); + background-color: var(--color-background-blue); + box-shadow: 0px 0px 0px 3px var(--color-background-blue); + } +} diff --git a/frontend/ui/src/pages/Channels/ChannelsSources/ChannelsConnected.tsx b/frontend/ui/src/pages/Channels/ChannelsSources/ChannelsConnected.tsx new file mode 100644 index 0000000000..5c5ab60f5b --- /dev/null +++ b/frontend/ui/src/pages/Channels/ChannelsSources/ChannelsConnected.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import {Channel} from 'httpclient'; +import {LinkButton} from '@airyhq/components'; +import styles from './ChannelsConnected.module.scss'; + +type connectedChannelsProps = { + showConnectedChannels: boolean; + showSumOfChannels: number; + connected: string; + connectedChannel: Channel[]; + placeholderImage?: JSX.Element; + extraChannel: boolean; + displayExtraChannel: number; + isConnected: string; + addAChannel: JSX.Element; + renderChannelId?: boolean; + ignoreSvgAvatar?: boolean; + displayFacebookImage?: boolean; +}; + +const ChannelsConnected = (props: connectedChannelsProps) => { + return ( + <> + {props.showConnectedChannels && ( + <> +
+
+

+ {props.showSumOfChannels} {props.connected} +

+
+
+
+ {props.connectedChannel.map((channel: Channel) => { + return ( + <> +
  • +
    + {channel.metadata.imageUrl && props.displayFacebookImage && ( + {channel.metadata.name} + )} + + {!props.ignoreSvgAvatar && ( +
    {props.placeholderImage}
    + )} + +
    {channel.metadata.name}
    + + {props.renderChannelId &&
    {channel.sourceChannelId}
    } +
    +
  • + + ); + })} +
    +
    + {props.extraChannel && ( + + +{props.displayExtraChannel} {props.isConnected} + + )} +
    +
    +
    + +
    + +
    + + )} + + ); +}; + +export default ChannelsConnected; diff --git a/frontend/ui/src/pages/Channels/ChannelsSources/ChatPluginSource.tsx b/frontend/ui/src/pages/Channels/ChannelsSources/ChatPluginSource.tsx new file mode 100644 index 0000000000..be32ffb686 --- /dev/null +++ b/frontend/ui/src/pages/Channels/ChannelsSources/ChatPluginSource.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import styles from './ChannelDetails.module.scss'; +import {ReactComponent as AiryLogo} from 'assets/images/icons/airy_avatar.svg'; +import {ReactComponent as AddChannel} from 'assets/images/icons/plus-circle.svg'; +import {Channel} from 'httpclient'; +import ChannelDetails from './ChannelDetails'; +import ChannelsConnected from './ChannelsConnected'; + +type chatPluginProps = {pluginSource: Channel[]}; + +const ChatPluginSource = (props: chatPluginProps) => { + const chatPluginSources = props.pluginSource.filter(channel => channel.source === 'chat_plugin').slice(0, 4); + const chatPluginSourcesExtra = props.pluginSource.filter(channel => channel.source === 'chat_plugin').slice(4); + const totalChatPluginSources = chatPluginSources.concat(chatPluginSourcesExtra); + + const connectedAttributes = { + showConnectedChannels: chatPluginSources.length > 0, + connectedChannel: chatPluginSources, + showSumOfChannels: totalChatPluginSources.length, + }; + + const connectedAttributesExtra = { + extraChannel: chatPluginSourcesExtra.length > 0, + displayExtraChannel: chatPluginSourcesExtra.length, + }; + + return ( +
    + } + buttonIcon={} + displayButton={chatPluginSources.length === 0} + /> + + } + isConnected="connected" + addAChannel={} + /> +
    + ); +}; + +export default ChatPluginSource; diff --git a/frontend/ui/src/pages/Channels/ChannelsSources/FacebookSource.tsx b/frontend/ui/src/pages/Channels/ChannelsSources/FacebookSource.tsx new file mode 100644 index 0000000000..c7fbb2b4e3 --- /dev/null +++ b/frontend/ui/src/pages/Channels/ChannelsSources/FacebookSource.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import styles from './ChannelDetails.module.scss'; +import {ReactComponent as FacebookLogo} from 'assets/images/icons/messenger_avatar.svg'; +import {ReactComponent as AddChannel} from 'assets/images/icons/plus-circle.svg'; +import {Channel} from 'httpclient'; +import ChannelDetails from './ChannelDetails'; +import ChannelsConnected from './ChannelsConnected'; + +type facebookSourceProps = {facebookSource: Channel[]}; + +const FacebookSource = (props: facebookSourceProps) => { + const facebookSources = props.facebookSource.filter(channel => channel.source === 'facebook').slice(0, 4); + const facebookSourcesExtra = props.facebookSource.filter(channel => channel.source === 'facebook').slice(4); + const totalFacebookSources = facebookSources.concat(facebookSourcesExtra); + + const connectedAttributes = { + showConnectedChannels: facebookSources.length > 0, + showSumOfChannels: totalFacebookSources.length, + connectedChannel: facebookSources, + displayFacebookImage: facebookSources.length > 0, + + ignoreSvgAvatar: facebookSources.length > 0, + }; + + const connectedAttributesExtra = { + extraChannel: facebookSourcesExtra.length > 0, + displayExtraChannel: facebookSourcesExtra.length, + }; + + return ( +
    + } + buttonIcon={} + displayButton={facebookSources.length === 0} + /> + + } + /> +
    + ); +}; + +export default FacebookSource; diff --git a/frontend/ui/src/pages/Channels/ChannelsSources/GoogleSource.tsx b/frontend/ui/src/pages/Channels/ChannelsSources/GoogleSource.tsx new file mode 100644 index 0000000000..f10ee9e09e --- /dev/null +++ b/frontend/ui/src/pages/Channels/ChannelsSources/GoogleSource.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import styles from './ChannelDetails.module.scss'; +import {ReactComponent as GoogleLogo} from 'assets/images/icons/google_avatar.svg'; +import {ReactComponent as AddChannel} from 'assets/images/icons/plus-circle.svg'; +import {Channel} from 'httpclient'; +import ChannelDetails from './ChannelDetails'; +import ChannelsConnected from './ChannelsConnected'; + +type googleSourceProps = {googleSource: Channel[]}; + +const GoogleSource = (props: googleSourceProps) => { + const googleSources = props.googleSource.filter(channel => channel.source === 'google').slice(0, 2); + const googleSourcesExtra = props.googleSource.filter(channel => channel.source === 'google').slice(2); + const totalGoogleSources = googleSources.concat(googleSourcesExtra); + + const connectedAttributes = { + showConnectedChannels: googleSources.length > 0, + connectedChannel: googleSources, + showSumOfChannels: totalGoogleSources.length, + }; + + const connectedAttributesExtra = { + extraChannel: googleSourcesExtra.length > 0, + displayExtraChannel: googleSourcesExtra.length, + }; + + return ( +
    + } + buttonIcon={} + displayButton={googleSources.length === 0} + /> + + } + isConnected="connected" + addAChannel={} + /> +
    + ); +}; + +export default GoogleSource; diff --git a/frontend/ui/src/pages/Channels/ChannelsSources/TwilloSmsSource.tsx b/frontend/ui/src/pages/Channels/ChannelsSources/TwilloSmsSource.tsx new file mode 100644 index 0000000000..63345ba218 --- /dev/null +++ b/frontend/ui/src/pages/Channels/ChannelsSources/TwilloSmsSource.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import styles from './ChannelDetails.module.scss'; +import {ReactComponent as SMSLogo} from 'assets/images/icons/sms.svg'; +import {ReactComponent as SMSChannelLogo} from 'assets/images/icons/sms-channel.svg'; +import {ReactComponent as AddChannel} from 'assets/images/icons/plus-circle.svg'; +import {Channel} from 'httpclient'; +import ChannelDetails from './ChannelDetails'; +import ChannelsConnected from './ChannelsConnected'; + +type twilloSmsSourceProps = {twilloSmsSource: Channel[]}; + +const TwilloSmsSource = (props: twilloSmsSourceProps) => { + const twilloSources = props.twilloSmsSource.filter(channel => channel.source === 'twilio.sms').slice(0, 2); + const twilloSourcesExtra = props.twilloSmsSource.filter(channel => channel.source === 'twilio.sms').slice(2); + const totalTwilloSources = twilloSources.concat(twilloSourcesExtra); + + const connectedAttributes = { + showConnectedChannels: twilloSources.length > 0, + connectedChannel: twilloSources, + showSumOfChannels: totalTwilloSources.length, + renderChannelId: twilloSources.length > 0, + }; + + const connectedAttributesExtra = { + extraChannel: twilloSourcesExtra.length > 0, + displayExtraChannel: twilloSourcesExtra.length, + }; + + return ( +
    + } + buttonIcon={} + displayButton={twilloSources.length === 0} + /> + + } + isConnected="connected" + addAChannel={} + /> +
    + ); +}; + +export default TwilloSmsSource; diff --git a/frontend/ui/src/pages/Channels/ChannelsSources/WhatsappSmsSource.tsx b/frontend/ui/src/pages/Channels/ChannelsSources/WhatsappSmsSource.tsx new file mode 100644 index 0000000000..918ca54e5f --- /dev/null +++ b/frontend/ui/src/pages/Channels/ChannelsSources/WhatsappSmsSource.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import styles from './ChannelDetails.module.scss'; +import {ReactComponent as WhatsappLogo} from 'assets/images/icons/whatsapp_avatar.svg'; +import {ReactComponent as AddChannel} from 'assets/images/icons/plus-circle.svg'; +import {Channel} from 'httpclient'; +import ChannelDetails from './ChannelDetails'; +import ChannelsConnected from './ChannelsConnected'; + +type whatsappSourceProps = {whatsappSmsSource: Channel[]}; + +const WhatsappSmsSource = (props: whatsappSourceProps) => { + const whatsappSources = props.whatsappSmsSource.filter(channel => channel.source === 'twilio.whatsapp').slice(0, 2); + const whatsappSourcesExtra = props.whatsappSmsSource.filter(channel => channel.source === 'twilio.whatsapp').slice(2); + const totalWhatsappSources = whatsappSources.concat(whatsappSourcesExtra); + + const connectedAttributes = { + showConnectedChannels: whatsappSources.length > 0, + connectedChannel: whatsappSources, + showSumOfChannels: totalWhatsappSources.length, + renderChannelId: whatsappSources.length > 0, + }; + + const connectedAttributesExtra = { + extraChannel: whatsappSourcesExtra.length > 0, + displayExtraChannel: whatsappSourcesExtra.length, + }; + + return ( +
    + } + buttonIcon={} + displayButton={whatsappSources.length === 0} + /> + + } + isConnected="connected" + addAChannel={} + /> +
    + ); +}; + +export default WhatsappSmsSource; diff --git a/frontend/ui/src/pages/Channels/index.module.scss b/frontend/ui/src/pages/Channels/index.module.scss index fa4b1f1653..59fb19a1ec 100644 --- a/frontend/ui/src/pages/Channels/index.module.scss +++ b/frontend/ui/src/pages/Channels/index.module.scss @@ -1,75 +1,44 @@ @import '../../assets/scss/fonts'; @import '../../assets/scss/colors'; -.headline { - display: flex; - justify-content: space-between; - margin-bottom: 64px; -} - -.headlineText { - @include font-xl; - font-weight: bold; -} - .channelsWrapper { - width: 100%; background: white; - padding: 32px; - margin: 88px 2.5em 5em 7.5em; + display: block; border-radius: 10px; - box-sizing: border-box; - overflow: visible; + padding-left: 96px; + padding-top: 88px; + width: 100%; + overflow-y: auto; + padding: 32px; + margin: 88px 1.5em 0 100px; min-height: calc(100vh - 170px); } - -.connectButton { - @include font-m; - font-weight: 700; - line-height: 16px; - font-size: 20px; - height: 40px; - background-color: var(--color-airy-blue); - color: white; - border-radius: 4px; - text-align: center; - border: none; - cursor: pointer; - padding: 8px 16px; - margin: 0 0; - &:hover { - background-color: var(--color-airy-blue-hover); - } - - &:active { - background: var(--color-airy-blue-pressed); - } - - &:disabled { - cursor: not-allowed; - color: var(--color-text-gray) !important; - background-color: var(--color-light-gray) !important; - border: none; - } -} - -.channelList { - list-style: none; +.channelsHeadline { + display: flex; + justify-content: space-between; + margin-bottom: 20px; + color: #212428; + @include font-base; + font-size: 39px; + font-weight: 900; + letter-spacing: 0; + margin-bottom: 14px; } -.channelListEntry { - display: flex; - border-bottom: 1px solid var(--color-light-gray); - align-items: center; - padding: 8px 0; +.channelsHeadlineText { + @include font-xl; + font-weight: 900; } -.channelName { - padding: 0 16px; +.channelsChoice { + list-style: none; + color: var(--color-text-gray); + padding-bottom: 20px; } -.channelAction { - flex-grow: 1; - justify-content: right; +.wrapper { display: flex; + flex-direction: column; + width: 20%; + margin-bottom: 1em; } diff --git a/frontend/ui/src/pages/Channels/index.tsx b/frontend/ui/src/pages/Channels/index.tsx index d2d9e8aeac..b5de21628d 100644 --- a/frontend/ui/src/pages/Channels/index.tsx +++ b/frontend/ui/src/pages/Channels/index.tsx @@ -1,17 +1,17 @@ -/* global FB */ -import React, {useCallback, useEffect, useState} from 'react'; +import React, {useEffect} from 'react'; import _, {connect, ConnectedProps} from 'react-redux'; import {RouteComponentProps} from 'react-router-dom'; -import FacebookLogin from 'react-facebook-login'; -import {Button} from '@airyhq/components'; - -import {Channel} from 'httpclient'; -import {AiryConfig} from '../../AiryConfig'; import {listChannels, exploreChannels, connectChannel, disconnectChannel} from '../../actions/channel'; -import {StateModel} from '../../reducers'; - +import {StateModel} from '../../reducers/index'; import styles from './index.module.scss'; + +import {allChannels} from '../../selectors/channels'; import {setPageTitle} from '../../services/pageTitle'; +import ChatPluginSource from '../Channels/ChannelsSources/ChatPluginSource'; +import FacebookSource from '../Channels/ChannelsSources/FacebookSource'; +import TwilloSmsSource from '../Channels/ChannelsSources/TwilloSmsSource'; +import WhatsappSmsSource from '../Channels/ChannelsSources/WhatsappSmsSource'; +import GoogleSource from '../Channels/ChannelsSources/GoogleSource'; const mapDispatchToProps = { listChannels, @@ -20,103 +20,39 @@ const mapDispatchToProps = { disconnectChannel, }; -const mapStateToProps = (state: StateModel) => { - return { - channels: state.data.channels, - }; -}; +const mapStateToProps = (state: StateModel) => ({ + channels: Object.values(allChannels(state)), +}); const connector = connect(mapStateToProps, mapDispatchToProps); type ChannelsConnectProps = {} & ConnectedProps & RouteComponentProps; const Channels = (props: ChannelsConnectProps) => { - const [facebookToken, setFacebookToken] = useState(''); useEffect(() => { props.listChannels(); setPageTitle('Channels'); }, []); - const connect = (token: string) => { - props.exploreChannels({ - source: 'facebook', - token, - }); - }; - - const fetchPages = () => { - FB.getLoginStatus(loginResponse => { - if (loginResponse.status === 'connected') { - setFacebookToken(loginResponse.authResponse.accessToken); - connect(loginResponse.authResponse.accessToken); - } else { - FB.login(loginResponse => { - setFacebookToken(loginResponse.authResponse.accessToken); - connect(loginResponse.authResponse.accessToken); - }); - } - }); - }; - - const connectClicked = useCallback( - (channel: Channel) => { - props.connectChannel({ - source: channel.source, - sourceChannelId: channel.sourceChannelId, - token: facebookToken, - }); - }, - [facebookToken] - ); - - const disconnectClicked = (channel: Channel) => { - props.disconnectChannel('facebook', {channelId: channel.sourceChannelId}); - }; - return (
    -
    -

    Channels

    - ( - - )} - /> +
    +
    +

    Channels

    +
    +
    +
    + {' '} +

    Choose a channel you want to connect

    +
    + +
    + + + + +
    -
      - {props.channels.map((channel: Channel) => { - const channelName = channel.metadata.name; - return ( -
    • - {channel.metadata.imageUrl && ( - {channelName} - )} -
      {channel.metadata.name}
      -
      - {channel.connected ? ( - - ) : ( - - )} -
      -
    • - ); - })} -
    ); }; diff --git a/frontend/ui/src/pages/Inbox/ConversationListHeader/index.module.scss b/frontend/ui/src/pages/Inbox/ConversationListHeader/index.module.scss index 191bc87750..1076e9cb78 100644 --- a/frontend/ui/src/pages/Inbox/ConversationListHeader/index.module.scss +++ b/frontend/ui/src/pages/Inbox/ConversationListHeader/index.module.scss @@ -19,7 +19,7 @@ flex-direction: row; align-items: stretch; justify-content: flex-start; - height: 40px; + height: 100px; width: 100%; animation-name: searchFieldAnimation; animation-duration: 500ms; diff --git a/frontend/ui/src/pages/Inbox/ConversationListHeader/index.tsx b/frontend/ui/src/pages/Inbox/ConversationListHeader/index.tsx index 0095af9e25..2438812dc3 100644 --- a/frontend/ui/src/pages/Inbox/ConversationListHeader/index.tsx +++ b/frontend/ui/src/pages/Inbox/ConversationListHeader/index.tsx @@ -6,8 +6,8 @@ import {StateModel} from '../../../reducers'; import {setSearch, resetFilteredConversationAction} from '../../../actions/conversationsFilter'; -import {ReactComponent as IconSearch} from '../../../assets/images/icons/search.svg'; -import {ReactComponent as BackIcon} from '../../../assets/images/icons/arrow-left-2.svg'; +import {ReactComponent as IconSearch} from 'assets/images/icons/search.svg'; +import {ReactComponent as BackIcon} from 'assets/images/icons/arrow-left-2.svg'; import styles from './index.module.scss'; diff --git a/frontend/ui/src/pages/Inbox/ConversationListItem/index.tsx b/frontend/ui/src/pages/Inbox/ConversationListItem/index.tsx index 69fdd7a95d..b9640b44f0 100644 --- a/frontend/ui/src/pages/Inbox/ConversationListItem/index.tsx +++ b/frontend/ui/src/pages/Inbox/ConversationListItem/index.tsx @@ -8,7 +8,7 @@ import AvatarImage from '../../../components/AvatarImage'; import {formatTimeOfMessage} from '../../../services/format/date'; import {Message} from 'httpclient'; -import {MergedConversation, StateModel} from '../../../reducers'; +import {MergedConversation} from '../../../reducers'; import {INBOX_CONVERSATIONS_ROUTE} from '../../../routes/routes'; import {readConversations} from '../../../actions/conversations'; @@ -24,22 +24,15 @@ type ConversationListItemProps = { style: CSSProperties; } & ConnectedProps; -const mapStateToProps = (state: StateModel) => { - return { - channels: state.data.channels, - }; -}; - const mapDispatchToProps = { readConversations, }; -const connector = connect(mapStateToProps, mapDispatchToProps); +const connector = connect(null, mapDispatchToProps); const FormattedMessage = ({message}: FormattedMessageProps) => { - if (message && message.content) { - const messageJSON = JSON.parse(message.content); - return <>{messageJSON.text}; + if (message?.content) { + return <>{message.content.message?.text || message.content.text}; } return
    ; }; diff --git a/frontend/ui/src/pages/Inbox/ConversationsFilter/Popup.tsx b/frontend/ui/src/pages/Inbox/ConversationsFilter/Popup.tsx index edacd82f11..cf56589df3 100644 --- a/frontend/ui/src/pages/Inbox/ConversationsFilter/Popup.tsx +++ b/frontend/ui/src/pages/Inbox/ConversationsFilter/Popup.tsx @@ -15,16 +15,17 @@ import {IconChannelFilter} from '../../../components/IconChannelFilter'; import DialogCustomizable from '../../../components/DialogCustomizable'; import Tag from '../../../components/Tag'; -import {ReactComponent as CheckmarkIcon} from '../../../assets/images/icons/checkmark.svg'; +import {ReactComponent as CheckmarkIcon} from 'assets/images/icons/checkmark.svg'; import styles from './Popup.module.scss'; +import {allChannels} from '../../../selectors/channels'; function mapStateToProps(state: StateModel) { return { user: state.data.user, filter: state.data.conversations.filtered.currentFilter, tags: state.data.tags.all, - channels: state.data.channels, + channels: Object.values(allChannels(state)), }; } diff --git a/frontend/ui/src/pages/Inbox/ConversationsFilter/index.module.scss b/frontend/ui/src/pages/Inbox/ConversationsFilter/index.module.scss index 9cc0b4ac4f..4ced951f5f 100644 --- a/frontend/ui/src/pages/Inbox/ConversationsFilter/index.module.scss +++ b/frontend/ui/src/pages/Inbox/ConversationsFilter/index.module.scss @@ -27,6 +27,14 @@ border-bottom: 4px solid var(--color-airy-blue); } +.filterButtonContainer { + margin-top: 9px; +} + +.filterButton:focus { + outline: none; +} + .filterButton { @include font-base; display: flex; diff --git a/frontend/ui/src/pages/Inbox/ConversationsFilter/index.tsx b/frontend/ui/src/pages/Inbox/ConversationsFilter/index.tsx index fa618cb7a0..6804951cf2 100644 --- a/frontend/ui/src/pages/Inbox/ConversationsFilter/index.tsx +++ b/frontend/ui/src/pages/Inbox/ConversationsFilter/index.tsx @@ -8,7 +8,7 @@ import {StateModel} from '../../../reducers'; import {setFilter, resetFilter} from '../../../actions/conversationsFilter'; import {isFilterActive} from '../../../selectors/conversations'; -import {ReactComponent as ChevronLeft} from '../../../assets/images/icons/chevron_left.svg'; +import {ReactComponent as ChevronLeft} from 'assets/images/icons/chevron_left.svg'; import Popup from './Popup'; import styles from './index.module.scss'; @@ -149,10 +149,12 @@ const ConversationsFilter = (props: ConversationsFilterProps) => {
    )} - +
    + +
    {isFilterOpen && }
    diff --git a/frontend/ui/src/pages/Inbox/MessageInput/index.tsx b/frontend/ui/src/pages/Inbox/MessageInput/index.tsx index 7f8cb9a93d..535cccb220 100644 --- a/frontend/ui/src/pages/Inbox/MessageInput/index.tsx +++ b/frontend/ui/src/pages/Inbox/MessageInput/index.tsx @@ -3,7 +3,7 @@ import {connect, ConnectedProps} from 'react-redux'; import {useParams} from 'react-router-dom'; import styles from './index.module.scss'; import {sendMessages} from '../../../actions/messages'; -import {ReactComponent as Paperplane} from '../../../assets/images/icons/paperplane.svg'; +import {ReactComponent as Paperplane} from 'assets/images/icons/paperplane.svg'; import {StateModel} from '../../../reducers'; import {getTextMessagePayload} from 'httpclient'; diff --git a/frontend/ui/src/pages/Inbox/Messenger/ConversationMetadata/index.tsx b/frontend/ui/src/pages/Inbox/Messenger/ConversationMetadata/index.tsx index b723fb3534..99206ff3c1 100644 --- a/frontend/ui/src/pages/Inbox/Messenger/ConversationMetadata/index.tsx +++ b/frontend/ui/src/pages/Inbox/Messenger/ConversationMetadata/index.tsx @@ -1,7 +1,7 @@ import React, {FormEvent, useEffect, useState} from 'react'; import _, {connect, ConnectedProps} from 'react-redux'; import {withRouter} from 'react-router-dom'; -import {Tag as TagModel, TagColor} from 'httpclient'; +import {Tag as TagModel, TagColor, getTags} from 'httpclient'; import {createTag, listTags} from '../../../../actions/tags'; import {addTagToConversation, removeTagFromConversation} from '../../../../actions/conversations'; @@ -59,11 +59,11 @@ const ConversationMetadata = (props: ConnectedProps) => { }; const filterForUnusedTags = (tags: TagModel[]): TagModel[] => { - return tags.filter(tag => !conversation.tags.includes(tag.id)); + return tags.filter(tag => !(tag.id in (conversation.metadata.tags || {}))); }; const filterForUsedTags = (tags: TagModel[]): TagModel[] => { - return tags.filter(tag => conversation.tags.includes(tag.id)); + return tags.filter(tag => tag.id in (conversation.metadata.tags || {})); }; const tagSorter = (tagA: TagModel, tagB: TagModel) => { @@ -195,7 +195,7 @@ const ConversationMetadata = (props: ConnectedProps) => {
    {tags && - conversation.tags + getTags(conversation) .map(tagId => findTag(tagId)) .sort(tagSorter) .map(tag => tag && removeTag(tag)} />)} diff --git a/frontend/ui/src/pages/Inbox/Messenger/MessageList/index.tsx b/frontend/ui/src/pages/Inbox/Messenger/MessageList/index.tsx index 38ff34b8e6..44fba615e5 100644 --- a/frontend/ui/src/pages/Inbox/Messenger/MessageList/index.tsx +++ b/frontend/ui/src/pages/Inbox/Messenger/MessageList/index.tsx @@ -17,6 +17,8 @@ import {getCurrentConversation, getCurrentMessages} from '../../../../selectors/ import {ConversationRouteProps} from '../../index'; import {isSameDay} from 'dates'; import {getSource, isFromContact} from 'httpclient'; +import {MessageInfoWrapper} from 'render/components/MessageInfoWrapper'; +import {formatTime} from 'dates'; type MessageListProps = ConnectedProps; @@ -157,6 +159,7 @@ const MessageList = (props: MessageListProps) => { const lastInGroup = nextMessage ? isFromContact(message) !== isFromContact(nextMessage) : true; const contactToShow = shouldShowContact ? conversation.metadata.contact : null; + const sentAt = lastInGroup ? formatTime(message.sentAt) : null; return (
    @@ -165,12 +168,19 @@ const MessageList = (props: MessageListProps) => { {formatDateOfMessage(message)}
    )} - + isChatPlugin={false}> + +
    ); })} diff --git a/frontend/ui/src/pages/Inbox/Messenger/MessengerContainer/index.tsx b/frontend/ui/src/pages/Inbox/Messenger/MessengerContainer/index.tsx index c5d357bae1..71c3e664a2 100644 --- a/frontend/ui/src/pages/Inbox/Messenger/MessengerContainer/index.tsx +++ b/frontend/ui/src/pages/Inbox/Messenger/MessengerContainer/index.tsx @@ -4,18 +4,16 @@ import {withRouter} from 'react-router-dom'; import {StateModel} from '../../../../reducers'; import MessageList from '../MessageList'; -import {ReactComponent as EmptyStateImage} from '../../../../assets/images/empty-state/inbox-empty-state.svg'; +import {ReactComponent as EmptyStateImage} from 'assets/images/empty-state/inbox-empty-state.svg'; import styles from './index.module.scss'; import ConversationMetadata from '../ConversationMetadata'; import MessageInput from '../../MessageInput'; -import {getCurrentConversation} from '../../../../selectors/conversations'; +import {allConversations, getCurrentConversation} from '../../../../selectors/conversations'; -const mapStateToProps = (state: StateModel, ownProps) => { - return { - conversations: state.data.conversations.all.items, - currentConversation: getCurrentConversation(state, ownProps), - }; -}; +const mapStateToProps = (state: StateModel, ownProps) => ({ + conversations: allConversations(state), + currentConversation: getCurrentConversation(state, ownProps), +}); const connector = connect(mapStateToProps); diff --git a/frontend/ui/src/pages/Inbox/Messenger/index.tsx b/frontend/ui/src/pages/Inbox/Messenger/index.tsx index c5e8099543..af7f24fa30 100644 --- a/frontend/ui/src/pages/Inbox/Messenger/index.tsx +++ b/frontend/ui/src/pages/Inbox/Messenger/index.tsx @@ -4,16 +4,16 @@ import _, {connect, ConnectedProps} from 'react-redux'; import ConversationList from '../ConversationList'; -import {StateModel} from '../../../reducers'; -import {AllConversationsState} from '../../../reducers/data/conversations'; +import {MergedConversation, StateModel} from '../../../reducers'; import styles from './index.module.scss'; import MessengerContainer from './MessengerContainer'; +import {allConversations} from '../../../selectors/conversations'; const mapStateToProps = (state: StateModel) => { return { loading: state.data.conversations.all.paginationData.loading, - conversations: state.data.conversations.all, + conversations: allConversations(state), }; }; @@ -22,7 +22,7 @@ const connector = connect(mapStateToProps); const Messenger = (props: ConnectedProps & RouteComponentProps) => { const {conversations, match} = props; - const waitForContentAndRedirect = (conversations: AllConversationsState) => { + const waitForContentAndRedirect = (conversations: MergedConversation[]) => { const conversationId = conversations[0].id; const targetPath = `/inbox/conversations/${conversationId}`; if (targetPath !== window.location.pathname) { @@ -30,13 +30,13 @@ const Messenger = (props: ConnectedProps & RouteComponentProps } }; - if (match.isExact && conversations.items.length) { + if (match.isExact && conversations.length) { return waitForContentAndRedirect(conversations); } return (
    - {!!conversations.items && ( + {!!conversations && (
    diff --git a/frontend/ui/src/pages/Login/index.tsx b/frontend/ui/src/pages/Login/index.tsx index 2d3168ec2a..b73879be6f 100644 --- a/frontend/ui/src/pages/Login/index.tsx +++ b/frontend/ui/src/pages/Login/index.tsx @@ -6,7 +6,7 @@ import {ErrorNotice, Input, Button} from '@airyhq/components'; import {loginViaEmail} from '../../actions/user'; -import logo from '../../assets/images/logo/airy_primary_rgb.svg'; +import logo from 'assets/images/logo/airy_primary_rgb.svg'; import styles from './index.module.scss'; import {setPageTitle} from '../../services/pageTitle'; diff --git a/frontend/ui/src/pages/Tags/EmptyStateTags.tsx b/frontend/ui/src/pages/Tags/EmptyStateTags.tsx index 5083d99e80..7e07a327cb 100644 --- a/frontend/ui/src/pages/Tags/EmptyStateTags.tsx +++ b/frontend/ui/src/pages/Tags/EmptyStateTags.tsx @@ -2,7 +2,7 @@ import React, {useState} from 'react'; import styles from './index.module.scss'; import {Button} from '@airyhq/components'; -import emptyImage from '../../assets/images/empty-state/tags-empty-state.svg'; +import {ReactComponent as EmptyImage} from 'assets/images/empty-state/tags-empty-state.svg'; import SimpleTagForm from './SimpleTagForm'; const EmptyStateTags: React.FC = (): JSX.Element => { @@ -17,7 +17,7 @@ const EmptyStateTags: React.FC = (): JSX.Element => {

    Tags provide a useful way to group related conversations together and to quickly filter and search them.

    - + diff --git a/frontend/ui/src/pages/Tags/TableRow.tsx b/frontend/ui/src/pages/Tags/TableRow.tsx index 52cbfb7596..3bab69299b 100644 --- a/frontend/ui/src/pages/Tags/TableRow.tsx +++ b/frontend/ui/src/pages/Tags/TableRow.tsx @@ -4,8 +4,8 @@ import _, {connect, ConnectedProps} from 'react-redux'; import styles from './TableRow.module.scss'; import {updateTag} from '../../actions/tags'; import {Button, LinkButton} from '@airyhq/components'; -import {ReactComponent as EditIcon} from '../../assets/images/icons/edit.svg'; -import {ReactComponent as TrashIcon} from '../../assets/images/icons/trash.svg'; +import {ReactComponent as EditIcon} from 'assets/images/icons/edit.svg'; +import {ReactComponent as TrashIcon} from 'assets/images/icons/trash.svg'; import ColorSelector from '../../components/ColorSelector'; import Tag from '../../components/Tag'; import {Tag as TagModel, TagColor} from 'httpclient'; diff --git a/frontend/ui/src/pages/Tags/index.module.scss b/frontend/ui/src/pages/Tags/index.module.scss index 50591516b5..9fbbb61071 100644 --- a/frontend/ui/src/pages/Tags/index.module.scss +++ b/frontend/ui/src/pages/Tags/index.module.scss @@ -100,10 +100,8 @@ &:hover { color: var(--color-airy-blue-hover); .plusButton { - svg { - path { - fill: var(--color-airy-blue-hover); - } + path { + fill: var(--color-airy-blue-hover); } } } @@ -111,11 +109,9 @@ .plusButton { display: inherit; margin-left: 4px; - svg { - width: 12px; - position: relative; - top: 1px; - } + width: 12px; + position: relative; + top: 1px; } } diff --git a/frontend/ui/src/pages/Tags/index.tsx b/frontend/ui/src/pages/Tags/index.tsx index 8a25f9bc6d..f76d01fb40 100644 --- a/frontend/ui/src/pages/Tags/index.tsx +++ b/frontend/ui/src/pages/Tags/index.tsx @@ -3,7 +3,7 @@ import _, {connect, ConnectedProps} from 'react-redux'; import {SettingsModal, LinkButton, Button, SearchField, Input} from '@airyhq/components'; -import plus from '../../assets/images/icons/plus.svg'; +import {ReactComponent as Plus} from 'assets/images/icons/plus.svg'; import {listTags, deleteTag, filterTags, errorTag} from '../../actions/tags'; import {filteredTags} from '../../selectors/tags'; @@ -168,7 +168,7 @@ class Tags extends Component, typeof initialSta {this.state.createDrawer && } diff --git a/frontend/ui/src/reducers/data/channels/index.ts b/frontend/ui/src/reducers/data/channels/index.ts index a44c9b44e8..18566e5f2f 100644 --- a/frontend/ui/src/reducers/data/channels/index.ts +++ b/frontend/ui/src/reducers/data/channels/index.ts @@ -1,30 +1,41 @@ import {ActionType, getType} from 'typesafe-actions'; import {Channel} from 'httpclient'; import * as actions from '../../../actions/channel'; -import {unionWith} from 'lodash-es'; +import * as metadataActions from '../../../actions/metadata'; +import {merge} from 'lodash-es'; -type Action = ActionType; +type Action = ActionType | ActionType; -export const initialState = []; +export interface ChannelsState { + [channelId: string]: Channel; +} -const mergeChannels = (channels: Channel[], newChannels: Channel[]) => - unionWith(newChannels, channels, (channelA: Channel, channelB: Channel) => { - return channelA.id === channelB.id; - }); +const setChannel = (state: ChannelsState, channel: Channel) => ({ + ...state, + [channel.id]: channel, +}); -const removeChannel = (channels: Channel[], removeChannel: Channel) => - channels.filter(item => item.id != removeChannel.id); - -const channelsReducer: any = (state = initialState, action: Action): Channel[] | {} => { +const channelsReducer = (state = {}, action: Action): ChannelsState => { switch (action.type) { + case getType(metadataActions.setMetadataAction): + if (action.payload.subject !== 'channel') { + return state; + } + + return { + ...state, + [action.payload.identifier]: { + id: action.payload.identifier, + ...state[action.payload.identifier], + metadata: merge({}, state[action.payload.identifier]?.metadata, action.payload.metadata), + }, + }; case getType(actions.setCurrentChannelsAction): - return action.payload; + return action.payload.reduce(setChannel, {}); case getType(actions.addChannelsAction): - return mergeChannels(state, action.payload); - case getType(actions.addChannelAction): - return mergeChannels(state, [action.payload]); - case getType(actions.removeChannelAction): - return removeChannel(state, action.payload); + return action.payload.reduce(setChannel, state); + case getType(actions.setChannelAction): + return setChannel(state, action.payload); default: return state; } diff --git a/frontend/ui/src/reducers/data/conversations/index.ts b/frontend/ui/src/reducers/data/conversations/index.ts index bfbd2ea2e1..a334064a47 100644 --- a/frontend/ui/src/reducers/data/conversations/index.ts +++ b/frontend/ui/src/reducers/data/conversations/index.ts @@ -1,6 +1,6 @@ import {ActionType, getType} from 'typesafe-actions'; import {combineReducers} from 'redux'; -import {cloneDeep, sortBy, uniq, merge} from 'lodash-es'; +import {cloneDeep, sortBy, merge, pickBy, pick} from 'lodash-es'; import {Conversation, ConversationFilter, Message} from 'httpclient'; @@ -8,6 +8,7 @@ import * as metadataActions from '../../../actions/metadata'; import * as actions from '../../../actions/conversations'; import * as filterActions from '../../../actions/conversationsFilter'; import * as messageActions from '../../../actions/messages'; +import {MetadataEvent, ConversationMetadata} from 'httpclient'; type Action = ActionType | ActionType; type FilterAction = ActionType; @@ -145,43 +146,25 @@ const initialState: AllConversationsState = { }, }; -const addTagToConversation = (state: AllConversationsState, conversationId, tagId) => { +const removeTagFromConversation = (state: AllConversationsState, conversationId, tagId) => { const conversation: Conversation = state.items[conversationId]; - if (conversation) { - const tags: string[] = [...state.items[conversationId].tags]; - tags.push(tagId); - - return { - ...state, - items: { - ...state.items, - [conversation.id]: { - ...conversation, - tags: uniq(tags), - }, - }, - }; + if (!conversation) { + return state; } - return state; -}; - -const removeTagFromConversation = (state: AllConversationsState, conversationId, tagId) => { - const conversation: Conversation = state.items[conversationId]; - if (conversation) { - return { - ...state, - items: { - ...state.items, - [conversation.id]: { - ...conversation, - tags: conversation.tags.filter(tag => tag !== tagId), + return { + ...state, + items: { + ...state.items, + [conversation.id]: { + ...conversation, + metadata: { + ...conversation.metadata, + tags: pickBy(conversation.metadata?.tags, (value, key) => key !== tagId), }, }, - }; - } - - return state; + }, + }; }; const lastMessageOf = (messages: Message[]): Message => { @@ -220,8 +203,30 @@ function allReducer( items: { ...state.items, [action.payload.identifier]: { + id: action.payload.identifier, ...state.items[action.payload.identifier], - metadata: merge({}, state.items[action.payload.identifier].metadata, action.payload.metadata), + metadata: { + // Ensure that there is always a display name present + ...pick(state.items[action.payload.identifier]?.metadata, 'contact.displayName'), + ...(>action.payload).metadata, + }, + }, + }, + }; + + case getType(metadataActions.mergeMetadataAction): + if (action.payload.subject !== 'conversation') { + return state; + } + + return { + ...state, + items: { + ...state.items, + [action.payload.identifier]: { + id: action.payload.identifier, + ...state.items[action.payload.identifier], + metadata: merge({}, state.items[action.payload.identifier]?.metadata, action.payload.metadata), }, }, }; @@ -268,9 +273,6 @@ function allReducer( }, }; - case getType(actions.addTagToConversationAction): - return addTagToConversation(state, action.payload.conversationId, action.payload.tagId); - case getType(actions.removeTagFromConversationAction): return removeTagFromConversation(state, action.payload.conversationId, action.payload.tagId); diff --git a/frontend/ui/src/reducers/data/index.ts b/frontend/ui/src/reducers/data/index.ts index 3fbe7a23db..b4fa891015 100644 --- a/frontend/ui/src/reducers/data/index.ts +++ b/frontend/ui/src/reducers/data/index.ts @@ -1,5 +1,5 @@ import _, {combineReducers, Reducer} from 'redux-starter-kit'; -import {User, Channel} from 'httpclient'; +import {User} from 'httpclient'; import {Tags} from './tags'; import {Settings} from './settings'; @@ -7,7 +7,7 @@ import user from './user'; import conversations, {ConversationsState} from './conversations'; import tags from './tags'; import settings from './settings'; -import channels from './channels'; +import channels, {ChannelsState} from './channels'; import messages, {Messages} from './messages'; export * from './channels'; @@ -22,7 +22,7 @@ export type DataState = { messages: Messages; tags: Tags; settings: Settings; - channels: Channel[]; + channels: ChannelsState; }; const reducers: Reducer = combineReducers({ diff --git a/frontend/ui/src/reducers/data/settings/index.ts b/frontend/ui/src/reducers/data/settings/index.ts index 7016396830..40bd68dd38 100644 --- a/frontend/ui/src/reducers/data/settings/index.ts +++ b/frontend/ui/src/reducers/data/settings/index.ts @@ -1,13 +1,8 @@ -import {ActionType} from 'typesafe-actions'; -import {DataState} from '../..'; +import {ActionType, getType} from 'typesafe-actions'; import * as actions from '../../../actions/settings'; type Action = ActionType; -export type SettingsState = { - data: DataState; -}; - export interface ColorSettings { default: string; background: string; @@ -26,10 +21,10 @@ const defaultState = { export default function tagsReducer(state = defaultState, action: Action): Settings { switch (action.type) { - case actions.ADD_SETTINGS_TO_STORE: + case getType(actions.fetchSettings): return { ...state, - colors: action.colors.colors, + ...action.payload, }; default: return state; diff --git a/frontend/ui/src/selectors/channels.ts b/frontend/ui/src/selectors/channels.ts new file mode 100644 index 0000000000..d26c11f6ef --- /dev/null +++ b/frontend/ui/src/selectors/channels.ts @@ -0,0 +1,7 @@ +import {StateModel} from '../reducers'; +import {pickBy} from 'lodash-es'; + +// Filter out channels that only have metadata +// I.e. Websocket channels don't necessarily have a name so we wait for the metadata +export const allChannels = (state: StateModel) => + pickBy(state.data.channels, ({id, metadata, ...restChannel}) => Object.keys(restChannel).length > 1); diff --git a/frontend/ui/src/selectors/conversations.ts b/frontend/ui/src/selectors/conversations.ts index 77e0d4f143..eb994dd066 100644 --- a/frontend/ui/src/selectors/conversations.ts +++ b/frontend/ui/src/selectors/conversations.ts @@ -1,7 +1,7 @@ import _, {createSelector} from 'reselect'; -import {filter, reverse, sortBy, values} from 'lodash-es'; +import {filter, pickBy, reverse, sortBy, values} from 'lodash-es'; import {Conversation} from 'httpclient'; -import {StateModel} from '../reducers'; +import {MergedConversation, StateModel} from '../reducers'; import {ConversationMap} from '../reducers/data/conversations'; import {ConversationRouteProps} from '../pages/Inbox'; @@ -16,12 +16,16 @@ export const filteredConversationSelector = createSelector( (conversations: ConversationMap) => Object.keys(conversations).map((cId: string) => ({...conversations[cId]})) ); -export const allConversationSelector = createSelector( - (state: StateModel) => state.data.conversations.all.items, - (conversations: ConversationMap) => Object.keys(conversations).map((cId: string) => ({...conversations[cId]})) -); +// Filter out conversations that only have metadata +export const allConversations = (state: StateModel): MergedConversation[] => + Object.values( + pickBy( + state.data.conversations.all.items, + ({id, metadata, ...restConversation}) => Object.keys(restConversation).length > 1 + ) + ); -export const newestConversationFirst = createSelector(allConversationSelector, conversations => { +export const newestConversationFirst = createSelector(allConversations, conversations => { return reverse( sortBy( values(conversations), diff --git a/go.mod b/go.mod new file mode 100644 index 0000000000..daa4aeb026 --- /dev/null +++ b/go.mod @@ -0,0 +1,27 @@ +module github.com/airyhq/airy + +go 1.13 + +// Used to update go_repositories.bzl with Gazelle +// Automatically generated by running //tools/update-deps + +require ( + github.com/alicebob/miniredis/v2 v2.13.3 + github.com/go-redis/redis/v8 v8.2.2 + github.com/spf13/cobra v1.1.1 + github.com/kr/pretty v0.2.1 + github.com/mitchellh/go-homedir v1.1.0 + github.com/spf13/viper v1.7.1 + github.com/stretchr/testify v1.6.1 + github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 // indirect + github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 // indirect + goji.io v2.0.2+incompatible + gopkg.in/yaml.v2 v2.4.0 + k8s.io/api v0.20.0 + k8s.io/apimachinery v0.20.0 + k8s.io/client-go v0.20.0 + k8s.io/klog v1.0.0 + golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect + k8s.io/utils v0.0.0-20201110183641-67b214c5f920 // indirect + golang.org/x/mod v0.4.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000000..2af47b6126 --- /dev/null +++ b/go.sum @@ -0,0 +1,541 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= +github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= +github.com/alicebob/miniredis/v2 v2.13.3/go.mod h1:uS970Sw5Gs9/iK3yBg0l9Uj9s25wXxSpQUE9EaJ/Blg= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-redis/redis/v8 v8.2.2/go.mod h1:ysgGY09J/QeDYbu3HikWEIPCwaeOkuNoTgKayTEaEOw= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opentelemetry.io/otel v0.11.0/go.mod h1:G8UCk+KooF2HLkgo8RHX9epABH/aRGYET7gQOqBVdB0= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +goji.io v2.0.2+incompatible/go.mod h1:sbqFwrtqZACxLBTQcdgVjFh54yGVCvwq8+w49MVMMIk= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.20.0/go.mod h1:HyLC5l5eoS/ygQYl1BXBgFzWNlkHiAuyNAbevIn+FKg= +k8s.io/apimachinery v0.20.0/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/client-go v0.20.0/go.mod h1:4KWh/g+Ocd8KkCwKF8vUNnmqgv+EVnQDK4MBF4oB5tY= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/go_repositories.bzl b/go_repositories.bzl index 16b6d92726..429cf0b5bf 100644 --- a/go_repositories.bzl +++ b/go_repositories.bzl @@ -7,23 +7,18 @@ def go_repositories(): sum = "h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U=", version = "v0.0.1-2020.1.3", ) - go_repository( - name = "com_github_afex_hystrix_go", - importpath = "github.com/afex/hystrix-go", - sum = "h1:rFw4nCn9iMW+Vajsk51NtYIcwSTkXr+JGrMd36kTDJw=", - version = "v0.0.0-20180502004556-fa1af6a1f4f5", - ) + go_repository( name = "com_github_alecthomas_template", importpath = "github.com/alecthomas/template", - sum = "h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=", - version = "v0.0.0-20190718012654-fb15b899a751", + sum = "h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=", + version = "v0.0.0-20160405071501-a0175ee3bccc", ) go_repository( name = "com_github_alecthomas_units", importpath = "github.com/alecthomas/units", - sum = "h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=", - version = "v0.0.0-20190924025748-f65c72e2690d", + sum = "h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=", + version = "v0.0.0-20151022065526-2efee857e7cf", ) go_repository( @@ -38,12 +33,7 @@ def go_repositories(): sum = "h1:kohgdtN58KW/r9ZDVmMJE3MrfbumwsDQStd0LPAGmmw=", version = "v2.13.3", ) - go_repository( - name = "com_github_apache_thrift", - importpath = "github.com/apache/thrift", - sum = "h1:5hryIiq9gtn+MiLVn0wP37kb/uTeRZgN08WoCsAhIhI=", - version = "v0.13.0", - ) + go_repository( name = "com_github_armon_circbuf", importpath = "github.com/armon/circbuf", @@ -51,12 +41,6 @@ def go_repositories(): version = "v0.0.0-20150827004946-bbbad097214e", ) - go_repository( - name = "com_github_armon_consul_api", - importpath = "github.com/armon/consul-api", - sum = "h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA=", - version = "v0.0.0-20180202201655-eb2c6b5be1b6", - ) go_repository( name = "com_github_armon_go_metrics", importpath = "github.com/armon/go-metrics", @@ -69,12 +53,6 @@ def go_repositories(): sum = "h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to=", version = "v0.0.0-20180808171621-7fddfc383310", ) - go_repository( - name = "com_github_aryann_difflib", - importpath = "github.com/aryann/difflib", - sum = "h1:pv34s756C4pEXnjgPfGYgdhg/ZdajGhyOvzx8k+23nw=", - version = "v0.0.0-20170710044230-e206f873d14a", - ) go_repository( name = "com_github_asaskevich_govalidator", @@ -82,24 +60,7 @@ def go_repositories(): sum = "h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=", version = "v0.0.0-20190424111038-f61b66f89f4a", ) - go_repository( - name = "com_github_aws_aws_lambda_go", - importpath = "github.com/aws/aws-lambda-go", - sum = "h1:SuCy7H3NLyp+1Mrfp+m80jcbi9KYWAs9/BXwppwRDzY=", - version = "v1.13.3", - ) - go_repository( - name = "com_github_aws_aws_sdk_go", - importpath = "github.com/aws/aws-sdk-go", - sum = "h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk=", - version = "v1.27.0", - ) - go_repository( - name = "com_github_aws_aws_sdk_go_v2", - importpath = "github.com/aws/aws-sdk-go-v2", - sum = "h1:qZ+woO4SamnH/eEbjM2IDLhRNwIwND/RQyVlBLp3Jqg=", - version = "v0.18.0", - ) + go_repository( name = "com_github_azure_go_autorest", importpath = "github.com/Azure/go-autorest", @@ -145,8 +106,8 @@ def go_repositories(): go_repository( name = "com_github_beorn7_perks", importpath = "github.com/beorn7/perks", - sum = "h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=", - version = "v1.0.1", + sum = "h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=", + version = "v1.0.0", ) go_repository( name = "com_github_bgentry_speakeasy", @@ -154,6 +115,12 @@ def go_repositories(): sum = "h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=", version = "v0.1.0", ) + go_repository( + name = "com_github_bketelsen_crypt", + importpath = "github.com/bketelsen/crypt", + sum = "h1:+0HFd5KSZ/mm3JmhmrDukiId5iR6w4+BdFtfSy4yWIc=", + version = "v0.0.3-0.20200106085610-5cbc8cc4026c", + ) go_repository( name = "com_github_burntsushi_toml", @@ -167,18 +134,6 @@ def go_repositories(): sum = "h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc=", version = "v0.0.0-20160522181843-27f122750802", ) - go_repository( - name = "com_github_casbin_casbin_v2", - importpath = "github.com/casbin/casbin/v2", - sum = "h1:bTwon/ECRx9dwBy2ewRVr5OiqjeXSGiTUY74sDPQi/g=", - version = "v2.1.2", - ) - go_repository( - name = "com_github_cenkalti_backoff", - importpath = "github.com/cenkalti/backoff", - sum = "h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=", - version = "v2.2.1+incompatible", - ) go_repository( name = "com_github_census_instrumentation_opencensus_proto", @@ -186,6 +141,12 @@ def go_repositories(): sum = "h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=", version = "v0.2.1", ) + go_repository( + name = "com_github_cespare_xxhash", + importpath = "github.com/cespare/xxhash", + sum = "h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=", + version = "v1.1.0", + ) go_repository( name = "com_github_cespare_xxhash_v2", @@ -211,12 +172,6 @@ def go_repositories(): sum = "h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=", version = "v0.0.0-20180213035817-a1ea475d72b1", ) - go_repository( - name = "com_github_clbanning_x2j", - importpath = "github.com/clbanning/x2j", - sum = "h1:EdRZT3IeKQmfCSrgo8SZ8V3MEnskuJP0wCYNpe+aiXo=", - version = "v0.0.0-20191024224557-825249438eec", - ) go_repository( name = "com_github_client9_misspell", @@ -225,64 +180,43 @@ def go_repositories(): version = "v0.3.4", ) go_repository( - name = "com_github_cockroachdb_datadriven", - importpath = "github.com/cockroachdb/datadriven", - sum = "h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y=", - version = "v0.0.0-20190809214429-80d97fb3cbaa", - ) - go_repository( - name = "com_github_codahale_hdrhistogram", - importpath = "github.com/codahale/hdrhistogram", - sum = "h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w=", - version = "v0.0.0-20161010025455-3a0bb77429bd", + name = "com_github_coreos_bbolt", + importpath = "github.com/coreos/bbolt", + sum = "h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s=", + version = "v1.3.2", ) go_repository( name = "com_github_coreos_etcd", importpath = "github.com/coreos/etcd", - sum = "h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04=", - version = "v3.3.10+incompatible", - ) - go_repository( - name = "com_github_coreos_go_etcd", - importpath = "github.com/coreos/go-etcd", - sum = "h1:bXhRBIXoTm9BYHS3gE0TtQuyNZyeEMux2sDi4oo5YOo=", - version = "v2.0.0+incompatible", + sum = "h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ=", + version = "v3.3.13+incompatible", ) + go_repository( name = "com_github_coreos_go_semver", importpath = "github.com/coreos/go-semver", - sum = "h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY=", - version = "v0.2.0", + sum = "h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=", + version = "v0.3.0", ) go_repository( name = "com_github_coreos_go_systemd", importpath = "github.com/coreos/go-systemd", - sum = "h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM=", - version = "v0.0.0-20180511133405-39ca1b05acc7", + sum = "h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=", + version = "v0.0.0-20190321100706-95778dfbb74e", ) go_repository( name = "com_github_coreos_pkg", importpath = "github.com/coreos/pkg", - sum = "h1:CAKfRE2YtTUIjjh1bkBtyYFaUT/WmOqsJjgtihT0vMI=", - version = "v0.0.0-20160727233714-3ac0863d7acf", + sum = "h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=", + version = "v0.0.0-20180928190104-399ea9e2e55f", ) + go_repository( name = "com_github_cpuguy83_go_md2man_v2", importpath = "github.com/cpuguy83/go-md2man/v2", - sum = "h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=", - version = "v2.0.0-20190314233015-f79a8a8ca69d", - ) - go_repository( - name = "com_github_cpuguy83_go_md2man", - importpath = "github.com/cpuguy83/go-md2man", - commit = "7762f7e404f8416dfa1d9bb6a8c192aa9acb4d19", - ) - go_repository( - name = "com_github_creack_pty", - importpath = "github.com/creack/pty", - sum = "h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A=", - version = "v1.1.7", + sum = "h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=", + version = "v2.0.0", ) go_repository( @@ -304,6 +238,13 @@ def go_repositories(): sum = "h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=", version = "v0.0.0-20200823014737-9f7001d12a5f", ) + go_repository( + name = "com_github_dgryski_go_sip13", + importpath = "github.com/dgryski/go-sip13", + sum = "h1:RMLoZVzv4GliuWafOuPuQDKSm1SJph7uCRnnS61JAn4=", + version = "v0.0.0-20181026042036-e10d5fee7954", + ) + go_repository( name = "com_github_docker_spdystream", importpath = "github.com/docker/spdystream", @@ -316,36 +257,6 @@ def go_repositories(): sum = "h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=", version = "v0.0.0-20180111231733-ee0de3bc6815", ) - go_repository( - name = "com_github_dustin_go_humanize", - importpath = "github.com/dustin/go-humanize", - sum = "h1:qk/FSDDxo05wdJH28W+p5yivv7LuLYLRXPPD8KQCtZs=", - version = "v0.0.0-20171111073723-bb3d318650d4", - ) - go_repository( - name = "com_github_eapache_go_resiliency", - importpath = "github.com/eapache/go-resiliency", - sum = "h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU=", - version = "v1.1.0", - ) - go_repository( - name = "com_github_eapache_go_xerial_snappy", - importpath = "github.com/eapache/go-xerial-snappy", - sum = "h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw=", - version = "v0.0.0-20180814174437-776d5712da21", - ) - go_repository( - name = "com_github_eapache_queue", - importpath = "github.com/eapache/queue", - sum = "h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=", - version = "v1.1.0", - ) - go_repository( - name = "com_github_edsrzf_mmap_go", - importpath = "github.com/edsrzf/mmap-go", - sum = "h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=", - version = "v1.0.0", - ) go_repository( name = "com_github_elazarl_goproxy", @@ -389,18 +300,6 @@ def go_repositories(): sum = "h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=", version = "v3.2.2+incompatible", ) - go_repository( - name = "com_github_franela_goblin", - importpath = "github.com/franela/goblin", - sum = "h1:gb2Z18BhTPJPpLQWj4T+rfKHYCHxRHCtRxhKKjRidVw=", - version = "v0.0.0-20200105215937-c9ffbefa60db", - ) - go_repository( - name = "com_github_franela_goreq", - importpath = "github.com/franela/goreq", - sum = "h1:a9ENSRDFBUPkJ5lCgVZh26+ZbGyoVJG7yb5SSzF5H54=", - version = "v0.0.0-20171204163338-bcd34c9993f8", - ) go_repository( name = "com_github_fsnotify_fsnotify", @@ -429,14 +328,14 @@ def go_repositories(): go_repository( name = "com_github_go_kit_kit", importpath = "github.com/go-kit/kit", - sum = "h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo=", - version = "v0.10.0", + sum = "h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=", + version = "v0.8.0", ) go_repository( name = "com_github_go_logfmt_logfmt", importpath = "github.com/go-logfmt/logfmt", - sum = "h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=", - version = "v0.5.0", + sum = "h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=", + version = "v0.4.0", ) go_repository( @@ -476,25 +375,21 @@ def go_repositories(): sum = "h1:A1tQgdeVF23Ojc1TIRpVuVfOadUdIM0vFVURigoPEMM=", version = "v8.2.2", ) - go_repository( - name = "com_github_go_sql_driver_mysql", - importpath = "github.com/go-sql-driver/mysql", - sum = "h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=", - version = "v1.4.0", - ) + go_repository( name = "com_github_go_stack_stack", importpath = "github.com/go-stack/stack", sum = "h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=", version = "v1.8.0", ) + go_repository( - name = "com_github_gogo_googleapis", - build_file_proto_mode = "disable_global", - importpath = "github.com/gogo/googleapis", - sum = "h1:kFkMAZBNAn4j7K0GiZr8cRYzejq68VbheufiV3YuyFI=", - version = "v1.1.0", + name = "com_github_gogo_protobuf", + importpath = "github.com/gogo/protobuf", + sum = "h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=", + version = "v1.3.1", ) + go_repository( name = "com_github_golang_glog", importpath = "github.com/golang/glog", @@ -520,12 +415,7 @@ def go_repositories(): sum = "h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=", version = "v1.4.3", ) - go_repository( - name = "com_github_golang_snappy", - importpath = "github.com/golang/snappy", - sum = "h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=", - version = "v0.0.0-20180518054509-2e65f85255db", - ) + go_repository( name = "com_github_google_btree", importpath = "github.com/google/btree", @@ -589,23 +479,12 @@ def go_repositories(): sum = "h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=", version = "v0.0.0-20181017120253-0766667cb4d1", ) - go_repository( - name = "com_github_gorilla_context", - importpath = "github.com/gorilla/context", - sum = "h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=", - version = "v1.1.1", - ) - go_repository( - name = "com_github_gorilla_mux", - importpath = "github.com/gorilla/mux", - sum = "h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=", - version = "v1.7.3", - ) + go_repository( name = "com_github_gorilla_websocket", importpath = "github.com/gorilla/websocket", - sum = "h1:Lh2aW+HnU2Nbe1gqD9SOJLJxW1jBMmQOktN2acDyJk8=", - version = "v0.0.0-20170926233335-4201258b820c", + sum = "h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=", + version = "v1.4.2", ) go_repository( name = "com_github_gregjones_httpcache", @@ -616,8 +495,8 @@ def go_repositories(): go_repository( name = "com_github_grpc_ecosystem_go_grpc_middleware", importpath = "github.com/grpc-ecosystem/go-grpc-middleware", - sum = "h1:z53tR0945TRRQO/fLEVPI6SMv7ZflF0TEaTAoU7tOzg=", - version = "v1.0.1-0.20190118093823-f849b5445de4", + sum = "h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=", + version = "v1.0.0", ) go_repository( name = "com_github_grpc_ecosystem_go_grpc_prometheus", @@ -628,20 +507,20 @@ def go_repositories(): go_repository( name = "com_github_grpc_ecosystem_grpc_gateway", importpath = "github.com/grpc-ecosystem/grpc-gateway", - sum = "h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI=", - version = "v1.9.5", + sum = "h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI=", + version = "v1.9.0", ) go_repository( name = "com_github_hashicorp_consul_api", importpath = "github.com/hashicorp/consul/api", - sum = "h1:HXNYlRkkM/t+Y/Yhxtwcy02dlYwIaoxzvxPnS+cqy78=", - version = "v1.3.0", + sum = "h1:BNQPM9ytxj6jbjjdRPioQ94T6YXriSopn0i8COv6SRA=", + version = "v1.1.0", ) go_repository( name = "com_github_hashicorp_consul_sdk", importpath = "github.com/hashicorp/consul/sdk", - sum = "h1:UOxjlb4xVNF93jak1mzzoBatyFju9nrkxpVwIp/QqxQ=", - version = "v0.3.0", + sum = "h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY=", + version = "v0.1.1", ) go_repository( name = "com_github_hashicorp_errwrap", @@ -703,12 +582,6 @@ def go_repositories(): sum = "h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=", version = "v1.0.1", ) - go_repository( - name = "com_github_hashicorp_go_version", - importpath = "github.com/hashicorp/go-version", - sum = "h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=", - version = "v1.2.0", - ) go_repository( name = "com_github_hashicorp_golang_lru", @@ -754,12 +627,7 @@ def go_repositories(): sum = "h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=", version = "v1.0.0", ) - go_repository( - name = "com_github_hudl_fargo", - importpath = "github.com/hudl/fargo", - sum = "h1:0U6+BtN6LhaYuTnIJq4Wyq5cpn6O2kWrxAtcqBmYY6w=", - version = "v1.3.0", - ) + go_repository( name = "com_github_ianlancetaylor_demangle", importpath = "github.com/ianlancetaylor/demangle", @@ -778,30 +646,13 @@ def go_repositories(): sum = "h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=", version = "v1.0.0", ) - go_repository( - name = "com_github_influxdata_influxdb1_client", - importpath = "github.com/influxdata/influxdb1-client", - sum = "h1:/WZQPMZNsjZ7IlCpsLGdQBINg5bxKQ1K1sh6awxLtkA=", - version = "v0.0.0-20191209144304-8bf82d3c094d", - ) - go_repository( - name = "com_github_jmespath_go_jmespath", - importpath = "github.com/jmespath/go-jmespath", - sum = "h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=", - version = "v0.0.0-20180206201540-c2b33e8439af", - ) + go_repository( name = "com_github_jonboulle_clockwork", importpath = "github.com/jonboulle/clockwork", sum = "h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=", version = "v0.1.0", ) - go_repository( - name = "com_github_jpillora_backoff", - importpath = "github.com/jpillora/backoff", - sum = "h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=", - version = "v1.0.0", - ) go_repository( name = "com_github_json_iterator_go", @@ -824,8 +675,8 @@ def go_repositories(): go_repository( name = "com_github_julienschmidt_httprouter", importpath = "github.com/julienschmidt/httprouter", - sum = "h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=", - version = "v1.3.0", + sum = "h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=", + version = "v1.2.0", ) go_repository( @@ -840,17 +691,12 @@ def go_repositories(): sum = "h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=", version = "v1.0.0", ) - go_repository( - name = "com_github_knetic_govaluate", - importpath = "github.com/Knetic/govaluate", - sum = "h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=", - version = "v3.0.1-0.20171022003610-9aa49832a739+incompatible", - ) + go_repository( name = "com_github_konsorten_go_windows_terminal_sequences", importpath = "github.com/konsorten/go-windows-terminal-sequences", - sum = "h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=", - version = "v1.0.3", + sum = "h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=", + version = "v1.0.1", ) go_repository( name = "com_github_kr_logfmt", @@ -862,8 +708,8 @@ def go_repositories(): go_repository( name = "com_github_kr_pretty", importpath = "github.com/kr/pretty", - sum = "h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=", - version = "v0.2.0", + sum = "h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=", + version = "v0.2.1", ) go_repository( name = "com_github_kr_pty", @@ -877,30 +723,12 @@ def go_repositories(): sum = "h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=", version = "v0.1.0", ) - go_repository( - name = "com_github_lightstep_lightstep_tracer_common_golang_gogo", - importpath = "github.com/lightstep/lightstep-tracer-common/golang/gogo", - sum = "h1:143Bb8f8DuGWck/xpNUOckBVYfFbBTnLevfRZ1aVVqo=", - version = "v0.0.0-20190605223551-bc2310a04743", - ) - go_repository( - name = "com_github_lightstep_lightstep_tracer_go", - importpath = "github.com/lightstep/lightstep-tracer-go", - sum = "h1:vi1F1IQ8N7hNWytK9DpJsUfQhGuNSc19z330K6vl4zk=", - version = "v0.18.1", - ) - go_repository( - name = "com_github_lyft_protoc_gen_validate", - importpath = "github.com/lyft/protoc-gen-validate", - sum = "h1:KNt/RhmQTOLr7Aj8PsJ7mTronaFyx80mRTT9qF261dA=", - version = "v0.0.13", - ) go_repository( name = "com_github_magiconair_properties", importpath = "github.com/magiconair/properties", - sum = "h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=", - version = "v1.8.0", + sum = "h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=", + version = "v1.8.1", ) go_repository( name = "com_github_mailru_easyjson", @@ -917,15 +745,10 @@ def go_repositories(): go_repository( name = "com_github_mattn_go_isatty", importpath = "github.com/mattn/go-isatty", - sum = "h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=", - version = "v0.0.4", - ) - go_repository( - name = "com_github_mattn_go_runewidth", - importpath = "github.com/mattn/go-runewidth", - sum = "h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o=", - version = "v0.0.2", + sum = "h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=", + version = "v0.0.3", ) + go_repository( name = "com_github_matttproud_golang_protobuf_extensions", importpath = "github.com/matttproud/golang_protobuf_extensions", @@ -947,8 +770,8 @@ def go_repositories(): go_repository( name = "com_github_mitchellh_go_homedir", importpath = "github.com/mitchellh/go-homedir", - sum = "h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=", - version = "v1.0.0", + sum = "h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=", + version = "v1.1.0", ) go_repository( name = "com_github_mitchellh_go_testing_interface", @@ -996,8 +819,8 @@ def go_repositories(): go_repository( name = "com_github_mwitkow_go_conntrack", importpath = "github.com/mwitkow/go-conntrack", - sum = "h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=", - version = "v0.0.0-20190716064945-2f068394615f", + sum = "h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc=", + version = "v0.0.0-20161129095857-cc309e4a2223", ) go_repository( @@ -1006,36 +829,6 @@ def go_repositories(): sum = "h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=", version = "v0.0.0-20140419014527-cca7078d478f", ) - go_repository( - name = "com_github_nats_io_jwt", - importpath = "github.com/nats-io/jwt", - sum = "h1:+RB5hMpXUUA2dfxuhBTEkMOrYmM+gKIZYS1KjSostMI=", - version = "v0.3.2", - ) - go_repository( - name = "com_github_nats_io_nats_go", - importpath = "github.com/nats-io/nats.go", - sum = "h1:ik3HbLhZ0YABLto7iX80pZLPw/6dx3T+++MZJwLnMrQ=", - version = "v1.9.1", - ) - go_repository( - name = "com_github_nats_io_nats_server_v2", - importpath = "github.com/nats-io/nats-server/v2", - sum = "h1:i2Ly0B+1+rzNZHHWtD4ZwKi+OU5l+uQo1iDHZ2PmiIc=", - version = "v2.1.2", - ) - go_repository( - name = "com_github_nats_io_nkeys", - importpath = "github.com/nats-io/nkeys", - sum = "h1:6JrEfig+HzTH85yxzhSVbjHRJv9cn0p6n3IngIcM5/k=", - version = "v0.1.3", - ) - go_repository( - name = "com_github_nats_io_nuid", - importpath = "github.com/nats-io/nuid", - sum = "h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=", - version = "v1.0.1", - ) go_repository( name = "com_github_nxadm_tail", @@ -1050,90 +843,37 @@ def go_repositories(): version = "v0.0.0-20170623195520-56545f4a5d46", ) go_repository( - name = "com_github_oklog_oklog", - importpath = "github.com/oklog/oklog", - sum = "h1:wVfs8F+in6nTBMkA7CbRw+zZMIB7nNM825cM1wuzoTk=", - version = "v0.3.2", + name = "com_github_oklog_ulid", + importpath = "github.com/oklog/ulid", + sum = "h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=", + version = "v1.3.1", ) go_repository( - name = "com_github_oklog_run", - importpath = "github.com/oklog/run", - sum = "h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=", - version = "v1.0.0", - ) - go_repository( - name = "com_github_olekukonko_tablewriter", - importpath = "github.com/olekukonko/tablewriter", - sum = "h1:58+kh9C6jJVXYjt8IE48G2eWl6BjwU5Gj0gqY84fy78=", - version = "v0.0.0-20170122224234-a0225b3f23b5", + name = "com_github_oneofone_xxhash", + importpath = "github.com/OneOfOne/xxhash", + sum = "h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=", + version = "v1.2.2", ) go_repository( name = "com_github_onsi_ginkgo", importpath = "github.com/onsi/ginkgo", - sum = "h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw=", - version = "v1.11.0", + sum = "h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4=", + version = "v1.14.1", ) go_repository( name = "com_github_onsi_gomega", importpath = "github.com/onsi/gomega", - sum = "h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=", - version = "v1.7.0", - ) - go_repository( - name = "com_github_op_go_logging", - importpath = "github.com/op/go-logging", - sum = "h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=", - version = "v0.0.0-20160315200505-970db520ece7", - ) - go_repository( - name = "com_github_opentracing_basictracer_go", - importpath = "github.com/opentracing/basictracer-go", - sum = "h1:YyUAhaEfjoWXclZVJ9sGoNct7j4TVk7lZWlQw5UXuoo=", - version = "v1.0.0", - ) - go_repository( - name = "com_github_opentracing_contrib_go_observer", - importpath = "github.com/opentracing-contrib/go-observer", - sum = "h1:lM6RxxfUMrYL/f8bWEUqdXrANWtrL7Nndbm9iFN0DlU=", - version = "v0.0.0-20170622124052-a52f23424492", - ) - go_repository( - name = "com_github_opentracing_opentracing_go", - importpath = "github.com/opentracing/opentracing-go", - sum = "h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=", - version = "v1.1.0", - ) - go_repository( - name = "com_github_openzipkin_contrib_zipkin_go_opentracing", - importpath = "github.com/openzipkin-contrib/zipkin-go-opentracing", - sum = "h1:ZCnq+JUrvXcDVhX/xRolRBZifmabN1HcS1wrPSvxhrU=", - version = "v0.4.5", - ) - go_repository( - name = "com_github_openzipkin_zipkin_go", - importpath = "github.com/openzipkin/zipkin-go", - sum = "h1:nY8Hti+WKaP0cRsSeQ026wU03QsM762XBeCXBb9NAWI=", - version = "v0.2.2", - ) - go_repository( - name = "com_github_pact_foundation_pact_go", - importpath = "github.com/pact-foundation/pact-go", - sum = "h1:OYkFijGHoZAYbOIb1LWXrwKQbMMRUv1oQ89blD2Mh2Q=", - version = "v1.0.4", + sum = "h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs=", + version = "v1.10.2", ) + go_repository( name = "com_github_pascaldekloe_goe", importpath = "github.com/pascaldekloe/goe", sum = "h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs=", version = "v0.0.0-20180627143212-57f6aae5913c", ) - go_repository( - name = "com_github_pborman_uuid", - importpath = "github.com/pborman/uuid", - sum = "h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=", - version = "v1.2.0", - ) go_repository( name = "com_github_pelletier_go_toml", @@ -1141,24 +881,13 @@ def go_repositories(): sum = "h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=", version = "v1.2.0", ) - go_repository( - name = "com_github_performancecopilot_speed", - importpath = "github.com/performancecopilot/speed", - sum = "h1:2WnRzIquHa5QxaJKShDkLM+sc0JPuwhXzK8OYOyt3Vg=", - version = "v3.0.0+incompatible", - ) + go_repository( name = "com_github_peterbourgon_diskv", importpath = "github.com/peterbourgon/diskv", sum = "h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=", version = "v2.0.1+incompatible", ) - go_repository( - name = "com_github_pierrec_lz4", - importpath = "github.com/pierrec/lz4", - sum = "h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=", - version = "v2.0.5+incompatible", - ) go_repository( name = "com_github_pkg_errors", @@ -1166,12 +895,6 @@ def go_repositories(): sum = "h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=", version = "v0.9.1", ) - go_repository( - name = "com_github_pkg_profile", - importpath = "github.com/pkg/profile", - sum = "h1:F++O52m40owAmADcojzM+9gyjmMOY/T4oYJkgFDH8RE=", - version = "v1.2.1", - ) go_repository( name = "com_github_pmezard_go_difflib", @@ -1188,27 +911,33 @@ def go_repositories(): go_repository( name = "com_github_prometheus_client_golang", importpath = "github.com/prometheus/client_golang", - sum = "h1:zvJNkoCFAnYFNC24FV8nW4JdRJ3GIFcLbg65lL/JDcw=", - version = "v1.8.0", + sum = "h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=", + version = "v0.9.3", ) go_repository( name = "com_github_prometheus_client_model", importpath = "github.com/prometheus/client_model", - sum = "h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=", - version = "v0.2.0", + sum = "h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=", + version = "v0.0.0-20190812154241-14fe0d1b01d4", ) go_repository( name = "com_github_prometheus_common", importpath = "github.com/prometheus/common", - sum = "h1:RHRyE8UocrbjU+6UvRzwi6HjiDfxrrBU91TtbKzkGp4=", - version = "v0.14.0", + sum = "h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=", + version = "v0.4.0", ) go_repository( name = "com_github_prometheus_procfs", importpath = "github.com/prometheus/procfs", - sum = "h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4=", - version = "v0.2.0", + sum = "h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=", + version = "v0.0.0-20190507164030-5867b95ac084", + ) + go_repository( + name = "com_github_prometheus_tsdb", + importpath = "github.com/prometheus/tsdb", + sum = "h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA=", + version = "v0.7.1", ) go_repository( @@ -1223,12 +952,7 @@ def go_repositories(): sum = "h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=", version = "v0.0.0-20170810143723-de5bf2ad4578", ) - go_repository( - name = "com_github_rcrowley_go_metrics", - importpath = "github.com/rcrowley/go-metrics", - sum = "h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ=", - version = "v0.0.0-20181016184325-3113b8401b8a", - ) + go_repository( name = "com_github_rogpeppe_fastuuid", importpath = "github.com/rogpeppe/fastuuid", @@ -1253,30 +977,14 @@ def go_repositories(): sum = "h1:UFr9zpz4xgTnIE5yIMtWAMngCdZ9p/+q6lTbgelo80M=", version = "v0.0.0-20160712163229-9b3edd62028f", ) - go_repository( - name = "com_github_samuel_go_zookeeper", - importpath = "github.com/samuel/go-zookeeper", - sum = "h1:p3Vo3i64TCLY7gIfzeQaUJ+kppEO5WQG3cL8iE8tGHU=", - version = "v0.0.0-20190923202752-2cc03de413da", - ) + go_repository( name = "com_github_sean_seed", importpath = "github.com/sean-/seed", sum = "h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=", version = "v0.0.0-20170313163322-e2103e2c3529", ) - go_repository( - name = "com_github_shopify_sarama", - importpath = "github.com/Shopify/sarama", - sum = "h1:9oksLxC6uxVPHPVYUmq6xhr1BOF/hHobWH2UzO67z1s=", - version = "v1.19.0", - ) - go_repository( - name = "com_github_shopify_toxiproxy", - importpath = "github.com/Shopify/toxiproxy", - sum = "h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=", - version = "v2.1.4+incompatible", - ) + go_repository( name = "com_github_shurcool_sanitized_anchor_name", importpath = "github.com/shurcooL/sanitized_anchor_name", @@ -1287,8 +995,8 @@ def go_repositories(): go_repository( name = "com_github_sirupsen_logrus", importpath = "github.com/sirupsen/logrus", - sum = "h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=", - version = "v1.7.0", + sum = "h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=", + version = "v1.2.0", ) go_repository( name = "com_github_smartystreets_assertions", @@ -1309,10 +1017,10 @@ def go_repositories(): version = "v0.1.4", ) go_repository( - name = "com_github_sony_gobreaker", - importpath = "github.com/sony/gobreaker", - sum = "h1:oMnRNZXX5j85zso6xCPRNPtmAycat+WcoKbklScLDgQ=", - version = "v0.4.1", + name = "com_github_spaolacci_murmur3", + importpath = "github.com/spaolacci/murmur3", + sum = "h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=", + version = "v0.0.0-20180118202830-f09979ecbc72", ) go_repository( @@ -1330,8 +1038,8 @@ def go_repositories(): go_repository( name = "com_github_spf13_cobra", importpath = "github.com/spf13/cobra", - sum = "h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=", - version = "v0.0.3", + sum = "h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=", + version = "v1.1.1", ) go_repository( name = "com_github_spf13_jwalterweatherman", @@ -1351,30 +1059,6 @@ def go_repositories(): sum = "h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=", version = "v1.7.1", ) - go_repository( - name = "com_github_subosito_gotenv", - importpath = "github.com/subosito/gotenv", - sum = "h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=", - version = "v1.2.0", - ) - go_repository( - name = "in_gopkg_ini_v1", - importpath = "gopkg.in/ini.v1", - sum = "h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=", - version = "v1.62.0", - ) - go_repository( - name = "com_github_streadway_amqp", - importpath = "github.com/streadway/amqp", - sum = "h1:WhxRHzgeVGETMlmVfqhRn8RIeeNoPr2Czh33I4Zdccw=", - version = "v0.0.0-20190827072141-edfb9018d271", - ) - go_repository( - name = "com_github_streadway_handy", - importpath = "github.com/streadway/handy", - sum = "h1:AhmOdSHeswKHBjhsLs/7+1voOxT+LLrSk/Nxvk35fug=", - version = "v0.0.0-20190108123426-d5acb3125c2a", - ) go_repository( name = "com_github_stretchr_objx", @@ -1388,11 +1072,18 @@ def go_repositories(): sum = "h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=", version = "v1.6.1", ) + go_repository( + name = "com_github_subosito_gotenv", + importpath = "github.com/subosito/gotenv", + sum = "h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=", + version = "v1.2.0", + ) + go_repository( name = "com_github_tmc_grpc_websocket_proxy", importpath = "github.com/tmc/grpc-websocket-proxy", - sum = "h1:ndzgwNDnKIqyCvHTXaCqh9KlOWKvBry6nuXMJmonVsE=", - version = "v0.0.0-20170815181823-89b8d40f7ca8", + sum = "h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=", + version = "v0.0.0-20190109142713-0ad062ec5ee5", ) go_repository( @@ -1401,18 +1092,7 @@ def go_repositories(): sum = "h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648=", version = "v0.0.0-20181204163529-d75b2dcb6bc8", ) - go_repository( - name = "com_github_urfave_cli", - importpath = "github.com/urfave/cli", - sum = "h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=", - version = "v1.22.1", - ) - go_repository( - name = "com_github_vividcortex_gohistogram", - importpath = "github.com/VividCortex/gohistogram", - sum = "h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=", - version = "v1.0.0", - ) + go_repository( name = "com_github_xiang90_probing", importpath = "github.com/xiang90/probing", @@ -1450,6 +1130,13 @@ def go_repositories(): sum = "h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ=", version = "v1.1.0", ) + go_repository( + name = "com_google_cloud_go_firestore", + importpath = "cloud.google.com/go/firestore", + sum = "h1:9x7Bx0A9R5/M9jibeJeZWqjeVEIxYW9fZYqB9a70/bY=", + version = "v1.1.0", + ) + go_repository( name = "com_google_cloud_go_pubsub", importpath = "cloud.google.com/go/pubsub", @@ -1468,12 +1155,7 @@ def go_repositories(): sum = "h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY=", version = "v0.0.0-20190408044501-666a987793e9", ) - go_repository( - name = "com_sourcegraph_sourcegraph_appdash", - importpath = "sourcegraph.com/sourcegraph/appdash", - sum = "h1:ucqkfpjg9WzSUubAO62csmucvxl4/JeW3F4I4909XkM=", - version = "v0.0.0-20190731080439-ebfcffb1b5c0", - ) + go_repository( name = "in_gopkg_alecthomas_kingpin_v2", importpath = "gopkg.in/alecthomas/kingpin.v2", @@ -1487,12 +1169,7 @@ def go_repositories(): sum = "h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=", version = "v1.0.0-20190902080502-41f04d3bba15", ) - go_repository( - name = "in_gopkg_cheggaaa_pb_v1", - importpath = "gopkg.in/cheggaaa/pb.v1", - sum = "h1:Ev7yu1/f6+d+b3pi5vPdRPc6nNtP1umSfcWiEfRqv6I=", - version = "v1.0.25", - ) + go_repository( name = "in_gopkg_errgo_v2", importpath = "gopkg.in/errgo.v2", @@ -1506,12 +1183,6 @@ def go_repositories(): sum = "h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=", version = "v1.4.7", ) - go_repository( - name = "in_gopkg_gcfg_v1", - importpath = "gopkg.in/gcfg.v1", - sum = "h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs=", - version = "v1.2.3", - ) go_repository( name = "in_gopkg_inf_v0", @@ -1519,6 +1190,13 @@ def go_repositories(): sum = "h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=", version = "v0.9.1", ) + go_repository( + name = "in_gopkg_ini_v1", + importpath = "gopkg.in/ini.v1", + sum = "h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=", + version = "v1.51.0", + ) + go_repository( name = "in_gopkg_resty_v1", importpath = "gopkg.in/resty.v1", @@ -1532,18 +1210,12 @@ def go_repositories(): sum = "h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=", version = "v1.0.0-20141024135613-dd632973f1e7", ) - go_repository( - name = "in_gopkg_warnings_v0", - importpath = "gopkg.in/warnings.v0", - sum = "h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=", - version = "v0.1.2", - ) go_repository( name = "in_gopkg_yaml_v2", importpath = "gopkg.in/yaml.v2", - sum = "h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=", - version = "v2.3.0", + sum = "h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=", + version = "v2.4.0", ) go_repository( name = "in_gopkg_yaml_v3", @@ -1554,14 +1226,8 @@ def go_repositories(): go_repository( name = "io_etcd_go_bbolt", importpath = "go.etcd.io/bbolt", - sum = "h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=", - version = "v1.3.3", - ) - go_repository( - name = "io_etcd_go_etcd", - importpath = "go.etcd.io/etcd", - sum = "h1:VcrIfasaLFkyjk6KNlXQSzO+B0fZcnECiDrKJsfxka0=", - version = "v0.0.0-20191023171146-3cf2f69b5738", + sum = "h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=", + version = "v1.3.2", ) go_repository( @@ -1573,6 +1239,7 @@ def go_repositories(): go_repository( name = "io_k8s_api", build_file_proto_mode = "disable_global", + build_naming_convention = "go_default_library", importpath = "k8s.io/api", sum = "h1:WwrYoZNM1W1aQEbyl8HNG+oWGzLpZQBlcerS9BQw9yI=", version = "v0.20.0", @@ -1580,6 +1247,7 @@ def go_repositories(): go_repository( name = "io_k8s_apimachinery", build_file_proto_mode = "disable_global", + build_naming_convention = "go_default_library", importpath = "k8s.io/apimachinery", sum = "h1:jjzbTJRXk0unNS71L7h3lxGDH/2HPxMPaQY+MjECKL8=", version = "v0.20.0", @@ -1587,6 +1255,7 @@ def go_repositories(): go_repository( name = "io_k8s_client_go", build_file_proto_mode = "legacy", + build_naming_convention = "go_default_library", importpath = "k8s.io/client-go", sum = "h1:Xlax8PKbZsjX4gFvNtt4F5MoJ1V5prDvCuoq9B7iax0=", version = "v0.20.0", @@ -1615,12 +1284,6 @@ def go_repositories(): sum = "h1:sOHNzJIkytDF6qadMNKhhDRpc6ODik8lVC6nOur7B2c=", version = "v0.0.0-20201113171705-d219536bb9fd", ) - go_repository( - name = "io_k8s_sigs_structured_merge_diff_v3", - importpath = "sigs.k8s.io/structured-merge-diff/v3", - sum = "h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E=", - version = "v3.0.0", - ) go_repository( name = "io_k8s_sigs_structured_merge_diff_v4", @@ -1649,8 +1312,8 @@ def go_repositories(): go_repository( name = "io_opentelemetry_go_otel", - importpath = "go.opentelemetry.io/otel", build_file_proto_mode = "disable", + importpath = "go.opentelemetry.io/otel", sum = "h1:IN2tzQa9Gc4ZVKnTaMbPVcHjvzOdg5n9QfnmlqiET7E=", version = "v0.11.0", ) @@ -1737,9 +1400,10 @@ def go_repositories(): ) go_repository( name = "org_golang_x_mod", + build_naming_convention = "go_default_library", importpath = "golang.org/x/mod", - sum = "h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=", - version = "v0.2.0", + sum = "h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY=", + version = "v0.4.1", ) go_repository( @@ -1792,27 +1456,23 @@ def go_repositories(): sum = "h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=", version = "v0.0.0-20200804184101-5ec99f83aff1", ) + go_repository( name = "org_uber_go_atomic", importpath = "go.uber.org/atomic", - sum = "h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=", - version = "v1.5.0", + sum = "h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=", + version = "v1.4.0", ) go_repository( name = "org_uber_go_multierr", importpath = "go.uber.org/multierr", - sum = "h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc=", - version = "v1.3.0", - ) - go_repository( - name = "org_uber_go_tools", - importpath = "go.uber.org/tools", - sum = "h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=", - version = "v0.0.0-20190618225709-2cfd321de3ee", + sum = "h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=", + version = "v1.1.0", ) + go_repository( name = "org_uber_go_zap", importpath = "go.uber.org/zap", - sum = "h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=", - version = "v1.13.0", + sum = "h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=", + version = "v1.10.0", ) diff --git a/infrastructure/airy b/infrastructure/airy new file mode 100755 index 0000000000..0537405eef Binary files /dev/null and b/infrastructure/airy differ diff --git a/infrastructure/cli/cmd/BUILD b/infrastructure/cli/cmd/BUILD index 4b275c53a6..7327c39975 100644 --- a/infrastructure/cli/cmd/BUILD +++ b/infrastructure/cli/cmd/BUILD @@ -12,6 +12,7 @@ go_library( deps = [ "//infrastructure/cli/cmd/api", "//infrastructure/cli/cmd/config", + "//infrastructure/cli/cmd/create", "//infrastructure/cli/cmd/status", "//infrastructure/cli/cmd/ui", "@com_github_mitchellh_go_homedir//:go-homedir", diff --git a/infrastructure/cli/cmd/config/BUILD b/infrastructure/cli/cmd/config/BUILD index 8c79bdd734..2bb17d35cb 100644 --- a/infrastructure/cli/cmd/config/BUILD +++ b/infrastructure/cli/cmd/config/BUILD @@ -13,9 +13,9 @@ go_library( "@com_github_mitchellh_go_homedir//:go-homedir", "@com_github_spf13_cobra//:cobra", "@in_gopkg_yaml_v2//:yaml_v2", - "@io_k8s_api//core/v1:core", - "@io_k8s_apimachinery//pkg/apis/meta/v1:meta", - "@io_k8s_client_go//kubernetes", - "@io_k8s_client_go//tools/clientcmd", + "@io_k8s_api//core/v1:go_default_library", + "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", + "@io_k8s_client_go//kubernetes:go_default_library", + "@io_k8s_client_go//tools/clientcmd:go_default_library", ], ) diff --git a/infrastructure/cli/cmd/create/BUILD b/infrastructure/cli/cmd/create/BUILD new file mode 100644 index 0000000000..582bff5f63 --- /dev/null +++ b/infrastructure/cli/cmd/create/BUILD @@ -0,0 +1,12 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "create", + srcs = ["create.go"], + importpath = "cli/cmd/create", + visibility = ["//visibility:public"], + deps = [ + "@com_github_spf13_cobra//:cobra", + "@com_github_spf13_viper//:viper", + ], +) diff --git a/infrastructure/cli/cmd/create/create.go b/infrastructure/cli/cmd/create/create.go new file mode 100644 index 0000000000..bc3475c987 --- /dev/null +++ b/infrastructure/cli/cmd/create/create.go @@ -0,0 +1,31 @@ +package create + +import ( + "fmt" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var ( + provider string + CreateCmd = &cobra.Command{ + Use: "create", + Short: "Creates an instance of Airy Core", + Long: ``, + Run: create, + } +) + +func init() { + CreateCmd.Flags().StringVar(&provider, "provider", "", "One of the supported providers (aws|local). Default is aws") + viper.SetDefault("provider", "aws") +} + +func create(cmd *cobra.Command, args []string) { + fmt.Println("⚙️ Creating core with provider", provider) + fmt.Println("🚀 Starting core with default components") + fmt.Println("🎉 Your Airy Core is ready") + fmt.Println("\t Link to the API") + fmt.Println("\t Link to the UI") + fmt.Println("\t Link to more docs") +} diff --git a/infrastructure/cli/cmd/root.go b/infrastructure/cli/cmd/root.go index 20736b6612..44ab867eb5 100644 --- a/infrastructure/cli/cmd/root.go +++ b/infrastructure/cli/cmd/root.go @@ -7,6 +7,7 @@ import ( "cli/cmd/api" "cli/cmd/config" + "cli/cmd/create" "cli/cmd/status" "cli/cmd/ui" @@ -122,4 +123,5 @@ func init() { RootCmd.AddCommand(ui.UICmd) RootCmd.AddCommand(versionCmd) RootCmd.AddCommand(initCmd) + RootCmd.AddCommand(create.CreateCmd) } diff --git a/infrastructure/cli/integration/golden/cli.no-args.golden b/infrastructure/cli/integration/golden/cli.no-args.golden index 292722d790..2eaaa6a9fe 100644 --- a/infrastructure/cli/integration/golden/cli.no-args.golden +++ b/infrastructure/cli/integration/golden/cli.no-args.golden @@ -6,6 +6,7 @@ Usage: Available Commands: api Interacts with the Airy Core HTTP API config Manages an Airy Core instance via airy.yaml + create Creates an instance of Airy Core help Help about any command init Inits your airy configuration status Reports the status of an Airy Core instance diff --git a/infrastructure/cli/integration/golden/cli.yaml b/infrastructure/cli/integration/golden/cli.yaml index 635d769f8b..d9ec1bb4a6 100644 --- a/infrastructure/cli/integration/golden/cli.yaml +++ b/infrastructure/cli/integration/golden/cli.yaml @@ -1,2 +1,3 @@ -apihost: http://localhost:51018 +apihost: http://localhost:51111 apijwttoken: eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMDM1MmU3NC00MjEyLTQyODItYWM3NC0wZjU4ZjgzYzJkYjUiLCJzdWIiOiIxMDM1MmU3NC00MjEyLTQyODItYWM3NC0wZjU4ZjgzYzJkYjUiLCJpYXQiOjE2MDc2MTk0ODksInVzZXJfaWQiOiIxMDM1MmU3NC00MjEyLTQyODItYWM3NC0wZjU4ZjgzYzJkYjUiLCJleHAiOjE2MDc3MDU4ODl9.ZuIv_t0D358n04gamNwz3U_tkxr4IO36gXuZyU9X3e4 +provider: aws diff --git a/infrastructure/controller/BUILD b/infrastructure/controller/BUILD index dbddf8602b..463ebdaf74 100644 --- a/infrastructure/controller/BUILD +++ b/infrastructure/controller/BUILD @@ -10,9 +10,9 @@ go_library( visibility = ["//visibility:private"], deps = [ "//infrastructure/controller/pkg/configmap-controller", - "@io_k8s_api//core/v1:core", - "@io_k8s_client_go//kubernetes", - "@io_k8s_client_go//tools/clientcmd", + "@io_k8s_api//core/v1:go_default_library", + "@io_k8s_client_go//kubernetes:go_default_library", + "@io_k8s_client_go//tools/clientcmd:go_default_library", "@io_k8s_klog//:klog", ], ) diff --git a/infrastructure/controller/pkg/configmap-controller/BUILD b/infrastructure/controller/pkg/configmap-controller/BUILD index dc329e973e..5750ade301 100644 --- a/infrastructure/controller/pkg/configmap-controller/BUILD +++ b/infrastructure/controller/pkg/configmap-controller/BUILD @@ -13,13 +13,13 @@ go_library( deps = [ "//infrastructure/lib/go/k8s/handler", "//infrastructure/lib/go/k8s/util", - "@io_k8s_api//core/v1:core", - "@io_k8s_apimachinery//pkg/fields", - "@io_k8s_apimachinery//pkg/util/runtime", - "@io_k8s_apimachinery//pkg/util/wait", - "@io_k8s_client_go//kubernetes", - "@io_k8s_client_go//tools/cache", - "@io_k8s_client_go//util/workqueue", + "@io_k8s_api//core/v1:go_default_library", + "@io_k8s_apimachinery//pkg/fields:go_default_library", + "@io_k8s_apimachinery//pkg/util/runtime:go_default_library", + "@io_k8s_apimachinery//pkg/util/wait:go_default_library", + "@io_k8s_client_go//kubernetes:go_default_library", + "@io_k8s_client_go//tools/cache:go_default_library", + "@io_k8s_client_go//util/workqueue:go_default_library", "@io_k8s_klog//:klog", ], ) diff --git a/infrastructure/helm-chart/charts/apps/charts/webhook/webhook-consumer/Chart.yaml b/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/Chart.yaml similarity index 100% rename from infrastructure/helm-chart/charts/apps/charts/webhook/webhook-consumer/Chart.yaml rename to infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/Chart.yaml diff --git a/infrastructure/helm-chart/charts/apps/charts/webhook/webhook-consumer/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/templates/deployment.yaml similarity index 74% rename from infrastructure/helm-chart/charts/apps/charts/webhook/webhook-consumer/templates/deployment.yaml rename to infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/templates/deployment.yaml index ccc6ddc098..8c520e8590 100644 --- a/infrastructure/helm-chart/charts/apps/charts/webhook/webhook-consumer/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/templates/deployment.yaml @@ -27,21 +27,6 @@ spec: image: "{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ .Values.global.appImageTag }}" imagePullPolicy: Always env: - - name: KAFKA_BROKERS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_BROKERS - - name: KAFKA_SCHEMA_REGISTRY_URL - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_SCHEMA_REGISTRY_URL - - name: KAFKA_COMMIT_INTERVAL_MS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_COMMIT_INTERVAL_MS - name: SERVICE_NAME value: webhook-consumer - name: REDIS_HOSTNAME diff --git a/infrastructure/helm-chart/charts/apps/charts/webhook/webhook-consumer/templates/service.yaml b/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/templates/service.yaml similarity index 100% rename from infrastructure/helm-chart/charts/apps/charts/webhook/webhook-consumer/templates/service.yaml rename to infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/templates/service.yaml diff --git a/infrastructure/helm-chart/charts/apps/charts/webhook/webhook-consumer/values.yaml b/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/values.yaml similarity index 100% rename from infrastructure/helm-chart/charts/apps/charts/webhook/webhook-consumer/values.yaml rename to infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/values.yaml diff --git a/infrastructure/helm-chart/templates/ingress.yaml b/infrastructure/helm-chart/templates/ingress.yaml index 5e5c0b6100..4a31312cc1 100644 --- a/infrastructure/helm-chart/templates/ingress.yaml +++ b/infrastructure/helm-chart/templates/ingress.yaml @@ -23,13 +23,6 @@ spec: port: number: 80 - path: /ws.communication - pathType: Prefix - backend: - service: - name: api-communication - port: - number: 80 - - path: /ws.events pathType: Prefix backend: service: @@ -99,6 +92,20 @@ spec: name: api-admin port: number: 80 + - path: /channels.info + pathType: Prefix + backend: + service: + name: api-admin + port: + number: 80 + - path: /channels.update + pathType: Prefix + backend: + service: + name: api-admin + port: + number: 80 - path: /channels.chatplugin.disconnect pathType: Prefix backend: diff --git a/infrastructure/lib/go/k8s/handler/BUILD b/infrastructure/lib/go/k8s/handler/BUILD index ba971cf2d2..1165b1d918 100644 --- a/infrastructure/lib/go/k8s/handler/BUILD +++ b/infrastructure/lib/go/k8s/handler/BUILD @@ -15,11 +15,11 @@ go_library( visibility = ["//visibility:public"], deps = [ "//infrastructure/lib/go/k8s/util", - "@io_k8s_api//apps/v1:apps", - "@io_k8s_api//core/v1:core", - "@io_k8s_apimachinery//pkg/apis/meta/v1:meta", - "@io_k8s_client_go//kubernetes", - "@io_k8s_client_go//util/retry", + "@io_k8s_api//apps/v1:go_default_library", + "@io_k8s_api//core/v1:go_default_library", + "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", + "@io_k8s_client_go//kubernetes:go_default_library", + "@io_k8s_client_go//util/retry:go_default_library", "@io_k8s_klog//:klog", ], ) diff --git a/infrastructure/lib/go/k8s/util/BUILD b/infrastructure/lib/go/k8s/util/BUILD index f7106742fc..a4c94bf9cf 100644 --- a/infrastructure/lib/go/k8s/util/BUILD +++ b/infrastructure/lib/go/k8s/util/BUILD @@ -8,8 +8,8 @@ go_library( importpath = "github.com/airyhq/airy/infrastructure/lib/go/k8s/util", visibility = ["//visibility:public"], deps = [ - "@io_k8s_api//core/v1:core", - "@io_k8s_apimachinery//pkg/apis/meta/v1:meta", + "@io_k8s_api//core/v1:go_default_library", + "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", "@io_k8s_klog//:klog", ], ) diff --git a/lib/java/spring/jwt/src/main/java/co/airy/spring/jwt/Jwt.java b/lib/java/spring/jwt/src/main/java/co/airy/spring/jwt/Jwt.java index 5dc3c2c698..040a13bf78 100644 --- a/lib/java/spring/jwt/src/main/java/co/airy/spring/jwt/Jwt.java +++ b/lib/java/spring/jwt/src/main/java/co/airy/spring/jwt/Jwt.java @@ -53,13 +53,16 @@ public String tokenFor(String userId, Map claims) { } public String authenticate(final String authHeader) { - Claims claims = null; - if (authHeader != null) { - try { - claims = extractClaims(authHeader); - } catch (Exception e) { - log.error("Failed to extract claims from token: " + e.getMessage()); - } + if (authHeader == null) { + return null; + } + + Claims claims; + try { + claims = extractClaims(authHeader); + } catch (Exception e) { + log.error("Failed to extract claims from token: " + e.getMessage()); + return null; } if (claims == null) { diff --git a/lib/typescript/httpclient/index.ts b/lib/typescript/httpclient/index.ts index 732c6a49c5..bcd595ca71 100644 --- a/lib/typescript/httpclient/index.ts +++ b/lib/typescript/httpclient/index.ts @@ -1,6 +1,3 @@ -import {ChannelsPayload} from './payload/ChannelsPayload'; -import {channelsMapper} from './mappers/channelsMapper'; -import {paginatedPayloadMapper} from './mappers/paginatedPayloadMapper'; import { ExploreChannelRequestPayload, ConnectChannelRequestPayload, @@ -10,25 +7,30 @@ import { CreateTagRequestPayload, LoginViaEmailRequestPayload, SendMessagesRequestPayload, + TagConversationRequestPayload, + UntagConversationRequestPayload, + MessagePayload, + TagPayload, + ListMessagesRequestPayload, + PaginatedPayload, + ConversationPayload, + ChannelPayload, + ChannelsPayload, } from './payload'; -import {PaginatedPayload} from './payload/PaginatedPayload'; -import {ChannelApiPayload} from './payload/ChannelApiPayload'; -import {connectChannelApiMapper} from './mappers/connectChannelApiMapper'; -import {channelMapper} from './mappers/channelMapper'; -import {disconnectChannelApiMapper} from './mappers/disconnectChannelApiMapper'; -import {ConversationPayload} from './payload/ConversationPayload'; -import {conversationsMapper} from './mappers/conversationsMapper'; -import {ListMessagesRequestPayload} from './payload/ListMessagesRequestPayload'; -import {TagConversationRequestPayload} from './payload/TagConversationRequestPayload'; -import {UntagConversationRequestPayload} from './payload/UntagConversationRequestPayload'; -import {MessagePayload} from './payload/MessagePayload'; -import {messageMapperData} from './mappers/messageMapperData'; -import {tagsMapper} from './mappers/tagsMapper'; +import { + connectChannelMapper, + channelMapper, + channelsMapper, + disconnectChannelApiMapper, + conversationsMapper, + messageMapperData, + tagsMapper, + userMapper, + paginatedPayloadMapper, + messageMapper, + conversationMapper, +} from './mappers'; import {TagColor, Tag} from './model'; -import {TagPayload} from './payload/TagPayload'; -import {userMapper} from './mappers/userMapper'; -import {messageMapper} from './mappers/messageMapper'; -import {conversationMapper} from './mappers/conversationMapper'; const headers = { Accept: 'application/json', @@ -62,7 +64,7 @@ export class HttpClient { let errorResult: any; if (body.length > 0) { - errorResult = JSON.parse(body); + errorResult = JSON.parse(body) as any; } if (response.status == 403 && this.unauthorizedErrorCallback) { @@ -96,7 +98,7 @@ export class HttpClient { } public async listChannels() { - const response: ChannelsPayload = await this.doFetchFromBackend('channels.list'); + const response: ChannelsPayload = await this.doFetchFromBackend('channels.list', {}); return channelsMapper(response); } @@ -106,9 +108,9 @@ export class HttpClient { } public async connectFacebookChannel(requestPayload: ConnectChannelRequestPayload) { - const response: ChannelApiPayload = await this.doFetchFromBackend( + const response: ChannelPayload = await this.doFetchFromBackend( 'channels.connect', - connectChannelApiMapper(requestPayload) + connectChannelMapper(requestPayload) ); return channelMapper(response); } diff --git a/lib/typescript/httpclient/mappers/channelMapper.ts b/lib/typescript/httpclient/mappers/channelMapper.ts index 86ec7f5174..392eb111a4 100644 --- a/lib/typescript/httpclient/mappers/channelMapper.ts +++ b/lib/typescript/httpclient/mappers/channelMapper.ts @@ -1,13 +1,13 @@ import {Channel} from '../model'; -import {ChannelApiPayload} from '../payload/ChannelApiPayload'; +import {ChannelPayload} from '../payload'; -export const channelMapper = (payload: ChannelApiPayload): Channel => { +export const channelMapper = (payload: ChannelPayload): Channel => { const channel = { ...payload, sourceChannelId: payload.source_channel_id, metadata: { ...payload.metadata, - imageUrl: payload.metadata.image_url, + imageUrl: payload.metadata?.image_url, }, connected: true, }; diff --git a/lib/typescript/httpclient/mappers/connectChannelApiMapper.ts b/lib/typescript/httpclient/mappers/connectChannelMapper.ts similarity index 67% rename from lib/typescript/httpclient/mappers/connectChannelApiMapper.ts rename to lib/typescript/httpclient/mappers/connectChannelMapper.ts index 65e3f20b12..73d77bb3bf 100644 --- a/lib/typescript/httpclient/mappers/connectChannelApiMapper.ts +++ b/lib/typescript/httpclient/mappers/connectChannelMapper.ts @@ -1,6 +1,6 @@ import {ConnectChannelRequestPayload, ConnectChannelRequestApiPayload} from '../payload'; -export const connectChannelApiMapper = (payload: ConnectChannelRequestPayload): ConnectChannelRequestApiPayload => ({ +export const connectChannelMapper = (payload: ConnectChannelRequestPayload): ConnectChannelRequestApiPayload => ({ source: payload.source, source_channel_id: payload.sourceChannelId, token: payload.token, diff --git a/lib/typescript/httpclient/mappers/conversationMapper.ts b/lib/typescript/httpclient/mappers/conversationMapper.ts index 7cc09af95b..653e2109e3 100644 --- a/lib/typescript/httpclient/mappers/conversationMapper.ts +++ b/lib/typescript/httpclient/mappers/conversationMapper.ts @@ -1,5 +1,5 @@ import {Conversation} from '../model'; -import {ConversationPayload} from '../payload/ConversationPayload'; +import {ConversationPayload} from '../payload'; import {messageMapper} from './messageMapper'; export const conversationMapper = (payload: ConversationPayload): Conversation => { @@ -15,7 +15,6 @@ export const conversationMapper = (payload: ConversationPayload): Conversation = unreadCount: payload.metadata.unread_count, }, createdAt: new Date(payload.created_at), - tags: Object.keys(payload.metadata.tags || {}), lastMessage: messageMapper(payload.last_message), }; }; diff --git a/lib/typescript/httpclient/mappers/index.ts b/lib/typescript/httpclient/mappers/index.ts index 0d0bc4da15..104d0b9489 100644 --- a/lib/typescript/httpclient/mappers/index.ts +++ b/lib/typescript/httpclient/mappers/index.ts @@ -1,10 +1,11 @@ export * from './channelMapper'; export * from './channelsMapper'; -export * from './connectChannelApiMapper'; +export * from './connectChannelMapper'; export * from './conversationMapper'; export * from './conversationsMapper'; export * from './disconnectChannelApiMapper'; export * from './messageMapper'; export * from './messageMapperData'; +export * from './paginatedPayloadMapper'; export * from './tagsMapper'; export * from './userMapper'; diff --git a/lib/typescript/httpclient/messagesForChannels/text/index.ts b/lib/typescript/httpclient/messagesForChannels/text/index.ts index d664e54bc6..36566fb11e 100644 --- a/lib/typescript/httpclient/messagesForChannels/text/index.ts +++ b/lib/typescript/httpclient/messagesForChannels/text/index.ts @@ -5,23 +5,30 @@ export const getTextMessagePayload = ( conversationId: string, text: string ): SendMessagesRequestPayload => { - let payload: SendMessagesRequestPayload; switch (channel) { - case 'chat_plugin' || 'twillo.sms' || 'google' || 'facebook' || 'twillo.whatsapp': - payload = { + case 'chat_plugin' || 'twillo.sms' || 'facebook' || 'twillo.whatsapp': + return { conversationId, message: { text, }, }; - return payload; + case 'google': + return { + conversationId, + message: { + text, + representative: { + representativeType: 'HUMAN', + }, + }, + }; default: - payload = { + return { conversationId, message: { text, }, }; - return payload; } }; diff --git a/lib/typescript/httpclient/model/Conversation.ts b/lib/typescript/httpclient/model/Conversation.ts index f428bbfdaf..86aef3ec40 100644 --- a/lib/typescript/httpclient/model/Conversation.ts +++ b/lib/typescript/httpclient/model/Conversation.ts @@ -6,6 +6,9 @@ import {Channel} from './Channel'; export type ConversationMetadata = Metadata & { contact: Contact; unreadCount: number; + tags: { + [tagId: string]: string; + }; }; export interface Conversation { @@ -13,10 +16,13 @@ export interface Conversation { channel: Channel; metadata: ConversationMetadata; createdAt: Date; - tags: string[]; lastMessage: Message; } +export function getTags(conversation: Conversation) { + return Object.keys(conversation.metadata.tags || {}); +} + export function getSource(conversation: Conversation) { return conversation?.channel?.source; } diff --git a/lib/typescript/httpclient/model/Message.ts b/lib/typescript/httpclient/model/Message.ts index 215822460d..4c748dac61 100644 --- a/lib/typescript/httpclient/model/Message.ts +++ b/lib/typescript/httpclient/model/Message.ts @@ -54,7 +54,7 @@ export function isFromContact(message: Message) { export interface Message { id: string; - content: string; + content: any; deliveryState: MessageState; senderType: SenderType; sentAt: Date; diff --git a/lib/typescript/httpclient/payload/ChannelApiPayload.ts b/lib/typescript/httpclient/payload/ChannelApiPayload.ts deleted file mode 100644 index 1f6e328a06..0000000000 --- a/lib/typescript/httpclient/payload/ChannelApiPayload.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface ChannelApiPayload { - id: string; - metadata: any & { - name: string; - image_url?: string; - }; - source: string; - source_channel_id: string; -} diff --git a/lib/typescript/httpclient/payload/ChannelPayload.ts b/lib/typescript/httpclient/payload/ChannelPayload.ts new file mode 100644 index 0000000000..919bf002fb --- /dev/null +++ b/lib/typescript/httpclient/payload/ChannelPayload.ts @@ -0,0 +1,12 @@ +import {Metadata} from '../model'; + +export interface ChannelPayload { + id: string; + metadata?: Metadata & { + name: string; + image_url?: string; + }; + source: string; + source_channel_id: string; + connected: boolean; +} diff --git a/lib/typescript/httpclient/payload/ChannelsPayload.ts b/lib/typescript/httpclient/payload/ChannelsPayload.ts index b5e3172bf3..7fa97b62a9 100644 --- a/lib/typescript/httpclient/payload/ChannelsPayload.ts +++ b/lib/typescript/httpclient/payload/ChannelsPayload.ts @@ -1,5 +1,5 @@ -import {ChannelApiPayload} from './ChannelApiPayload'; +import {ChannelPayload} from './ChannelPayload'; export interface ChannelsPayload { - data: ChannelApiPayload[]; + data: ChannelPayload[]; } diff --git a/lib/typescript/httpclient/payload/SendMessagesRequestPayload.ts b/lib/typescript/httpclient/payload/SendMessagesRequestPayload.ts index 73c4a2d7f1..2bd8595e2b 100644 --- a/lib/typescript/httpclient/payload/SendMessagesRequestPayload.ts +++ b/lib/typescript/httpclient/payload/SendMessagesRequestPayload.ts @@ -2,5 +2,8 @@ export interface SendMessagesRequestPayload { conversationId: string; message: { text: string; + representative?: { + representativeType?: string; + }; }; } diff --git a/lib/typescript/httpclient/payload/index.ts b/lib/typescript/httpclient/payload/index.ts index 7e68f3cdcf..987d8ce1ec 100644 --- a/lib/typescript/httpclient/payload/index.ts +++ b/lib/typescript/httpclient/payload/index.ts @@ -7,8 +7,14 @@ export * from './ExploreChannelRequestPayload'; export * from './ListConversationsRequestPayload'; export * from './ListTagsResponsePayload'; export * from './LoginViaEmailRequestPayload'; +export * from './PaginatedPayload'; export * from './PaginatedResponse'; export * from './MessagePayload'; +export * from './ChannelPayload'; +export * from './ChannelsPayload'; +export * from './TagPayload'; +export * from './ConversationPayload'; +export * from './ListMessagesRequestPayload'; export * from './TagConversationRequestPayload'; export * from './UntagConversationRequestPayload'; export * from './SendMessagesRequestPayload'; diff --git a/lib/typescript/render/BUILD b/lib/typescript/render/BUILD index fda08bad96..2660e30d4c 100644 --- a/lib/typescript/render/BUILD +++ b/lib/typescript/render/BUILD @@ -14,5 +14,6 @@ ts_library( "@npm//@types/resize-observer-browser", "@npm//linkifyjs", "@npm//react", + "@npm//react-markdown", ], ) diff --git a/lib/typescript/render/assets/icons/chevron_left.svg b/lib/typescript/render/assets/icons/chevron_left.svg deleted file mode 100644 index 12d18614eb..0000000000 --- a/lib/typescript/render/assets/icons/chevron_left.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lib/typescript/render/components/Carousel/index.module.scss b/lib/typescript/render/components/Carousel/index.module.scss new file mode 100644 index 0000000000..058191fa17 --- /dev/null +++ b/lib/typescript/render/components/Carousel/index.module.scss @@ -0,0 +1,69 @@ +@import 'assets/scss/fonts.scss'; +@import 'assets/scss/colors.scss'; + +.wrapper { + display: flex; + align-items: flex-end; + flex-direction: column; + margin-top: 8px; + overflow: hidden; + position: relative; +} + +.carouselChildren { + display: flex; + flex-direction: row; + overflow: auto; + max-width: 100%; +} + +.buttonScroll { + position: absolute; + width: 30px; + height: 40px; + top: 50%; + transform: translateY(-50%); + border: none; + background-color: #ffffff; + padding: 0; + line-height: 0; + cursor: pointer; + + &:focus { + border: none; + outline: none; + } + &:active { + transition-duration: 0.2s; + transition: ease-in-out; + svg { + width: 18px; + height: 18px; + } + } +} + +.buttonLeft { + @extend .buttonScroll; + position: absolute; + border-top-right-radius: 12px; + border-bottom-right-radius: 12px; + left: 0; +} + +.buttonRight { + @extend .buttonScroll; + position: absolute; + border-top-left-radius: 12px; + border-bottom-left-radius: 12px; + right: 0px; +} + +.scrollButton { + width: 24px; +} + +.messageTime { + @include font-s; + color: var(--color-text-gray); +} diff --git a/lib/typescript/render/components/Carousel/index.tsx b/lib/typescript/render/components/Carousel/index.tsx new file mode 100644 index 0000000000..53620dbf0f --- /dev/null +++ b/lib/typescript/render/components/Carousel/index.tsx @@ -0,0 +1,77 @@ +import React, {useCallback, useEffect, useRef} from 'react'; +import styles from './index.module.scss'; +import {ReactComponent as LeftArrow} from 'assets/images/icons/leftArrow.svg'; +import {ReactComponent as RightArrow} from 'assets/images/icons/rightArrow.svg'; + +export const Carousel = ({children}) => { + const carouselChildren = useRef(null); + const buttonLeft = useRef(null); + const buttonRight = useRef(null); + + const getScrollBy = (element: HTMLDivElement) => { + return element.clientWidth * 0.92; + }; + + const moveLeft = useCallback(() => { + carouselChildren.current.scroll({ + left: carouselChildren.current.scrollLeft - getScrollBy(carouselChildren.current), + behavior: 'smooth', + }); + }, [carouselChildren]); + + const moveRight = useCallback(() => { + carouselChildren.current.scroll({ + left: carouselChildren.current.scrollLeft + getScrollBy(carouselChildren.current), + behavior: 'smooth', + }); + }, [carouselChildren]); + + const resetScrollButtons = useCallback(() => { + const element = carouselChildren.current; + if (buttonLeft.current) { + if (element.scrollLeft > 0) { + buttonLeft.current.style.display = 'block'; + } else { + buttonLeft.current.style.display = 'none'; + } + } + if (buttonRight.current) { + if (element.scrollLeft + element.clientWidth < element.scrollWidth && element.scrollWidth > element.clientWidth) { + buttonRight.current.style.display = 'block'; + } else { + buttonRight.current.style.display = 'none'; + } + } + }, [carouselChildren, buttonLeft, buttonRight]); + + const registerObserver = useCallback(() => { + const resizeObserver = new ResizeObserver(() => { + resetScrollButtons(); + }); + resizeObserver.observe(carouselChildren.current); + resetScrollButtons(); + carouselChildren.current.addEventListener('scroll', () => { + resetScrollButtons(); + }); + }, [carouselChildren]); + + useEffect(() => { + setTimeout(registerObserver, 200); + }, []); + + return ( +
    +
    + {children} +
    +
    + + +
    +
    + ); +}; diff --git a/lib/typescript/render/components/Image/index.tsx b/lib/typescript/render/components/Image/index.tsx index 936564566a..7f92df6e0c 100644 --- a/lib/typescript/render/components/Image/index.tsx +++ b/lib/typescript/render/components/Image/index.tsx @@ -1,32 +1,27 @@ import React from 'react'; import styles from './index.module.scss'; -import {Avatar} from '../Avatar'; import {DefaultMessageRenderingProps} from '../index'; type ImageRenderProps = DefaultMessageRenderingProps & { imageUrl: string; + altText?: string; }; -export const Image = ({contact, sentAt, fromContact, imageUrl}: ImageRenderProps) => ( +export const Image = ({fromContact, imageUrl, altText}: ImageRenderProps) => (
    {!fromContact ? (
    - + {altText
    - {sentAt &&
    {sentAt}
    }
    ) : (
    -
    - -
    - {sentAt &&
    {sentAt}
    }
    )} diff --git a/lib/typescript/render/components/MessageInfoWrapper/index.module.scss b/lib/typescript/render/components/MessageInfoWrapper/index.module.scss new file mode 100644 index 0000000000..01d8573b8b --- /dev/null +++ b/lib/typescript/render/components/MessageInfoWrapper/index.module.scss @@ -0,0 +1,31 @@ +@import 'assets/scss/fonts.scss'; +@import 'assets/scss/colors.scss'; + +.contact { + display: flex; + align-items: flex-start; +} + +.member { + display: flex; + flex-direction: column; + align-items: flex-end; +} + +.memberContent { + overflow-x: hidden; + max-width: 100%; +} + +.avatar { + width: 40px; + height: 40px; + margin: 6px 8px 0 0; +} + +.time { + @include font-s; + color: var(--color-text-gray); + margin-top: 5px; + padding-left: 50px; +} diff --git a/lib/typescript/render/components/MessageInfoWrapper/index.tsx b/lib/typescript/render/components/MessageInfoWrapper/index.tsx new file mode 100644 index 0000000000..86954c4684 --- /dev/null +++ b/lib/typescript/render/components/MessageInfoWrapper/index.tsx @@ -0,0 +1,41 @@ +import React, {ReactNode} from 'react'; +import {Avatar} from '../Avatar'; +import {DefaultMessageRenderingProps} from '../../components/index'; +import styles from './index.module.scss'; + +type MessageInfoWrapperProps = { + children?: ReactNode; + lastInGroup?: boolean; + isChatPlugin: boolean; +} & DefaultMessageRenderingProps; + +export const MessageInfoWrapper = (props: MessageInfoWrapperProps) => { + const {sentAt, contact, fromContact, children, lastInGroup, isChatPlugin} = props; + + const isContact = isChatPlugin ? !fromContact : fromContact; + + const MemberMessage = () => ( +
    +
    {children}
    +
    {sentAt}
    +
    + ); + + const ContactMessage = () => ( + <> +
    + {sentAt && ( +
    + +
    + )} +
    + {children} +
    +
    +
    {sentAt}
    + + ); + + return <>{isContact ? : }; +}; diff --git a/lib/typescript/render/components/RichCard/index.tsx b/lib/typescript/render/components/RichCard/index.tsx index 082e41be46..232f82d121 100644 --- a/lib/typescript/render/components/RichCard/index.tsx +++ b/lib/typescript/render/components/RichCard/index.tsx @@ -51,27 +51,29 @@ export const RichCard = ({title, description, suggestions, media, cardWidth, com }; return ( -
    -
    - -
    -
    - {title &&

    {title}

    } - {description && {description}} -
    - {suggestions.map((suggestion, idx) => ( - - ))} + <> +
    +
    + +
    +
    + {title &&

    {title}

    } + {description && {description}} +
    + {suggestions.map((suggestion: Suggestion, idx: number) => ( + + ))} +
    -
    + ); }; diff --git a/lib/typescript/render/components/RichCardCarousel/index.module.scss b/lib/typescript/render/components/RichCardCarousel/index.module.scss index cac6d1952c..ad9f348e95 100644 --- a/lib/typescript/render/components/RichCardCarousel/index.module.scss +++ b/lib/typescript/render/components/RichCardCarousel/index.module.scss @@ -1,113 +1,6 @@ @import 'assets/scss/fonts.scss'; @import 'assets/scss/colors.scss'; -.containerContact { - display: flex; - flex-direction: column; - align-items: flex-start; -} - -.containerChatpluginContact { - @extend .containerContact; - transform: scale(0.75); -} - -.containerMember { - display: flex; - flex-direction: column; - align-items: flex-end; -} - -.containerChatpluginMember { - @extend .containerMember; - transform: scale(0.75); -} - -.richCardCarouselContainer { - display: flex; - overflow-x: hidden; - padding-left: 20px; -} - -.isContact { - margin-top: 5px; - display: flex; -} - -.isMember { - margin-top: 5px; - display: flex; -} - .richCard { padding-right: 5px; } - -.richCard:last-child { - padding-right: 20px; -} - -.containerButton { - position: relative; -} - -.moveNext { - img { - height: 24px; - width: 24px; - } - position: absolute; - width: 30px; - height: 40px; - border-top-left-radius: 12px; - border-bottom-left-radius: 12px; - border: none; - background: white; - right: 0%; - top: 40%; - cursor: pointer; -} -.moveNext:focus { - outline: none; -} - -.moveNext:active { - transition-duration: 0.2s; - transition: ease-in-out; - img { - height: 20px; - width: 20px; - } -} - -.moveBack { - img { - height: 24px; - width: 24px; - } - display: flex; - flex-direction: row-reverse; - align-items: center; - position: absolute; - width: 30px; - height: 40px; - border-top-right-radius: 12px; - border-bottom-right-radius: 12px; - border: none; - background: white; - top: 40%; - left: 0%; - cursor: pointer; -} -.moveBack:focus { - outline: none; -} - -.moveBack:active { - transition-duration: 0.2s; - transition: ease-in-out; - img { - height: 20px; - width: 20px; - } -} diff --git a/lib/typescript/render/components/RichCardCarousel/index.tsx b/lib/typescript/render/components/RichCardCarousel/index.tsx index 7165d821de..3a799a6f78 100644 --- a/lib/typescript/render/components/RichCardCarousel/index.tsx +++ b/lib/typescript/render/components/RichCardCarousel/index.tsx @@ -1,10 +1,9 @@ -import React, {useState} from 'react'; +import React from 'react'; import styles from './index.module.scss'; import {MediaRenderProps} from '../RichCard/Media'; import {DefaultMessageRenderingProps} from '../index'; import {RichCard, Suggestion} from '../RichCard'; -import leftArrow from 'assets/images/icons/leftArrow.svg'; -import rightArrow from 'assets/images/icons/rightArrow.svg'; +import {Carousel} from '../Carousel'; type Card = { id?: string; @@ -14,169 +13,31 @@ type Card = { suggestions: Suggestion[]; }; -enum Direction { - back = 'back', - next = 'next', -} - -enum Width { - short = 'SHORT', - medium = 'MEDIUM', -} - -// Given width of RichCards -enum CardWidth { - short = 136, - medium = 280, -} - -enum VisibleArea { - short = 176, - medium = 320, -} - export type RichCardCarouselRenderProps = DefaultMessageRenderingProps & { cardWidth: string; cardContents: [Card]; - id: string; - isChatPlugin: boolean; }; export const RichCardCarousel = (props: RichCardCarouselRenderProps) => { - const {cardContents, cardWidth, id, fromContact, isChatPlugin} = props; - const [position, setPosition] = useState(0); - const [disabled, setDiabled] = useState(false); - const amountCards = cardContents.length; - - const button = (position: number, amountCards: number, id: string) => { - if (position == 0) { - return ( - - ); - } - if (position == amountCards - 1) { - return ( - - ); - } else { - return ( - <> - - - - ); - } - }; - - const carouselMove = (cardWidth: string, direction: Direction, id: string) => { - setDiabled(true); - setTimeout(function() { - setDiabled(false); - }, 600); - direction == Direction.back ? setPosition(position - 1) : setPosition(position + 1); - const elem = document.getElementById(id); - const padding = 5; - const space = - cardWidth == Width.short ? VisibleArea.short - CardWidth.short : VisibleArea.medium - CardWidth.medium; - const scrollDistance = - cardWidth == Width.short ? VisibleArea.short - space + padding : VisibleArea.medium - space + padding; - - return elem.scrollBy({ - behavior: 'smooth', - left: direction == Direction.back ? -scrollDistance : scrollDistance, - }); - }; + const {cardContents, cardWidth, fromContact} = props; return ( - <> - {fromContact ? ( - <> -
    -
    - {button(position, amountCards, id)} -
    -
    {button(position, amountCards, id)}
    -
    - {cardContents.map((card: Card, idx: number) => { - return ( -
    - -
    - ); - })} -
    -
    -
    -
    - - ) : ( - <> -
    -
    - {button(position, amountCards, id)} -
    -
    - {cardContents.map((card: Card, idx: number) => { - return ( -
    - -
    - ); - })} -
    -
    -
    + + {cardContents.map((card: Card, idx: number) => { + return ( +
    +
    - - )} - + ); + })} +
    ); }; diff --git a/lib/typescript/render/components/RichText/index.module.scss b/lib/typescript/render/components/RichText/index.module.scss index 574bbecfc2..4ff77b327a 100644 --- a/lib/typescript/render/components/RichText/index.module.scss +++ b/lib/typescript/render/components/RichText/index.module.scss @@ -1,33 +1,9 @@ -@import 'assets/scss/fonts.scss'; @import 'assets/scss/colors.scss'; -.container { - display: flex; - align-self: flex-end; +.contactContent { width: 100%; overflow-wrap: break-word; word-break: break-word; -} - -.avatar { - width: 40px; - height: 40px; - margin: 6px 8px 0 0; -} - -.userContainer { - display: flex; - flex-direction: row; -} - -.user { - align-self: flex-start; - text-align: left; - position: relative; -} - -.userText { - display: inline-flex; padding: 10px; margin-top: 5px; background: var(--color-background-blue); @@ -35,25 +11,39 @@ border-radius: 8px; } -.member { - margin-top: 5px; - justify-content: flex-end; +.memberContent { width: 100%; - text-align: right; -} - -.memberText { - display: inline-flex; + overflow-wrap: break-word; + word-break: break-word; padding: 10px; - position: relative; + margin-top: 5px; background: var(--color-airy-blue); color: white; - position: relative; - text-align: left; border-radius: 8px; -} - -.time { - @include font-s; - color: var(--color-text-gray); + a { + color: white; + &:visited, + &:hover { + color: white; + } + } +} + +.richText { + p:first-child { + margin-top: 0; + } + p:last-child { + margin-bottom: 0; + } + + b, + strong { + font-weight: 700; + } + + i, + em { + font-style: italic; + } } diff --git a/lib/typescript/render/components/RichText/index.tsx b/lib/typescript/render/components/RichText/index.tsx index 37bb56afb6..35d6b53569 100644 --- a/lib/typescript/render/components/RichText/index.tsx +++ b/lib/typescript/render/components/RichText/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import styles from './index.module.scss'; -import {Avatar} from '../Avatar'; +import ReactMarkdown from 'react-markdown'; import {Message} from 'httpclient'; +import styles from './index.module.scss'; import {DefaultMessageRenderingProps} from '..'; type RichTextRenderProps = DefaultMessageRenderingProps & { @@ -12,23 +12,12 @@ type RichTextRenderProps = DefaultMessageRenderingProps & { }; export const RichText = (props: RichTextRenderProps) => { - const {message, text, fallback, containsRichText, fromContact, sentAt, contact} = props; + const {message, text, fromContact} = props; return ( -
    - {!fromContact ? ( -
    -
    {containsRichText ? text : fallback}
    - {sentAt &&
    {sentAt}
    } -
    - ) : ( -
    -
    {contact && }
    -
    -
    {containsRichText ? text : fallback}
    - {sentAt &&
    {sentAt}
    } -
    -
    - )} +
    + + {text} +
    ); }; diff --git a/lib/typescript/render/components/Text/index.module.scss b/lib/typescript/render/components/Text/index.module.scss index a64a7cae2c..bfaa901157 100644 --- a/lib/typescript/render/components/Text/index.module.scss +++ b/lib/typescript/render/components/Text/index.module.scss @@ -1,39 +1,9 @@ -@import 'assets/scss/fonts.scss'; @import 'assets/scss/colors.scss'; -.wrapper { - display: flex; - flex: none; -} - -.messageListItem { - display: flex; - align-self: flex-end; +.contactContent { width: 100%; overflow-wrap: break-word; word-break: break-word; -} - -.messageAvatar { - width: 40px; - height: 40px; - margin: 6px 8px 0 0; -} - -.messageListUserContainer { - display: flex; - flex-direction: row; -} - -.messageListItemUser { - align-self: flex-start; - text-align: left; - position: relative; -} - -.messageListItemUserText { - font-family: 'Lato', sans-serif; - display: inline-flex; padding: 10px; margin-top: 5px; background: var(--color-background-blue); @@ -41,26 +11,13 @@ border-radius: 8px; } -.messageListItemMember { - font-family: 'Lato', sans-serif; - margin-top: 5px; - justify-content: flex-end; +.memberContent { width: 100%; - text-align: right; -} - -.messageListItemMemberText { - font-family: 'Lato', sans-serif; - display: inline-flex; + overflow-wrap: break-word; + word-break: break-word; padding: 10px; - position: relative; + margin-top: 5px; background: var(--color-airy-blue); color: white; - text-align: left; border-radius: 8px; } - -.messageTime { - @include font-s; - color: var(--color-text-gray); -} diff --git a/lib/typescript/render/components/Text/index.tsx b/lib/typescript/render/components/Text/index.tsx index a40047b8a3..2e8f2a2292 100644 --- a/lib/typescript/render/components/Text/index.tsx +++ b/lib/typescript/render/components/Text/index.tsx @@ -1,31 +1,11 @@ import React from 'react'; import styles from './index.module.scss'; -import {Avatar} from '../Avatar'; import {DefaultMessageRenderingProps} from '../index'; type TextRenderProps = DefaultMessageRenderingProps & { text: string; }; -export const Text = ({contact, sentAt, fromContact, text}: TextRenderProps) => ( -
    -
    - {!fromContact ? ( -
    -
    {text}
    - {sentAt &&
    {sentAt}
    } -
    - ) : ( -
    -
    - -
    -
    -
    {text}
    - {sentAt &&
    {sentAt}
    } -
    -
    - )} -
    -
    +export const Text = ({text, fromContact}: TextRenderProps) => ( +
    {text}
    ); diff --git a/lib/typescript/render/index.tsx b/lib/typescript/render/index.tsx index a3d31ce2a1..590557dedf 100644 --- a/lib/typescript/render/index.tsx +++ b/lib/typescript/render/index.tsx @@ -6,13 +6,40 @@ import {getDefaultMessageRenderingProps, MessageRenderProps} from './shared'; export * from './shared'; -export const SourceMessage = (props: MessageRenderProps) => { - const provider = renderProviders[props.source]; - - try { - return provider(props); - } catch (e) { - console.error(e); - return ; - } +type SourceMessageState = { + hasError: boolean; }; + +export class SourceMessage extends React.Component { + constructor(props: MessageRenderProps) { + super(props); + this.state = {hasError: false}; + } + + static getDerivedStateFromError() { + return {hasError: true}; + } + + componentDidCatch(error, errorInfo) { + console.error(error, errorInfo); + } + + errorFallback() { + return ; + } + + render() { + if (this.state.hasError) { + return this.errorFallback(); + } + + const provider = renderProviders[this.props.source]; + + try { + return provider(this.props); + } catch (e) { + console.error(e); + return this.errorFallback(); + } + } +} diff --git a/lib/typescript/render/providers/chatplugin/ChatPluginRender.tsx b/lib/typescript/render/providers/chatplugin/ChatPluginRender.tsx index 921518ffd4..34d06ed342 100644 --- a/lib/typescript/render/providers/chatplugin/ChatPluginRender.tsx +++ b/lib/typescript/render/providers/chatplugin/ChatPluginRender.tsx @@ -12,8 +12,6 @@ export const ChatPluginRender = (props: MessageRenderProps) => { }; function render(content: ContentUnion, props: MessageRenderProps) { - const messageContent = JSON.parse(props.message.content); - const defaultProps = getDefaultMessageRenderingProps(props); const invertedProps = {...defaultProps, fromContact: !defaultProps.fromContact}; const propsToUse = props.invertSides ? invertedProps : defaultProps; @@ -28,9 +26,9 @@ function render(content: ContentUnion, props: MessageRenderProps) { ); case 'richCard': @@ -44,29 +42,14 @@ function render(content: ContentUnion, props: MessageRenderProps) { /> ); case 'richCardCarousel': - return ( - - ); + return ; } } function mapContent(message: Message): ContentUnion { - const messageContent = JSON.parse(message.content); - - if (messageContent.text) { - return { - type: 'text', - text: JSON.parse(message.content).text, - }; - } + const messageContent = message.content; - if (messageContent.richCard.standaloneCard) { + if (messageContent.richCard?.standaloneCard) { const { richCard: { standaloneCard: {cardContent}, @@ -82,7 +65,7 @@ function mapContent(message: Message): ContentUnion { }; } - if (messageContent.richCard.carouselCard) { + if (messageContent.richCard?.carouselCard) { return { type: 'richCardCarousel', cardWidth: messageContent.richCard.carouselCard.cardWidth, @@ -97,4 +80,22 @@ function mapContent(message: Message): ContentUnion { postbackData: messageContent.postbackData, }; } + + if (messageContent.containsRichText) { + return { + type: 'richText', + text: messageContent.text, + fallback: messageContent.fallback, + containsRichtText: parseBoolean(messageContent.containsRichText), + }; + } + + if (messageContent.text) { + return { + type: 'text', + text: messageContent.text, + }; + } } + +const parseBoolean = value => (typeof value == 'boolean' ? value : /^true$/i.test(value)); diff --git a/lib/typescript/render/providers/facebook/FacebookRender.tsx b/lib/typescript/render/providers/facebook/FacebookRender.tsx index f28d141c28..6e618f3b32 100644 --- a/lib/typescript/render/providers/facebook/FacebookRender.tsx +++ b/lib/typescript/render/providers/facebook/FacebookRender.tsx @@ -20,6 +20,9 @@ function render(content: ContentUnion, props: MessageRenderProps) { case 'text': return ; + case 'postback': + return ; + case 'image': return ; @@ -50,13 +53,17 @@ const parseAttachment = (attachement: SimpleAttachment | ButtonAttachment | Gene type: 'image', imageUrl: attachement.payload.url, }; - } else if (attachement.type === 'template' && attachement.payload.template_type == 'button') { + } + + if (attachement.type === 'template' && attachement.payload.template_type == 'button') { return { type: 'buttonTemplate', text: attachement.payload.text, buttons: attachement.payload.buttons, }; - } else if (attachement.type === 'template' && attachement.payload.template_type == 'generic') { + } + + if (attachement.type === 'template' && attachement.payload.template_type == 'generic') { return { type: 'genericTemplate', elements: attachement.payload.elements, @@ -77,36 +84,51 @@ const parseAttachment = (attachement: SimpleAttachment | ButtonAttachment | Gene }; function facebookInbound(message: Message): ContentUnion { - const messageJson = JSON.parse(message.content); + const messageJson = message.content; - if (messageJson.message.attachments?.length) { + if (messageJson.message?.attachments?.length) { return parseAttachment(messageJson.message.attachments[0]); - } else if (messageJson.message.text) { + } else if (messageJson.message?.text) { return { type: 'text', - text: messageJson.message.text, + text: messageJson.message?.text, + }; + } + + if (messageJson.postback?.title) { + return { + type: 'postback', + title: messageJson.postback.title, + payload: messageJson.postback.payload, }; - } else { + } + + if (messageJson.message?.text) { return { type: 'text', - text: 'Unkown message type', + text: messageJson.message.text, }; } + + return { + type: 'text', + text: 'Unkown message type', + }; } function facebookOutbound(message: Message): ContentUnion { - const messageJson = JSON.parse(message.content); + const messageJson = message.content; if (messageJson.quick_replies) { if (messageJson.quick_replies.length > 13) { messageJson.quick_replies = messageJson.quick_replies.slice(0, 13); } - if (messageJson.attachment) { + if (messageJson.attachment || messageJson.message?.attachments) { return { type: 'quickReplies', - attachment: parseAttachment(messageJson.attachment), - quickReplies: messageJson.quick_replies, + attachment: parseAttachment(messageJson.attachment || messageJson.message?.attachments), + quickReplies: messageJson.quick_replies || messageJson.message?.quick_replies, }; } @@ -117,14 +139,14 @@ function facebookOutbound(message: Message): ContentUnion { }; } - if (messageJson.attachment) { - return parseAttachment(messageJson.attachment); + if (messageJson.attachment || messageJson.message?.attachments) { + return parseAttachment(messageJson.attachment || messageJson.message?.attachments[0]); } - if (messageJson.text) { + if (messageJson.text || messageJson.message?.text) { return { type: 'text', - text: messageJson.text, + text: messageJson.text || messageJson.message?.text, }; } diff --git a/lib/typescript/render/providers/facebook/components/GenericTemplate/index.module.scss b/lib/typescript/render/providers/facebook/components/GenericTemplate/index.module.scss index 3f50303885..a4edfc4e84 100644 --- a/lib/typescript/render/providers/facebook/components/GenericTemplate/index.module.scss +++ b/lib/typescript/render/providers/facebook/components/GenericTemplate/index.module.scss @@ -1,22 +1,6 @@ @import 'assets/scss/fonts.scss'; @import 'assets/scss/colors.scss'; -.wrapper { - display: flex; - align-items: flex-end; - flex-direction: column; - margin-top: 8px; - overflow: hidden; - position: relative; -} - -.templates { - display: flex; - flex-direction: row; - overflow: auto; - max-width: 100%; -} - .template { background-color: var(--color-template-gray); border-radius: 16px; @@ -60,37 +44,3 @@ text-decoration: none; } } - -.buttonScroll { - position: absolute; - top: 50%; - transform: translateY(-50%); - border: 1px solid #f7f7f7; - box-shadow: 0 2px 1px 0 #98a4ab; - border-radius: 100%; - background-color: #ffffff; - padding: 0; - line-height: 0; - cursor: pointer; -} - -.buttonLeft { - @extend .buttonScroll; - position: absolute; - left: 4px; -} - -.buttonRight { - @extend .buttonScroll; - position: absolute; - right: 4px; -} - -.scrollRight { - transform: scaleX(-1); -} - -.messageTime { - @include font-s; - color: var(--color-text-gray); -} diff --git a/lib/typescript/render/providers/facebook/components/GenericTemplate/index.tsx b/lib/typescript/render/providers/facebook/components/GenericTemplate/index.tsx index 60b19ae559..5c1edaecdb 100644 --- a/lib/typescript/render/providers/facebook/components/GenericTemplate/index.tsx +++ b/lib/typescript/render/providers/facebook/components/GenericTemplate/index.tsx @@ -1,90 +1,38 @@ -import React, {useCallback, useEffect, useRef} from 'react'; +import React from 'react'; import styles from './index.module.scss'; import {DefaultMessageRenderingProps} from '../../../../components/index'; import {GenericTemplate as GenericTemplateModel} from '../../facebookModel'; -import {ReactComponent as ChevronLeft} from '../../../../assets/icons/chevron_left.svg'; +import {Carousel} from 'render/components/Carousel'; type GenericTemplateRendererProps = DefaultMessageRenderingProps & { template: GenericTemplateModel; }; -export const GenericTemplate = ({sentAt, template}: GenericTemplateRendererProps) => { - const templatesEl = useRef(null); - const buttonEl = useRef(null); - - const moveLeft = useCallback(() => { - templatesEl.current.scroll({ - left: templatesEl.current.scrollLeft - 320, - behavior: 'smooth', - }); - }, [templatesEl]); - - const moveRight = useCallback(() => { - templatesEl.current.scroll({ - left: templatesEl.current.scrollLeft + 320, - behavior: 'smooth', - }); - }, [templatesEl]); - - const resetScrollButtons = useCallback(() => { - if (buttonEl.current) { - if (templatesEl.current.scrollWidth > templatesEl.current.clientWidth) { - buttonEl.current.style.display = 'block'; - } else { - buttonEl.current.style.display = 'none'; - } - } - }, [templatesEl, buttonEl]); - - const registerObserver = useCallback(() => { - const resizeObserver = new ResizeObserver(() => { - resetScrollButtons(); - }); - resizeObserver.observe(templatesEl.current); - resetScrollButtons(); - }, [templatesEl]); - - useEffect(() => { - setTimeout(registerObserver, 200); - }, []); - +export const GenericTemplate = ({template}: GenericTemplateRendererProps) => { return ( -
    -
    - {template.elements.map((element, idx) => ( -
    - {element.image_url?.length && } -
    -
    {element.title}
    -
    {element.subtitle}
    - {element.buttons.map((button, idx) => { - return ( -
    - {button.type == 'web_url' && button.url.length ? ( - - {button.title} - - ) : ( -
    {button.title}
    - )} -
    - ); - })} -
    + + {template.elements.map((element, idx) => ( +
    + {element.image_url?.length && } +
    +
    {element.title}
    +
    {element.subtitle}
    + {element.buttons.map((button, idx) => { + return ( +
    + {button.type == 'web_url' && button.url.length ? ( + + {button.title} + + ) : ( +
    {button.title}
    + )} +
    + ); + })}
    - ))} -
    - {template.elements.length > 1 && ( -
    - -
    - )} - {sentAt &&
    {sentAt}
    } -
    + ))} + ); }; diff --git a/lib/typescript/render/providers/facebook/facebookModel.ts b/lib/typescript/render/providers/facebook/facebookModel.ts index 1a3cb356ac..542ecb5ec7 100644 --- a/lib/typescript/render/providers/facebook/facebookModel.ts +++ b/lib/typescript/render/providers/facebook/facebookModel.ts @@ -46,6 +46,12 @@ export interface TextContent extends Content { text: string; } +export interface Postback extends Content { + type: 'postback'; + title: string; + payload: string; +} + export interface ImageContent extends Content { type: 'image'; imageUrl: string; @@ -90,6 +96,7 @@ export interface GenericTemplate extends Content { // Add a new facebook content model here: export type ContentUnion = | TextContent + | Postback | ImageContent | VideoContent | ButtonTemplate diff --git a/lib/typescript/render/providers/google/GoogleRender.tsx b/lib/typescript/render/providers/google/GoogleRender.tsx new file mode 100644 index 0000000000..8432728ab1 --- /dev/null +++ b/lib/typescript/render/providers/google/GoogleRender.tsx @@ -0,0 +1,139 @@ +import React from 'react'; +import {getDefaultMessageRenderingProps, MessageRenderProps} from '../../shared'; +import {Suggestions} from './components/Suggestions'; +import {Text} from '../../components/Text'; +import {ContentUnion} from './googleModel'; +import {Message, isFromContact} from 'httpclient'; +import {Image} from '../../components/Image'; + +export const GoogleRender = (props: MessageRenderProps) => { + const message = props.message; + const content = isFromContact(message) ? googleInbound(message) : googleOutbound(message); + return render(content, props); +}; + +function render(content: ContentUnion, props: MessageRenderProps) { + switch (content.type) { + case 'text': + return ; + + case 'image': + return ; + + case 'suggestions': + return ( + + ); + } +} + +function googleInbound(message: Message): ContentUnion { + const messageJson = message.content.message; + + if (messageJson.suggestionResponse) { + return { + type: 'text', + text: messageJson.suggestionResponse.text, + }; + } + + if (messageJson.authenticationResponse) { + if (messageJson.authenticationResponse.code && !messageJson.authenticationResponse.errorDetails) { + return { + type: 'text', + text: 'Authentication was successful', + }; + } + + if (messageJson.authenticationResponse.errorDetails && !messageJson.authenticationResponse.code) { + return { + type: 'text', + text: messageJson.authenticationResponse.errorDetails.errorDescription ?? 'Authentication failed', + }; + } + } + + if ( + messageJson.text && + messageJson.text.includes('https://storage.googleapis.com') && + messageJson.text.toLowerCase().includes('x-goog-algorithm') && + messageJson.text.toLowerCase().includes('x-goog-credential') + ) { + return { + type: 'image', + imageUrl: messageJson.text, + }; + } + + if (messageJson.text) { + return { + type: 'text', + text: messageJson.text, + }; + } + + return { + type: 'text', + text: 'Unkown message type', + }; +} + +function googleOutbound(message: Message): ContentUnion { + const messageJson = message.content; + const maxNumberOfSuggestions = 13; + + if (messageJson.suggestions) { + if (messageJson.suggestions.length > maxNumberOfSuggestions) { + messageJson.suggestions = messageJson.suggestions.slice(0, 13); + } + + if (messageJson.text) { + return { + type: 'suggestions', + text: messageJson.text, + suggestions: messageJson.suggestions, + }; + } + + if (messageJson.image) { + return { + type: 'suggestions', + image: {fileUrl: messageJson.image.contentInfo.fileUrl, altText: messageJson.image.contentInfo.altText}, + suggestions: messageJson.suggestions, + }; + } + } + + if (messageJson.image) { + return { + type: 'image', + imageUrl: messageJson.image.contentInfo.fileUrl, + altText: messageJson.image.contentInfo.altText, + }; + } + + if (messageJson.text) { + return { + type: 'text', + text: messageJson.text, + }; + } + + if (messageJson.fallback) { + return { + type: 'text', + text: messageJson.fallback, + }; + } + + return { + type: 'text', + text: 'Unknown message type', + }; +} diff --git a/lib/typescript/render/providers/google/components/Suggestions/index.module.scss b/lib/typescript/render/providers/google/components/Suggestions/index.module.scss new file mode 100644 index 0000000000..c70959728e --- /dev/null +++ b/lib/typescript/render/providers/google/components/Suggestions/index.module.scss @@ -0,0 +1,85 @@ +@import 'assets/scss/fonts.scss'; +@import 'assets/scss/colors.scss'; + +.wrapper { + display: flex; + flex: none; +} + +.item { + display: flex; + align-self: flex-end; + width: 100%; + overflow-wrap: break-word; + word-break: break-word; +} + +.itemMember { + margin-top: 5px; + justify-content: flex-end; + width: 100%; + text-align: right; +} + +.suggestionsContainer { + display: flex; + flex-wrap: wrap; + margin-top: 5px; + justify-content: flex-end; + position: relative; + width: 100%; +} + +.replyButton { + width: auto; + height: auto; + display: flex; + justify-content: center; + align-items: center; + margin: 0px 0px 5px 5px; + padding: 4px 8px; + border-radius: 16px; + border: 1px solid var(--color-dark-elements-gray); + background-color: var(--color-template-highlight); +} + +.title { + @include font-base; + color: var(--color-text-contrast); + text-decoration: none; + font-weight: normal; +} + +.actionImage { + width: 20px; + height: 20px; + margin-right: 4px; +} + +.hoverTextContainer { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: flex-end; + position: absolute; + padding: 5px; + border-radius: 6px; + background-color: var(--color-light-gray); + z-index: 1; + opacity: 0; + transition: opacity 0.8s; + box-sizing: content-box; +} + +.hoverTextContainer:hover { + visibility: visible; + opacity: 1; + cursor: not-allowed; +} + +.hoverText { + @include font-s; + color: var(--color-text-contrast); + text-align: center; +} diff --git a/lib/typescript/render/providers/google/components/Suggestions/index.tsx b/lib/typescript/render/providers/google/components/Suggestions/index.tsx new file mode 100644 index 0000000000..dae31c0c4c --- /dev/null +++ b/lib/typescript/render/providers/google/components/Suggestions/index.tsx @@ -0,0 +1,106 @@ +import React from 'react'; +import styles from './index.module.scss'; +import {DefaultMessageRenderingProps} from '../../../../components/index'; +import {SuggestionsUnion} from '../../googleModel'; +import {Image} from '../../../../components/Image'; +import {Text} from '../../../../components/Text'; +import linkIcon from 'assets/images/icons/link.svg'; +import phoneIcon from 'assets/images/icons/phone.svg'; + +type SuggestionsRendererProps = DefaultMessageRenderingProps & { + text?: string; + fallback?: string; + image?: { + fileUrl: string; + altText: string; + }; + suggestions: SuggestionsUnion[]; +}; + +export const Suggestions = ({text, fallback, image, suggestions, contact, fromContact}: SuggestionsRendererProps) => ( +
    +
    +
    + {text && } + + {image && ( + + )} + + {!text && !image && fallback && } + +
    +
    + {(suggestions as SuggestionsUnion[]).map(elem => { + if ('reply' in elem) { + return ( + + ); + } + + if ('action' in elem) { + return ( + + ); + } + + if ('authenticationRequest' in elem) { + return ( + + ); + } + + if ('liveAgentRequest' in elem) { + return ( + + ); + } + })} + {suggestions && + suggestions[0] && + (('action' in suggestions[0] && !('openUrlAction' in suggestions[0].action)) || + 'reply' in suggestions[0] || + 'authenticationRequest' in suggestions[0] || + 'liveAgentRequest' in suggestions[0]) && ( +
    + This action can only be triggered by your contact. +
    + )} +
    +
    +
    +
    +
    +); diff --git a/lib/typescript/render/providers/google/googleModel.ts b/lib/typescript/render/providers/google/googleModel.ts new file mode 100644 index 0000000000..a6573ab1b5 --- /dev/null +++ b/lib/typescript/render/providers/google/googleModel.ts @@ -0,0 +1,67 @@ +export interface Content { + type: 'text' | 'image' | 'suggestions'; +} + +export interface TextContent extends Content { + type: 'text'; + text: string; +} + +export interface ImageContent extends Content { + type: 'image'; + imageUrl: string; + altText?: string; +} + +interface SuggestedReplies { + reply: { + text: string; + postbackData: string; + }; +} + +interface SuggestedActions { + action: { + text: string; + postbackData: string; + openUrlAction?: { + url: string; + }; + dialAction?: { + phoneNumber: string; + }; + }; +} + +interface AuthenticationRequestSuggestion { + authenticationRequest: { + oauth: { + clientId: string; + codeChallenge: string; + scopes: [string]; + }; + }; +} + +interface LiveAgentRequestSuggestion { + liveAgentRequest: {}; +} + +export type SuggestionsUnion = + | SuggestedReplies + | SuggestedActions + | AuthenticationRequestSuggestion + | LiveAgentRequestSuggestion; + +export interface SuggestionsContent extends Content { + type: 'suggestions'; + text?: string; + fallback?: string; + image?: { + fileUrl: string; + altText: string; + }; + suggestions: SuggestionsUnion[]; +} + +export type ContentUnion = TextContent | ImageContent | SuggestionsContent; diff --git a/lib/typescript/render/providers/twilio/twilioSMS/TwilioSMSRender.tsx b/lib/typescript/render/providers/twilio/twilioSMS/TwilioSMSRender.tsx new file mode 100644 index 0000000000..a7e1608743 --- /dev/null +++ b/lib/typescript/render/providers/twilio/twilioSMS/TwilioSMSRender.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import {isFromContact, Message} from '../../../../httpclient/model'; +import {Text} from '../../../components/Text'; +import {getDefaultMessageRenderingProps, MessageRenderProps} from '../../../shared'; +import {ContentUnion} from './twilioSMSModel'; + +export const TwilioSMSRender = (props: MessageRenderProps) => { + const {message} = props; + const content = isFromContact(message) ? inboundContent(message) : outboundContent(message); + return render(content, props); +}; + +function render(content: ContentUnion, props: MessageRenderProps) { + switch (content.type) { + case 'text': + return ; + } +} + +const inboundContent = (message: Message): ContentUnion => { + const messageContent = message.content; + const startText = messageContent.search('&Body='); + const endText = messageContent.search('&FromCountry='); + const textLength = endText - startText; + const enCodedText = messageContent.substring(startText + 6, startText + textLength); + const replaced = enCodedText.split('+').join(' '); + const text = decodeURIComponent(replaced); + + return { + type: 'text', + text: text, + }; +}; + +const outboundContent = (message: Message): ContentUnion => { + const messageContent = message.content; + return { + type: 'text', + text: messageContent.text, + }; +}; diff --git a/lib/typescript/render/providers/twilio/twilioSMS/twilioSMSModel.ts b/lib/typescript/render/providers/twilio/twilioSMS/twilioSMSModel.ts new file mode 100644 index 0000000000..f3bc0e0cd5 --- /dev/null +++ b/lib/typescript/render/providers/twilio/twilioSMS/twilioSMSModel.ts @@ -0,0 +1,10 @@ +export interface Content { + type: 'text'; +} + +export interface TextContent extends Content { + type: 'text'; + text: string; +} + +export type ContentUnion = TextContent; diff --git a/lib/typescript/render/providers/twilio/twilioWhatsapp/TwilioWhatsappRender.tsx b/lib/typescript/render/providers/twilio/twilioWhatsapp/TwilioWhatsappRender.tsx new file mode 100644 index 0000000000..3060005710 --- /dev/null +++ b/lib/typescript/render/providers/twilio/twilioWhatsapp/TwilioWhatsappRender.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import {isFromContact, Message} from '../../../../httpclient/model'; +import {Text} from '../../../components/Text'; +import {getDefaultMessageRenderingProps, MessageRenderProps} from '../../../shared'; +import {ContentUnion} from './twilioWhatsappModel'; + +export const TwilioWhatsappRender = (props: MessageRenderProps) => { + const {message} = props; + const content = isFromContact(message) ? inboundContent(message) : outboundContent(message); + return render(content, props); +}; + +function render(content: ContentUnion, props: MessageRenderProps) { + switch (content.type) { + case 'text': + return ; + } +} + +const inboundContent = (message: Message): ContentUnion => { + const messageContent = message.content; + const startText = messageContent.search('&Body='); + const endText = messageContent.search('&To=whatsapp'); + const textLength = endText - startText; + const enCodedText = messageContent.substring(startText + 6, startText + textLength); + const replaced = enCodedText.split('+').join(' '); + const text = decodeURIComponent(replaced); + + return { + type: 'text', + text: text, + }; +}; + +const outboundContent = (message: Message): ContentUnion => { + const messageContent = message.content; + return { + type: 'text', + text: messageContent.text, + }; +}; diff --git a/lib/typescript/render/providers/twilio/twilioWhatsapp/twilioWhatsappModel.ts b/lib/typescript/render/providers/twilio/twilioWhatsapp/twilioWhatsappModel.ts new file mode 100644 index 0000000000..f3bc0e0cd5 --- /dev/null +++ b/lib/typescript/render/providers/twilio/twilioWhatsapp/twilioWhatsappModel.ts @@ -0,0 +1,10 @@ +export interface Content { + type: 'text'; +} + +export interface TextContent extends Content { + type: 'text'; + text: string; +} + +export type ContentUnion = TextContent; diff --git a/lib/typescript/render/renderProviders.ts b/lib/typescript/render/renderProviders.ts index 8c51f024fa..0cd12c585e 100644 --- a/lib/typescript/render/renderProviders.ts +++ b/lib/typescript/render/renderProviders.ts @@ -1,5 +1,8 @@ import {FacebookRender} from './providers/facebook/FacebookRender'; import {ChatPluginRender} from './providers/chatplugin/ChatPluginRender'; +import {TwilioSMSRender} from './providers/twilio/twilioSMS/TwilioSMSRender'; +import {TwilioWhatsappRender} from './providers/twilio/twilioWhatsapp/TwilioWhatsappRender'; +import {GoogleRender} from './providers/google/GoogleRender'; import {MessageRenderProps} from './shared'; type Provider = (messageRenderProps: MessageRenderProps) => JSX.Element; @@ -7,4 +10,7 @@ type Provider = (messageRenderProps: MessageRenderProps) => JSX.Element; export const renderProviders: {[key: string]: Provider} = { facebook: FacebookRender, chat_plugin: ChatPluginRender, + 'twilio.sms': TwilioSMSRender, + 'twilio.whatsapp': TwilioWhatsappRender, + google: GoogleRender, }; diff --git a/lib/typescript/websocketclient/index.ts b/lib/typescript/websocketclient/index.ts index 56c7234845..6267fae6ef 100644 --- a/lib/typescript/websocketclient/index.ts +++ b/lib/typescript/websocketclient/index.ts @@ -1,16 +1,18 @@ import {StompWrapper} from './stompWrapper'; -import {Message, Channel} from 'httpclient'; -import {messageMapper} from 'httpclient/mappers/messageMapper'; -import {channelMapper} from 'httpclient/mappers/channelMapper'; +import {Message, Channel, messageMapper, MetadataEvent} from 'httpclient'; +import {EventPayloadUnion} from './payload'; +import {channelMapper} from '../httpclient/mappers'; type CallbackMap = { onMessage?: (conversationId: string, channelId: string, message: Message) => void; - onUnreadCountUpdated?: (conversationId: string, unreadMessageCount: number) => void; - onChannelConnected?: (channel: Channel) => void; - onChannelDisconnected?: (channel: Channel) => void; + onMetadata?: (metadataEvent: MetadataEvent) => void; + onChannel?: (channel: Channel) => void; onError?: () => void; }; +// https: -> wss: and http: -> ws: +const protocol = location.protocol.replace('http', 'ws'); + export class WebSocketClient { public readonly token?: string; public readonly apiUrlConfig?: string; @@ -18,25 +20,16 @@ export class WebSocketClient { stompWrapper: StompWrapper; callbackMap: CallbackMap; - constructor(token: string, callbackMap: CallbackMap, baseUrl: string) { + constructor(token: string, callbackMap: CallbackMap = {}, baseUrl: string) { this.token = token; this.callbackMap = callbackMap; - this.apiUrlConfig = `ws://${baseUrl}/ws.communication`; + this.apiUrlConfig = `${protocol}//${baseUrl}/ws.communication`; this.stompWrapper = new StompWrapper( this.apiUrlConfig, { - '/queue/message': item => { - this.parseMessageBody(item.body); - }, - '/queue/unread-count': item => { - this.parseUnreadBody(item.body); - }, - '/queue/channel/connected': item => { - this.parseChannelConnected(item.body); - }, - '/queue/channel/disconnected': item => { - this.parseChannelDisconnected(item.body); + '/events': item => { + this.onEvent(item.body); }, }, this.token, @@ -49,36 +42,28 @@ export class WebSocketClient { this.stompWrapper.destroyConnection(); }; - parseMessageBody = (body: string) => { - if (this.callbackMap && this.callbackMap.onMessage) { - const json = JSON.parse(body); - const message = messageMapper(json.message); - this.callbackMap.onMessage(json.conversation_id, json.channel_id, message); - } - }; - - parseUnreadBody = (body: string) => { - if (this.callbackMap && this.callbackMap.onUnreadCountUpdated) { - const json = JSON.parse(body); - this.callbackMap.onUnreadCountUpdated(json.conversation_id, json.unread_message_count); - } - }; - - parseChannelConnected = (body: string) => { - if (this.callbackMap && this.callbackMap.onChannelConnected) { - const json = JSON.parse(body); - this.callbackMap.onChannelConnected(channelMapper(json)); - } - }; - - parseChannelDisconnected = (body: string) => { - if (this.callbackMap && this.callbackMap.onChannelDisconnected) { - const json = JSON.parse(body); - this.callbackMap.onChannelDisconnected(channelMapper(json)); + onEvent = (body: string) => { + const json: EventPayloadUnion = JSON.parse(body) as any; + switch (json.type) { + case 'channel': + this.callbackMap.onChannel?.(channelMapper(json.payload)); + break; + case 'message': + this.callbackMap.onMessage?.( + json.payload.conversation_id, + json.payload.channel_id, + messageMapper(json.payload.message) + ); + break; + case 'metadata': + this.callbackMap.onMetadata?.(json.payload); + break; + default: + console.error('Unknown /events payload', json); } }; onError = () => { - this.callbackMap && this.callbackMap.onError && this.callbackMap.onError(); + this.callbackMap.onError && this.callbackMap.onError(); }; } diff --git a/lib/typescript/websocketclient/payload.ts b/lib/typescript/websocketclient/payload.ts new file mode 100644 index 0000000000..54189bb376 --- /dev/null +++ b/lib/typescript/websocketclient/payload.ts @@ -0,0 +1,26 @@ +import {MessagePayload, MetadataEvent, ChannelPayload} from 'httpclient'; + +interface Event { + type: 'message' | 'channel' | 'metadata'; +} + +export interface MessageEventPayload extends Event { + type: 'message'; + payload: { + conversation_id: string; + channel_id: string; + message: MessagePayload; + }; +} + +export interface ChannelEventPayload extends Event { + type: 'channel'; + payload: ChannelPayload; +} + +export interface MetadataEventPayload extends Event { + type: 'metadata'; + payload: MetadataEvent; +} + +export type EventPayloadUnion = MessageEventPayload | ChannelEventPayload | MetadataEventPayload; diff --git a/main.go b/main.go new file mode 100644 index 0000000000..d51b3cccaf --- /dev/null +++ b/main.go @@ -0,0 +1,6 @@ +package main + +// This is a dummy package so that we can run go get on +// the virtual go.mod file +func main() { +} diff --git a/package.json b/package.json index 35cce0b864..0d7652fd91 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "react": "16.12.0", "react-dom": "16.12.0", "react-facebook-login": "^4.1.1", + "react-markdown": "^5.0.3", "react-redux": "7.1.3", "react-router-dom": "5.1.2", "react-window": "1.8.5", @@ -35,8 +36,9 @@ "devDependencies": { "@babel/core": "7.8.4", "@babel/preset-env": "^7.8.4", - "@bazel/ibazel": "0.12.2", - "@bazel/typescript": "1.6.0", + "@bazel/bazelisk": "^1.7.5", + "@bazel/ibazel": "^0.14.0", + "@bazel/typescript": "^3.2.0", "@svgr/webpack": "^5.4.0", "@types/lodash-es": "^4.17.3", "@types/react-window-infinite-loader": "^1.0.3", diff --git a/tools/update-deps/BUILD b/tools/update-deps/BUILD new file mode 100644 index 0000000000..91d4f888ae --- /dev/null +++ b/tools/update-deps/BUILD @@ -0,0 +1,23 @@ +# gazelle:prefix github.com/airyhq/airy/tools/update-deps +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "update-deps_lib", + srcs = [ + "main.go", + "modules.go", + ], + importpath = "github.com/airyhq/airy/tools/update-deps", + visibility = ["//visibility:private"], + deps = [ + "@org_golang_x_mod//modfile:go_default_library", + "@org_golang_x_mod//semver:go_default_library", + ], +) + +go_binary( + name = "update-deps", + out = "update-deps", + embed = [":update-deps_lib"], + visibility = ["//visibility:public"], +) diff --git a/tools/update-deps/README.md b/tools/update-deps/README.md new file mode 100644 index 0000000000..36986b994b --- /dev/null +++ b/tools/update-deps/README.md @@ -0,0 +1,15 @@ +# Update Golang deps tool + +```shell script +bazel run //tools/update-deps +``` + +Gazelle does not support version management for multiple `go.mod` files [[issue](https://github.com/bazelbuild/bazel-gazelle/issues/634)]. Without this tool, you have to run `update-repo` +for every module in the repository. + +This tool solves the problem by implementing the following algorithm: + +1. Find all modules in the repository +2. Merge their require sections, while always picking the latest version, and write them to the root `go.mod` file +3. Install the packages using `go get` and run `update-repo` on the root `go.mod` +4. To ensure that all packages use root version of each dependency merge the require statements back and update the modules diff --git a/tools/update-deps/go.mod b/tools/update-deps/go.mod new file mode 100644 index 0000000000..a6b0c0c997 --- /dev/null +++ b/tools/update-deps/go.mod @@ -0,0 +1,8 @@ +module github.com/tools/update-deps + +go 1.15 + +require ( + golang.org/x/mod v0.4.1 + k8s.io/apimachinery v0.20.0 +) diff --git a/tools/update-deps/go.sum b/tools/update-deps/go.sum new file mode 100644 index 0000000000..84859e3bae --- /dev/null +++ b/tools/update-deps/go.sum @@ -0,0 +1,176 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/apimachinery v0.20.0/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/tools/update-deps/main.go b/tools/update-deps/main.go new file mode 100644 index 0000000000..fb1113aa0b --- /dev/null +++ b/tools/update-deps/main.go @@ -0,0 +1,96 @@ +package main + +import ( + "flag" + "golang.org/x/mod/modfile" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" +) + +func main() { + log.SetFlags(0) + + var dryRun bool + flag.BoolVar(&dryRun, "dry_run", false, "Print to stdout instead of writing files") + flag.Parse() + + workingDir := os.Getenv("BUILD_WORKSPACE_DIRECTORY") + err := os.Chdir(workingDir) + if err != nil { + log.Fatal(err) + } + + packages := flag.Args() + if len(packages) == 0 { + packages = FindSourceModules("./") + log.Printf("Found %v go.mod files to merge: %v", len(packages), packages) + } + + rootModule := LoadModule("go.mod") + + modules := LoadModules(packages) + + rootModule.SetRequire(make([]*modfile.Require, 0)) + for _, sourceModule := range modules { + rootModule = MergeModuleRequire(rootModule, sourceModule.Module) + } + + fileContent, err := rootModule.Format() + if err != nil { + log.Fatal(err) + } + + if dryRun == true { + log.Println("Merged go.mod:\n---------------") + log.Print(string(fileContent)) + os.Exit(0) + } else { + if err = ioutil.WriteFile("go.mod", fileContent, 644); err != nil { + log.Fatal(err) + } + log.Println("Updated go.mod") + } + + err = exec.Command("go", "get", ".").Start() + if err != nil { + log.Fatal(err) + } + log.Println("Installed packages and updating go.sum using go get") + + err = exec.Command("bazel", "run", "//:gazelle", "--", "-update-repos", "-from_file=go.mod", "-prune").Start() + if err != nil { + log.Fatal(err) + } + log.Println("Updated go_repositories.bzl with Gazelle") + + // Update the source go.mod files and their go.sums + for _, sourceModule := range modules { + modified, file := UpdateModule(sourceModule.Module, rootModule) + if !modified { + continue + } + + content, err := file.Format() + if err != nil { + log.Fatal(err) + } + + if err = ioutil.WriteFile(sourceModule.Path, content, 644); err != nil { + log.Fatal(err) + } + + if err = os.Chdir(filepath.Dir(sourceModule.Path)); err != nil { + log.Fatal(err) + } + + if err = exec.Command("go", "get", ".").Start(); err != nil { + log.Fatal(err) + } + + log.Printf("Modified %v", sourceModule.Path) + os.Chdir(workingDir) + } +} diff --git a/tools/update-deps/modules.go b/tools/update-deps/modules.go new file mode 100644 index 0000000000..0aeab93ca7 --- /dev/null +++ b/tools/update-deps/modules.go @@ -0,0 +1,107 @@ +package main + +import ( + "golang.org/x/mod/modfile" + "golang.org/x/mod/semver" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" +) + +type SourceModule struct { + Path string + Module modfile.File +} + +func FindSourceModules(rootPath string) []string { + paths := make([]string, 0) + + err := filepath.Walk(rootPath, func(path string, f os.FileInfo, err error) error { + // Exclude the root go mod + if filepath.Base(path) == "go.mod" && path != "go.mod" { + paths = append(paths, path) + } + return nil + }) + + if err != nil { + log.Fatal(err) + } + + return paths +} + +func LoadModules(paths []string) []SourceModule { + modules := make([]SourceModule, 0) + for _, path := range paths { + modules = append(modules, SourceModule{ + Path: path, + Module: LoadModule(path), + }) + } + + return modules +} + +func LoadModule(path string) modfile.File { + data, err := ioutil.ReadFile(path) + if err != nil { + log.Fatal(err) + } + + file, parseErr := modfile.Parse("go.mod", data, nil) + if parseErr != nil { + log.Fatal(parseErr) + } + return *file +} + +// Merge all require statements from the modules list into the target module +// Pick the latest version encountered for each package +func MergeModuleRequire(targetModule modfile.File, sourceModule modfile.File) modfile.File { + for _, require := range sourceModule.Require { + // Skip paths within the same repository because gazelle cannot use them + if strings.HasPrefix(require.Mod.Path, targetModule.Module.Mod.Path) { + continue + } + + need := true + for _, existingRequire := range targetModule.Require { + if require.Mod.Path != existingRequire.Mod.Path { + continue + } + + if semver.Compare(require.Mod.Version, existingRequire.Mod.Version) == 1 { + if err := targetModule.AddRequire(require.Mod.Path, require.Mod.Version); err != nil { + log.Fatal(err) + } + } + need = false + } + + if need == true { + targetModule.AddNewRequire(require.Mod.Path, require.Mod.Version, require.Indirect) + } + } + + return targetModule +} + +// Update the require and version statements in each module with the source module +func UpdateModule(targetModule modfile.File, sourceModule modfile.File) (bool, modfile.File) { + modified := false + for _, require := range sourceModule.Require { + for _, existingRequire := range targetModule.Require { + if require.Mod.Path == existingRequire.Mod.Path && require.Mod.Version != existingRequire.Mod.Version { + modified = true + if err := targetModule.AddRequire(require.Mod.Path, require.Mod.Version); err != nil { + log.Fatal(err) + } + } + } + } + + return modified, targetModule +} diff --git a/yarn.lock b/yarn.lock index c839a03d82..a2b11f5b9f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1613,15 +1613,20 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" -"@bazel/ibazel@0.12.2": - version "0.12.2" - resolved "https://registry.yarnpkg.com/@bazel/ibazel/-/ibazel-0.12.2.tgz#98a290dbf8025076dd3040eab4f13a3d7e99357c" - integrity sha512-CktVREceZn+6VokQ7A2qByuXBSGRM0THuyv68JUShHYyCkYS94nYMZEIe5NXk12CRzcpMLfZGD5Gdtr+jWs9pw== +"@bazel/bazelisk@^1.7.5": + version "1.7.5" + resolved "https://registry.yarnpkg.com/@bazel/bazelisk/-/bazelisk-1.7.5.tgz#dd1a52e3d23464f72de55aa3dc4777847fa85373" + integrity sha512-JHwP9JhfZUSoj4sku471Bjw4uE773U2Agujnx0CdPkeRk25khy1l3VyjaPaHB+z1fmMnM6ED3M7tetQUsovUQg== -"@bazel/typescript@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-1.6.0.tgz" - integrity sha512-vAKuwy1Hgl+t3M3sH/G0oqHRYN35TdENj+0lsCI3x7EbSzyI6cbA3YQrLrlyvdScksqOpZa3PZ3UBGqfJJq2DA== +"@bazel/ibazel@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@bazel/ibazel/-/ibazel-0.14.0.tgz#86fa0002bed2ce1123b7ad98d4dd4623a0d93244" + integrity sha512-s0gyec6lArcRDwVfIP6xpY8iEaFpzrSpyErSppd3r2O49pOEg7n6HGS/qJ8ncvme56vrDk6crl/kQ6VAdEO+rg== + +"@bazel/typescript@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-3.2.0.tgz#299bd173fe04f98407ab9be4f654662c1c28470e" + integrity sha512-RKdy9ThbcUAqZR3AJK7AR/nxbJqdHi7pPayIGUSMIpxVkeTxVRQpf1aGe2H02HdZ9fR/uk1xXhO/Ff9TLvTgHQ== dependencies: protobufjs "6.8.8" semver "5.6.0" @@ -1893,6 +1898,13 @@ resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz" integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== +"@types/mdast@^3.0.0", "@types/mdast@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.3.tgz#2d7d671b1cd1ea3deb306ea75036c2a0407d2deb" + integrity sha512-SXPBMnFVQg1s00dlMCc/jCdvPqdE4mXaMMCeRlxLDmTAEoegHT53xKtkDnzDTOcmMHUfcjyf36/YYZ6SxRdnsw== + dependencies: + "@types/unist" "*" + "@types/node@*": version "13.13.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.2.tgz" @@ -2010,6 +2022,11 @@ dependencies: source-map "^0.6.1" +"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" + integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== + "@types/webpack-sources@*": version "0.1.7" resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-0.1.7.tgz" @@ -2640,6 +2657,11 @@ babel-plugin-dynamic-import-node@^2.3.3: dependencies: object.assign "^4.1.0" +bail@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" + integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ== + balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz" @@ -3025,6 +3047,21 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +character-entities-legacy@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" + integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA== + +character-entities@^1.0.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b" + integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw== + +character-reference-invalid@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" + integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== + chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz" @@ -3559,7 +3596,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: dependencies: ms "2.0.0" -debug@^4.0.1, debug@^4.1.1: +debug@^4.0.0, debug@^4.0.1, debug@^4.1.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz" integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== @@ -3710,6 +3747,15 @@ dom-serializer@0: domelementtype "^2.0.1" entities "^2.0.0" +dom-serializer@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.2.0.tgz#3433d9136aeb3c627981daa385fc7f32d27c48f1" + integrity sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.0.0" + entities "^2.0.0" + dom-walk@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz" @@ -3730,6 +3776,11 @@ domelementtype@^2.0.1: resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz" integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== +domelementtype@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.1.0.tgz#a851c080a6d1c3d94344aed151d99f669edf585e" + integrity sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w== + domhandler@^2.3.0: version "2.4.2" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz" @@ -3737,6 +3788,20 @@ domhandler@^2.3.0: dependencies: domelementtype "1" +domhandler@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-3.3.0.tgz#6db7ea46e4617eb15cf875df68b2b8524ce0037a" + integrity sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA== + dependencies: + domelementtype "^2.0.1" + +domhandler@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.0.0.tgz#01ea7821de996d85f69029e81fa873c21833098e" + integrity sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA== + dependencies: + domelementtype "^2.1.0" + domutils@1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz" @@ -3753,6 +3818,15 @@ domutils@^1.5.1, domutils@^1.7.0: dom-serializer "0" domelementtype "1" +domutils@^2.4.2: + version "2.4.4" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.4.4.tgz#282739c4b150d022d34699797369aad8d19bbbd3" + integrity sha512-jBC0vOsECI4OMdD0GC9mGn7NXPLb+Qt6KW1YDQzeQYRUFKmNG8lh7mO5HiELfr+lLQE7loDVI4QcAxV80HS+RA== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.0.1" + domhandler "^4.0.0" + dot-case@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.3.tgz" @@ -4299,7 +4373,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@~3.0.2: +extend@^3.0.0, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -4971,6 +5045,16 @@ html-minifier-terser@^5.0.1: relateurl "^0.2.7" terser "^4.6.3" +html-to-react@^1.3.4: + version "1.4.5" + resolved "https://registry.yarnpkg.com/html-to-react/-/html-to-react-1.4.5.tgz#59091c11021d1ef315ef738460abb6a4a41fe1ce" + integrity sha512-KONZUDFPg5OodWaQu2ymfkDmU0JA7zB1iPfvyHehTmMUZnk0DS7/TyCMTzsLH6b4BvxX15g88qZCXFhJWktsmA== + dependencies: + domhandler "^3.3.0" + htmlparser2 "^5.0" + lodash.camelcase "^4.3.0" + ramda "^0.27.1" + html-webpack-plugin@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.2.0.tgz" @@ -4998,6 +5082,16 @@ htmlparser2@^3.3.0: inherits "^2.0.1" readable-stream "^3.1.1" +htmlparser2@^5.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-5.0.1.tgz#7daa6fc3e35d6107ac95a4fc08781f091664f6e7" + integrity sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ== + dependencies: + domelementtype "^2.0.1" + domhandler "^3.3.0" + domutils "^2.4.2" + entities "^2.0.0" + http-errors@1.7.2: version "1.7.2" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz" @@ -5212,6 +5306,19 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" +is-alphabetical@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d" + integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg== + +is-alphanumerical@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf" + integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A== + dependencies: + is-alphabetical "^1.0.0" + is-decimal "^1.0.0" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz" @@ -5236,6 +5343,11 @@ is-buffer@^1.0.2, is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== +is-buffer@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== + is-callable@^1.1.4, is-callable@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz" @@ -5272,6 +5384,11 @@ is-date-object@^1.0.1: resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz" integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== +is-decimal@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" + integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== + is-descriptor@^0.1.0: version "0.1.6" resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz" @@ -5343,6 +5460,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-hexadecimal@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" + integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== + is-negative-zero@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz" @@ -5360,6 +5482,11 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-plain-obj@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz" @@ -5745,6 +5872,11 @@ lodash-es@^4.17.15: resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz" integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ== +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= + lodash@^4.0.0, lodash@^4.17.13, lodash@^4.17.15, lodash@~4.17.12: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz" @@ -5857,6 +5989,29 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" +mdast-add-list-metadata@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdast-add-list-metadata/-/mdast-add-list-metadata-1.0.1.tgz#95e73640ce2fc1fa2dcb7ec443d09e2bfe7db4cf" + integrity sha512-fB/VP4MJ0LaRsog7hGPxgOrSL3gE/2uEdZyDuSEnKCv/8IkYHiDkIQSbChiJoHyxZZXZ9bzckyRk+vNxFzh8rA== + dependencies: + unist-util-visit-parents "1.1.2" + +mdast-util-from-markdown@^0.8.0: + version "0.8.5" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz#d1ef2ca42bc377ecb0463a987910dae89bd9a28c" + integrity sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ== + dependencies: + "@types/mdast" "^3.0.0" + mdast-util-to-string "^2.0.0" + micromark "~2.11.0" + parse-entities "^2.0.0" + unist-util-stringify-position "^2.0.0" + +mdast-util-to-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz#b8cfe6a713e1091cb5b728fc48885a4767f8b97b" + integrity sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w== + mdn-data@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz" @@ -5947,6 +6102,14 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= +micromark@~2.11.0: + version "2.11.4" + resolved "https://registry.yarnpkg.com/micromark/-/micromark-2.11.4.tgz#d13436138eea826383e822449c9a5c50ee44665a" + integrity sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA== + dependencies: + debug "^4.0.0" + parse-entities "^2.0.0" + micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz" @@ -6671,6 +6834,18 @@ parse-asn1@^5.0.0: pbkdf2 "^3.0.3" safe-buffer "^5.1.1" +parse-entities@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" + integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ== + dependencies: + character-entities "^1.0.0" + character-entities-legacy "^1.0.0" + character-reference-invalid "^1.0.0" + is-alphanumerical "^1.0.0" + is-decimal "^1.0.0" + is-hexadecimal "^1.0.0" + parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz" @@ -7104,6 +7279,11 @@ querystring@0.2.0, querystring@^0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= +ramda@^0.27.1: + version "0.27.1" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9" + integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw== + randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz" @@ -7177,7 +7357,7 @@ react-is@^16.6.0, react-is@^16.7.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz" integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q== -react-is@^16.8.1, react-is@^16.9.0: +react-is@^16.8.1, react-is@^16.8.6, react-is@^16.9.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -7187,6 +7367,22 @@ react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4: resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== +react-markdown@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-5.0.3.tgz#41040ea7a9324b564b328fb81dd6c04f2a5373ac" + integrity sha512-jDWOc1AvWn0WahpjW6NK64mtx6cwjM4iSsLHJPNBqoAgGOVoIdJMqaKX4++plhOtdd4JksdqzlDibgPx6B/M2w== + dependencies: + "@types/mdast" "^3.0.3" + "@types/unist" "^2.0.3" + html-to-react "^1.3.4" + mdast-add-list-metadata "1.0.1" + prop-types "^15.7.2" + react-is "^16.8.6" + remark-parse "^9.0.0" + unified "^9.0.0" + unist-util-visit "^2.0.0" + xtend "^4.0.1" + react-modal@^3.11.2: version "3.12.1" resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.12.1.tgz" @@ -7495,6 +7691,13 @@ relateurl@^0.2.7: resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz" integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= +remark-parse@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-9.0.0.tgz#4d20a299665880e4f4af5d90b7c7b8a935853640" + integrity sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw== + dependencies: + mdast-util-from-markdown "^0.8.0" + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz" @@ -8597,6 +8800,11 @@ trim-newlines@^1.0.0: resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz" integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= +trough@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" + integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== + "true-case-path@^1.0.2": version "1.0.3" resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.3.tgz" @@ -8718,6 +8926,18 @@ unicode-property-aliases-ecmascript@^1.0.4: resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz" integrity sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw== +unified@^9.0.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.0.tgz#67a62c627c40589edebbf60f53edfd4d822027f8" + integrity sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg== + dependencies: + bail "^1.0.0" + extend "^3.0.0" + is-buffer "^2.0.0" + is-plain-obj "^2.0.0" + trough "^1.0.0" + vfile "^4.0.0" + union-value@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz" @@ -8747,6 +8967,40 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" +unist-util-is@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.0.4.tgz#3e9e8de6af2eb0039a59f50c9b3e99698a924f50" + integrity sha512-3dF39j/u423v4BBQrk1AQ2Ve1FxY5W3JKwXxVFzBODQ6WEvccguhgp802qQLKSnxPODE6WuRZtV+ohlUg4meBA== + +unist-util-stringify-position@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da" + integrity sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g== + dependencies: + "@types/unist" "^2.0.2" + +unist-util-visit-parents@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-1.1.2.tgz#f6e3afee8bdbf961c0e6f028ea3c0480028c3d06" + integrity sha512-yvo+MMLjEwdc3RhhPYSximset7rwjMrdt9E41Smmvg25UQIenzrN83cRnF1JMzoMi9zZOQeYXHSDf7p+IQkW3Q== + +unist-util-visit-parents@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz#65a6ce698f78a6b0f56aa0e88f13801886cdaef6" + integrity sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^4.0.0" + +unist-util-visit@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" + integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^4.0.0" + unist-util-visit-parents "^3.0.0" + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz" @@ -8893,6 +9147,24 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +vfile-message@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a" + integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ== + dependencies: + "@types/unist" "^2.0.0" + unist-util-stringify-position "^2.0.0" + +vfile@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.1.tgz#03f1dce28fc625c625bc6514350fbdb00fa9e624" + integrity sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA== + dependencies: + "@types/unist" "^2.0.0" + is-buffer "^2.0.0" + unist-util-stringify-position "^2.0.0" + vfile-message "^2.0.0" + vm-browserify@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz" @@ -9091,7 +9363,7 @@ ws@^7.3.1: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.3.tgz" integrity sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA== -xtend@^4.0.0, xtend@~4.0.1: +xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==