diff --git a/README.md b/README.md index ef5d81304b..1714fe1308 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

Airy-logo

The open source, fully-featured, production ready
-
Conversational Platform
+
Data Platform

# Airy Core @@ -15,10 +15,9 @@ --- -![Airy_Explainer_Highlevel_Readme](https://user-images.githubusercontent.com/124274/113720584-18a8d500-96ef-11eb-97c3-362eebd6253d.jpeg) +![Airy_Explainer_Highlevel_Readme](https://airy.co/docs/core/img/getting-started/introduction-light.png) -Airy Core is an open source, fully-featured, production ready conversational -platform. With Airy you can process conversational data from a variety of +Airy Core is an is an open-source streaming app framework to train ML models and supply them with historical and real-time data. With Airy you can process data from a variety of sources: - **Facebook** @@ -27,26 +26,25 @@ sources: - **SMS** - **Website Chat Plugins, like our own open source Live Chat** - **Twilio** -- **Your own conversational channels** +- **Any source you want with Custom Connectors** You can then use Airy to: -- **Unify your messaging channels** -- **Stream your conversational data wherever you want** -- **Integrate with different NLP frameworks** -- **Mediate open requests with Agents via our messaging UI** -- **Analyze your conversations** +- **Join historical and real-time data in the stream to create smarter ML and AI applications.** +- **Build real-time data pipelines and make real-time data universally accessible with our open-source streaming app framework.** +- **Standardize complex data ingestion and consume data directly from Kafka. Stream it directly to standard and customized applications, using pre-built, easily configured connectors.** +- **Significantly simplify deployment and reduce development times and increase the robustness of your infrastructure and apps.** Since Airy's infrastructure is built around Apache Kafka, it can process a large -amount of conversations and messages simultaneously and stream the relevant -conversational data to wherever you need it. +amount of events simultaneously and stream the relevant +real-time and historical data to wherever you need it. --- ## About Airy - **What does Airy do? 🚀** - [Learn more on our Website](https://airy.co/developers) + [Learn more on our Website](https://airy.co/) - **I'm new to Airy 😄** [Get Started with Airy](https://airy.co/docs/core/) @@ -69,21 +67,15 @@ conversational data to wherever you need it. ![Airy_Explainer_Components_Readme (1)](https://user-images.githubusercontent.com/12533283/112460661-6de3fe80-8d5f-11eb-8274-8446fbfcf5c8.png) -Airy Core contains the following components: +Airy Core comes with all the components you need to stream historical and real-time data. -- 💬 Connectors for all [conversational sources](https://airy.co/docs/core/sources/introduction) +- 💬 Pre-built and easily configurable [connectors](https://airy.co/docs/core/sources/introduction) -Connect anything from our free open-source [live chat -plugin](https://airy.co/docs/core/sources/chat-plugin) to Facebook -Messenger & Google's Business Messages to your Airy Core. This is -all possible through an ingestion platform that heavily relies on [Apache -Kafka](https://kafka.apache.org) to process incoming webhook data from different -sources. We make sense of the data and reshape it into source independent -contacts, conversations, and messages. +By ingesting all real-time events and continuously processing, aggregating and joining them in the stream, development time can be significantly reduced. Through integrations with pre-built and easily configured connectors, events are consumed from any source, including business systems such as ERP/CRM, conversational sources, third party APIs. Airy also comes with an SDK to build custom connectors to any source. - ⚡[APIs](https://airy.co/docs/core/api/introduction) to access your data -An [API](https://airy.co/docs/core/api/introduction) to access conversational +An [API](https://airy.co/docs/core/api/introduction) to access data with blazing fast HTTP endpoints. - 🔌[WebSockets](https://airy.co/docs/core/api/websocket) to power real-time applications @@ -91,16 +83,14 @@ data with blazing fast HTTP endpoints. A [WebSocket server](https://airy.co/docs/core/api/websocket) that allows clients to receive near real-time updates about data flowing through the system. -- 🎣[Webhook](https://airy.co/docs/core/api/webhook) to listen to events and participate programmatically in conversations +- 🎣[Webhook](https://airy.co/docs/core/api/webhook) to listen to events and create actionable workflows -A webhook integration server that allows its users to programmatically -participate in conversations by sending messages (the webhook integration +A webhook integration server that allows its users to create actionable workflows (the webhook integration exposes events users can "listen" to and react programmatically.) -- 💎[UI: From an inbox to dashboards](https://airy.co/docs/core/apps/ui/introduction) +- 💎[UI: From a control center to dashboards](https://airy.co/docs/core/apps/ui/introduction) -Not every message can be handled by code, this is why Airy comes with different -UIs ready for you and your teams to use. +No-code interfaces to manage and control Airy, your connectors and your streams. ## How to contribute diff --git a/VERSION b/VERSION index 4f9b378b40..7f422a161a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.52.0 +0.53.0 diff --git a/backend/components/cognigy/BUILD b/backend/components/cognigy/BUILD index 1aa1098022..00dbf6a40c 100644 --- a/backend/components/cognigy/BUILD +++ b/backend/components/cognigy/BUILD @@ -15,6 +15,7 @@ app_deps = [ "//backend/model/metadata", "//:feign", "//lib/java/log", + "//lib/java/sources-parser", "//lib/java/spring/kafka/core:spring-kafka-core", "//lib/java/spring/core:spring-core", "//lib/java/spring/kafka/streams:spring-kafka-streams", diff --git a/backend/components/cognigy/src/main/java/co/airy/core/cognigy_connector/MessageHandler.java b/backend/components/cognigy/src/main/java/co/airy/core/cognigy_connector/MessageHandler.java index ceae5dbcda..e5ca2de2af 100644 --- a/backend/components/cognigy/src/main/java/co/airy/core/cognigy_connector/MessageHandler.java +++ b/backend/components/cognigy/src/main/java/co/airy/core/cognigy_connector/MessageHandler.java @@ -4,11 +4,10 @@ import co.airy.avro.communication.Message; import co.airy.core.cognigy.models.MessageSendResponse; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.JsonNode; import org.springframework.stereotype.Service; +import co.airy.sources_parser.SourcesParser; import java.time.Instant; import java.util.Map; @@ -47,47 +46,10 @@ public String getContent(String source, MessageSendResponse response) throws Jso final String text = messageNode.get("text").textValue(); final JsonNode data = messageNode.findValue("data"); - final ObjectNode node = getNode(); - switch (source) { - case "google": { - final ObjectNode representative = getNode(); - representative.put("representativeType", "BOT"); - node.set("representative", representative); - node.put("text", text); - return mapper.writeValueAsString(node); - } - case "viber": { - node.put("text", text); - node.put("type", "text"); - return mapper.writeValueAsString(node); - } - case "chatplugin": - case "instagram": - case "facebook": { - node.put("text", text); - if(!data.isEmpty()){ - node.put("message", data); - } - return mapper.writeValueAsString(node); - } - case "twilio.sms": - case "twilio.whatsapp": { - node.put("Body", text); - return mapper.writeValueAsString(node); - } - case "whatsapp": { - node.put("Body", text); - return mapper.writeValueAsString(node); - } - - default: { - return null; - } + if(text == null && data == null){ + return null; } - } - private ObjectNode getNode() { - final JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance; - return jsonNodeFactory.objectNode(); + return SourcesParser.mapContent(source, text, data); } } diff --git a/backend/components/ibm-watson-assistant/BUILD b/backend/components/ibm-watson-assistant/BUILD index a831a6ff6b..79dc8fa38b 100644 --- a/backend/components/ibm-watson-assistant/BUILD +++ b/backend/components/ibm-watson-assistant/BUILD @@ -15,6 +15,7 @@ app_deps = [ "//backend/model/metadata", "//:feign", "//lib/java/log", + "//lib/java/sources-parser", "//lib/java/spring/kafka/core:spring-kafka-core", "//lib/java/spring/core:spring-core", "//lib/java/spring/kafka/streams:spring-kafka-streams", diff --git a/backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm-watson-assistant-connector/IbmWatsonAssistantClient.java b/backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm_watson_assistant_connector/IbmWatsonAssistantClient.java similarity index 100% rename from backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm-watson-assistant-connector/IbmWatsonAssistantClient.java rename to backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm_watson_assistant_connector/IbmWatsonAssistantClient.java diff --git a/backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm-watson-assistant-connector/IbmWatsonAssistantClientConfig.java b/backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm_watson_assistant_connector/IbmWatsonAssistantClientConfig.java similarity index 100% rename from backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm-watson-assistant-connector/IbmWatsonAssistantClientConfig.java rename to backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm_watson_assistant_connector/IbmWatsonAssistantClientConfig.java diff --git a/backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm-watson-assistant-connector/IbmWatsonAssistantConnectorService.java b/backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm_watson_assistant_connector/IbmWatsonAssistantConnectorService.java similarity index 100% rename from backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm-watson-assistant-connector/IbmWatsonAssistantConnectorService.java rename to backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm_watson_assistant_connector/IbmWatsonAssistantConnectorService.java diff --git a/backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm-watson-assistant-connector/MessageHandler.java b/backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm_watson_assistant_connector/MessageHandler.java similarity index 56% rename from backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm-watson-assistant-connector/MessageHandler.java rename to backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm_watson_assistant_connector/MessageHandler.java index 78f4cb24e9..b4808eb911 100644 --- a/backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm-watson-assistant-connector/MessageHandler.java +++ b/backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm_watson_assistant_connector/MessageHandler.java @@ -4,11 +4,10 @@ import co.airy.avro.communication.Message; import co.airy.core.ibm_watson_assistant.models.MessageSendResponse; import co.airy.log.AiryLoggerFactory; +import co.airy.sources_parser.SourcesParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.fasterxml.jackson.databind.node.ObjectNode; import org.springframework.stereotype.Service; import org.slf4j.Logger; @@ -54,51 +53,17 @@ public String getContent(String source, MessageSendResponse response) throws Jso text = nestedNode.get("text").textValue(); } - if (text != "") { - final ObjectNode node = getNode(); - switch (source) { - case "google": { - final ObjectNode representative = getNode(); - representative.put("representativeType", "BOT"); - node.set("representative", representative); - node.put("text", text); - return mapper.writeValueAsString(node); - } - case "viber": { - node.put("text", text); - node.put("type", "text"); - return mapper.writeValueAsString(node); - } - case "chatplugin": - case "instagram": - case "facebook": { - node.put("text", text); - return mapper.writeValueAsString(node); - } - case "twilio.sms": - case "twilio.whatsapp": { - node.put("Body", text); - return mapper.writeValueAsString(node); - } - case "whatsapp": { - node.put("Body", text); - return mapper.writeValueAsString(node); - } - - default: { - return null; - } - } + if (text == null) { + return null; } + + return SourcesParser.mapContent(source, text, null); + + } catch (Exception e) { log.error(String.format("could not find the text node in the response %s %s", response.toString(), e)); } return null; } - - private ObjectNode getNode() { - final JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance; - return jsonNodeFactory.objectNode(); - } } diff --git a/backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm-watson-assistant-connector/Stores.java b/backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm_watson_assistant_connector/Stores.java similarity index 100% rename from backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm-watson-assistant-connector/Stores.java rename to backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm_watson_assistant_connector/Stores.java diff --git a/backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm-watson-assistant-connector/models/MessageSend.java b/backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm_watson_assistant_connector/models/MessageSend.java similarity index 100% rename from backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm-watson-assistant-connector/models/MessageSend.java rename to backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm_watson_assistant_connector/models/MessageSend.java diff --git a/backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm-watson-assistant-connector/models/MessageSendResponse.java b/backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm_watson_assistant_connector/models/MessageSendResponse.java similarity index 100% rename from backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm-watson-assistant-connector/models/MessageSendResponse.java rename to backend/components/ibm-watson-assistant/src/main/java/co/airy/core/ibm_watson_assistant_connector/models/MessageSendResponse.java diff --git a/backend/components/ibm-watson-assistant/test/java/co/airy/core/ibm-watson-assistant_connector/IbmWatsonAssistantConnectorServiceTest.java b/backend/components/ibm-watson-assistant/test/java/co/airy/core/ibm_watson_assistant_connector/IbmWatsonAssistantConnectorServiceTest.java similarity index 100% rename from backend/components/ibm-watson-assistant/test/java/co/airy/core/ibm-watson-assistant_connector/IbmWatsonAssistantConnectorServiceTest.java rename to backend/components/ibm-watson-assistant/test/java/co/airy/core/ibm_watson_assistant_connector/IbmWatsonAssistantConnectorServiceTest.java diff --git a/backend/components/rasa/BUILD b/backend/components/rasa/BUILD index 2697002c64..a518584eaf 100644 --- a/backend/components/rasa/BUILD +++ b/backend/components/rasa/BUILD @@ -15,6 +15,7 @@ app_deps = [ "//backend/model/metadata", "//:feign", "//lib/java/log", + "//lib/java/sources-parser", "//lib/java/spring/kafka/core:spring-kafka-core", "//lib/java/spring/core:spring-core", "//lib/java/spring/kafka/streams:spring-kafka-streams", diff --git a/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/MessageHandler.java b/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/MessageHandler.java index 5472d57fad..8959c711df 100644 --- a/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/MessageHandler.java +++ b/backend/components/rasa/src/main/java/co/airy/core/rasa_connector/MessageHandler.java @@ -3,11 +3,13 @@ import co.airy.avro.communication.DeliveryState; import co.airy.avro.communication.Message; import co.airy.core.rasa_connector.models.MessageSendResponse; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.core.JsonProcessingException; import org.springframework.stereotype.Service; +import co.airy.sources_parser.SourcesParser; + import java.time.Instant; import java.util.Map; @@ -15,7 +17,6 @@ @Service public class MessageHandler { - private final ObjectMapper mapper = new ObjectMapper(); MessageHandler() { } @@ -40,50 +41,34 @@ public Message getMessage(Message contactMessage, MessageSendResponse response) .build(); } + + public String getContent(String source, MessageSendResponse response) throws JsonProcessingException { final String text = response.getText(); - if (text == null) { + final String image = response.getImage(); + + ObjectMapper mapper = new ObjectMapper(); + ObjectNode rootNode = mapper.createObjectNode(); + ObjectNode childNode1 = mapper.createObjectNode(); + ObjectNode childNode2 = mapper.createObjectNode(); + + if (text == null && image == null) { return null; } - final ObjectNode node = getNode(); - switch (source) { - case "google": { - final ObjectNode representative = getNode(); - representative.put("representativeType", "BOT"); - node.set("representative", representative); - node.put("text", text); - return mapper.writeValueAsString(node); - } - case "viber": { - node.put("text", text); - node.put("type", text); - return mapper.writeValueAsString(node); - } - case "chatplugin": - case "instagram": - case "facebook": { - node.put("text", text); - return mapper.writeValueAsString(node); - } - case "twilio.sms": - case "twilio.whatsapp": { - node.put("Body", text); - return mapper.writeValueAsString(node); - } - case "whatsapp": { - node.put("Body", text); - return mapper.writeValueAsString(node); - } + if(image != null){ + childNode1.put("type", "image"); + childNode2.put("url", image); + childNode1.put("payload", childNode2); + rootNode.put("attachment", childNode1); + JsonNode imageJsonNode = mapper.convertValue(rootNode, JsonNode.class); - default: { - return null; - } + return SourcesParser.mapContent(source, text, imageJsonNode); + } else { + return SourcesParser.mapContent(source, text, null); } - } - private ObjectNode getNode() { - final JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance; - return jsonNodeFactory.objectNode(); + } + } diff --git a/backend/components/websocket/BUILD b/backend/components/websocket/BUILD index b51ddf2590..d591851784 100644 --- a/backend/components/websocket/BUILD +++ b/backend/components/websocket/BUILD @@ -13,6 +13,7 @@ app_deps = [ "//backend/model/metadata", "//backend/model/tag", "//lib/java/date", + "//lib/java/kafka/schema:ops-application-components", "//lib/java/spring/auth:spring-auth", "//lib/java/spring/web:spring-web", "//lib/java/spring/kafka/core:spring-kafka-core", diff --git a/backend/components/websocket/src/main/java/co/airy/core/api/websocket/Stores.java b/backend/components/websocket/src/main/java/co/airy/core/api/websocket/Stores.java index bd2b36c0b8..577dc9e168 100644 --- a/backend/components/websocket/src/main/java/co/airy/core/api/websocket/Stores.java +++ b/backend/components/websocket/src/main/java/co/airy/core/api/websocket/Stores.java @@ -8,6 +8,7 @@ import co.airy.kafka.schema.application.ApplicationCommunicationMessages; import co.airy.kafka.schema.application.ApplicationCommunicationMetadata; import co.airy.kafka.schema.application.ApplicationCommunicationTags; +import co.airy.kafka.schema.ops.OpsApplicationComponents; import co.airy.kafka.streams.KafkaStreamsWrapper; import co.airy.model.metadata.dto.MetadataMap; import org.apache.kafka.streams.KeyValue; @@ -53,6 +54,12 @@ public void onApplicationEvent(ApplicationStartedEvent event) { .toStream() .peek((identifier, metadataMap) -> webSocketController.onMetadata(metadataMap)); + builder.table(new OpsApplicationComponents().name()) + .groupBy((metadataId, metadata) -> KeyValue.pair(getSubject(metadata).getIdentifier(), metadata)) + .aggregate(MetadataMap::new, MetadataMap::adder, MetadataMap::subtractor) + .toStream() + .peek((identifier, metadataMap) -> webSocketController.onComponentUpdate(metadataMap)); + streams.start(builder.build(), appId); } diff --git a/backend/components/websocket/src/main/java/co/airy/core/api/websocket/WebSocketController.java b/backend/components/websocket/src/main/java/co/airy/core/api/websocket/WebSocketController.java index 8fb434b6e8..e49f26ebea 100644 --- a/backend/components/websocket/src/main/java/co/airy/core/api/websocket/WebSocketController.java +++ b/backend/components/websocket/src/main/java/co/airy/core/api/websocket/WebSocketController.java @@ -8,6 +8,7 @@ import co.airy.model.event.payload.MessageCreated; import co.airy.model.event.payload.MetadataUpdated; import co.airy.model.event.payload.TagEvent; +import co.airy.model.event.payload.ComponentUpdated; import co.airy.model.metadata.dto.MetadataMap; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Service; @@ -46,4 +47,11 @@ public void onMetadata(MetadataMap metadataMap) { public void onTag(Tag tag) { messagingTemplate.convertAndSend(QUEUE_EVENTS, TagEvent.fromTag(tag)); } + + public void onComponentUpdate(MetadataMap componentUpdateMap) { + if (componentUpdateMap.isEmpty()) { + return; + } + messagingTemplate.convertAndSend(QUEUE_EVENTS, ComponentUpdated.fromComponentUpdateMap(componentUpdateMap)); + } } diff --git a/backend/components/websocket/src/test/java/co/airy/core/api/websocket/WebSocketControllerTest.java b/backend/components/websocket/src/test/java/co/airy/core/api/websocket/WebSocketControllerTest.java index f2ebd5364d..ee2e1d6216 100644 --- a/backend/components/websocket/src/test/java/co/airy/core/api/websocket/WebSocketControllerTest.java +++ b/backend/components/websocket/src/test/java/co/airy/core/api/websocket/WebSocketControllerTest.java @@ -11,6 +11,8 @@ import co.airy.kafka.schema.application.ApplicationCommunicationMessages; import co.airy.kafka.schema.application.ApplicationCommunicationMetadata; import co.airy.kafka.schema.application.ApplicationCommunicationTags; +import co.airy.kafka.schema.ops.OpsApplicationComponents; + import co.airy.kafka.test.KafkaTestHelper; import co.airy.kafka.test.junit.SharedKafkaTestResource; import co.airy.model.event.payload.ChannelUpdated; @@ -69,6 +71,7 @@ public class WebSocketControllerTest { private static final ApplicationCommunicationChannels applicationCommunicationChannels = new ApplicationCommunicationChannels(); private static final ApplicationCommunicationMetadata applicationCommunicationMetadata = new ApplicationCommunicationMetadata(); private static final ApplicationCommunicationTags applicationCommunicationTags = new ApplicationCommunicationTags(); + private static final OpsApplicationComponents opsApplicationComponents = new OpsApplicationComponents(); @Value("${local.server.port}") private int port; @@ -82,7 +85,8 @@ static void beforeAll() throws Exception { applicationCommunicationMessages, applicationCommunicationChannels, applicationCommunicationMetadata, - applicationCommunicationTags + applicationCommunicationTags, + opsApplicationComponents ); kafkaTestHelper.beforeAll(); } diff --git a/backend/model/event/src/main/java/co/airy/model/event/payload/ComponentUpdated.java b/backend/model/event/src/main/java/co/airy/model/event/payload/ComponentUpdated.java new file mode 100644 index 0000000000..d13cd86edd --- /dev/null +++ b/backend/model/event/src/main/java/co/airy/model/event/payload/ComponentUpdated.java @@ -0,0 +1,53 @@ +package co.airy.model.event.payload; + +import co.airy.avro.communication.Metadata; +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; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +import static co.airy.model.metadata.MetadataObjectMapper.getMetadataPayload; +import static co.airy.model.metadata.MetadataRepository.getSubject; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class ComponentUpdated extends Event implements Serializable { + private ComponentUpdatedEventPayload payload; + private Long timestamp; + + @Override + public EventType getTypeId() { + return EventType.COMPONENT_UPDATED; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class ComponentUpdatedEventPayload { + private String subject; + private String identifier; + private JsonNode metadata; + } + + public static ComponentUpdated fromComponentUpdateMap(MetadataMap metadataMap) { + final Metadata someMetadata = metadataMap.values().iterator().next(); + final Subject subject = getSubject(someMetadata); + return new ComponentUpdated( + ComponentUpdatedEventPayload.builder() + .subject(subject.getNamespace()) + .identifier(subject.getIdentifier()) + .metadata(getMetadataPayload(metadataMap)) + .build(), + metadataMap.getUpdatedAt()); + } +} diff --git a/backend/model/event/src/main/java/co/airy/model/event/payload/Event.java b/backend/model/event/src/main/java/co/airy/model/event/payload/Event.java index 5e98c9b568..75bf105796 100644 --- a/backend/model/event/src/main/java/co/airy/model/event/payload/Event.java +++ b/backend/model/event/src/main/java/co/airy/model/event/payload/Event.java @@ -12,7 +12,8 @@ @JsonSubTypes.Type(value = ConversationUpdated.class, name = "conversation.updated"), @JsonSubTypes.Type(value = MetadataUpdated.class, name = "metadata.updated"), @JsonSubTypes.Type(value = ChannelUpdated.class, name = "channel.updated"), - @JsonSubTypes.Type(value = TagEvent.class, name = "tag.updated") + @JsonSubTypes.Type(value = TagEvent.class, name = "tag.updated"), + @JsonSubTypes.Type(value = ComponentUpdated.class, name = "component.updated") }) public abstract class Event { @JsonIgnore diff --git a/backend/model/event/src/main/java/co/airy/model/event/payload/EventType.java b/backend/model/event/src/main/java/co/airy/model/event/payload/EventType.java index 9143eea812..4e96e4b75b 100644 --- a/backend/model/event/src/main/java/co/airy/model/event/payload/EventType.java +++ b/backend/model/event/src/main/java/co/airy/model/event/payload/EventType.java @@ -11,7 +11,8 @@ public enum EventType { CHANNEL_UPDATED("channel.updated"), TAG_UPDATED("tag.updated"), METADATA_ITEM("metadata.item"), - METADATA_UPDATED("metadata.updated"); + METADATA_UPDATED("metadata.updated"), + COMPONENT_UPDATED("component.updated"); private final String eventType; diff --git a/docs/docs/api/websocket.md b/docs/docs/api/websocket.md index 21e60395b2..118064e571 100644 --- a/docs/docs/api/websocket.md +++ b/docs/docs/api/websocket.md @@ -102,3 +102,22 @@ Includes the full and current state of a metadata object given a namespace-ident } } ``` + +### `component.updated` + +Includes the current status of a component + +**Sample payload** + +```json5 +{ + "type": "component.updated", + "payload": { + "subject": "component", + "identifier": "component name", + "metadata": { + "installationStatus": "pending | uninstalled | installed" + } + } +} +``` diff --git a/docs/docs/changelog.md b/docs/docs/changelog.md index bff0150c73..f572700af3 100644 --- a/docs/docs/changelog.md +++ b/docs/docs/changelog.md @@ -3,41 +3,91 @@ title: Changelog sidebar_label: 📝 Changelog --- +## 0.53.0 + +#### 🚀 Features + +- [[#3890](https://github.com/airyhq/airy/issues/3890)] Abstract sources parser for connectors in backend [[#3956](https://github.com/airyhq/airy/pull/3956)] +- [[#3841](https://github.com/airyhq/airy/issues/3841)] Add translations catalog components [[#3972](https://github.com/airyhq/airy/pull/3972)] +- [[#3919](https://github.com/airyhq/airy/issues/3919)] Component Websocket update [[#3930](https://github.com/airyhq/airy/pull/3930)] + +#### 🐛 Bug Fixes + +- [[#3995](https://github.com/airyhq/airy/issues/3995)] Fix resources for installer [[#3999](https://github.com/airyhq/airy/pull/3999)] +- [[#3997](https://github.com/airyhq/airy/issues/3997)] Fixed navigation chatplugin [[#3998](https://github.com/airyhq/airy/pull/3998)] +- [[#3995](https://github.com/airyhq/airy/issues/3995)] Add init container to the installer [[#3996](https://github.com/airyhq/airy/pull/3996)] +- [[#3988](https://github.com/airyhq/airy/issues/3988)] Fix deprecated TF parameter [[#3989](https://github.com/airyhq/airy/pull/3989)] +- [[#3974](https://github.com/airyhq/airy/issues/3974)] Fix pushing local images to Minikube [[#3981](https://github.com/airyhq/airy/pull/3981)] +- [[#3969](https://github.com/airyhq/airy/issues/3969)] Fixed health status icon [[#3971](https://github.com/airyhq/airy/pull/3971)] +- [[#3957](https://github.com/airyhq/airy/issues/3957)] Disable Preemptible nodes in GCP [[#3958](https://github.com/airyhq/airy/pull/3958)] + +#### 📚 Documentation + +- [[#3475](https://github.com/airyhq/airy/issues/3475)] Updated Status and Catalog docs [[#3994](https://github.com/airyhq/airy/pull/3994)] +- [[#3966](https://github.com/airyhq/airy/issues/3966)] Update Catalog Images [[#3990](https://github.com/airyhq/airy/pull/3990)] +- [[#3974](https://github.com/airyhq/airy/issues/3974)] Fix broken link in the docs [[#3978](https://github.com/airyhq/airy/pull/3978)] +- [[#3974](https://github.com/airyhq/airy/issues/3974)] Update docs on Airy Core [[#3975](https://github.com/airyhq/airy/pull/3975)] +- [[#3960](https://github.com/airyhq/airy/issues/3960)] Updated kafka topic link [[#3961](https://github.com/airyhq/airy/pull/3961)] + +#### 🧰 Maintenance + +- Bump decode-uri-component from 0.2.0 to 0.2.2 in /docs [[#3982](https://github.com/airyhq/airy/pull/3982)] + +#### Airy CLI + +You can download the Airy CLI for your operating system from the following links: + +[MacOS](https://airy-core-binaries.s3.amazonaws.com/0.53.0/darwin/amd64/airy) +[Linux](https://airy-core-binaries.s3.amazonaws.com/0.53.0/linux/amd64/airy) +[Windows](https://airy-core-binaries.s3.amazonaws.com/0.53.0/windows/amd64/airy.exe) + ## 0.52.0 #### Changes +- [[#3935](https://github.com/airyhq/airy/issues/3935)] Enhance catalog filter [[#3983](https://github.com/airyhq/airy/pull/3983)] +- [[#3974](https://github.com/airyhq/airy/issues/3974)] Readme Components Section - the data version [[#3977](https://github.com/airyhq/airy/pull/3977)] +- [[#3974](https://github.com/airyhq/airy/issues/3974)] Updated Readme to reflect Data Launch [[#3976](https://github.com/airyhq/airy/pull/3976)] - [[#3943](https://github.com/airyhq/airy/issues/3943)] Fix of the gcp-gke terraform modules output file [[#3944](https://github.com/airyhq/airy/pull/3944)] - Revert "[[#3878](https://github.com/airyhq/airy/issues/3878)] Adding Loading indicators to control-center" [[#3916](https://github.com/airyhq/airy/pull/3916)] #### 🚀 Features +- [[#3890](https://github.com/airyhq/airy/issues/3890)] abstract sources parser for connectors in backend [[#3956](https://github.com/airyhq/airy/pull/3956)] +- [[#3841](https://github.com/airyhq/airy/issues/3841)] Add translations catalog components [[#3972](https://github.com/airyhq/airy/pull/3972)] +- [[#3919](https://github.com/airyhq/airy/issues/3919)] component Websocket update [[#3930](https://github.com/airyhq/airy/pull/3930)] - [[#3927](https://github.com/airyhq/airy/issues/3927)] Added airy contacts to catalog [[#3932](https://github.com/airyhq/airy/pull/3932)] - [[#3764](https://github.com/airyhq/airy/issues/3764)] Kafka 3.3.1 docker image and helm chart [[#3882](https://github.com/airyhq/airy/pull/3882)] - [[#3878](https://github.com/airyhq/airy/issues/3878)] Adding Loading indicators to control-center [[#3922](https://github.com/airyhq/airy/pull/3922)] -- [[#3901](https://github.com/airyhq/airy/issues/3901)] Create kafka topic for components events [[#3914](https://github.com/airyhq/airy/pull/3914)] +- [[#3901](https://github.com/airyhq/airy/issues/3901)] [[#3900](https://github.com/airyhq/airy/issues/3900)] Create kafka topic for components events [[#3914](https://github.com/airyhq/airy/pull/3914)] - [[#3878](https://github.com/airyhq/airy/issues/3878)] Adding Loading indicators to control-center [[#3906](https://github.com/airyhq/airy/pull/3906)] #### 🐛 Bug Fixes -- [[#3941](https://github.com/airyhq/airy/issues/3941)] add ingress rule for /conversations.mark-read [[#3942](https://github.com/airyhq/airy/pull/3942)] +- [[#3941](https://github.com/airyhq/airy/issues/3941)] Add ingress rule for /conversations.mark-read [[#3942](https://github.com/airyhq/airy/pull/3942)] - [[#3939](https://github.com/airyhq/airy/issues/3939)] Fix showing airy products in connectors [[#3940](https://github.com/airyhq/airy/pull/3940)] - [[#3923](https://github.com/airyhq/airy/issues/3923)] Use auth-provider in GCP kubeconfig file [[#3924](https://github.com/airyhq/airy/pull/3924)] -- [[#3907](https://github.com/airyhq/airy/issues/3907)] fixed input disable [[#3913](https://github.com/airyhq/airy/pull/3913)] -- [[#3909](https://github.com/airyhq/airy/issues/3909)] rasa control center fix [[#3911](https://github.com/airyhq/airy/pull/3911)] -- [[#3886](https://github.com/airyhq/airy/issues/3886)] fixed status page error [[#3903](https://github.com/airyhq/airy/pull/3903)] -- [[#3894](https://github.com/airyhq/airy/issues/3894)] fixes for ibm watson assistant [[#3898](https://github.com/airyhq/airy/pull/3898)] +- [[#3907](https://github.com/airyhq/airy/issues/3907)] Fixed input disable [[#3913](https://github.com/airyhq/airy/pull/3913)] +- [[#3909](https://github.com/airyhq/airy/issues/3909)] Rasa control center fix [[#3911](https://github.com/airyhq/airy/pull/3911)] +- [[#3886](https://github.com/airyhq/airy/issues/3886)] Fixed status page error [[#3903](https://github.com/airyhq/airy/pull/3903)]Cli +- [[#3894](https://github.com/airyhq/airy/issues/3894)] Fixes for IBM Watson assistant [[#3898](https://github.com/airyhq/airy/pull/3898)] #### 📚 Documentation +- [[#3475](https://github.com/airyhq/airy/issues/3475)] Updated Status and Catalog docs [[#3994](https://github.com/airyhq/airy/pull/3994)] +- [[#3966](https://github.com/airyhq/airy/issues/3966)] Update Catalog Images [[#3990](https://github.com/airyhq/airy/pull/3990)] +- [[#3974](https://github.com/airyhq/airy/issues/3974)] Fix broken link in the docs [[#3978](https://github.com/airyhq/airy/pull/3978)] +- [[#3974](https://github.com/airyhq/airy/issues/3974)] Update docs on Airy Core [[#3975](https://github.com/airyhq/airy/pull/3975)] +- [[#3960](https://github.com/airyhq/airy/issues/3960)] Updated kafka topic link [[#3961](https://github.com/airyhq/airy/pull/3961)] - [[#3949](https://github.com/airyhq/airy/issues/3949)] Updated conversation.list docs [[#3950](https://github.com/airyhq/airy/pull/3950)] - [[#2456](https://github.com/airyhq/airy/issues/2456)] Documentation update in quickstart - consume from Kafka [[#3938](https://github.com/airyhq/airy/pull/3938)] -- [[#3646](https://github.com/airyhq/airy/issues/3646)] update airy cli for gcp provider [[#3904](https://github.com/airyhq/airy/pull/3904)] -- [[#3905](https://github.com/airyhq/airy/issues/3905)] rasa docs update [[#3912](https://github.com/airyhq/airy/pull/3912)] -- [[#3819](https://github.com/airyhq/airy/issues/3819)] docs for ibm watson assistant [[#3889](https://github.com/airyhq/airy/pull/3889)] +- [[#3646](https://github.com/airyhq/airy/issues/3646)] Update airy cli for gcp provider [[#3904](https://github.com/airyhq/airy/pull/3904)] +- [[#3905](https://github.com/airyhq/airy/issues/3905)] Rasa docs update [[#3912](https://github.com/airyhq/airy/pull/3912)] +- [[#3819](https://github.com/airyhq/airy/issues/3819)] Docs for ibm watson assistant [[#3889](https://github.com/airyhq/airy/pull/3889)] #### 🧰 Maintenance +- Bump decode-uri-component from 0.2.0 to 0.2.2 in /docs [[#3982](https://github.com/airyhq/airy/pull/3982)] - Bump jest-environment-jsdom from 29.2.0 to 29.2.2 [[#3896](https://github.com/airyhq/airy/pull/3896)] #### Airy CLI @@ -1335,31 +1385,3 @@ This release has breaking changes in the structure of the airy.yaml file. When u file cli.yaml - Rename the `ingress:` section to `ingress-controller:` -## 0.32.0 - -#### Changes - -#### 🚀 Features - -- [[#2443](https://github.com/airyhq/airy/issues/2443)] Allow for compatability with mobile login flows [[#2444](https://github.com/airyhq/airy/pull/2444)] -- [[#2423](https://github.com/airyhq/airy/issues/2423)] track core installation [[#2430](https://github.com/airyhq/airy/pull/2430)] - -#### 🐛 Bug Fixes - -- [[#2390](https://github.com/airyhq/airy/issues/2390)] Remove unnecessary annotations [[#2452](https://github.com/airyhq/airy/pull/2452)] -- [[#2434](https://github.com/airyhq/airy/issues/2434)] Fix instagram echo ingestion [[#2451](https://github.com/airyhq/airy/pull/2451)] - -#### 🧰 Maintenance - -- Bump webpack from 5.51.1 to 5.54.0 [[#2440](https://github.com/airyhq/airy/pull/2440)] -- Bump core-js from 3.17.2 to 3.18.1 [[#2438](https://github.com/airyhq/airy/pull/2438)] -- Bump @typescript-eslint/parser from 4.31.0 to 4.31.2 [[#2439](https://github.com/airyhq/airy/pull/2439)] - -#### Airy CLI - -You can download the Airy CLI for your operating system from the following links: - -[MacOS](https://airy-core-binaries.s3.amazonaws.com/0.32.0/darwin/amd64/airy) -[Linux](https://airy-core-binaries.s3.amazonaws.com/0.32.0/linux/amd64/airy) -[Windows](https://airy-core-binaries.s3.amazonaws.com/0.32.0/windows/amd64/airy.exe) - diff --git a/docs/docs/concepts/kafka.md b/docs/docs/concepts/kafka.md index 0229e69267..8c7ca6d1ae 100644 --- a/docs/docs/concepts/kafka.md +++ b/docs/docs/concepts/kafka.md @@ -9,7 +9,7 @@ Airy Core. ## Topic naming conventions Inspired by [this -article](https://riccomini.name/how-paint-bike-shed-kafka-topic-naming-conventions), +article](https://cnr.sh/essays/how-paint-bike-shed-kafka-topic-naming-conventions), our naming conventions follow these rules: - A topic has a three-part name: `..` diff --git a/docs/docs/connectors/sources/whatsapp-cloud.md b/docs/docs/connectors/sources/whatsapp-cloud.md index c48e3eaa5f..18a04744be 100644 --- a/docs/docs/connectors/sources/whatsapp-cloud.md +++ b/docs/docs/connectors/sources/whatsapp-cloud.md @@ -56,7 +56,7 @@ Airy will use these to generate long-lived access tokens and fetch conversation ## Step 2: Install the Whatsapp cloud source -Here we assume that you already have a running Airy core instance ([get started](getting-started/installation/introduction)). +Here we assume that you already have a running Airy core instance ([get started](/getting-started/installation/introduction)). Now in order to use this source you have to first install it either by navigating to the [control center](/ui/control-center/connectors) or by directly calling the components installation API like so: `POST /components.install` diff --git a/docs/docs/getting-started/components.md b/docs/docs/getting-started/components.md index 2a6070e179..f1d01b7f97 100644 --- a/docs/docs/getting-started/components.md +++ b/docs/docs/getting-started/components.md @@ -15,7 +15,7 @@ import TLDR from "@site/src/components/TLDR"; -Airy Core comes with all the components you need for a fully-featured conversational platform. +Airy Core comes with all the components you need to stream historical and real-time data. @@ -36,7 +36,7 @@ Airy Core contains the following components: icon={} iconInvertible={true} title='Connectors' - description="Connect anything to your Airy Core - from our Open-Source Airy Live Chat Plugin to Facebook Messenger, Instagram, Google's Business Messages, IBM Watson Assistant, and more. This is all possible through an ingestion platform that heavily relies on Apache Kafka to process incoming webhook data from different sources. We make sense of the data and reshape it into source independent contacts, conversations, and messages." + description="By ingesting all real-time events and continuously processing, aggregating and joining them in the stream, development time can be significantly reduced. Through integrations with pre-built and easily configured connectors, events are consumed from any source, including business systems such as ERP/CRM, conversational sources, third party APIs. Airy also comes with an SDK to build custom connectors to any source." link='/connectors/sources/introduction' /> } iconInvertible={true} - title='UI: From an inbox to dashboards' - description="Not every message can be handled by code, this is why Airy comes with different UIs ready for you and your teams to use." + title='UI: From a control center to dashboards' + description="No-code interfaces to manage and control Airy, your connectors and your streams." link='/ui/inbox/introduction' /> diff --git a/docs/docs/getting-started/installation/helm.md b/docs/docs/getting-started/installation/helm.md index f5686f83e3..03dcc7038f 100644 --- a/docs/docs/getting-started/installation/helm.md +++ b/docs/docs/getting-started/installation/helm.md @@ -399,3 +399,22 @@ where `VERSION_NUMBER` is a previous revision number of the `airy` helm chart. If you need further help, refer to our [Troubleshooting section](/getting-started/troubleshooting). ::: + +## Uninstall + +For uninstalling your `Airy Core` instance using helm, run the following command: + +``` +helm uninstall airy +``` + +Uninstalling the Helm chart will not remove the data which is stored in Kafka. To remove all the volumes associated with `Airy Core` run the following commands: + +``` +kubectl delete pvc data-beanstalkd-0 +kubectl delete pvc datadir-0-kafka-0 +kubectl delete pvc datadir-zookeeper-0 +kubectl delete pvc datalogdir-zookeeper-0 +``` + +After this you also need to remove or destroy the created Kubernetes cluster, if one was created particularly for running the Airy platform. diff --git a/docs/docs/getting-started/introduction.md b/docs/docs/getting-started/introduction.md index d008c186d3..ea4ecebc36 100644 --- a/docs/docs/getting-started/introduction.md +++ b/docs/docs/getting-started/introduction.md @@ -19,12 +19,11 @@ import AiryBubbleSVG from "@site/static/icons/airyBubble.svg"; -Airy Core is an **open-source**, **fully-featured**, **production-ready** -conversational platform. +Airy Core is an is an **open-source** **streaming** **app framework** to train ML models and supply them with historical and real-time data. - +

Get Airy up and running with one command

@@ -38,26 +37,25 @@ airy create --provider=aws ## What Airy is used for -With Airy Core you can process conversational data from a variety of sources: +With Airy Core you can process data from a variety of sources: - Facebook Messenger - WhatsApp Business API - Google's Business Messages - SMS - Website Chat Plugins -- Your own conversational channels +- Any source you want with Custom Connectors You can then use Airy Core to: -- Unify your messaging channels -- Stream your conversational data wherever you want -- Integrate with different NLP frameworks -- Mediate open requests with Agents via our messaging UI -- Analyze your conversations +- Join historical and real-time data in the stream to create smarter ML and AI applications. +- Build real-time data pipelines and make real-time data universally accessible with our open-source streaming app framework. +- Standardize complex data ingestion and consume data directly from Kafka. Stream it directly to standard and customized applications, using pre-built, easily configured connectors. +- Significantly simplify deployment and reduce development times and increase the robustness of your infrastructure and apps. -Since Airy's infrastructure is built around Apache Kafka, it can process a large -amount of conversations and messages simultaneously and stream the relevant -conversational data to wherever you need it. +Since Airy's infrastructure is built around Apache Kafka, +it can process a large amount of events simultaneously and stream the relevant real-time +and historical data to wherever you need it. ## Next steps @@ -65,7 +63,7 @@ conversational data to wherever you need it. } title="What are Airy's components?" - description="Learn about Airy's messaging platform and components" + description="Learn about Airy's app framework and components" link='getting-started/components' /> | Services are not running, but they are targeting pods and can be healthy or not, depending if all the underlying pods are healthy | +| | Component needs configuration | +| | Component/pods are running | +| | Component is disabled and not running | + +
+ The data displayed in this comprehensible table is served by the [client.config endpoint](/api/endpoints/client-config). Control Center diff --git a/docs/static/icons/checkmarkFilled.svg b/docs/static/icons/checkmarkFilled.svg new file mode 100644 index 0000000000..57b9a38d69 --- /dev/null +++ b/docs/static/icons/checkmarkFilled.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/static/icons/uncheckIconGray.svg b/docs/static/icons/uncheckIconGray.svg new file mode 100644 index 0000000000..686e26e4b3 --- /dev/null +++ b/docs/static/icons/uncheckIconGray.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/static/icons/uncheckIconRed.svg b/docs/static/icons/uncheckIconRed.svg new file mode 100644 index 0000000000..bf753633e4 --- /dev/null +++ b/docs/static/icons/uncheckIconRed.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/static/icons/uncheckIconYellow.svg b/docs/static/icons/uncheckIconYellow.svg new file mode 100644 index 0000000000..8c596005d9 --- /dev/null +++ b/docs/static/icons/uncheckIconYellow.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/static/img/getting-started/introduction-dark.png b/docs/static/img/getting-started/introduction-dark.png deleted file mode 100644 index 8e45d07564..0000000000 Binary files a/docs/static/img/getting-started/introduction-dark.png and /dev/null differ diff --git a/docs/static/img/getting-started/introduction-light.png b/docs/static/img/getting-started/introduction-light.png index ec18c985b1..1779f039b2 100644 Binary files a/docs/static/img/getting-started/introduction-light.png and b/docs/static/img/getting-started/introduction-light.png differ diff --git a/docs/static/img/ui/controlCenterCatalog.png b/docs/static/img/ui/controlCenterCatalog.png index 745ab19ba1..dd74b2ca4f 100644 Binary files a/docs/static/img/ui/controlCenterCatalog.png and b/docs/static/img/ui/controlCenterCatalog.png differ diff --git a/docs/yarn.lock b/docs/yarn.lock index 70c8445f06..7588658d40 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -3589,9 +3589,9 @@ decamelize@^1.2.0: integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + version "0.2.2" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" + integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== decompress-response@^3.3.0: version "3.3.0" diff --git a/frontend/control-center/src/App.module.scss b/frontend/control-center/src/App.module.scss index 81ee869bc5..3d140ac2de 100644 --- a/frontend/control-center/src/App.module.scss +++ b/frontend/control-center/src/App.module.scss @@ -4,10 +4,8 @@ .container { @include font-base; display: flex; - width: 100vw; + width: 100%; height: 100%; - min-width: 1366px; - min-height: 100vh; justify-content: center; flex-direction: column; } @@ -17,6 +15,6 @@ justify-content: flex-start; overflow: hidden; width: 100%; - min-height: 100vh; + height: 100vh; background-color: var(--color-blue-white); } diff --git a/frontend/control-center/src/App.tsx b/frontend/control-center/src/App.tsx index b13e6dcbb6..def8c7d437 100644 --- a/frontend/control-center/src/App.tsx +++ b/frontend/control-center/src/App.tsx @@ -16,6 +16,7 @@ import Status from './pages/Status'; import Inbox from './pages/Inbox'; import ConnectorConfig from './pages/Connectors/ConnectorConfig'; import CatalogProductPage from './pages/Catalog/CatalogItemDetails'; +import AiryWebSocket from './components/AiryWebsocket'; import {getConnectorsConfiguration, listChannels, listComponents} from './actions'; const mapDispatchToProps = { @@ -52,35 +53,37 @@ const App = (props: ConnectedProps) => { }, [location]); return ( -
-
- - - - } /> + +
+
+ + + + } /> - } /> + } /> - }> - } /> - } /> - } /> - } /> - + }> + } /> + } /> + } /> + } /> + - }> - } /> - } /> - + }> + } /> + } /> + - } /> + } /> - } /> - } /> - } /> - + } /> + } /> + } /> + +
-
+ ); }; diff --git a/frontend/control-center/src/actions/catalog/index.ts b/frontend/control-center/src/actions/catalog/index.ts index 5bb0837d8f..70101cd6ed 100644 --- a/frontend/control-center/src/actions/catalog/index.ts +++ b/frontend/control-center/src/actions/catalog/index.ts @@ -7,6 +7,13 @@ import {Components} from 'model'; const LIST_COMPONENT = '@@catalog/LIST_COMPONENT'; const INSTALL_COMPONENT = '@@catalog/INSTALL_COMPONENT'; const UNINSTALL_COMPONENT = '@@catalog/UNINSTALL_COMPONENT'; +const UPDATE_INSTALLATION_STATUS = '@@catalog/UPDATE_INSTALLATION_STATUS'; + +type ComponentInstallationStatusPayload = { + subject: 'component'; + identifier: string; + metadata: {installationStatus: 'installed' | 'pending' | 'uninstalled'}; +}; export const listComponentsAction = createAction( LIST_COMPONENT, @@ -23,6 +30,11 @@ export const uninstallComponentAction = createAction( (uninstalledComponent: InstallUninstallComponentRequestPayload) => uninstalledComponent )(); +export const updateComponentInstallationStatusAction = createAction( + UPDATE_INSTALLATION_STATUS, + (update: ComponentInstallationStatusPayload) => update +)(); + export const listComponents = () => (dispatch: Dispatch) => { return HttpClientInstance.listComponents().then(response => { dispatch(listComponentsAction(response)); @@ -45,3 +57,8 @@ export const uninstallComponent = return Promise.resolve(true); }); }; + +export const updateComponentStatus = + (installationStatusPayload: ComponentInstallationStatusPayload) => (dispatch: Dispatch) => { + return dispatch(updateComponentInstallationStatusAction(installationStatusPayload)); + }; diff --git a/frontend/control-center/src/components/AiryWebsocket/index.tsx b/frontend/control-center/src/components/AiryWebsocket/index.tsx new file mode 100644 index 0000000000..22d5f3f8a6 --- /dev/null +++ b/frontend/control-center/src/components/AiryWebsocket/index.tsx @@ -0,0 +1,38 @@ +import React, {useState, useEffect} from 'react'; +import {connect, ConnectedProps} from 'react-redux'; +import {WebSocketClient} from 'websocketclient'; +import {updateComponentInstallationStatusAction} from '../../actions'; + +import {env} from '../../env'; + +type AiryWebSocketProps = {children: React.ReactNode} & ConnectedProps; + +export const AiryWebSocketContext = React.createContext({refreshSocket: null}); + +const mapDispatchToProps = () => dispatch => ({ + onComponentUpdate: update => dispatch(updateComponentInstallationStatusAction(update)), +}); + +const connector = connect(null, mapDispatchToProps); + +const AiryWebSocket: React.FC = props => { + const {children, onComponentUpdate} = props; + const [webSocketClient, setWebSocketClient] = useState(null); + + const refreshSocket = () => { + if (webSocketClient) { + webSocketClient.destroyConnection(); + } + setWebSocketClient( + new WebSocketClient(env.API_HOST, { + onComponentUpdate, + }) + ); + }; + + useEffect(() => refreshSocket(), []); + + return {children}; +}; + +export default connector(AiryWebSocket); diff --git a/frontend/control-center/src/components/TopBar/index.tsx b/frontend/control-center/src/components/TopBar/index.tsx index 4cc977b31f..815e7b9e5e 100644 --- a/frontend/control-center/src/components/TopBar/index.tsx +++ b/frontend/control-center/src/components/TopBar/index.tsx @@ -14,7 +14,7 @@ import {ReactComponent as FlagFrance} from 'assets/images/icons/flagFrance.svg'; import {ReactComponent as FlagSpain} from 'assets/images/icons/flagSpain.svg'; import styles from './index.module.scss'; import {env} from '../../env'; -import {useAnimation} from 'render'; +import {useAnimation} from 'components'; import {useTranslation} from 'react-i18next'; import i18next from 'i18next'; import {Language, Source} from 'model'; diff --git a/frontend/control-center/src/pages/Catalog/CatalogCard/index.tsx b/frontend/control-center/src/pages/Catalog/CatalogCard/index.tsx index 682b1428f4..2709036984 100644 --- a/frontend/control-center/src/pages/Catalog/CatalogCard/index.tsx +++ b/frontend/control-center/src/pages/Catalog/CatalogCard/index.tsx @@ -7,12 +7,15 @@ import {installComponent} from '../../../actions/catalog'; import {ComponentInfo, ConnectorPrice, InstallationStatus, NotificationModel} from 'model'; import {Button, NotificationComponent, SettingsModal, Tooltip} from 'components'; import {getChannelAvatar} from '../../../components/ChannelAvatar'; -import {getCatalogProductRouteForComponent, getConnectedRouteForComponent} from '../../../services'; +import { + getCatalogProductRouteForComponent, + getConnectedRouteForComponent, + getNewChannelRouteForComponent, +} from '../../../services'; import {DescriptionComponent, getDescriptionSourceName} from '../../../components/Description'; import {ReactComponent as CheckmarkIcon} from 'assets/images/icons/checkmarkFilled.svg'; import styles from './index.module.scss'; import NotifyMeModal from '../NotifyMeModal'; -import {CONNECTORS_ROUTE} from '../../../routes/routes'; import {getMergedConnectors} from '../../../selectors'; import {InstallerLoader} from 'components/loaders/InstallerLoader'; @@ -67,7 +70,7 @@ const CatalogCard = (props: CatalogCardProps) => { const componentCard = useRef(null); const {t} = useTranslation(); const navigate = useNavigate(); - const navigateConfigure = `${CONNECTORS_ROUTE}/${componentInfo?.source}/configure`; + const navigateConfigure = getNewChannelRouteForComponent(componentInfo?.source); //Commented until backend is ready for this!!! // const notifiedEmail = t('infoNotifyMe') + ` ${notified}`; @@ -206,7 +209,7 @@ const CatalogCard = (props: CatalogCardProps) => { installing={installStatus === InstallationStatus.pending || isPending} borderRadius={10} marginRight={28} - marginBottom={36} + marginBottom={28} >
diff --git a/frontend/control-center/src/pages/Catalog/CatalogItemDetails/index.module.scss b/frontend/control-center/src/pages/Catalog/CatalogItemDetails/index.module.scss index 17e375dc4e..1d6488eeea 100644 --- a/frontend/control-center/src/pages/Catalog/CatalogItemDetails/index.module.scss +++ b/frontend/control-center/src/pages/Catalog/CatalogItemDetails/index.module.scss @@ -23,6 +23,8 @@ } .componentDescription { + height: 100%; + overflow-y: scroll; padding-left: 30px; padding-bottom: 30px; diff --git a/frontend/control-center/src/pages/Catalog/CatalogItemDetails/index.tsx b/frontend/control-center/src/pages/Catalog/CatalogItemDetails/index.tsx index b2489902c6..0de55b40f3 100644 --- a/frontend/control-center/src/pages/Catalog/CatalogItemDetails/index.tsx +++ b/frontend/control-center/src/pages/Catalog/CatalogItemDetails/index.tsx @@ -4,7 +4,7 @@ import {useTranslation} from 'react-i18next'; import {connect, ConnectedProps} from 'react-redux'; import {installComponent, listComponents, uninstallComponent} from '../../../actions/catalog'; import {StateModel} from '../../../reducers'; -import {ComponentInfo, ConnectorPrice, InstallationStatus, Modal, ModalType, NotificationModel} from 'model'; +import {ComponentInfo, ConnectorPrice, InstallationStatus, Language, Modal, ModalType, NotificationModel} from 'model'; import {ContentWrapper, Button, LinkButton, SettingsModal, NotificationComponent, SmartButton} from 'components'; import {getChannelAvatar} from '../../../components/ChannelAvatar'; import {availabilityFormatted} from '../CatalogCard'; @@ -39,7 +39,7 @@ const CatalogItemDetails = (props: ConnectedProps) => { const location = useLocation(); const locationState = location.state as LocationState; const {componentInfo} = locationState; - + const currentLanguage = localStorage.getItem('language'); const [isModalVisible, setIsModalVisible] = useState(false); const [isNotifyMeModalVisible, setIsNotifyMeModalVisible] = useState(false); const [modal, setModal] = useState(null); @@ -157,13 +157,26 @@ const CatalogItemDetails = (props: ConnectedProps) => { useEffect(() => { if (bodyContentDescription && bodyContentDescription?.current) { - parseContent(componentInfo.description); + switch (currentLanguage) { + case Language.english: + parseContent(componentInfo.description); + break; + case Language.german: + parseContent(componentInfo.descriptionDE); + break; + case Language.french: + parseContent(componentInfo.descriptionFR); + break; + case Language.spanish: + parseContent(componentInfo.descriptionES); + break; + } } }, [bodyContentDescription]); return (
-

{t('Description')}

+

{t('description')}

); }; diff --git a/frontend/control-center/src/pages/Catalog/CatalogSearchBar/CatalogSearchBar.tsx b/frontend/control-center/src/pages/Catalog/CatalogSearchBar/CatalogSearchBar.tsx index 4f1da8b8ea..f5a066fdf2 100644 --- a/frontend/control-center/src/pages/Catalog/CatalogSearchBar/CatalogSearchBar.tsx +++ b/frontend/control-center/src/pages/Catalog/CatalogSearchBar/CatalogSearchBar.tsx @@ -5,7 +5,7 @@ import styles from './CatalogSearchBar.module.scss'; import {ListenOutsideClick, SearchField} from 'components'; import {useTranslation} from 'react-i18next'; import {FilterCatalogModal, FilterTypes} from './FilterCatalogModal/FilterCatalogModal'; -import {useAnimation} from 'render'; +import {useAnimation} from 'components'; type CatalogSearchBarProps = { currentFilter: FilterTypes; diff --git a/frontend/control-center/src/pages/Catalog/index.module.scss b/frontend/control-center/src/pages/Catalog/index.module.scss index 1ac01061c5..6e7fea4af0 100644 --- a/frontend/control-center/src/pages/Catalog/index.module.scss +++ b/frontend/control-center/src/pages/Catalog/index.module.scss @@ -1,13 +1,13 @@ @import 'assets/scss/fonts.scss'; @import 'assets/scss/colors.scss'; +@import 'assets/scss/animations.scss'; .catalogWrapper { background: var(--color-background-white); border-top-right-radius: 10px; border-top-left-radius: 10px; - padding: 32px; - margin: 88px 1.5em 0 191px; - height: calc(100vh - 88px); + padding: 28px; + height: calc(100%); overflow-y: scroll; overflow-x: hidden; width: 100%; @@ -16,18 +16,25 @@ .headlineSearchBarContainer { display: flex; justify-content: space-between; - align-items: center; + flex-direction: column; + height: 100%; + padding: 28px 18px; } .catalogListContainer { display: flex; flex-wrap: wrap; + margin-bottom: 28px; } .catalogHeadlineText { @include font-xl; font-weight: 900; - margin-bottom: 15px; + color: var(--color-text-contrast); +} + +.catalogDescription { + @include font-base; color: var(--color-text-contrast); } diff --git a/frontend/control-center/src/pages/Catalog/index.tsx b/frontend/control-center/src/pages/Catalog/index.tsx index 302f64008a..3d475f31e0 100644 --- a/frontend/control-center/src/pages/Catalog/index.tsx +++ b/frontend/control-center/src/pages/Catalog/index.tsx @@ -7,11 +7,11 @@ import {ComponentInfo, ConnectorPrice, InstallationStatus, NotificationModel} fr import CatalogCard, {ObservationInstallStatus} from './CatalogCard'; import styles from './index.module.scss'; import {listComponents} from '../../actions'; -import {CatalogSearchBar} from './CatalogSearchBar/CatalogSearchBar'; import {FilterTypes} from './CatalogSearchBar/FilterCatalogModal/FilterCatalogModal'; import {AiryLoader} from 'components/loaders/AiryLoader'; import {getMergedConnectors} from '../../selectors'; -import {NotificationComponent} from 'components'; +import {ContentWrapper, NotificationComponent} from 'components'; +import {FilterBar} from 'components/general/FilterBar'; const mapStateToProps = (state: StateModel) => { return { @@ -145,43 +145,72 @@ const Catalog = (props: ConnectedProps) => { } }, [catalogList, currentFilter, query]); - return ( - <> -
-
+ const HeaderContent = () => { + return ( +
+

{catalogPageTitle}

- {catalogList.length > 0 && ( - - )} +

{t('catalogDescription')}

- {catalogList.length > 0 ? ( -
- {orderedCatalogList && orderedCatalogList.length > 0 ? ( - orderedCatalogList.map((infoItem: ComponentInfo) => { - if (infoItem?.name && infoItem?.displayName) { - return ( - - ); - } - }) - ) : ( -
-

{t('nothingFound')}

- {t('noMatchingCatalogs')} -
- )} -
- ) : ( - - )}
+ ); + }; + + const handleQuery = (query: string) => { + setQuery(query); + }; + + return ( + <> + } + transparent + isSideColumn={false} + content={ + <> + +
+ {catalogList.length > 0 ? ( +
+ {orderedCatalogList && orderedCatalogList.length > 0 ? ( + orderedCatalogList.map((infoItem: ComponentInfo) => { + if (infoItem?.name && infoItem?.displayName) { + return ( + + ); + } + }) + ) : ( +
+

{t('nothingFound')}

+ {t('noMatchingCatalogs')} +
+ )} +
+ ) : ( + + )} +
+ + } + /> + {notification?.show && ( { tooltipContent={t('enabled')} direction="right" /> - ) : isNotHealthy ? ( + ) : isDisabled || needsConfig ? ( } + hoverElement={} hoverElementHeight={20} hoverElementWidth={20} - tooltipContent={t('notHealthy')} + tooltipContent={t('disabled')} direction="right" /> ) : ( - isDisabled && ( + isNotHealthy && ( } + hoverElement={} hoverElementHeight={20} hoverElementWidth={20} - tooltipContent={t('disabled')} + tooltipContent={t('notHealthy')} direction="right" /> ) diff --git a/frontend/control-center/src/reducers/data/catalog/index.ts b/frontend/control-center/src/reducers/data/catalog/index.ts index 65d0cd712c..a0005b6c0c 100644 --- a/frontend/control-center/src/reducers/data/catalog/index.ts +++ b/frontend/control-center/src/reducers/data/catalog/index.ts @@ -35,6 +35,15 @@ export default function connectorsReducer(state = defaultState, action: Action): }, }; } + case getType(actions.updateComponentInstallationStatusAction): { + return { + ...state, + [action.payload.identifier]: { + ...state[action.payload.identifier], + installationStatus: action.payload.metadata.installationStatus, + }, + }; + } default: return state; } diff --git a/frontend/inbox/src/components/TopBar/index.tsx b/frontend/inbox/src/components/TopBar/index.tsx index 50d34cfd4e..8d07ff3588 100644 --- a/frontend/inbox/src/components/TopBar/index.tsx +++ b/frontend/inbox/src/components/TopBar/index.tsx @@ -16,7 +16,7 @@ import {ReactComponent as FlagSpain} from 'assets/images/icons/flagSpain.svg'; import {ReactComponent as AiryLogo} from 'assets/images/logo/airyLogo.svg'; import styles from './index.module.scss'; import {env} from '../../env'; -import {useAnimation} from 'render'; +import {useAnimation} from 'components'; import {useTranslation} from 'react-i18next'; import {Language} from 'model/Config'; import i18next from 'i18next'; diff --git a/frontend/inbox/src/pages/Contacts/ContactInformation/index.tsx b/frontend/inbox/src/pages/Contacts/ContactInformation/index.tsx index 47192d2d94..160adbdb48 100644 --- a/frontend/inbox/src/pages/Contacts/ContactInformation/index.tsx +++ b/frontend/inbox/src/pages/Contacts/ContactInformation/index.tsx @@ -4,7 +4,7 @@ import {useTranslation} from 'react-i18next'; import {connect, ConnectedProps} from 'react-redux'; import styles from './index.module.scss'; import ContactDetails from '../../../components/ContactDetails'; -import {useAnimation} from 'render'; +import {useAnimation} from 'components'; import {Contact} from 'model'; import {Avatar} from 'components/message/Avatar'; import {Input} from 'components/inputs/Input'; diff --git a/frontend/inbox/src/pages/Inbox/ConversationListHeader/index.tsx b/frontend/inbox/src/pages/Inbox/ConversationListHeader/index.tsx index b971bb98da..e9924b842c 100644 --- a/frontend/inbox/src/pages/Inbox/ConversationListHeader/index.tsx +++ b/frontend/inbox/src/pages/Inbox/ConversationListHeader/index.tsx @@ -16,7 +16,7 @@ import {cySearchButton, cySearchField, cySearchFieldBackButton} from 'handles'; import Popup from '../QuickFilter/Popup'; import {formatConversationCount} from '../../../services/format/numbers'; import {useTranslation} from 'react-i18next'; -import {useAnimation} from 'render'; +import {useAnimation} from 'components'; const mapDispatchToProps = { setSearch, diff --git a/frontend/inbox/src/pages/Inbox/MessageInput/index.module.scss b/frontend/inbox/src/pages/Inbox/MessageInput/index.module.scss index e52ef61059..2d1fdfbbe5 100644 --- a/frontend/inbox/src/pages/Inbox/MessageInput/index.module.scss +++ b/frontend/inbox/src/pages/Inbox/MessageInput/index.module.scss @@ -158,7 +158,7 @@ p { @include font-s; - color: var(--color-text-contrast); + color: var(--color-tooltip-text-gray); } } diff --git a/frontend/inbox/src/pages/Inbox/Messenger/ConversationMetadata/index.tsx b/frontend/inbox/src/pages/Inbox/Messenger/ConversationMetadata/index.tsx index 9f5da41801..aff4981f09 100644 --- a/frontend/inbox/src/pages/Inbox/Messenger/ConversationMetadata/index.tsx +++ b/frontend/inbox/src/pages/Inbox/Messenger/ConversationMetadata/index.tsx @@ -8,7 +8,7 @@ import {Avatar} from 'components'; import ColorSelector from '../../../../components/ColorSelector'; import Dialog from '../../../../components/Dialog'; import {StateModel, isComponentHealthy} from '../../../../reducers'; -import {useAnimation} from 'render'; +import {useAnimation} from 'components'; import ContactDetails from '../../../../components/ContactDetails'; import styles from './index.module.scss'; diff --git a/infrastructure/helm-chart/charts/ingress-controller/templates/letsencrypt.yaml b/infrastructure/helm-chart/charts/ingress-controller/templates/letsencrypt.yaml index fe5fa64308..3a435b01cc 100644 --- a/infrastructure/helm-chart/charts/ingress-controller/templates/letsencrypt.yaml +++ b/infrastructure/helm-chart/charts/ingress-controller/templates/letsencrypt.yaml @@ -34,7 +34,7 @@ metadata: annotations: "helm.sh/hook": "post-install,post-upgrade" "helm.sh/hook-weight": "11" - "helm.sh/hook-delete-policy": "hook-succeeded" + "helm.sh/hook-delete-policy": "hook-succeeded,hook-failed" labels: core.airy.co/managed: "true" spec: diff --git a/infrastructure/helm-chart/templates/components/api-components-installer/deployment.yaml b/infrastructure/helm-chart/templates/components/api-components-installer/deployment.yaml index 0550a1f9d7..fd865b890b 100644 --- a/infrastructure/helm-chart/templates/components/api-components-installer/deployment.yaml +++ b/infrastructure/helm-chart/templates/components/api-components-installer/deployment.yaml @@ -23,66 +23,83 @@ spec: app: {{ .Values.components.api.components.installer.name }} spec: containers: - - name: app - image: "{{ .Values.global.containerRegistry}}/{{ .Values.components.api.components.installer.image }}:{{ default .Chart.Version }}" - imagePullPolicy: Always - args: - - "-Djdk.tls.client.protocols=TLSv1.2" - - "-jar" - - "app_springboot.jar" - - "-XshowSettings:vm" - - "-XX:MaxRAMPercentage=70" - - "-XX:-UseCompressedOops" - - "-Dsun.net.inetaddr.ttl=0" - envFrom: - - configMapRef: - name: security - - configMapRef: - name: kafka-config - env: - - name: SERVICE_NAME - value: {{ .Values.components.api.components.installer.name }} - - name: KUBERNETES_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace - - name: CATALOG_URI - value: "https://github.com/airyhq/catalog.git" - - name: CATALOG_DIRECTORY - value: "/repo" - - name: POD_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace - - name: REQUESTED_CPU - valueFrom: - resourceFieldRef: - containerName: app - resource: requests.cpu - - name: LIMIT_CPU - valueFrom: - resourceFieldRef: - containerName: app - resource: limits.cpu - - name: LIMIT_MEMORY - valueFrom: - resourceFieldRef: - containerName: app - resource: limits.memory - livenessProbe: - httpGet: - path: /actuator/health - port: 8080 - httpHeaders: - - name: Health-Check - value: health-check - initialDelaySeconds: 60 - periodSeconds: 10 - failureThreshold: 3 + - name: app + image: "{{ .Values.global.containerRegistry}}/{{ .Values.components.api.components.installer.image }}:{{ default .Chart.Version }}" + imagePullPolicy: Always + args: + - "-Djdk.tls.client.protocols=TLSv1.2" + - "-jar" + - "app_springboot.jar" + - "-XshowSettings:vm" + - "-XX:MaxRAMPercentage=70" + - "-XX:-UseCompressedOops" + - "-Dsun.net.inetaddr.ttl=0" + envFrom: + - configMapRef: + name: security + - configMapRef: + name: kafka-config + env: + - name: SERVICE_NAME + value: {{ .Values.components.api.components.installer.name }} + - name: KUBERNETES_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + - name: CATALOG_URI + value: "https://github.com/airyhq/catalog.git" + - name: CATALOG_DIRECTORY + value: "/repo" + - name: POD_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + - name: REQUESTED_CPU + valueFrom: + resourceFieldRef: + containerName: app + resource: requests.cpu + - name: LIMIT_CPU + valueFrom: + resourceFieldRef: + containerName: app + resource: limits.cpu + - name: LIMIT_MEMORY + valueFrom: + resourceFieldRef: + containerName: app + resource: limits.memory + livenessProbe: + httpGet: + path: /actuator/health + port: 8080 + httpHeaders: + - name: Health-Check + value: health-check + initialDelaySeconds: 60 + periodSeconds: 10 + failureThreshold: 3 + resources: +{{ toYaml .Values.components.api.components.installer.resources | indent 10 }} + initContainers: + - name: wait + image: "{{ .Values.global.busyboxImage }}" + imagePullPolicy: IfNotPresent + command: ["/bin/sh", "/opt/provisioning/wait-for-minimum-kafkas.sh"] + envFrom: + - configMapRef: + name: kafka-config + volumeMounts: + - name: provisioning-scripts + mountPath: /opt/provisioning serviceAccountName: {{ .Values.serviceAccount }} volumes: - name: {{ .Values.components.api.components.installer.name }} configMap: name: {{ .Values.components.api.components.installer.name }} + - name: provisioning-scripts + configMap: + name: provisioning-scripts + diff --git a/infrastructure/terraform/modules/core/main.tf b/infrastructure/terraform/modules/core/main.tf index d2c9a4e734..a2eaef529b 100644 --- a/infrastructure/terraform/modules/core/main.tf +++ b/infrastructure/terraform/modules/core/main.tf @@ -3,7 +3,7 @@ data "http" "core_version" { } locals { - core_version = var.core_version != "" ? var.core_version : trimspace(data.http.core_version.body) + core_version = var.core_version != "" ? var.core_version : trimspace(data.http.core_version.response_body) } resource "helm_release" "airy_core" { diff --git a/infrastructure/terraform/modules/gcp-gke/main.tf b/infrastructure/terraform/modules/gcp-gke/main.tf index fee8392fb3..a9c0e579c4 100644 --- a/infrastructure/terraform/modules/gcp-gke/main.tf +++ b/infrastructure/terraform/modules/gcp-gke/main.tf @@ -24,7 +24,7 @@ resource "google_container_node_pool" "gke_core_nodes" { node_locations = var.gke_node_locations node_config { - preemptible = true + preemptible = false machine_type = var.gke_instance_type oauth_scopes = [ diff --git a/lib/java/sources-parser/BUILD b/lib/java/sources-parser/BUILD new file mode 100644 index 0000000000..ebe9aab956 --- /dev/null +++ b/lib/java/sources-parser/BUILD @@ -0,0 +1,13 @@ +load("@com_github_airyhq_bazel_tools//lint:buildifier.bzl", "check_pkg") +load("//tools/build:java_library.bzl", "custom_java_library") + +custom_java_library( + name = "sources-parser", + srcs = glob(["src/main/java/co/airy/sources-parser/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//:jackson", + ], +) + +check_pkg(name = "buildifier") diff --git a/lib/java/sources-parser/src/main/java/co/airy/sources-parser/SourcesParser.java b/lib/java/sources-parser/src/main/java/co/airy/sources-parser/SourcesParser.java new file mode 100644 index 0000000000..a9fab20510 --- /dev/null +++ b/lib/java/sources-parser/src/main/java/co/airy/sources-parser/SourcesParser.java @@ -0,0 +1,60 @@ +package co.airy.sources_parser; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.core.JsonProcessingException; + + +public class SourcesParser { + private static final ObjectMapper sourceMapper = new ObjectMapper(); + + public static String mapContent(String source, String text, JsonNode data) throws JsonProcessingException { + + final ObjectNode node = getNode(); + switch (source) { + case "google": { + final ObjectNode representative = getNode(); + representative.put("representativeType", "BOT"); + node.set("representative", representative); + node.put("text", text); + return sourceMapper.writeValueAsString(node); + } + case "viber": { + node.put("text", text); + node.put("type", text); + return sourceMapper.writeValueAsString(node); + } + case "chatplugin": + case "instagram": + case "facebook": { + node.put("text", text); + if(data != null && !data.isEmpty()){ + node.put("message", data); + } + return sourceMapper.writeValueAsString(node); + } + case "twilio.sms": + case "twilio.whatsapp": { + node.put("Body", text); + return sourceMapper.writeValueAsString(node); + } + case "whatsapp": { + node.put("Body", text); + return sourceMapper.writeValueAsString(node); + } + + default: { + return null; + } + } + }; + + + private static ObjectNode getNode() { + final JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance; + return jsonNodeFactory.objectNode(); + } + +} diff --git a/lib/typescript/assets/scss/colors.scss b/lib/typescript/assets/scss/colors.scss index 8489f3dc59..eda4dd37d3 100644 --- a/lib/typescript/assets/scss/colors.scss +++ b/lib/typescript/assets/scss/colors.scss @@ -24,6 +24,7 @@ --color-template-border-gray: #ccc; --color-disable-status-config: #737373; --color-dark-elements-gray: #98a4ab; + --color-tooltip-text-gray: #212428; --color-icons-gray: #a0abb2; --color-light-gray: #cad5db; --color-greyish-white: #e8eaea; @@ -68,7 +69,7 @@ html[data-theme='dark'] { --color-background-white: #272822; --color-channel-icon-circle: #272822; --color-template-highlight: #272822; - --color-tooltip-gray: #1f201c; + --color-tooltip-gray: #e8eaea; --color-background-blue: #243037; --color-template-gray: #484848; --color-shadow-gray: #4d5153; diff --git a/lib/typescript/components/general/FilterBar/index.module.scss b/lib/typescript/components/general/FilterBar/index.module.scss new file mode 100644 index 0000000000..0d15571807 --- /dev/null +++ b/lib/typescript/components/general/FilterBar/index.module.scss @@ -0,0 +1,77 @@ +@import 'assets/scss/fonts.scss'; +@import 'assets/scss/colors.scss'; +@import 'assets/scss/animations.scss'; + +.itemSearchContainer { + display: flex; + flex-direction: row; + width: 100%; + height: 60px; + align-items: center; + justify-content: space-between; + background: var(--color-background-white); + border-top-left-radius: 10px; + border-top-right-radius: 10px; +} + +.itemsContainer { + display: flex; + flex-direction: row; +} + +.item { + white-space: nowrap; + display: flex; + justify-content: center; + align-items: center; + height: 30px; + width: 200px; + color: var(--color-text-contrast); + &:hover { + cursor: pointer; + } +} + +.itemActive { + position: relative; + top: 9.5px; + z-index: 10; + background-color: var(--color-airy-blue); + height: 5px; + width: 200px; +} + +.line { + height: 1px; + width: 100%; + background: var(--color-dark-elements-gray); + margin-top: -3.5px; +} + +.searchIcon { + margin-right: 24px; + &:hover { + cursor: pointer; + } +} + +.searchField { + width: 300px; + background: var(--color-background-white); + border: 1px solid var(--color-airy-blue); + border-radius: 4px; + height: 32px; + margin-right: 24px; + + svg { + color: black; + } +} + +.animateIn { + animation: searchfieldAnimIn 300ms ease; +} + +.animateOut { + animation: searchfieldAnimOut 300ms forwards; +} diff --git a/lib/typescript/components/general/FilterBar/index.tsx b/lib/typescript/components/general/FilterBar/index.tsx new file mode 100644 index 0000000000..020e4e22ed --- /dev/null +++ b/lib/typescript/components/general/FilterBar/index.tsx @@ -0,0 +1,103 @@ +import React, {Dispatch, SetStateAction, useCallback, useState} from 'react'; +import styles from './index.module.scss'; +import {ReactComponent as SearchIcon} from 'assets/images/icons/search.svg'; +import {ListenOutsideClick} from '../ListenOutsideClick'; +import {SearchField} from 'components/inputs'; +import {useTranslation} from 'react-i18next'; +import {Tooltip} from 'components/tooltip'; +import {useAnimation} from 'components/services/useAnimation'; + +export enum FilterTypes { + all = 'all', + installed = 'installed', + comingSoon = 'comingSoon', + notInstalled = 'notInstalled', +} + +type FilterBarProps = { + currentFilter: FilterTypes; + setQuery?: (query: string) => void; + items: { + name: string; + filter: FilterTypes; + setFilter: Dispatch>; + }[]; +}; + +export const FilterBar = (props: FilterBarProps) => { + const {items, currentFilter} = props; + const {t} = useTranslation(); + const [showSearchField, setShowingSearchField] = useState(false); + const [animationActionSearchfield, setAnimationActionSearchfield] = useState(false); + const [query, setQuery] = useState(''); + + const showSearchFieldToggle = useCallback(() => { + useAnimation(showSearchField, setShowingSearchField, setAnimationActionSearchfield, 300); + setQuery(''); + props.setQuery(''); + }, [showSearchField, setShowingSearchField]); + + const Items = () => { + return ( +
+ {items.map(item => ( + { + item.setFilter(item.filter); + localStorage.setItem('catalogCurrentTypeFilter', item.filter); + }} + > +
{item.name}
+
+
+ } + hoverElementWidth={200} + hoverElementHeight={30} + tooltipContent={t(`${item.filter}CatalogFilter`)} + direction={item.filter === FilterTypes.all ? 'topRight' : 'top'} + /> + ))} +
+ ); + }; + + return ( +
+
+ + {showSearchField ? ( + + { + setQuery(value); + props.setQuery(value); + }} + /> + + ) : ( + + } + hoverElementWidth={20} + hoverElementHeight={20} + tooltipContent={t('searchComponent')} + direction={'topLeft'} + /> + )} +
+
+
+ ); +}; diff --git a/lib/typescript/components/general/index.ts b/lib/typescript/components/general/index.ts index c36853899a..a65341283e 100644 --- a/lib/typescript/components/general/index.ts +++ b/lib/typescript/components/general/index.ts @@ -5,3 +5,4 @@ export * from './AudioClip'; export * from './ConnectorAvatar'; export * from './Pagination'; export * from './TabsPanel'; +export * from './FilterBar'; diff --git a/lib/typescript/components/index.ts b/lib/typescript/components/index.ts index 6d029e4ed6..145fbccbe1 100644 --- a/lib/typescript/components/index.ts +++ b/lib/typescript/components/index.ts @@ -6,3 +6,4 @@ export * from './loaders'; export * from './message'; export * from './tooltip'; export * from './wrapper/ContentWrapper'; +export * from './services/useAnimation'; diff --git a/lib/typescript/components/inputs/Input/style.module.scss b/lib/typescript/components/inputs/Input/style.module.scss index 9fcfb2f915..65177e054f 100644 --- a/lib/typescript/components/inputs/Input/style.module.scss +++ b/lib/typescript/components/inputs/Input/style.module.scss @@ -228,7 +228,7 @@ border-radius: 4px; margin-left: 4px; padding: 4px 6px; - color: var(--color-text-contrast); + color: var(--color-tooltip-text-gray); border-color: transparent var(--color-tooltip-gray) transparent transparent; } diff --git a/lib/typescript/render/services/useAnimation.ts b/lib/typescript/components/services/useAnimation.ts similarity index 100% rename from lib/typescript/render/services/useAnimation.ts rename to lib/typescript/components/services/useAnimation.ts diff --git a/lib/typescript/components/tooltip/index.module.scss b/lib/typescript/components/tooltip/index.module.scss index 456d501db3..d390a3f19e 100644 --- a/lib/typescript/components/tooltip/index.module.scss +++ b/lib/typescript/components/tooltip/index.module.scss @@ -59,13 +59,15 @@ .tooltipContent { @include font-s-bold; - display: none; + display: flex; background: var(--color-tooltip-gray); border-radius: 4px; padding: 4px 6px; - color: var(--color-text-contrast); + color: var(--color-tooltip-text-gray); position: relative; z-index: 4; + opacity: 0; + visibility: hidden; } .hoverElement:hover ~ .tooltipContent.top { @@ -74,6 +76,9 @@ line-height: 14px; white-space: nowrap; margin-bottom: 12px; + opacity: 1; + transition: opacity 150ms ease-in 150ms; + visibility: visible; } .hoverElement:hover ~ .tooltipContent.right { @@ -81,6 +86,9 @@ line-height: 14px; white-space: nowrap; margin-left: 12px; + opacity: 1; + transition: opacity 150ms ease-in 150ms; + visibility: visible; } .hoverElement:hover ~ .tooltipContent.bottom { @@ -88,6 +96,9 @@ line-height: 14px; white-space: nowrap; margin-top: 12px; + opacity: 1; + transition: opacity 150ms ease-in 150ms; + visibility: visible; } .hoverElement:hover ~ .tooltipContent.left { @@ -95,34 +106,49 @@ line-height: 14px; white-space: nowrap; margin-right: 12px; + opacity: 1; + transition: opacity 150ms ease-in 150ms; + visibility: visible; } .hoverElement:hover ~ .tooltipContent.topLeft { display: flex; line-height: 14px; white-space: nowrap; - bottom: 32px; + bottom: 38px; + opacity: 1; + transition: opacity 150ms ease-in 150ms; + visibility: visible; } .hoverElement:hover ~ .tooltipContent.topRight { display: flex; line-height: 14px; white-space: nowrap; - bottom: 32px; + bottom: 38px; + opacity: 1; + transition: opacity 150ms ease-in 150ms; + visibility: visible; } .hoverElement:hover ~ .tooltipContent.bottomLeft { display: flex; line-height: 14px; white-space: nowrap; - top: 32px; + top: 38px; + opacity: 1; + transition: opacity 150ms ease-in 150ms; + visibility: visible; } .hoverElement:hover ~ .tooltipContent.bottomRight { display: flex; line-height: 14px; white-space: nowrap; - top: 32px; + top: 38px; + opacity: 1; + transition: opacity 150ms ease-in 200ms; + visibility: visible; } .hoverElement ~ .tooltipContent.top::after { diff --git a/lib/typescript/components/tooltip/index.tsx b/lib/typescript/components/tooltip/index.tsx index 66f2d1f98f..351f179a4d 100644 --- a/lib/typescript/components/tooltip/index.tsx +++ b/lib/typescript/components/tooltip/index.tsx @@ -14,6 +14,7 @@ type TooltipProps = { bottom?: number; left?: number; navigateTo?: string; + delay?: number; //in ms }; export const Tooltip = (props: TooltipProps) => { @@ -29,6 +30,7 @@ export const Tooltip = (props: TooltipProps) => { right, bottom, left, + delay, } = props; const navigate = useNavigate(); const leftDirection = direction === 'bottomLeft' || direction === 'topLeft'; @@ -55,15 +57,67 @@ export const Tooltip = (props: TooltipProps) => { {tooltipContent} diff --git a/lib/typescript/components/wrapper/ContentWrapper/index.module.scss b/lib/typescript/components/wrapper/ContentWrapper/index.module.scss index f87d1f30a3..a42fd7d258 100644 --- a/lib/typescript/components/wrapper/ContentWrapper/index.module.scss +++ b/lib/typescript/components/wrapper/ContentWrapper/index.module.scss @@ -2,9 +2,7 @@ display: flex; flex-direction: column; margin: 88px 16px 0 191px; - overflow: hidden; width: 100%; - height: 100vh; } .transparentHeader { @@ -12,7 +10,6 @@ color: var(--color-text-contrast); border-top-left-radius: 10px; border-top-right-radius: 10px; - overflow: hidden; width: 100%; margin-bottom: 48px; } @@ -39,8 +36,7 @@ color: var(--color-text-contrast); border-top-left-radius: 10px; border-top-right-radius: 10px; - height: 100%; - overflow: scroll; + height: calc(100% - 170px); } .leftOffset { @@ -53,8 +49,5 @@ border-top-left-radius: 10px; border-top-right-radius: 10px; margin: 88px 16px 0 191px; - height: calc(100vh - 88px); - overflow-y: scroll; - overflow-x: hidden; width: 100%; } diff --git a/lib/typescript/components/wrapper/ContentWrapper/index.tsx b/lib/typescript/components/wrapper/ContentWrapper/index.tsx index 4905b6a6be..f7b99a1e78 100644 --- a/lib/typescript/components/wrapper/ContentWrapper/index.tsx +++ b/lib/typescript/components/wrapper/ContentWrapper/index.tsx @@ -5,13 +5,14 @@ type ContentWrapperProps = { transparent: boolean; content: React.ReactNode; header?: React.ReactNode; + bar?: React.ReactNode; variantHeight?: 'medium' | 'big' | 'large'; isSideColumn?: boolean; sideColumnContent?: React.ReactNode; }; export const ContentWrapper = (props: ContentWrapperProps) => { - const {transparent, content, header, variantHeight, isSideColumn, sideColumnContent} = props; + const {transparent, content, header, bar, variantHeight, isSideColumn, sideColumnContent} = props; return ( <> @@ -23,11 +24,14 @@ export const ContentWrapper = (props: ContentWrapperProps) => { ? styles.headerMedium : variantHeight === 'big' ? styles.headerBig - : styles.headerLarge + : variantHeight === 'large' + ? styles.headerBig + : {} }`} > {header}
+ {bar}
{sideColumnContent}
{content}
diff --git a/lib/typescript/model/Components.ts b/lib/typescript/model/Components.ts index 7ddd5a5975..c3508dd6d9 100644 --- a/lib/typescript/model/Components.ts +++ b/lib/typescript/model/Components.ts @@ -14,6 +14,9 @@ export interface ComponentInfo { name: string; availableFor: string; description: string; + descriptionDE: string; + descriptionFR: string; + descriptionES: string; category: string; price: string; docs: string; diff --git a/lib/typescript/render/services/index.ts b/lib/typescript/render/services/index.ts index 8da12f9e25..4069e04a96 100644 --- a/lib/typescript/render/services/index.ts +++ b/lib/typescript/render/services/index.ts @@ -1,3 +1,2 @@ export * from './mediaAttachments'; export * from './decodeURIComponentMessage'; -export * from './useAnimation'; diff --git a/lib/typescript/translations/translations.ts b/lib/typescript/translations/translations.ts index 5a21228a2c..ff3a542495 100644 --- a/lib/typescript/translations/translations.ts +++ b/lib/typescript/translations/translations.ts @@ -521,6 +521,14 @@ const resources = { searchByNamePlaceholder: 'Search by name', searchByType: 'Search by type', noMatchingCatalogs: 'We could not find a catalog matching your criterias.', + description: 'Description', + catalogDescription: + 'Airy supports all the components below to be Installed to your Airy instance in just one click!', + allCatalogFilter: 'View for all Airy components: Installed, Coming Soon, and already installed', + installedCatalogFilter: `View of 'Installed' components`, + notInstalledCatalogFilter: 'View of available components to be Installed', + comingSoonCatalogFilter: `View of 'In developement' components`, + searchComponent: 'Search a component by name', //NotFound notFound: `Oops! We couldn't find that here.`, @@ -1088,6 +1096,14 @@ const resources = { searchByNamePlaceholder: 'Suche nach Name', searchByType: 'Suche nach Typ', noMatchingCatalogs: 'Wir konnten keinen Catalog finden, der Ihren Kriterien entspricht.', + description: 'Beschreibung', + catalogDescription: + 'Airy unterstützt alle unten aufgeführten Komponenten, die mit nur einem Klick in Ihrer Airy-Instanz installiert werden können!', + allCatalogFilter: 'Ansicht für alle Airy-Komponenten: Installiert, Demnächst, und bereits installiert', + installedCatalogFilter: `Ansicht der 'installierten' Komponenten`, + notInstalledCatalogFilter: 'Ansicht der verfügbaren zu installierenden Komponenten', + comingSoonCatalogFilter: `Ansicht der 'In Entwicklung befindlichen' Komponenten`, + searchComponent: 'Eine Komponente nach Namen suchen', //NotFound notFound: 'Huch! Das konnten wir hier nicht finden.', @@ -1646,6 +1662,14 @@ const resources = { searchByNamePlaceholder: 'Recherche par nom', searchByType: 'Recherche par type', noMatchingCatalogs: 'Aucun résultat pour ces critères de recherche.', + description: 'Description', + catalogDescription: + 'Airy supporte tous les composants ci-dessous qui peuvent être installés sur votre instance Airy en un seul clic!', + allCatalogFilter: `Vue pour tous les composants d'Airy : Installés, à venir, et déjà installés`, + installedCatalogFilter: `Vue des composants 'installés'`, + notInstalledCatalogFilter: 'Vue des composants disponibles pour être installés', + comingSoonCatalogFilter: `Vue des composants 'En développement'`, + searchComponent: 'Rechercher un composant par son nom', //NotFound notFound: 'Oups! Page non trouvée.', @@ -2208,6 +2232,14 @@ const resources = { searchByNamePlaceholder: 'Buscar por nombre', searchByType: 'Buscar por tipo', noMatchingCatalogs: 'No hemos podido encontrar un catálogo que coincida con sus criterios.', + description: 'Descripción', + catalogDescription: + 'Airy admite todos los componentes que se indican a continuación para ser instalados en su instancia de Airy en un solo clic!', + allCatalogFilter: `Vista de todos los componentes de Airy: Instalados, Próximamente y ya instalados`, + installedCatalogFilter: `Vista de los componentes 'Instalados'`, + notInstalledCatalogFilter: 'Vista de los componentes disponibles para instalar', + comingSoonCatalogFilter: `Vista de los componentes 'En desarrollo'`, + searchComponent: 'Buscar un componente por su nombre', //NotFound notFound: '¡Uy! No pudimos encontrarlo aquí.', diff --git a/lib/typescript/websocketclient/index.ts b/lib/typescript/websocketclient/index.ts index 1c326f4503..7f5888a7db 100644 --- a/lib/typescript/websocketclient/index.ts +++ b/lib/typescript/websocketclient/index.ts @@ -9,6 +9,7 @@ type CallbackMap = { onChannel?: (channel: Channel) => void; onTag?: (tag: Tag) => void; onError?: () => void; + onComponentUpdate?: (update: MetadataEvent) => void; }; // https: -> wss: and http: -> ws: @@ -43,6 +44,7 @@ export class WebSocketClient { onEvent = (body: string) => { const json = JSON.parse(body) as EventPayload; + switch (json.type) { case 'channel.updated': this.callbackMap.onChannel?.(camelcaseKeys(json.payload, {deep: true, stopPaths: ['metadata.user_data']})); @@ -65,6 +67,9 @@ export class WebSocketClient { case 'tag.updated': this.callbackMap.onTag?.(json.payload); break; + case 'component.updated': + this.callbackMap.onComponentUpdate?.(json.payload); + break; default: console.error('Unknown /events payload', json); } diff --git a/lib/typescript/websocketclient/payload.ts b/lib/typescript/websocketclient/payload.ts index 7037a5dd3d..7189a75e97 100644 --- a/lib/typescript/websocketclient/payload.ts +++ b/lib/typescript/websocketclient/payload.ts @@ -1,7 +1,13 @@ import {DeliveryState, Metadata, MetadataEvent, Source, Tag} from 'model'; interface Event { - type: 'message.created' | 'message.updated' | 'channel.updated' | 'metadata.updated' | 'tag.updated'; + type: + | 'message.created' + | 'message.updated' + | 'channel.updated' + | 'metadata.updated' + | 'tag.updated' + | 'component.updated'; } export interface MessageCreatedPayload extends Event { @@ -63,6 +69,15 @@ export interface MetadataUpdatedPayload extends Event { payload: MetadataEvent; } +interface ComponentUpdate { + installationStatus: 'installed' | 'uninstalled' | 'pending'; +} + +export interface ComponentUpdatedPayload extends Event { + type: 'component.updated'; + payload: MetadataEvent; +} + export interface TagUpdatedPayload extends Event { type: 'tag.updated'; payload: Tag; @@ -73,4 +88,5 @@ export type EventPayload = | MessageUpdatedPayload | ChannelUpdatedPayload | MetadataUpdatedPayload - | TagUpdatedPayload; + | TagUpdatedPayload + | ComponentUpdatedPayload;