diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e26f341c1f..91fba25c17 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -65,6 +65,7 @@ jobs: if: startsWith(github.ref, 'refs/heads/develop') run: | aws s3 cp bazel-bin/infrastructure/cli/airy_linux_bin s3://airy-core-binaries/develop/linux/amd64/airy + aws s3 cp bazel-bin/infrastructure/cli/airy_darwin_bin s3://airy-core-binaries/develop/darwin/amd64/airy env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} diff --git a/VERSION b/VERSION index 54d1a4f2a4..a803cc227f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.13.0 +0.14.0 diff --git a/WORKSPACE b/WORKSPACE index bae3fb82ed..571e558d0d 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -9,9 +9,9 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") # Airy Bazel tools git_repository( name = "com_github_airyhq_bazel_tools", - commit = "b34a57be44e3cdf739e074dde1c3db5b5347cc35", + commit = "3382e1968deebb90f33320b504f7adca3f92c08b", remote = "https://github.com/airyhq/bazel-tools.git", - shallow_since = "1615392891 +0100", + shallow_since = "1616430778 +0100", ) load("@com_github_airyhq_bazel_tools//:repositories.bzl", "airy_bazel_tools_dependencies", "airy_jvm_deps") @@ -30,6 +30,7 @@ maven_install( "com.fasterxml.jackson.core:jackson-core:2.10.0", "com.fasterxml.jackson.core:jackson-databind:2.10.0", "com.fasterxml.jackson.module:jackson-module-afterburner:2.10.0", + "com.github.everit-org.json-schema:org.everit.json.schema:1.12.2", "com.google.auth:google-auth-library-oauth2-http:0.20.0", "com.jayway.jsonpath:json-path:2.4.0", "com.twilio.sdk:twilio:7.51.0", @@ -137,7 +138,7 @@ go_embed_data_dependencies() go_register_toolchains( nogo = "@//:airy_nogo", version = "1.16", -) # my_nogo is in the top-level BUILD file of this workspace +) # airy_nogo is in the top-level BUILD file of this workspace load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies") diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/WebhooksController.java b/backend/api/admin/src/main/java/co/airy/core/api/admin/WebhooksController.java index d8d4c5cf31..0b1e201814 100644 --- a/backend/api/admin/src/main/java/co/airy/core/api/admin/WebhooksController.java +++ b/backend/api/admin/src/main/java/co/airy/core/api/admin/WebhooksController.java @@ -11,7 +11,6 @@ import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; -import java.util.Optional; import java.util.UUID; import java.util.concurrent.ExecutionException; @@ -25,12 +24,7 @@ public WebhooksController(Stores stores) { @PostMapping("/webhooks.subscribe") public ResponseEntity subscribe(@RequestBody @Valid WebhookSubscriptionPayload payload) { - final String apiSecret = Optional.ofNullable(stores.getWebhook()) - .map(Webhook::getApiSecret) - .orElse(UUID.randomUUID().toString()); - final Webhook webhook = Webhook.newBuilder() - .setApiSecret(apiSecret) .setId(UUID.randomUUID().toString()) .setEndpoint(payload.getUrl()) .setStatus(Status.Subscribed) @@ -79,7 +73,6 @@ public ResponseEntity webhookInfo() { private GetWebhookResponse fromWebhook(Webhook webhook) { return GetWebhookResponse.builder() .headers(webhook.getHeaders()) - .apiSecret(webhook.getApiSecret()) .status(webhook.getStatus().toString()) .url(webhook.getEndpoint()) .build(); diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/GetWebhookResponse.java b/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/GetWebhookResponse.java index 37241cb61c..8a5b21adb1 100644 --- a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/GetWebhookResponse.java +++ b/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/GetWebhookResponse.java @@ -10,6 +10,5 @@ public class GetWebhookResponse { private String status; private String url; - private String apiSecret; private Map headers; } diff --git a/backend/api/admin/src/test/java/co/airy/core/api/admin/WebhooksControllerTest.java b/backend/api/admin/src/test/java/co/airy/core/api/admin/WebhooksControllerTest.java index fbbdcaeb22..0f5f1a7052 100644 --- a/backend/api/admin/src/test/java/co/airy/core/api/admin/WebhooksControllerTest.java +++ b/backend/api/admin/src/test/java/co/airy/core/api/admin/WebhooksControllerTest.java @@ -84,24 +84,19 @@ public void canManageWebhook() throws Exception { webTestHelper.post("/webhooks.subscribe", payload, "user-id") .andExpect(status().isOk()) .andExpect(jsonPath("$.url", equalTo(url))) - .andExpect(jsonPath("$.headers['X-Auth']", equalTo(xAuthHeader))) - .andExpect(jsonPath("$.api_secret", is(not(nullValue())))); + .andExpect(jsonPath("$.headers['X-Auth']", equalTo(xAuthHeader))); retryOnException(() -> webTestHelper.post("/webhooks.info", "{}", "user-id") .andExpect(status().isOk()) .andExpect(jsonPath("$.url", equalTo(url))) - .andExpect(jsonPath("$.headers['X-Auth']", equalTo(xAuthHeader))) - .andExpect(jsonPath("$.api_secret", is(not(nullValue())))), + .andExpect(jsonPath("$.headers['X-Auth']", equalTo(xAuthHeader))), "Webhook was not stored" ); webTestHelper.post("/webhooks.unsubscribe", payload, "user-id") .andExpect(status().isOk()) .andExpect(jsonPath("$.url", equalTo(url))) - .andExpect(jsonPath("$.headers['X-Auth']", equalTo(xAuthHeader))) - .andExpect(jsonPath("$.api_secret", is(not(nullValue())))); - - //TODO add assertion? + .andExpect(jsonPath("$.headers['X-Auth']", equalTo(xAuthHeader))); } } diff --git a/backend/api/auth/src/main/java/co/airy/core/api/auth/config/JdbiConfiguration.java b/backend/api/auth/src/main/java/co/airy/core/api/auth/config/JdbiConfiguration.java index 1c3db3cb1a..08363d9649 100644 --- a/backend/api/auth/src/main/java/co/airy/core/api/auth/config/JdbiConfiguration.java +++ b/backend/api/auth/src/main/java/co/airy/core/api/auth/config/JdbiConfiguration.java @@ -1,6 +1,5 @@ package co.airy.core.api.auth.config; -import co.airy.core.api.auth.dao.InvitationDAO; import co.airy.core.api.auth.dao.UserDAO; import co.airy.log.AiryLoggerFactory; import org.jdbi.v3.core.Jdbi; @@ -44,14 +43,8 @@ public void logBeforeExecution(StatementContext context) { return jdbi; } - //TODO: BeanFactory to generate bindings for DAOs @Bean public UserDAO userDAO(Jdbi jdbi) { return jdbi.onDemand(UserDAO.class); } - - @Bean - public InvitationDAO invitationDAO(Jdbi jdbi) { - return jdbi.onDemand(InvitationDAO.class); - } } diff --git a/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/UsersController.java b/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/UsersController.java index 9c4634f78f..516aec9f61 100644 --- a/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/UsersController.java +++ b/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/UsersController.java @@ -1,17 +1,11 @@ package co.airy.core.api.auth.controllers; -import co.airy.core.api.auth.controllers.payload.AcceptInvitationRequestPayload; -import co.airy.core.api.auth.controllers.payload.AcceptInvitationResponsePayload; -import co.airy.core.api.auth.controllers.payload.InviteUserRequestPayload; -import co.airy.core.api.auth.controllers.payload.InviteUserResponsePayload; +import co.airy.core.api.auth.controllers.payload.ListResponsePayload; import co.airy.core.api.auth.controllers.payload.LoginRequestPayload; -import co.airy.core.api.auth.controllers.payload.LoginResponsePayload; import co.airy.core.api.auth.controllers.payload.PasswordResetRequestPayload; import co.airy.core.api.auth.controllers.payload.SignupRequestPayload; -import co.airy.core.api.auth.controllers.payload.SignupResponsePayload; -import co.airy.core.api.auth.dao.InvitationDAO; +import co.airy.core.api.auth.controllers.payload.UserPayload; import co.airy.core.api.auth.dao.UserDAO; -import co.airy.core.api.auth.dto.Invitation; import co.airy.core.api.auth.dto.User; import co.airy.core.api.auth.services.Mail; import co.airy.core.api.auth.services.Password; @@ -20,6 +14,7 @@ import co.airy.spring.web.payload.EmptyResponsePayload; import co.airy.spring.web.payload.RequestErrorResponsePayload; import org.jdbi.v3.core.statement.UnableToExecuteStatementException; +import org.postgresql.util.PSQLException; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -28,26 +23,26 @@ import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; -import java.time.Instant; +import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import static java.util.stream.Collectors.toList; + @RestController public class UsersController { public static final String RESET_PWD_FOR = "reset_pwd_for"; - private final InvitationDAO invitationDAO; private final UserDAO userDAO; private final Password passwordService; private final Jwt jwt; private final Mail mail; private final ExecutorService executor; - public UsersController(Password passwordService, UserDAO userDAO, InvitationDAO invitationDAO, Jwt jwt, Mail mail) { + public UsersController(Password passwordService, UserDAO userDAO, Jwt jwt, Mail mail) { this.passwordService = passwordService; this.userDAO = userDAO; - this.invitationDAO = invitationDAO; this.jwt = jwt; this.mail = mail; executor = Executors.newSingleThreadExecutor(); @@ -80,11 +75,11 @@ ResponseEntity signupUser(@RequestBody @Valid SignupRequestPayload signupRequ try { userDAO.insert(user); - } catch (UnableToExecuteStatementException e) { + } catch (UnableToExecuteStatementException e) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } - return ResponseEntity.ok(SignupResponsePayload.builder() + return ResponseEntity.ok(UserPayload.builder() .firstName(firstName) .lastName(lastName) .token(jwt.tokenFor(userId.toString())) @@ -94,7 +89,7 @@ ResponseEntity signupUser(@RequestBody @Valid SignupRequestPayload signupRequ } @PostMapping("/users.login") - ResponseEntity loginUser(@RequestBody @Valid LoginRequestPayload loginRequestPayload) { + ResponseEntity loginUser(@RequestBody @Valid LoginRequestPayload loginRequestPayload) { final String password = loginRequestPayload.getPassword(); final String email = loginRequestPayload.getEmail(); @@ -104,7 +99,7 @@ ResponseEntity loginUser(@RequestBody @Valid LoginRequestP return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } - return ResponseEntity.ok(LoginResponsePayload.builder() + return ResponseEntity.ok(UserPayload.builder() .firstName(user.getFirstName()) .lastName(user.getLastName()) .token(jwt.tokenFor(user.getId().toString())) @@ -161,50 +156,23 @@ private String getResetToken(String userId) { return jwt.tokenFor(userId, refreshClaim); } - //TODO: Write a custom ExceptionHandler for JDBI - @PostMapping("/users.invite") - ResponseEntity inviteUser(@RequestBody @Valid InviteUserRequestPayload inviteUserRequestPayload) { - final UUID id = UUID.randomUUID(); - final Instant now = Instant.now(); - - invitationDAO.insert(Invitation.builder() - .id(id) - .acceptedAt(null) - .createdAt(now) - .email(inviteUserRequestPayload.getEmail()) - .sentAt(null) - .updatedAt(now) - .createdBy(null) - .build()); - return ResponseEntity.status(HttpStatus.CREATED).body(InviteUserResponsePayload.builder() - .id(id) - .build()); - } - - @PostMapping("/users.accept-invitation") - ResponseEntity acceptInvitation(@RequestBody @Valid AcceptInvitationRequestPayload payload) { - final Invitation invitation = invitationDAO.findById(payload.getId()); + @PostMapping("/users.list") + ResponseEntity listUsers() { + final List users = userDAO.list(); - if (!invitationDAO.accept(invitation.getId(), Instant.now())) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new EmptyResponsePayload()); - } - - final User user = User.builder() - .id(UUID.randomUUID()) - .email(invitation.getEmail()) - .firstName(payload.getFirstName()) - .lastName(payload.getLastName()) - .passwordHash(passwordService.hashPassword(payload.getPassword())) - .build(); + ListResponsePayload listResponsePayload = new ListResponsePayload(); - userDAO.insert(user); + listResponsePayload.setData(users + .stream() + .map(u -> UserPayload.builder() + .firstName(u.getFirstName()) + .lastName(u.getLastName()) + .token(null) + .id(u.getId().toString()) + .build()) + .collect(toList())); - return ResponseEntity.ok(AcceptInvitationResponsePayload.builder() - .firstName(user.getFirstName()) - .lastName(user.getLastName()) - .token(jwt.tokenFor(user.getId().toString())) - .id(user.getId().toString()) - .build() - ); + return ResponseEntity.ok(listResponsePayload); } + } diff --git a/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/AcceptInvitationRequestPayload.java b/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/AcceptInvitationRequestPayload.java deleted file mode 100644 index 090c82170c..0000000000 --- a/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/AcceptInvitationRequestPayload.java +++ /dev/null @@ -1,17 +0,0 @@ -package co.airy.core.api.auth.controllers.payload; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.UUID; - -@AllArgsConstructor -@NoArgsConstructor -@Data -public class AcceptInvitationRequestPayload { - private UUID id; - private String firstName; - private String lastName; - private String password; -} diff --git a/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/InviteUserRequestPayload.java b/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/InviteUserRequestPayload.java deleted file mode 100644 index 4353f5ca4a..0000000000 --- a/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/InviteUserRequestPayload.java +++ /dev/null @@ -1,15 +0,0 @@ -package co.airy.core.api.auth.controllers.payload; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import javax.validation.constraints.NotBlank; - -@Data -@AllArgsConstructor -@NoArgsConstructor -public class InviteUserRequestPayload { - @NotBlank - private String email; -} diff --git a/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/InviteUserResponsePayload.java b/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/InviteUserResponsePayload.java deleted file mode 100644 index 49a2bf5296..0000000000 --- a/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/InviteUserResponsePayload.java +++ /dev/null @@ -1,12 +0,0 @@ -package co.airy.core.api.auth.controllers.payload; - -import lombok.Builder; -import lombok.Data; - -import java.util.UUID; - -@Builder -@Data -public class InviteUserResponsePayload { - private UUID id; -} diff --git a/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/ListResponsePayload.java b/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/ListResponsePayload.java new file mode 100644 index 0000000000..9ed4e61241 --- /dev/null +++ b/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/ListResponsePayload.java @@ -0,0 +1,10 @@ +package co.airy.core.api.auth.controllers.payload; + +import lombok.Data; + +import java.util.List; + +@Data +public class ListResponsePayload { + private List data; +} diff --git a/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/LoginResponsePayload.java b/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/LoginResponsePayload.java deleted file mode 100644 index b4d9410d25..0000000000 --- a/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/LoginResponsePayload.java +++ /dev/null @@ -1,17 +0,0 @@ -package co.airy.core.api.auth.controllers.payload; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class LoginResponsePayload { - private String id; - private String firstName; - private String lastName; - private String token; -} diff --git a/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/SignupResponsePayload.java b/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/SignupResponsePayload.java deleted file mode 100644 index 0e8a198154..0000000000 --- a/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/SignupResponsePayload.java +++ /dev/null @@ -1,17 +0,0 @@ -package co.airy.core.api.auth.controllers.payload; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class SignupResponsePayload { - private String id; - private String firstName; - private String lastName; - private String token; -} diff --git a/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/AcceptInvitationResponsePayload.java b/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/UserPayload.java similarity index 64% rename from backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/AcceptInvitationResponsePayload.java rename to backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/UserPayload.java index 19da72ae83..7e2f4b366f 100644 --- a/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/AcceptInvitationResponsePayload.java +++ b/backend/api/auth/src/main/java/co/airy/core/api/auth/controllers/payload/UserPayload.java @@ -1,15 +1,19 @@ package co.airy.core.api.auth.controllers.payload; +import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class AcceptInvitationResponsePayload { +@JsonInclude(NON_NULL) +public class UserPayload { private String id; private String firstName; private String lastName; diff --git a/backend/api/auth/src/main/java/co/airy/core/api/auth/dao/InvitationDAO.java b/backend/api/auth/src/main/java/co/airy/core/api/auth/dao/InvitationDAO.java deleted file mode 100644 index 05b4b2fe61..0000000000 --- a/backend/api/auth/src/main/java/co/airy/core/api/auth/dao/InvitationDAO.java +++ /dev/null @@ -1,25 +0,0 @@ -package co.airy.core.api.auth.dao; - -import co.airy.core.api.auth.dto.Invitation; -import org.jdbi.v3.sqlobject.config.RegisterBeanMapper; -import org.jdbi.v3.sqlobject.customizer.BindBean; -import org.jdbi.v3.sqlobject.statement.SqlQuery; -import org.jdbi.v3.sqlobject.statement.SqlUpdate; -import org.springframework.stereotype.Component; - -import java.time.Instant; -import java.util.UUID; - -@Component -public interface InvitationDAO { - @SqlUpdate("INSERT INTO invitations(id, email, sent_at, accepted_at, created_at, updated_at) VALUES (:id, :email, :sentAt, :acceptedAt, :createdAt, :updatedAt)") - @RegisterBeanMapper(Invitation.class) - void insert(@BindBean Invitation invitation); - - @SqlQuery("select id, email, sent_at, accepted_at, created_at, updated_at from invitations where id = ?") - @RegisterBeanMapper(Invitation.class) - Invitation findById(UUID id); - - @SqlUpdate("update invitations set accepted_at = :acceptedAt, updated_at = :acceptedAt where id = :id") - boolean accept(UUID id, Instant acceptedAt); -} diff --git a/backend/api/auth/src/main/java/co/airy/core/api/auth/dao/UserDAO.java b/backend/api/auth/src/main/java/co/airy/core/api/auth/dao/UserDAO.java index acabfd0dd1..fb9cd029b6 100644 --- a/backend/api/auth/src/main/java/co/airy/core/api/auth/dao/UserDAO.java +++ b/backend/api/auth/src/main/java/co/airy/core/api/auth/dao/UserDAO.java @@ -7,6 +7,7 @@ import org.jdbi.v3.sqlobject.statement.SqlUpdate; import org.springframework.stereotype.Component; +import java.util.List; import java.util.UUID; @Component @@ -21,6 +22,10 @@ public interface UserDAO { @RegisterBeanMapper(User.class) User findById(UUID id); + @SqlQuery("SELECT * FROM users ") + @RegisterBeanMapper(User.class) + List list(); + @SqlQuery("SELECT * FROM users WHERE email = :email") @RegisterBeanMapper(User.class) User findByEmail(String email); diff --git a/backend/api/auth/src/test/java/co/airy/core/api/auth/UsersControllerTest.java b/backend/api/auth/src/test/java/co/airy/core/api/auth/UsersControllerTest.java index 9ac15346ce..33ae38bb4e 100644 --- a/backend/api/auth/src/test/java/co/airy/core/api/auth/UsersControllerTest.java +++ b/backend/api/auth/src/test/java/co/airy/core/api/auth/UsersControllerTest.java @@ -1,9 +1,6 @@ package co.airy.core.api.auth; import co.airy.core.api.auth.controllers.UsersController; -import co.airy.core.api.auth.dao.InvitationDAO; -import co.airy.core.api.auth.dao.UserDAO; -import co.airy.core.api.auth.dto.User; import co.airy.core.api.auth.services.Mail; import co.airy.spring.core.AirySpringBootApplication; import co.airy.spring.jwt.Jwt; @@ -21,19 +18,19 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestPropertySource; import java.util.Map; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; +import static co.airy.test.Timing.retryOnException; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.collection.IsCollectionWithSize.hasSize; import static org.hamcrest.core.IsEqual.equalTo; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doNothing; +import static org.springframework.test.annotation.DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -42,6 +39,7 @@ @AutoConfigureMockMvc @FlywayDataSource @TestPropertySource(value = "classpath:test.properties") +@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) public class UsersControllerTest { @Autowired private ObjectMapper objectMapper; @@ -52,12 +50,6 @@ public class UsersControllerTest { @Autowired private Jwt jwt; - @Autowired - private InvitationDAO invitationDAO; - - @Autowired - private UserDAO userDAO; - @MockBean private Mail mail; @@ -126,63 +118,11 @@ void canResetPassword() throws Exception { final String passwordResetRequest = "{\"email\":\"" + email + "\",\"password\":\"trustno1\"}"; - doNothing().when(mail).send(Mockito.eq(email), anyString(), anyString()); - webTestHelper.post("/users.request-password-reset", passwordResetRequest) .andExpect(status().isOk()); - TimeUnit.MILLISECONDS.sleep(500); - - Mockito.verify(mail).send(Mockito.eq(email), anyString(), anyString()); - } - - @Test - void canInviteUsers() throws Exception { - final String userId = "user-id"; - final String rawResponse = webTestHelper.post("/users.invite", - "{\"email\": \"katherine.johnson@example.com\"}", userId) - .andExpect(status().isCreated()) - .andReturn() - .getResponse() - .getContentAsString(); - - final String invitationId = objectMapper.readValue(rawResponse, JsonNode.class).get("id").asText(); - assertThat(invitationId, is(not(nullValue()))); - - assertThat(invitationDAO.findById(UUID.fromString(invitationId)), is(not(nullValue()))); - } - - @Test - void canAcceptInvitations() throws Exception { - final String email = "katherine.johnson@example.com"; - final String userId = "user-id"; - final String rawResponse = webTestHelper.post("/users.invite", - "{\"email\": \"katherine.johnson@example.com\"}", userId) - .andExpect(status().isCreated()) - .andReturn() - .getResponse() - .getContentAsString(); - - final String invitationId = objectMapper.readValue(rawResponse, JsonNode.class).get("id").asText(); - final String requestContent = "{\"id\":\"" + invitationId + "\",\"first_name\":\"" + "Katherine" + "\"," + - "\"last_name\":\"Johnson\",\"password\":\"trustno1\"}"; - - final String responseString = webTestHelper.post("/users.accept-invitation", - requestContent, "user-id") - .andExpect(status().isOk()) - .andReturn() - .getResponse() - .getContentAsString(); - - final JsonNode jsonNode = objectMapper.readTree(responseString); - final String id = jsonNode.get("id").textValue(); - - User user = userDAO.findById(UUID.fromString(id)); - - assertThat(user.getEmail(), equalTo(email)); - assertThat(user.getFirstName(), equalTo("Katherine")); - assertThat(user.getLastName(), equalTo("Johnson")); - assertThat(user.getPasswordHash(), is(not(nullValue()))); + retryOnException(() -> Mockito.verify(mail).send(Mockito.eq(email), anyString(), anyString()), + "could not send email"); } @Test @@ -211,5 +151,23 @@ void canChangePassword() throws Exception { webTestHelper.post("/users.password-reset", passwordResetRequest).andExpect(status().isOk()); } + + @Test + void canListUsers() throws Exception { + for (int i = 0; i < 5; i++) { + final String firstName = "grace-" + i; + final String email = "grace-" + i + "@example.com"; + final String password = "trustno1"; + + final String signUpRequest = "{\"email\":\"" + email + "\",\"first_name\":\"" + firstName + "\"," + + "\"last_name\":\"hopper\",\"password\":\"" + password + "\"}"; + + webTestHelper.post("/users.signup", signUpRequest).andExpect(status().isOk()); + } + + webTestHelper.post("/users.list", "{}", "user-1") + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data", hasSize(5))); + } } diff --git a/backend/avro/communication/webhook.avsc b/backend/avro/communication/webhook.avsc index d282a0e20d..3399f44ce5 100644 --- a/backend/avro/communication/webhook.avsc +++ b/backend/avro/communication/webhook.avsc @@ -22,10 +22,6 @@ ], "default": null }, - {"name": "status", "type": {"name": "Status", "type": "enum", "symbols": ["Subscribed", "Unsubscribed"]}}, - { - "name": "apiSecret", - "type": ["null", "string"], "default": null - } + {"name": "status", "type": {"name": "Status", "type": "enum", "symbols": ["Subscribed", "Unsubscribed"]}} ] } diff --git a/backend/webhook/publisher/src/test/java/co/airy/core/webhook/publisher/PublisherTest.java b/backend/webhook/publisher/src/test/java/co/airy/core/webhook/publisher/PublisherTest.java index 8db0021317..33a7ef63b0 100644 --- a/backend/webhook/publisher/src/test/java/co/airy/core/webhook/publisher/PublisherTest.java +++ b/backend/webhook/publisher/src/test/java/co/airy/core/webhook/publisher/PublisherTest.java @@ -86,7 +86,6 @@ void canPublishMessageToQueue() throws Exception { kafkaTestHelper.produceRecord( new ProducerRecord<>(applicationCommunicationWebhooks.name(), "339ab777-92aa-43a5-b452-82e73c50fc59", Webhook.newBuilder() - .setApiSecret("such secret") .setEndpoint("http://somesalesforce.com/form") .setHeaders(Map.of()) .setId("339ab777-92aa-43a5-b452-82e73c50fc59") diff --git a/docs/docs/api/authentication.md b/docs/docs/api/authentication.md index 19fb6a2c56..180c479169 100644 --- a/docs/docs/api/authentication.md +++ b/docs/docs/api/authentication.md @@ -11,9 +11,13 @@ To start using the API, you need to **authenticate first**. -In order to communicate with the Airy Core API, you need a valid -[JWT Token](https://jwt.io/) token. The login endpoint [login](#login) returns such a short-lived -token which can be used to authenticate with endpoints via the [Bearer Authorization header](https://tools.ietf.org/html/rfc6750#section-2.1). +In order to communicate with the Airy Core API, you need either a valid, short-lived +[JWT](https://jwt.io/) or an API token. + +The jwt can be obtained by calling the login endpoint [login](#login), while the API token needs to be +applied as a cluster [configuration](getting-started/installation/configuration.md). + +To use either token type for authentication you can set them as a value on the [Bearer Authorization header](https://tools.ietf.org/html/rfc6750#section-2.1) when making requests. ## Login diff --git a/docs/docs/api/endpoints/channels.md b/docs/docs/api/endpoints/channels.md index 0c2bde7821..71845a2998 100644 --- a/docs/docs/api/endpoints/channels.md +++ b/docs/docs/api/endpoints/channels.md @@ -152,86 +152,17 @@ import ConnectGoogle from './connect-google.mdx' -### SMS - Twilio - -After you created a Twilio phone number you must [point its webhook -integration](https://www.twilio.com/docs/sms/tutorials/how-to-receive-and-reply-java#configure-your-webhook-url) -to your running Airy Core instance. - -Next call the Platform API: - -``` -POST /channels.twilio.sms.connect -``` - -- `phone_number` The phone number as listed in your [Twilio - dashboard](https://www.twilio.com/console/phone-numbers/). It must _not_ contain - spaces and must include the country code. -- `name` Custom name for the connected phone number -- `image_url` Custom image URL - -**Sample request** - -```json5 -{ - "phone_number": "+491234567", - "name": "SMS for receipts", - "image_url": "https://example.com/custom-image.jpg" // optional -} -``` - -**Sample response** - -```json5 -{ - "id": "channel-uuid-1", - "metadata": {"name": "SMS for receipts", "image_url": "https://example.com/custom-image.jpg"}, - "source": "twilio.sms", - "source_channel_id": "+491234567", - "connected": true -} -``` - -### Whatsapp - Twilio - -After you created a Twilio phone number, you must [point its -webhook integration](https://www.twilio.com/docs/sms/tutorials/how-to-receive-and-reply-java#configure-your-webhook-url) -to your Airy Core running instance. +### SMS -Next call the Airy Core API for connecting channels: +import ConnectSMSTwilio from './connect-twilioSms.mdx' -``` -POST /channels.twilio.whatsapp.connect -``` - -- `phone_number` The phone number as listed in your [Twilio - dashboard](https://www.twilio.com/console/phone-numbers/). - It must _not_ have spaces and must include the country - code. -- `name` Custom name for the connected phone number -- `image_url` Custom image URL - -**Sample request** + -```json5 -{ - "phone_number": "+491234567", - "name": "WhatsApp Marketing", - "image_url": "https://example.com/custom-image.jpg" // optional -} -``` +### Whatsapp -**Sample response** +import ConnectWhatsappTwilio from './connect-twilioWhatsApp.mdx' -```json5 -{ - "id": "channel-uuid-1", - "metadata": {"name": "WhatsApp Marketing", "image_url": "https://example.com/custom-image.jpg"}, - "source": "twilio.whatsapp", - "source_channel_id": "whatsapp:+491234567", - "connected": true -} -``` + ## Disconnecting channels diff --git a/docs/docs/api/endpoints/connect-twilioSms.mdx b/docs/docs/api/endpoints/connect-twilioSms.mdx new file mode 100644 index 0000000000..a30943477f --- /dev/null +++ b/docs/docs/api/endpoints/connect-twilioSms.mdx @@ -0,0 +1,31 @@ +``` +POST /channels.twilio.sms.connect +``` + +- `phone_number` The phone number as listed in your [Twilio + dashboard](https://www.twilio.com/console/phone-numbers/). It must _not_ contain + spaces and must include the country code. +- `name` Custom name for the connected phone number +- `image_url` Custom image URL + +**Sample request** + +```json5 +{ + phone_number: '+491234567', + name: 'SMS for receipts', + image_url: 'https://example.com/custom-image.jpg', // optional +} +``` + +**Sample response** + +```json5 +{ + id: 'channel-uuid-1', + metadata: {name: 'SMS for receipts', image_url: 'https://example.com/custom-image.jpg'}, + source: 'twilio.sms', + source_channel_id: '+491234567', + connected: true, +} +``` diff --git a/docs/docs/api/endpoints/connect-twilioWhatsApp.mdx b/docs/docs/api/endpoints/connect-twilioWhatsApp.mdx new file mode 100644 index 0000000000..98671d7b86 --- /dev/null +++ b/docs/docs/api/endpoints/connect-twilioWhatsApp.mdx @@ -0,0 +1,32 @@ +``` +POST /channels.twilio.whatsapp.connect +``` + +- `phone_number` The phone number as listed in your [Twilio + dashboard](https://www.twilio.com/console/phone-numbers/). + It must _not_ have spaces and must include the country + code. +- `name` Custom name for the connected phone number +- `image_url` Custom image URL + +**Sample request** + +```json5 +{ + phone_number: '+491234567', + name: 'WhatsApp Marketing', + image_url: 'https://example.com/custom-image.jpg', // optional +} +``` + +**Sample response** + +```json5 +{ + id: 'channel-uuid-1', + metadata: {name: 'WhatsApp Marketing', image_url: 'https://example.com/custom-image.jpg'}, + source: 'twilio.whatsapp', + source_channel_id: 'whatsapp:+491234567', + connected: true, +} +``` diff --git a/docs/docs/api/introduction.md b/docs/docs/api/introduction.md index 8919878034..31f257fdf9 100644 --- a/docs/docs/api/introduction.md +++ b/docs/docs/api/introduction.md @@ -27,19 +27,22 @@ interacting with data: } + icon={} + iconInvertible={true} title='HTTP API' description='Access your conversational data with blazing fast HTTP endpoints' link='api/endpoints/introduction' /> } + icon={} + iconInvertible={true} title='WebSocket Server' description='Power real-time applications with STOMP style WebSocket' link='api/websocket' /> } + icon={} + iconInvertible={true} title='Webhook' description='Participate programmatically in conversations by listening to events' link='api/webhook' diff --git a/docs/docs/apps/ui/components.md b/docs/docs/apps/ui/components.md index 22586b216a..dd083cc004 100644 --- a/docs/docs/apps/ui/components.md +++ b/docs/docs/apps/ui/components.md @@ -3,9 +3,29 @@ title: UI Components sidebar_label: UI Components --- +import ButtonBox from "@site/src/components/ButtonBox"; +import GithubIconSVG from "@site/static/icons/githubIcon.svg"; +import ComponentsSVG from "@site/static/icons/information-architecture.svg"; +import useBaseUrl from '@docusaurus/useBaseUrl'; + We built Airy to fulfill all your conversational needs. Additionally we build some react components for your use so you can expand the UI with a matching design. -Check out Components -Buttons, Inputs, Loaders and all Airy UI Components in https://components.airy.co/. +} +iconInvertible={true} +title='UI Components' +description='Buttons, Inputs, Loaders and all Airy UI Components ' +link='https://components.airy.co/' +/> +
+} +title='Component repo' +description=' Directly access the code for each component ' +link='https://github.com/airyhq/components.' +/> +
+ +**Sample Button Component** -See the component repo here: https://github.com/airyhq/components. Directly access the code for each component. +Button Template Example diff --git a/docs/docs/apps/ui/inbox.md b/docs/docs/apps/ui/inbox.md index 7e4b56d041..0cfea050a9 100644 --- a/docs/docs/apps/ui/inbox.md +++ b/docs/docs/apps/ui/inbox.md @@ -5,13 +5,19 @@ sidebar_label: Inbox import ButtonBox from "@site/src/components/ButtonBox"; import AiryBubbleSVG from "@site/static/icons/airy-bubble.svg"; +import PriceTag from "@site/static/icons/price-tag.svg"; +import useBaseUrl from '@docusaurus/useBaseUrl'; + +## Introduction Airy’s Inbox gives you an UI for all your conversations. -See all conversations from the sources you connected, no matter if they come via the Live Chat Plugin, Facebook Messenger, Google’s Business Messages, SMS, WhatsApp or a custom source. +See all conversations from the sources you connected, no matter if they come via the [Live Chat Plugin](sources/chatplugin/overview.md), [Facebook Messenger](sources/facebook.md), [Google’s Business Messages](sources/google.md), [SMS](sources/sms-twilio.md), [WhatsApp](sources/whatsapp-twilio.md) or a custom source. The inbox supports not only text messages but a variety of different message types. +## Message Types + **Send & Receive Messages** You and your team members can use the inbox to receive and send messages from different sources. @@ -20,12 +26,91 @@ Each of these sources have different character limits. **Facebook Templates** A template is a simple structured message that can include a title, subtitle, image, and up to three buttons. -Airy’s Inbox supports all templates that Facebook supports, from Generic Templates to Button Templates. +Airy’s Inbox supports all templates that Facebook supports, from [Generic Templates](https://developers.facebook.com/docs/messenger-platform/send-messages/template/generic) to [Button Templates](https://developers.facebook.com/docs/messenger-platform/send-messages/template/button). + +**Sample Button Template Message** + +Button Template Example + +**Sample request** + +```json5 +{ + "conversation_id": "a688d36c-a85e-44af-bc02-4248c2c97622", + "message": { + "attachment": { + "type": "template", + "payload": { + "template_type": "button", + "text": "What do you want to do next?", + "buttons": [ + { + "type": "web_url", + "url": "https://www.messenger.com", + "title": "Visit Messenger" + }, + { + "type": "web_url", + "url": "https://www.messenger.com", + "title": "Visit Website" + }, + { + "type": "web_url", + "url": "https://www.messenger.com", + "title": "Test Button" + } + ] + } + } + } +} +``` **Google’s Rich Cards** Rich Cards are Google’s templates: a simple structured message that can include a title, description, media and up to 4 suggested replies (buttons). -Airy’s Inbox supports all Google’s Rich Cards variants from Rich Cards to Carousels. +Airy’s Inbox supports all [Google’s Rich Cards variants from Rich Cards to Carousels](https://developers.google.com/business-communications/business-messages/guides/build/send). + +**Sample request** + +```json5 +{ + "conversation_id": "a688d36c-a85e-44af-bc02-4248c2c97622", + "message": { + "fallback": 'Hello, world!\n\nReply with "A" or "B"', + "richCard": { + "standaloneCard": { + "cardContent": { + "title": "Hello, world!", + "description": "Sent with Business Messages.", + "media": { + "height": "TALL", + "contentInfo": { + "altText": "Google logo", + "fileUrl": "https://picsum.photos/200", + "forceRefresh": "false" + } + }, + "suggestions": [ + { + "reply": { + "text": "Suggestion #1", + "postbackData": "suggestion_1" + } + }, + { + "reply": { + "text": "Suggestion #2", + "postbackData": "suggestion_2" + } + } + ] + } + } + } + } +} +``` **Render Templates for Chat Plugin** @@ -33,13 +118,74 @@ Airy’s Live Chat Plugin supports templates too. The template payload is the sa This enables you and your teams to interact with your website visitors in a richer way, and also enables chat bots in the templates. } +icon={} title='Airy Live Chat Plugin' description='The Airy Live Chat Plugin enables conversations with website visitors through a web chat plugin' link='sources/chatplugin/overview' /> - -## Search & Filter +
+ +**Sample RichCard Carousel Message** + +Rich Card Carousel Example + +**Sample request** + +```json5 +{ + "conversation_id": "a688d36c-a85e-44af-bc02-4248c2c97622", + "message": { + "fallback": 'Card #1\n #1\n\nCard #2\n\n\nReply with "Card #1" or "Card #2"', + "richCard": { + "carouselCard": { + "cardWidth": "MEDIUM", + "cardContents": [ + { + "title": "Card #1", + "description": "The description for card #1", + "suggestions": [ + { + "reply": { + "text": "Card #1", + "postbackData": "card_1" + } + } + ], + "media": { + "height": "MEDIUM", + "contentInfo": { + "fileUrl": "https://picsum.photos/id/237/200", + "forceRefresh": "false" + } + } + }, + { + "title": "Card #2", + "description": "The description for card #2", + "suggestions": [ + { + "reply": { + "text": "Card #2", + "postbackData": "card_2" + } + } + ], + "media": { + "height": "MEDIUM", + "contentInfo": { + "fileUrl": "https://picsum.photos/id/238/200", + "forceRefresh": "false" + } + } + } + ] + } + } + } +} +``` + +## Search and filter **Search** @@ -50,16 +196,23 @@ The inbox enables you to search by: - Name - Tags -**Tags** - -Tag your conversations for easy filtering, searching & segmenting +} +title='Tags' +description='Tag your conversations for easy filtering, searching & segmenting' +link='apps/ui/tags' +/> +
**Filter** -Filtering enables you to only show conversations in the inbox according to the filter currently set. +Filtering enables you to only show conversations in the inbox according to the +filter currently set. The inbox can filter by: - Read/Unread Conversations - Sources - Tags + +Filter Inbox diff --git a/docs/docs/apps/ui/introduction.md b/docs/docs/apps/ui/introduction.md index f07bbf0742..0d901fb45d 100644 --- a/docs/docs/apps/ui/introduction.md +++ b/docs/docs/apps/ui/introduction.md @@ -21,32 +21,31 @@ Additional features like [Filters, Search](inbox) and [Tags](tags) help you. } + icon={} + iconInvertible={true} title='UI Quickstart' description='Step by Step Guide on getting up and running with the UI' link='apps/ui/quickstart' /> } + icon={} + iconInvertible={true} title='Inbox' description='One inbox to see all your conversations & respond to them' link='apps/ui/inbox' /> } + icon={} + iconInvertible={true} title='Tags' description='Tag your conversations for easy filtering, searching & segmenting' link='apps/ui/tags' /> } + icon={} + iconInvertible={true} title='UI Components' description='Buttons, Inputs, Loaders and all Airy UI Components ' link='apps/ui/components' /> - -**Overview** - -Demo Tags diff --git a/docs/docs/apps/ui/quickstart.md b/docs/docs/apps/ui/quickstart.md index 5abeaa3a9e..5008784661 100644 --- a/docs/docs/apps/ui/quickstart.md +++ b/docs/docs/apps/ui/quickstart.md @@ -23,8 +23,8 @@ in to the UI** ## Introduction -The easiest way to see the Airy Core UI in action is to launch the [Vagrant -Box](getting-started/installation/vagrant.md) and then follow these three simple +The easiest way to see the Airy Core UI in action is to launch the [minikube +provider](getting-started/installation/minikube.md) and then follow these three steps. ## Step 1: Registration @@ -48,8 +48,7 @@ airy api signup ## Step 2: Open the UI -Either go to [airy.core/ui/login](http://airy.core/ui/login) in your browser or execute -this [CLI command](cli/reference.md#api-login): +Run this [CLI command](cli/reference.md#ui): ```bash airy ui @@ -72,7 +71,8 @@ Now enter your credentials on the login page and click **Login**. ### } +icon={} +iconInvertible={true} title='Next Step: Discover the Inbox' description='Now that you can access the UI it is time to discover the Inbox and its features ' link='apps/ui/inbox' diff --git a/docs/docs/apps/ui/tags.md b/docs/docs/apps/ui/tags.md index 8f798f1f95..3f0213bb3e 100644 --- a/docs/docs/apps/ui/tags.md +++ b/docs/docs/apps/ui/tags.md @@ -4,10 +4,9 @@ sidebar_label: Tags --- import useBaseUrl from '@docusaurus/useBaseUrl'; +import TLDR from "@site/src/components/TLDR"; -**Introduction** - -Tags are words, or combinations of words, you can use to add more context to conversations and contacts. +Tags are words, or combinations of words, you can use to add more context to conversations and contacts. Tags provide you with an unlimited amount of flexibility to manage and customize your conversational workflow. @@ -17,20 +16,19 @@ Here are the ways you can create and use tags: - Your users can create them manually directly in conversations - They can be created in the Tag Manager -Demo Tags - -**Create Tags** +## Create Tags When you create a tag you can choose a color to visually identify it better in the inbox. -This can also be done via the tags create API. +This can also be done via the [Create Tags API](api/endpoints/tags.md#create). + +Demo Tags -**Edit Tags** +## Edit Tags When editing tags you can change the name and the color of each tag. -This can also be done via the tags update API. +This can also be done via the [Edit Tags API](api/endpoints/tags.md#update). -**Delete Tags** +## Delete Tags Deleting tags deletes them completely. -This can also be done via the tags delete API. +This can also be done via the [Delete Tags API](api/endpoints/tags.md#delete). diff --git a/docs/docs/changelog.md b/docs/docs/changelog.md index edf75ce577..4028398635 100644 --- a/docs/docs/changelog.md +++ b/docs/docs/changelog.md @@ -3,21 +3,90 @@ title: Changelog sidebar_label: 📝 Changelog --- +## + +#### Changes + +- Docs/1301 add docs for twilio sources [[#1332](https://github.com/airyhq/airy/pull/1332)] +- Bump webpack-cli from 3.3.12 to 4.5.0 [[#1287](https://github.com/airyhq/airy/pull/1287)] +- Bump css-loader from 3.6.0 to 5.1.3 [[#1320](https://github.com/airyhq/airy/pull/1320)] +- Bump react-window from 1.8.5 to 1.8.6 [[#1309](https://github.com/airyhq/airy/pull/1309)] +- Bump react-window-infinite-loader from 1.0.5 to 1.0.7 [[#1286](https://github.com/airyhq/airy/pull/1286)] +- [[#1235](https://github.com/airyhq/airy/issues/1235)] Add test to connect a chatplugin channel [[#1269](https://github.com/airyhq/airy/pull/1269)] + +#### 🚀 Features + +- [[#1312](https://github.com/airyhq/airy/issues/1312)] Add users.list [[#1333](https://github.com/airyhq/airy/pull/1333)] +- [[#1049](https://github.com/airyhq/airy/issues/1049)] Script that executes all integration tests [[#1344](https://github.com/airyhq/airy/pull/1344)] +- [[#970](https://github.com/airyhq/airy/issues/970)] Improved ui components docs [[#1341](https://github.com/airyhq/airy/pull/1341)] +- [[#1272](https://github.com/airyhq/airy/issues/1272)] Make cypress tests independent from each other [[#1317](https://github.com/airyhq/airy/pull/1317)] +- [[#1328](https://github.com/airyhq/airy/issues/1328)] Develop version of airy CLI is not… [[#1330](https://github.com/airyhq/airy/pull/1330)] +- [[#1267](https://github.com/airyhq/airy/issues/1267)] Created generic logo component [[#1329](https://github.com/airyhq/airy/pull/1329)] +- [[#677](https://github.com/airyhq/airy/issues/677)] Render Suggested Replies [[#1324](https://github.com/airyhq/airy/pull/1324)] +- API key authentication [[#1316](https://github.com/airyhq/airy/pull/1316)] +- [[#968](https://github.com/airyhq/airy/issues/968)] Improve UI/Inbox docs [[#1280](https://github.com/airyhq/airy/pull/1280)] +- [[#1041](https://github.com/airyhq/airy/issues/1041)] Minikube provider [[#1179](https://github.com/airyhq/airy/pull/1179)] +- [[#1278](https://github.com/airyhq/airy/issues/1278)] GIFs in docs are too large [[#1295](https://github.com/airyhq/airy/pull/1295)] +- [[#1281](https://github.com/airyhq/airy/issues/1281)] Added RichCard and RichCardCarousel to google [[#1288](https://github.com/airyhq/airy/pull/1288)] +- [[#1222](https://github.com/airyhq/airy/issues/1222)] Improved structure in channels pages [[#1271](https://github.com/airyhq/airy/pull/1271)] +- [[#1270](https://github.com/airyhq/airy/issues/1270)] Installation: Toggle broken \& Update for… [[#1273](https://github.com/airyhq/airy/pull/1273)] +- [[#1050](https://github.com/airyhq/airy/issues/1050)] Add test for filtering and creating a tag [[#1252](https://github.com/airyhq/airy/pull/1252)] + +#### 🐛 Bug Fixes + +- Fix/1239 message wrapper for render library [[#1297](https://github.com/airyhq/airy/pull/1297)] +- [[#1306](https://github.com/airyhq/airy/issues/1306)] Fix contact metadata problem [[#1349](https://github.com/airyhq/airy/pull/1349)] +- [[#1343](https://github.com/airyhq/airy/issues/1343)] Save button doesn't work for adding a… [[#1347](https://github.com/airyhq/airy/pull/1347)] +- [[#1298](https://github.com/airyhq/airy/issues/1298)] MessageTextArea in inbox doesn't shrink… [[#1340](https://github.com/airyhq/airy/pull/1340)] +- [[#1303](https://github.com/airyhq/airy/issues/1303)] Long messages from contacts shrink the… [[#1334](https://github.com/airyhq/airy/pull/1334)] +- [[#1267](https://github.com/airyhq/airy/issues/1267)] Updated sourceLogo component [[#1331](https://github.com/airyhq/airy/pull/1331)] +- [[#1041](https://github.com/airyhq/airy/issues/1041)] follow up fix: missing quotes in web dev script [[#1311](https://github.com/airyhq/airy/pull/1311)] +- [[#1090](https://github.com/airyhq/airy/issues/1090)] Add fallback image to channels [[#1254](https://github.com/airyhq/airy/pull/1254)] + +#### 📚 Documentation + +- [[#1323](https://github.com/airyhq/airy/issues/1323)] Fix minikube command [[#1327](https://github.com/airyhq/airy/pull/1327)] +- [[#1314](https://github.com/airyhq/airy/issues/1314)] Have one TLDR [[#1315](https://github.com/airyhq/airy/pull/1315)] +- [[#1264](https://github.com/airyhq/airy/issues/1264)] Prepare config page for the new milestone [[#1308](https://github.com/airyhq/airy/pull/1308)] +- [[#1265](https://github.com/airyhq/airy/issues/1265)] Merge cheatsheet from introduction into reference [[#1277](https://github.com/airyhq/airy/pull/1277)] +- [[#969](https://github.com/airyhq/airy/issues/969)] Cleanup tag docs [[#1275](https://github.com/airyhq/airy/pull/1275)] +- [[#1263](https://github.com/airyhq/airy/issues/1263)] Airy Core Components: Move to own page [[#1276](https://github.com/airyhq/airy/pull/1276)] + +#### 🧰 Maintenance + +- Fix build [[#1346](https://github.com/airyhq/airy/pull/1346)] +- Bump react-redux from 7.2.2 to 7.2.3 [[#1335](https://github.com/airyhq/airy/pull/1335)] +- Bump @babel/preset-env from 7.13.10 to 7.13.12 [[#1336](https://github.com/airyhq/airy/pull/1336)] +- Bump @typescript-eslint/parser from 4.18.0 to 4.19.0 [[#1337](https://github.com/airyhq/airy/pull/1337)] +- Remove ejs compiled loader [[#1322](https://github.com/airyhq/airy/pull/1322)] +- Invert icons on darkTheme [[#1319](https://github.com/airyhq/airy/pull/1319)] +- Bump style-loader from 1.3.0 to 2.0.0 [[#1313](https://github.com/airyhq/airy/pull/1313)] +- Bump redux-starter-kit from 0.8.1 to 2.0.0 [[#1296](https://github.com/airyhq/airy/pull/1296)] +- Bump node-sass from 4.14.0 to 5.0.0 [[#1226](https://github.com/airyhq/airy/pull/1226)] +- Bump @svgr/webpack from 5.4.0 to 5.5.0 [[#1257](https://github.com/airyhq/airy/pull/1257)] +- Bump @types/node from 12.11.1 to 14.14.35 [[#1258](https://github.com/airyhq/airy/pull/1258)] + +#### 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.13.1/darwin/amd64/airy) +[Linux](https://airy-core-binaries.s3.amazonaws.com/0.13.1/linux/amd64/airy) +[Windows](https://airy-core-binaries.s3.amazonaws.com/0.13.1/windows/amd64/airy.exe) + ## 0.13.0 #### Changes - - Bump typesafe-actions from 4.4.2 to 5.1.0 [[#1210](https://github.com/airyhq/airy/pull/1210)] - [[#783](https://github.com/airyhq/airy/issues/783)] Introduce changelog [[#1221](https://github.com/airyhq/airy/pull/1221)] - Bump yargs-parser from 5.0.0 to 5.0.1 [[#1213](https://github.com/airyhq/airy/pull/1213)] - Bump react-dom from 16.12.0 to 16.14.0 [[#1188](https://github.com/airyhq/airy/pull/1188)] -- [[#659](https://github.com/airyhq/airy/issues/659)] Enable to connect via facebook [[#1130](https://github.com/airyhq/airy/pull/1130)] - [[#773](https://github.com/airyhq/airy/issues/773)] change searchbar to the left [[#1192](https://github.com/airyhq/airy/pull/1192)] #### 🚀 Features - - [[#1247](https://github.com/airyhq/airy/issues/1247)] Optional variables for templates creation [[#1248](https://github.com/airyhq/airy/pull/1248)] - [[#656](https://github.com/airyhq/airy/issues/656)] Enable users to connect via Twilio Sms and Whatsapp [[#1223](https://github.com/airyhq/airy/pull/1223)] +- [[#659](https://github.com/airyhq/airy/issues/659)] Enable to connect via facebook [[#1130](https://github.com/airyhq/airy/pull/1130)] - [[#871](https://github.com/airyhq/airy/issues/871)] Httpclient methods need return value [[#1199](https://github.com/airyhq/airy/pull/1199)] - [[#868](https://github.com/airyhq/airy/issues/868)] Templates manager [[#1123](https://github.com/airyhq/airy/pull/1123)] - [[#1228](https://github.com/airyhq/airy/issues/1228)] Scope templates list by source type [[#1230](https://github.com/airyhq/airy/pull/1230)] diff --git a/docs/docs/cli/installation.md b/docs/docs/cli/installation.md index 4538284f6e..2fcbeb4aee 100644 --- a/docs/docs/cli/installation.md +++ b/docs/docs/cli/installation.md @@ -1,5 +1,5 @@ --- -title: Installation +title: Install the Airy CLI sidebar_label: Installation hide_table_of_contents: false --- @@ -10,11 +10,14 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -Install the CLI quickly with our step-by-step guide or build it from source + +The Airy CLI is a developer tool to help you **build**, **test**, and **manage** +Airy Core instances directly from your terminal. + -Installing the Airy CLI is easy and straightforward. -You can follow the next steps for a quick setup: +Installing the Airy CLI is easy and straightforward. You can follow the next +steps for a quick setup: - [Step 1: Check the requirements](installation.md#step-1-check-the-requirements) - [Step 2: Install the Airy CLI](installation.md#step-2-install-the-airy-cli) @@ -43,6 +46,25 @@ values={[ {label: 'Linux', value: 'linux'}, ] }> + + + +Make sure you have the [Xcode Command Line +Tools](https://developer.apple.com/library/archive/technotes/tn2339/_index.html#//apple_ref/doc/uid/DTS40014588-CH1-WHAT_IS_THE_COMMAND_LINE_TOOLS_PACKAGE_) +installed. Otherwise you can install them with: + +```bash +xcode-select --install +``` + +Now you can get the CLI straight from our tap: + +```bash +brew install airyhq/airy/cli +``` + + + ```bash @@ -64,22 +86,12 @@ For example, to download version 0.6.0 on macOS, type: curl https://airy-core-binaries.s3.amazonaws.com/0.6.0/darwin/amd64/airy -o "airy" ::: - - - -Make sure you have the [Xcode Command Line -Tools](https://developer.apple.com/library/archive/technotes/tn2339/_index.html#//apple_ref/doc/uid/DTS40014588-CH1-WHAT_IS_THE_COMMAND_LINE_TOOLS_PACKAGE_) -installed. Otherwise you can install them with: +#### Move the **airy** binary to a file location on your system PATH -```bash -xcode-select --install ``` - -Now you can get the CLI straight from our tap: - -```bash -brew install airyhq/airy/cli +sudo mv ./airy /usr/local/bin/airy && \ +sudo chown root: /usr/local/bin/airy ``` @@ -99,31 +111,6 @@ For example, to download version 0.6.0 on Linux, type: curl https://airy-core-binaries.s3.amazonaws.com/0.6.0/linux/amd64/airy -o "airy" ::: - - - - - - - -#### Move the **airy** binary to a file location on your system PATH - -``` -sudo mv ./airy /usr/local/bin/airy && \ -sudo chown root: /usr/local/bin/airy -``` - - - - #### Install the binary @@ -131,9 +118,6 @@ sudo chown root: /usr/local/bin/airy sudo install -o root -g root -m 0755 airy /usr/local/bin/airy ``` - - - diff --git a/docs/docs/cli/introduction.md b/docs/docs/cli/introduction.md deleted file mode 100644 index 9fe46994a8..0000000000 --- a/docs/docs/cli/introduction.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: Introduction -sidebar_label: Introduction ---- - -import TLDR from "@site/src/components/TLDR"; - - - -The Airy CLI is a developer tool to help you **build**, **test**, and **manage** -the Airy Core directly from your terminal. - - - -### Cheat Sheet - -| Command | Effect | -| ------------------- | ---------------------------------------------------------------------------------- | -| `airy init` | Inits your airy configuration | -| `airy status` | Reports the status of an Airy Core instance | -| `airy config apply` | Applies configuration values from airy.yaml configuration to an Airy Core instance | -| `airy ui` | Opens the Airy Core UI in your local browser | -| `airy api signup` | Creates a default user | -| `airy api login` | Returns a valid jwt token | -| `airy api login` | Returns a valid jwt token | -| `airy --help` | Prints all available commands with parameters | diff --git a/docs/docs/cli/reference.md b/docs/docs/cli/reference.md index e15ac30866..143aeb132c 100644 --- a/docs/docs/cli/reference.md +++ b/docs/docs/cli/reference.md @@ -3,6 +3,30 @@ title: Command Reference sidebar_label: Reference --- +## Api Endpoint + +Get the endpoint of the Airy Core API + +``` +airy api endpoint [flags] +``` + +#### Options + +``` + -h, --help help for endpoint +``` + +#### Options inherited from parent commands + +``` + --apihost string Airy Core HTTP API endpoint + --cli-config string config file (default is $HOME/.airy/cli.yaml) +``` + + +*** + ## Api Login Login into an Airy Core instance @@ -22,7 +46,7 @@ airy api login [flags] #### Options inherited from parent commands ``` - --apihost string Airy Core HTTP API host (default "http://airy.core") + --apihost string Airy Core HTTP API endpoint --cli-config string config file (default is $HOME/.airy/cli.yaml) ``` @@ -50,7 +74,7 @@ airy api signup [flags] #### Options inherited from parent commands ``` - --apihost string Airy Core HTTP API host (default "http://airy.core") + --apihost string Airy Core HTTP API endpoint --cli-config string config file (default is $HOME/.airy/cli.yaml) ``` @@ -74,10 +98,9 @@ airy config apply [flags] #### Options inherited from parent commands ``` - --apihost string Airy Core HTTP API host (default "http://airy.core") - --cli-config string config file (default is $HOME/.airy/cli.yaml) - --config string Configuration file for an Airy Core instance (default "./airy.yaml") - --kube-config string Kubernetes config file for the cluster of an Airy Core instance (default "~/.airy/kube.conf") + --apihost string Airy Core HTTP API endpoint + --cli-config string config file (default is $HOME/.airy/cli.yaml) + --config string Configuration file for an Airy Core instance (default "./airy.yaml") ``` @@ -96,13 +119,13 @@ airy create [flags] ``` -h, --help help for create --namespace string (optional) Kubernetes namespace that Airy should be installed to. (default "default") - --provider string One of the supported providers (aws|local|minikube). (default "local") + --provider string One of the supported providers (aws|minikube). (default "local") ``` #### Options inherited from parent commands ``` - --apihost string Airy Core HTTP API host (default "http://airy.core") + --apihost string Airy Core HTTP API endpoint --cli-config string config file (default is $HOME/.airy/cli.yaml) ``` @@ -126,7 +149,7 @@ airy init [flags] #### Options inherited from parent commands ``` - --apihost string Airy Core HTTP API host (default "http://airy.core") + --apihost string Airy Core HTTP API endpoint --cli-config string config file (default is $HOME/.airy/cli.yaml) ``` @@ -150,7 +173,7 @@ airy status [flags] #### Options inherited from parent commands ``` - --apihost string Airy Core HTTP API host (default "http://airy.core") + --apihost string Airy Core HTTP API endpoint --cli-config string config file (default is $HOME/.airy/cli.yaml) ``` @@ -174,7 +197,7 @@ airy ui [flags] #### Options inherited from parent commands ``` - --apihost string Airy Core HTTP API host (default "http://airy.core") + --apihost string Airy Core HTTP API endpoint --cli-config string config file (default is $HOME/.airy/cli.yaml) ``` @@ -198,7 +221,7 @@ airy version [flags] #### Options inherited from parent commands ``` - --apihost string Airy Core HTTP API host (default "http://airy.core") + --apihost string Airy Core HTTP API endpoint --cli-config string config file (default is $HOME/.airy/cli.yaml) ``` diff --git a/docs/docs/concepts/architecture.md b/docs/docs/concepts/architecture.md index 70f760bfe7..9ecf0eeb00 100644 --- a/docs/docs/concepts/architecture.md +++ b/docs/docs/concepts/architecture.md @@ -52,4 +52,5 @@ and reloading the appropriate `Airy Components` based on the provided configurat ## Airy CLI Every release features a command line binary, used to configure and fetch status -information from your Airy Core instance. This tool is referred to as the [Airy CLI](/cli/introduction.md) throughout the documentation. +information from your Airy Core instance. This tool is referred to as the [Airy +CLI](/cli/installation.md) throughout the documentation. diff --git a/docs/docs/concepts/release-process.md b/docs/docs/concepts/release-process.md index 6728ac221c..656fcbbf70 100644 --- a/docs/docs/concepts/release-process.md +++ b/docs/docs/concepts/release-process.md @@ -14,7 +14,7 @@ Here's an outline of the process: - We clean up the draft release and name it `x.y.z` - We run `./scripts/release.sh start x.y.z` - Once release days comes, we execute the following steps: - - We test our release (`./scripts/bootstrap.sh`) and any + - We test our release (`airy create --provider=minikube`) and any additional hot-fix is committed directly to the release branch - Once we're satisfied with the release, we finish the release by running `./scripts/release.sh finish x.y.z` - We update the version string and the sha in the [Homebrew Formula](https://github.com/airyhq/homebrew-airy/blob/main/Formula/cli.rb) for the CLI. diff --git a/docs/docs/getting-started/components.md b/docs/docs/getting-started/components.md new file mode 100644 index 0000000000..3c610dc240 --- /dev/null +++ b/docs/docs/getting-started/components.md @@ -0,0 +1,60 @@ +--- +title: Core Components +sidebar_label: Core Components +--- + +import ButtonBoxList from "@site/src/components/ButtonBoxList"; +import ButtonBox from "@site/src/components/ButtonBox"; +import DiamondSVG from "@site/static/icons/diamond.svg"; +import SpeechBalloonSVG from "@site/static/icons/speech-balloon.svg"; +import HighVoltageSVG from "@site/static/icons/high-voltage.svg"; +import ElectricPlugSVG from "@site/static/icons/electric-plug.svg"; +import FishingPoleSVG from "@site/static/icons/fishing-pole.svg"; +import GearSVG from "@site/static/icons/gear.svg"; + +The Airy Core platform contains the following core components: + + +} + iconInvertible={true} + title='Connectors for all conversational sources' + description="Connect anything from our free open-source live chat plugin, Facebook Messenger, Google's Business Messages to your Airy Core. 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." + link='/sources/introduction' +/> +} + iconInvertible={true} + title='APIs to access your data' + description="An API to access conversational data with blazing fast HTTP endpoints." + link='/api/endpoints/introduction' +/> +} + iconInvertible={true} + title='WebSockets to power real-time applications' + description="A WebSocket server that allows clients to receive near real-time updates about data flowing through the system." + link='/api/websocket' +/> +} + iconInvertible={true} + title='Webhook integration to connect custom apps' + description="A webhook integration server that allows its users to programmatically participate in conversations by sending messages (the webhook integration exposes events users can listen to and react programmatically.)" + link='/api/webhook' +/> +} + 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." + link='/apps/ui/introduction' +/> +} + iconInvertible={true} + title='Integrations' + description="Pre-made integrations into popular conversational tools, for example NLP tools like Rasa" + link='/integrations/rasa' +/> + diff --git a/docs/docs/getting-started/installation/configuration.md b/docs/docs/getting-started/installation/configuration.md index 29e346e21a..4863be47d3 100644 --- a/docs/docs/getting-started/installation/configuration.md +++ b/docs/docs/getting-started/installation/configuration.md @@ -1,24 +1,37 @@ --- -title: Configuration +title: Configuration your Airy Core instance sidebar_label: Configuration --- -## Airy Core Configuration File +import TLDR from "@site/src/components/TLDR"; -The `infrastructure/airy.tpl.yaml` file contains examples of all possible configuration options. Create your own `airy.yaml` like so: + -```bash -cd infrastructure -cp airy.tpl.yaml airy.yaml +Use an airy.yaml configuration file to customize your Airy Core instance + + + +The configuration workflow is as simple as: + +```sh +$EDITOR airy.yaml # create your airy.yaml +airy config --config path/to/airy.yml # apply your config! ``` -We will guide you through the different sections so you can make the changes you are looking for. Keys can also be omitted for services which you do not wish to configure. +Your Airy Core instance will start and stop components accordingly to your +configuration. For example, if you do not wish to start Facebook components, it +is enough not to provide any facebook specific configuration. + +Now let's have a look at the different sections so you can make the changes you +are looking for. ### Global - `appImageTag` the image tag of the container images for the **Airy Components** - If you want to launch an older version refer to our [Releases](https://github.com/airyhq/airy/releases) for the correct version number or if you are feeling adventurous try `develop` at your own risk. + If you want to launch an older version refer to our + [Releases](https://github.com/airyhq/airy/releases) for the correct version + number or if you are feeling adventurous try `develop` at your own risk. - `containerRegistry` the URL of the container registry @@ -28,7 +41,8 @@ We will guide you through the different sections so you can make the changes you ### Prerequisites -These settings are used to connect the **Airy Components** to your Kafka cluster, PostgreSQL and Redis. +These settings are used to connect the **Airy Components** to your Kafka +cluster, PostgreSQL, and Redis. - `kafka` @@ -41,19 +55,18 @@ These settings are used to connect the **Airy Components** to your Kafka cluster - `hostname` - `port` - Redis is needed as a queue for the [Webhooks](/api/webhook.md) - - `postgres` - `endpoint` in `:` format - - `dbName` name of the database in the PostgreSQL server + - `dbName` name of the PostgreSQL database - `username` these credentials will be passed to the **API Auth Component** - - `password` and they will be used to create the Postgres if you are deploying with **Vagrant** + - `password` and they will be used to create the Postgres database ### Components - `api` - - `jwtSecret` should be set to a long secure secret in production environments + - `jwtSecret` must be set to a long secure secret in production environments + - `token` set to a long secure secret to use for machine [API authentication](api/authentication.md) - `allowedOrigins` your sites origin to prevent CORS-based attacks - `sources` @@ -62,7 +75,8 @@ These settings are used to connect the **Airy Components** to your Kafka cluster - `google` - `twilio` - The **Airy Controller** only starts configured sources. To keep system load to a minimum, only add the sources you are using. + The **Airy Controller** only starts configured sources. To keep system load to + a minimum, only add the sources you are using. - `webhooks` - `name` @@ -72,15 +86,18 @@ These settings are used to connect the **Airy Components** to your Kafka cluster ### Tools -These settings are used to enable or disable some external tools, used to monitor or debug the **Airy Core**. +These settings are used to enable or disable some external tools, used to +monitor or debug the **Airy Core**. - `akhq` Kafka GUI for Apache Kafka (For more information visit [akhq.io](https://akhq.io/)) - `enabled` set to either `true` to start AKHQ or `false` (default) to disable it. ## Applying the configuration -If you made changes in `airy.yaml` and want to apply it to your instance you can use the [airy config apply](/cli/reference.md#config-apply) by running the following [Airy CLI](/cli/introduction.md) command. +If you made changes in `airy.yaml` and want to apply it to your instance you can +use the [airy config apply](/cli/reference.md#config-apply) by running the +following [Airy CLI](/cli/installation.md) command. ```bash -airy config apply --config ./airy.yaml --kube-config /path/to/your/kube.conf +airy config apply --config ./airy.yaml ``` diff --git a/docs/docs/getting-started/installation/introduction.md b/docs/docs/getting-started/installation/introduction.md index b71681a6f2..b567e84fd1 100644 --- a/docs/docs/getting-started/installation/introduction.md +++ b/docs/docs/getting-started/installation/introduction.md @@ -7,8 +7,8 @@ import useBaseUrl from "@docusaurus/useBaseUrl"; import TLDR from "@site/src/components/TLDR"; import ButtonBoxList from "@site/src/components/ButtonBoxList"; import ButtonBox from "@site/src/components/ButtonBox"; -import VagrantSVG from "@site/static/icons/vagrant.svg"; import KafkaSVG from "@site/static/icons/kafka.svg"; +import Minikube from "@site/static/icons/minikube.svg"; import RocketSVG from "@site/static/icons/rocket.svg"; @@ -26,19 +26,20 @@ easy to install and works on macOS, Windows, and Linux. } +icon={} +iconInvertible={true} title='CLI' description='Install the Airy Core CLI application' link='/cli/installation' /> } -title='Local test environment with Vagrant' +icon={} +title='Local test environment with Minikube' description='Step by step guide to run Airy Core on your local machine' -link='getting-started/installation/vagrant' +link='getting-started/installation/minikube' /> } +icon={} title='Production ready environment with Kafka' description='Manual step by step guide for running Airy Core in production' link='getting-started/installation/production' diff --git a/docs/docs/getting-started/installation/minikube.md b/docs/docs/getting-started/installation/minikube.md new file mode 100644 index 0000000000..796d7d80fa --- /dev/null +++ b/docs/docs/getting-started/installation/minikube.md @@ -0,0 +1,84 @@ +--- +title: Run Airy on Minikube +sidebar_label: Minikube +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +The goal of this document is to provide an overview of how to run Airy Core on +your local machine using [minikube](https://minikube.sigs.k8s.io/). + +## Creating a cluster + +First download and install minikube from their [release +page](https://github.com/kubernetes/minikube/releases) and the [Airy +CLI](cli/installation.md). Now you can run this command, which will create a new +minikube cluster on your system and install Airy core on it: + +```bash +airy create --provider=minikube +``` + +This will print URLs for accessing the UIs and APIs as seen in this recording: + +import Script from "@site/src/components/Script"; + + + +If you want to customize your `Airy Core` instance please see our [Configuration +Section](configuration.md). + +## Public webhooks + +In order to integrate with the webhook of most sources on your local machine, +we include a [ngrok](https://ngrok.com/) as a deployment to tunnel the traffic to the ingress controller. +ngrok is an open source reverse proxy which +creates a secure tunnel from a public endpoint to a local service. The ngrok +client connects to a ngrok server which has public access to the internet and +then provides a reversed proxy connectivity back to the webhook services, +running inside the Kubernetes cluster. + +To get the ngrok URL of your local Airy Core installation you can run: + +```sh +echo "https://$(minikube -p airy-core kubectl -- get cm core-config -o jsonpath='{.data.CORE_ID}').tunnel.airy.co" +``` + +By default, the ngrok client is configured to use the ngrok server created by +Airy and runs on https://tunnel.airy.co. This configuration is specified in +the `ngrok-client-config` ConfigMap. + +``` +apiVersion: v1 +kind: ConfigMap +metadata: + name: ngrok-client-config + namespace: default +data: + config.yml: | + server_addr: proxy.tunnel.airy.co:4443 + trust_host_root_certs: true +``` + +If you prefer to use your own ngrok implementation or point the ngrok client to +connect to the service provided by the ngrok company at `https://ngrok.io`, +change the setting for `server_addr` in the ConfigMap. + +## Where to go from here + +Now that you have a running local installation of Minikube you can connect it to messaging sources. Check out the +[source documentation](/sources/introduction.md) to learn more. + +## Third party tools + +Third party tools can be activated in the `airy.yaml` configuration file, under the `tools` section. +For more details please see our [Configuration Section](configuration.md). + +## Uninstall Airy Core + +You can remove the Airy Core minikube node from your machine completely running +the following command: + +```sh +minikube -p airy-core delete +``` diff --git a/docs/docs/getting-started/installation/vagrant.md b/docs/docs/getting-started/installation/vagrant.md deleted file mode 100644 index e5aee4e012..0000000000 --- a/docs/docs/getting-started/installation/vagrant.md +++ /dev/null @@ -1,261 +0,0 @@ ---- -title: Vagrant -sidebar_label: Vagrant ---- - -import useBaseUrl from '@docusaurus/useBaseUrl'; - -The goal of this document is to provide an overview of how to run Airy Core on -your local machine using [Vagrant](https://www.vagrantup.com). - -To facilitate bootstrapping Airy Core on a single machine, we included a Vagrant -configuration inside the `infrastructure` directory. - -The Vagrant box is based on Alpine Linux and contains a pre-configured -Kubernetes cluster [K3OS](https://k3os.io/) to deploy and run Airy Core -components. - -## Run the Bootstrap script - -Create an Airy Core instance locally by entering the following commands: - -```bash -git clone -b main https://github.com/airyhq/airy -cd airy -./scripts/bootstrap.sh -``` - -The bootstrap installation requires -[Vagrant](https://www.vagrantup.com/downloads) and -[VirtualBox](https://www.virtualbox.org/wiki/Downloads). If they are not found, -the script will attempt to install them for you. - -If Vagrant or VirtualBox cannot be installed with the `bootstrap.sh` script, you -need to install them manually. - -The script will also ask for your administrative credentials as we are using the -[Vagrant Host Manager -Plugin](https://github.com/devopsgroup-io/vagrant-hostmanager) to add an entry to -your hosts file. You can skip this step and add the following line to your -hosts file yourself. - -``` -192.168.50.5 airy.core -``` - -After the bootstrap process finishes, it will download the Kubernetes -configuration file to the local host machine under `~/.airy/kube.conf`. That -file is required for the Airy Command Line tool [Airy CLI](/cli/introduction.md), in order to access -the Kubernetes cluster where your Airy Core instance is running. You can also -use that configuration file with the `kubectl` utility, for example: - -```sh -kubectl --kubeconfig ~/.airy/kube.conf get pods -``` - -If you want to customize your `Airy Core` instance please see our [Configuration Section](configuration.md). - -## Access the API - -The API services are available under the domain `http://airy.core` from your -local machine. You can see an example request to the API by running the -`status` command. - -## Access the UI - -The UI can be accessed at http://airy.core/ui/. - -The frontend UI for the Airy chat plugin can be accessed at -`http://airy.core/chatplugin/example`. Refer to [the Airy Live Chat -plugin](/sources/chatplugin/overview.md) documentation for detailed information. - -## Public webhooks - -The public webhook URLs are generated during the bootstrap process and are -displayed after the process finishes. Find your current webhook URLs and your -API local address by running the `/vagrant/scripts/status.sh` command from -inside the Airy Core Vagrant box. - -In order to integrate with the webhook of most sources on your local machine, -we included a [ngrok](https://ngrok.com/) client as a sidecar container in each -`sources-SOURCE_NAME-webhook` pods. ngrok is an open source reverse proxy which -creates a secure tunnel from a public endpoint to a local service. The ngrok -client connects to a ngrok server which has public access to the internet and -then provides a reversed proxy connectivity back to the webhook services, -running inside the Kubernetes cluster. - -By default, the ngrok client is configured to use the ngrok server created by -Airy and run on https://tunnel.airy.co. This configuration is specified in -the `ngrok-client-config` ConfigMap. - -``` -apiVersion: v1 -kind: ConfigMap -metadata: - name: ngrok-client-config - namespace: default -data: - config.yml: | - server_addr: proxy.tunnel.airy.co:4443 - trust_host_root_certs: true -``` - -If you prefer to use your own ngrok implementation or point the ngrok client to -connect to the service provided by the ngrok company at `https://ngrok.io`, -change the setting for `server_addr` in the ConfigMap or in this helm chart -document -`infrastructure/helm-chart/templates/ngrok.yaml`. - -Ngrok can be disabled during the bootstrap process: - -```bash -NGROK_ENABLED=false ./scripts/bootstrap.sh -``` - -The bootstrap process creates a random URL which is then provisioned inside the -Helm chart. To configure these URLs, you can specify them in the -`infrastructure/helm-chart/values.yaml` document. Alternatively you can edit the -`airy.yaml` file by setting the following parameter (see `airy.tpl.yaml` for -more examples): - -``` -sources: - SOURCE_NAME: - webhookPublicUrl: https://public-url-for-SOURCE_NAME-webhook -``` - -## Connect sources - -Integrating sources into the `Airy Core` often requires specific configuration -settings, refer to the [source specific docs](/sources/introduction.md) for details. - -## External tools - -The optional external tools can be activated in the `airy.yaml` configuration file, under the `tools` section. -For more details please see our [Configuration Section](configuration.md). - -### AKHQ - -AKHQ is a GUI for inspecting Apache Kafka. If enabled, it can be accessed under `http://airy.core/tools/akhq`. -Username is `admin` and the auto-generated password can be retrieved from a configMap: - -```sh -cd infrastructure -vagrant ssh -kubectl get configmap akhq-config -o jsonpath="{.data.password}" -``` - -## Uninstall Airy Core - -You can remove the Airy Core Vagrant box from your machine completely running -the following commands: - -```sh -cd infrastructure -vagrant destroy -``` - -## Manage your Vagrant box - -You can ssh inside the Airy Core box for testing and debugging purposes with -`vagrant ssh` or run commands directly with `vagrant ssh -c COMMAND` - -### Status - -Run the following `status command` to print the information on how to access Airy Core. - -```sh -cd infrastructure -vagrant ssh -c /vagrant/scripts/status.sh -``` - -The status command will print the following information: - -```sh -"Your public url for the Facebook Webhook is:" -${FACEBOOK_WEBHOOK_PUBLIC_URL}/facebook - -"Your public url for the Google Webhook is:" -${GOOGLE_WEBHOOK_PUBLIC_URL}/google - -"Your public url for the Twilio Webhook is:" -${TWILIO_WEBHOOK_PUBLIC_URL}/twilio - -"You can access the API of Airy Core at:" -"http://airy.core/" - -"Example:" -"curl -X POST -H 'Content-Type: application/json' -d '{\"first_name\": \"Grace\",\"last_name\": \"Hopper\",\"password\": \"the_answer_is_42\",\"email\": \"grace@example.com\"}' http://airy.core/users.signup -``` - -To inspect the status of the Vagrant box run: - -```sh -cd infrastructure -vagrant status -``` - -### Overwrite default CPUs and memory - -You can specify number of CPU and memory (in MB) you want to use for your Airy Core box with the following ENV variables: - -```sh -AIRY_CORE_CPUS=2 AIRY_CORE_MEMORY=4096 ./scripts/bootstrap.sh -``` - -### Inspect Kubernetes - -```sh -cd infrastructure -vagrant ssh -kubectl get pods -``` - -### Start, stop, restart - -You can stop, start or restart the Airy Core box with the following -commands: - -```sh -cd infrastructure -vagrant halt -vagrant up -vagrant reload -``` - -:::note - -If you bootstrapped your Airy Core with custom CPU/RAM values, you must specify them again when you restart your box. - -```sh -cd infrastructure -vagrant halt -AIRY_CORE_CPUS=2 AIRY_CORE_MEMORY=4096 vagrant up -``` - -::: - -### Re-create the environment - -You can delete and re-create the whole environment with the following commands: - -```sh -cd infrastructure -vagrant destroy -vagrant up -``` - -## Known Issues - -If you have just installed VirtualBox and see this error during the bootstrap -you should [give VirtualBox -permissions](https://www.howtogeek.com/658047/how-to-fix-virtualboxs-%E2%80%9Ckernel-driver-not-installed-rc-1908-error/). - -``` -There was an error while executing `VBoxManage`, a CLI used by Vagrant -for controlling VirtualBox. The command and stderr is shown below. -Command: ["hostonlyif", "create"] -Stderr: 0%... -Progress state: NS_ERROR_FAILURE -VBoxManage: error: Failed to create the host-only adapter -``` diff --git a/docs/docs/getting-started/introduction.md b/docs/docs/getting-started/introduction.md index c3ff5d2e8a..2391dd9dc2 100644 --- a/docs/docs/getting-started/introduction.md +++ b/docs/docs/getting-started/introduction.md @@ -1,5 +1,5 @@ --- -title: Introduction +title: Welcome to Airy! sidebar_label: Introduction slug: / --- @@ -14,6 +14,7 @@ import HighVoltageSVG from "@site/static/icons/high-voltage.svg"; import ElectricPlugSVG from "@site/static/icons/electric-plug.svg"; import FishingPoleSVG from "@site/static/icons/fishing-pole.svg"; import GearSVG from "@site/static/icons/gear.svg"; +import AiryBubbleSVG from "@site/static/icons/airy-bubble.svg"; ## What is Airy Core? @@ -49,7 +50,17 @@ conversational data to wherever you need it. } + icon={} + title='What is Airy?' + description='Learn about Airys messaging platform and components' + link='getting-started/components' +/> + + + +} + iconInvertible={true} title='Start Installation' description='Install Airy with our CLI, locally or in the cloud' link='cli/installation' @@ -60,7 +71,8 @@ Once you have Airy installed, follow our Quick Start for guidance. } + icon={} + iconInvertible={true} title='To the Quick Start' description='Learn the Airy Basics with our Quick Start' link='getting-started/quickstart' @@ -73,46 +85,3 @@ We'll guide you through the following journey: - Send Messages - Use the API to list conversations - Consume directly from Kafka - -## Airy Core Components - -The platform contains the following core components: - - - } - title='Connectors for all conversational sources' - description="Connect anything from our free open-source live chat plugin, Facebook Messenger, Google's Business Messages to your Airy Core. 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." - link='/sources/introduction' -/> - } - title='APIs to access your data' - description="An API to access conversational data with blazing fast HTTP endpoints." - link='/api/endpoints/introduction' -/> - } - title='WebSockets to power real-time applications' - description="A WebSocket server that allows clients to receive near real-time updates about data flowing through the system." - link='/api/websocket' -/> - } - title='Webhook integration to connect custom apps' - description="A webhook integration server that allows its users to programmatically participate in conversations by sending messages (the webhook integration exposes events users can listen to and react programmatically.)" - link='/api/webhook' -/> - } - 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." - link='/apps/ui/introduction' -/> - } - title='Integrations' - description="Pre-made integrations into popular conversational tools, for example NLP tools like Rasa" - link='/integrations/rasa' -/> - diff --git a/docs/docs/getting-started/quickstart.md b/docs/docs/getting-started/quickstart.md index c3c1caa84d..7c2fb6d1d5 100644 --- a/docs/docs/getting-started/quickstart.md +++ b/docs/docs/getting-started/quickstart.md @@ -25,7 +25,7 @@ directly in Apache Kafka. - [Step 4: Consume directly from Apache Kafka](#step-4-consume-directly-from-apache-kafka) } +icon={} title='Did you already install the Airy CLI?' description='To get going with the Quickstart, you must install Airy first. Once the CLI is up and running you are good to go.' link='/getting-started/installation/introduction' @@ -45,11 +45,11 @@ token=$(echo $(curl -H 'Content-Type: application/json' -d \ "{ \ \"email\":\"grace@example.com\", \ \"password\":\"the_answer_is_42\" \ -}" airy.core/users.login) | jq -r '.token') +}" http://airy.core/users.login) | jq -r '.token') curl -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d \ "{ \"name\": \"chat plugin source\" -}" airy.core/channels.chatplugin.connect +}" http://airy.core/channels.chatplugin.connect ``` channels_connect @@ -81,7 +81,7 @@ created. it should return the message you have just sent. ```bash curl -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d "{}" \ -airy.core/conversations.list | jq . +http://airy.core/conversations.list | jq . ``` ## Step 4: Consume directly from Apache Kafka @@ -90,7 +90,6 @@ You can also consume the messages directly from the Kafka `application.communication.messages` topic: ``` -cd infrastructure && vagrant ssh kubectl exec -it kafka-0 -- /bin/bash kafka-console-consumer \ --bootstrap-server airy-cp-kafka:9092 \ diff --git a/docs/docs/getting-started/troubleshooting.md b/docs/docs/getting-started/troubleshooting.md index 5acd0f3884..39d89a5374 100644 --- a/docs/docs/getting-started/troubleshooting.md +++ b/docs/docs/getting-started/troubleshooting.md @@ -26,7 +26,7 @@ out for help. Our community and Core developers are often logged in and ready to answer questions. } +icon={} title='Join our community' description='Ask questions and get answers from the community and our developers' link='https://airy.co/community' @@ -38,7 +38,7 @@ Looking for help & tips running a full scale messaging platform? Airy’s Suppor engineers can help. } +icon={} title='Contact Airy Support' description='Fill out our contact form to get quick support' link='https://airy.co/get-a-demo' diff --git a/docs/docs/integrations/rasa.md b/docs/docs/integrations/rasa.md index 195086707c..76d49e1519 100644 --- a/docs/docs/integrations/rasa.md +++ b/docs/docs/integrations/rasa.md @@ -86,7 +86,7 @@ The connector requires the following configuration values: - `auth_token` the Airy Core JWT token you used to connect the webhook. -- `api_host` The url where you host your Airy Core API (`http://airy.core` for a local installation). +- `api_host` The url where you host your Airy Core API. Add them to your existing Rasa `credentials.yml`: diff --git a/docs/docs/sources/chatplugin/installation.md b/docs/docs/sources/chatplugin/installation.md index 66e1735fd6..bfc0a82952 100644 --- a/docs/docs/sources/chatplugin/installation.md +++ b/docs/docs/sources/chatplugin/installation.md @@ -1,5 +1,5 @@ --- -title: Installation +title: Install the Airy Live Chat Plugin on your website sidebar_label: Installation --- @@ -23,7 +23,7 @@ the `` section: You must replace `CHANNEL_ID` with the channel id obtained when [connecting](#connecting-a-channel) the source and `SCRIPT_HOST` with the host -of your Chat Plugin server. When using the local vagrant environment +of your Chat Plugin server. When using the local minikube environment `SCRIPT_HOST` must be set to `airy.core`. :::note diff --git a/docs/docs/sources/chatplugin/overview.md b/docs/docs/sources/chatplugin/overview.md index 0598646870..7b891d995e 100644 --- a/docs/docs/sources/chatplugin/overview.md +++ b/docs/docs/sources/chatplugin/overview.md @@ -1,5 +1,5 @@ --- -title: Overview +title: ChatPlugin Overview sidebar_label: Overview --- @@ -33,7 +33,7 @@ Out of the box Airy’s Live Chat Plugin supports: ## How it's build The Airy Live Chat Plugin is JavaScript library built with -[preact](https://preactjs.com/)and +[preact](https://preactjs.com/) and [TypeScript](https://www.typescriptlang.org/). The library makes heavy use of [render @@ -48,7 +48,7 @@ Completely customize your Live Chat and make it match your brand: } + icon={} title='Learn more about the customization of your Chat Plugin' description='From colors to shapes and sizes: everything is editable' link='/sources/chatplugin/customization' diff --git a/docs/docs/sources/chatplugin/quickstart.md b/docs/docs/sources/chatplugin/quickstart.md index 6b07ec9d84..a9c367dcea 100644 --- a/docs/docs/sources/chatplugin/quickstart.md +++ b/docs/docs/sources/chatplugin/quickstart.md @@ -25,13 +25,13 @@ directly in Apache Kafka. - [Step 4: Consume directly from Apache Kafka](#step-4-consume-directly-from-apache-kafka) } +icon={} title='Did you already install the Airy CLI?' description='To get going with the Quickstart, you must install Airy first. Once the CLI is up and running you are good to go.' link='/getting-started/installation/introduction' /> -## Step 1: How to setup your first source +## Step 1: Set up your first source Once you [signed up](/api/endpoints/users.md#signup), you must [log in](/api/authentication.md#login) so you can obtain a valid JWT token for the @@ -42,11 +42,11 @@ token=$(echo $(curl -H 'Content-Type: application/json' -d \ "{ \ \"email\":\"grace@example.com\", \ \"password\":\"the_answer_is_42\" \ -}" airy.core/users.login) | jq -r '.token') +}" http://airy.core/users.login) | jq -r '.token') curl -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d \ "{ \"name\": \"chat plugin source\" -}" airy.core/channels.chatplugin.connect +}" http://airy.core/channels.chatplugin.connect ``` channels_connect @@ -76,9 +76,9 @@ created. it should return the message you have just sent. conversations.list -```bash +```sh curl -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d "{}" \ -airy.core/conversations.list | jq . +http://airy.core/conversations.list | jq . ``` ## Step 4: Consume directly from Apache Kafka @@ -87,7 +87,6 @@ You can also consume the messages directly from the Kafka `application.communication.messages` topic: ``` -cd infrastructure && vagrant ssh kubectl exec -it kafka-0 -- /bin/bash kafka-console-consumer \ --bootstrap-server airy-cp-kafka:9092 \ diff --git a/docs/docs/sources/debugging-twilio.mdx b/docs/docs/sources/debugging-twilio.mdx new file mode 100644 index 0000000000..17cc759812 --- /dev/null +++ b/docs/docs/sources/debugging-twilio.mdx @@ -0,0 +1,25 @@ +import useBaseUrl from '@docusaurus/useBaseUrl'; + +If the channel has been successfully connected, but the message you sent does not appear in a conversation and a conversation is not created, please see below for debugging advices. + +:::note + +If you encounter errors, please follow these debugging advices: + +- make sure that the authToken and accountSid tokens you have added to the airy.yaml file (refer back to step 1) have been applied to your Airy Core instance. + An Airy Core instance should be created after editing the airy.yaml file. + +- verify your webhook integration (refer back to step 2). Make sure that your Twilio Webhook URL has been correctly added to your phone number in your Twilio dashboard (check for typos). + The Webhook URL should be added in the 'Messaging', 'incoming message' section. + +- check the messaging logs in your Twilio dashboard (see screenshot below): select your phone number from the phone numbers list, + click on the Messaging Logs tab, and select Incoming from the dropdown. The [error codes](https://www.twilio.com/docs/api/errors) from the logs + are precious hints of API errors' causes. + +``` +https://www.twilio.com/console/phone-numbers/instance/INSTANCE_STRING +``` + +Twilio webhook + +::: diff --git a/docs/docs/sources/facebook.md b/docs/docs/sources/facebook.md index 4e825fa364..0a3b7f949c 100644 --- a/docs/docs/sources/facebook.md +++ b/docs/docs/sources/facebook.md @@ -74,7 +74,7 @@ Refer to the [Configuration Docs](/getting-started/installation/configuration.md#components) on how to input these values. -Refer to the [test](getting-started/installation/vagrant.md#connect-sources) +Refer to the [test](getting-started/installation/minikube.md#connect-sources) guide or the [production](getting-started/installation/production.md#connect-sources) one to set these variables in your Airy Core instance. @@ -116,12 +116,10 @@ This will open a modal box: add your Callback URL (your instance's Facebook Webh Your Facebook Webhook URL should have the following format: ``` -`https://fb-RANDOM_STRING.tunnel.airy.co/facebook` +`https://RANDOM_STRING.tunnel.airy.co/facebook` ``` -Refer to [our section on the Vagrant box -status](/getting-started/installation/vagrant#status) for information about how -to find your Facebook Webhook URL. +Refer to [the section on public webhooks](/getting-started/installation/minikube#public-webhooks) for information about how to find your Facebook Webhook URL. ::: @@ -131,7 +129,7 @@ successfully set to your Airy Core instance. :::note -Refer to the [test](/getting-started/installation/vagrant.md#connect-sources) +Refer to the [test](/getting-started/installation/minikube.md#connect-sources) guide or the [production](/getting-started/installation/production.md#connect-sources) one to set these variables in your Airy Core instance. @@ -191,7 +189,7 @@ Success! You are now ready to connect a Facebook page to your Airy Core instance The next step is to send a request to the [Channels endpoint](/api/endpoints/channels#facebook) to connect a Facebook page to your instance. } +icon={} title='Channels endpoint' description='Connect a Facebook source to your Airy Core instance through the Channels endpoint' link='api/endpoints/channels#facebook' @@ -207,26 +205,6 @@ import ConnectFacebook from '../api/endpoints/connect-facebook.mdx' After connecting the source to your instance, you will be able to send messages through the [Messages endpoint](/api/endpoints/messages#send). - } -title='Messages endpoint' -description='Send messages to your Airy Core instance from a Facebook source through the Messages endpoint' -link='api/endpoints/messages#send' -/> - -
- -import MessagesSend from '../api/endpoints/messages-send.mdx' +import InboxMessages from './inbox-messages.mdx' - - -## Send and receive messages with the Inbox UI - -Now that you connected Facebook Messenger to your instance and started a conversation, you can see the conversations, messages, and templates in the [Airy Inbox](/apps/ui/inbox), and use it to respond to the messages. - - } -title='Inbox' -description='Receive messages from a Facebook source and send messages using the Inbox UI' -link='apps/ui/inbox' -/> + diff --git a/docs/docs/sources/google.md b/docs/docs/sources/google.md index cedc85ec1d..3bc7f05fd3 100644 --- a/docs/docs/sources/google.md +++ b/docs/docs/sources/google.md @@ -70,7 +70,7 @@ Once the verification process has been completed, Google's Business Messages wil After the configuration, you can connect Google's Business Messages source to your instance by sending a request to the [Channels endpoint](/api/endpoints/channels#google). } +icon={} title='Channels endpoint' description="Connect Google's Business Messages source to your Airy Core instance through the Channels endpoint" link='api/endpoints/channels#google' @@ -86,28 +86,6 @@ import ConnectGoogle from '../api/endpoints/connect-google.mdx' After connecting the source to your instance, you will be able to send messages through the [Messages endpoint](/api/endpoints/messages#send). - } -title='Messages endpoint' -description="Send messages to your Airy Core instance from Google's Business Messages through the Messages endpoint" -link='api/endpoints/messages#send-from-googles-business-messages-source' -/> - -
- -import GoogleMessagesSend from '../api/endpoints/google-messages-send.mdx' - - +import InboxMessages from './inbox-messages.mdx' -## Send and receive messages with the Inbox UI - -Now that you connected Google's Business Messages to your instance and started a conversation, you can see the conversations, messages in the [Airy Inbox](/apps/ui/inbox), and use it to respond to the messages. - -The [Inbox's UI](/apps/ui/inbox) is able render the messages types you can send from Google's Business Messages, such as Rich Cards or Suggestions. - - } -title='Inbox' -description="Receive messages from Google's Business Messages and send messages using the Inbox UI" -link='apps/ui/inbox' -/> + diff --git a/docs/docs/sources/inbox-messages.mdx b/docs/docs/sources/inbox-messages.mdx new file mode 100644 index 0000000000..affcbe24a4 --- /dev/null +++ b/docs/docs/sources/inbox-messages.mdx @@ -0,0 +1,32 @@ +import ButtonBox from '@site/src/components/ButtonBox'; +import InboxSVG from '@site/static/icons/prototype.svg'; +import BoltSVG from '@site/static/icons/bolt.svg'; + +Once the conversation has been successfully created, you will be able to send messages through +the [Messages endpoint](/api/endpoints/messages#send). + + } + title="Messages endpoint" + description="Send messages to your Airy Core instance from different sources through the Messages endpoint" + link="api/endpoints/messages#send" +/> + +
+ +import MessagesSend from '../api/endpoints/messages-send.mdx'; + + + +## Send and receive messages with the Inbox UI + +Now that you connected this source to your instance and started a conversation, +you can see the conversations, messages, and templates in the [Airy Inbox](/apps/ui/inbox), +and use it to respond to the messages. + + } + title="Inbox" + description="Receive messages from different sources and send messages using the Inbox UI" + link="apps/ui/inbox" +/> diff --git a/docs/docs/sources/introduction.md b/docs/docs/sources/introduction.md index c751d35755..41e544d2ec 100644 --- a/docs/docs/sources/introduction.md +++ b/docs/docs/sources/introduction.md @@ -27,35 +27,35 @@ Business Messages, and so on). } + icon={} title='Airy Live Chat Plugin' description='The Airy Live Chat Plugin enables conversations with website visitors through a web chat plugin' link='/sources/chatplugin/overview' /> } +icon={} title='Facebook Messenger' description='Send and receive messages from Facebook Pages' link='sources/facebook' /> } +icon={} title='Google’s Business Messages' description='Start conversations from Google Maps & Google Search' link='sources/google' /> } +icon={} title='WhatsApp Business API' description='Connect with more than 1.5 billion people on WhatsApp' link='sources/whatsapp-twilio' /> } +icon={} title='SMS' description='Connect Text Messaging to Airy & send and receive SMS' link='sources/sms-twilio' diff --git a/docs/docs/sources/sms-twilio.md b/docs/docs/sources/sms-twilio.md index 09a4bc3ee1..66af639637 100644 --- a/docs/docs/sources/sms-twilio.md +++ b/docs/docs/sources/sms-twilio.md @@ -1,9 +1,12 @@ --- title: SMS via Twilio -sidebar_label: SMS +sidebar_label: Twilio SMS --- import TLDR from "@site/src/components/TLDR"; +import useBaseUrl from '@docusaurus/useBaseUrl'; +import ButtonBox from "@site/src/components/ButtonBox"; +import SuccessBox from "@site/src/components/SuccessBox"; @@ -11,17 +14,91 @@ import TLDR from "@site/src/components/TLDR"; +This document provides a step by step guide to integrate a Twilio SMS source with your Airy +Core Platform instance. + The Twilio SMS source provides a channel for sending and receiving SMS using the [Twilio API](https://www.twilio.com/). -:::note +:::tip What you will learn -This document assumes that you have a Twilio account. +- The required steps to configure the Twilio SMS source +- How to connect a Twilio SMS source to Airy Core ::: ## Configuration -import TwilioSource from './twilio-source.mdx' +import TwilioConfig from './twilio-config.mdx' + + + +import TwilioSources from './twilio-sources.mdx' + + + + + +Success! You are now ready to connect a Twilio.SMS source to your Airy Core instance 🎉 + + + +### Connect a Twilio provider to your instance + +There are 2 options to connect a Twilio.SMS source to your instance: + +- you can connect the source via an API request (using curl or platforms such as Postman) +- you can connect the source via the UI + +We cover both options in this document. + +## Connect a Twilio.SMS source via API request + +You connect connect a Twilio.SMS source by sending a request to the Channels endpoint. + + } +title='Channels endpoint' +description='Connect a Twilio SMS source to your Airy Core instance through the Channels endpoint' +link='api/endpoints/channels#sms' +/> + +
+ + + +import ConnectTwilioSms from '../api/endpoints/connect-twilioSms.mdx' + +## Connect a Twilio.SMS source via the UI + +You can connect a Twilio.SMS source via your Airy Core instance UI. + +On your instance's Airy Core UI, click on 'Channels' on the left sidebar menu and select the SMS channel. Add your Twilio phone number in the Twilio Phone Number field. You can optionally add a name and an image. + +``` +http://localhost:8080/ui/channels +``` + +TwilioSMS connect + +Your twilio.sms channel will appear as connected in the UI. + +``` +http://localhost:8080/ui/channels/connected/twilio.sms/ +``` + +## Send and receive messages with the Inbox UI + +After connecting the source to your instance, it's time to create a conversation between your +Airy Core instance and a Twilio.SMS source. + +Send a text message (SMS) from a mobile phone to the Twilio phone number you have used. +This will create a conversation: a Twilio.SMS conversation will appear in the UI with the text message you have sent. + +import DebuggingTwilio from './debugging-twilio.mdx' + + + +import InboxMessages from './inbox-messages.mdx' - + diff --git a/docs/docs/sources/twilio-config.mdx b/docs/docs/sources/twilio-config.mdx new file mode 100644 index 0000000000..53c4a2268d --- /dev/null +++ b/docs/docs/sources/twilio-config.mdx @@ -0,0 +1,12 @@ +[Twilio](https://www.twilio.com/) is a communication platform specialized in phone services. Airy Core supports Twilio as a provider of 2 different sources: Twilio WhatsApp and Twilio SMS. +The Twilio provider requires the following configuration: + +- [Configuration](#configuration) + - [Step 1: Find the authToken and accountSid](#step-1-find-the-authToken-and-accountSid) + - [Step 2: Configure the webhook integration](#step-2-configure-the-webhook-integration) +- [Connect a Twilio provider to your instance](#connect-a-twilio-provider-to-your-instance) +- [Send and receive messages with the Inbox UI](#send-and-receive-messages-with-the-inbox-ui) + +Let's proceed step by step. + +First, to connect a Twilio provider to your Airy Core instance, you must have a Twilio account and a phone number. If this is not your case, head over to [Twilio's website](https://www.twilio.com/): create an account and buy a phone number. diff --git a/docs/docs/sources/twilio-source.mdx b/docs/docs/sources/twilio-source.mdx deleted file mode 100644 index c6bf5389f1..0000000000 --- a/docs/docs/sources/twilio-source.mdx +++ /dev/null @@ -1,8 +0,0 @@ -You must create a [Twilio auth -token](https://support.twilio.com/hc/en-us/articles/223136027-Auth-Tokens-and-How-to-Change-Them) -and add it to `infrastructure/airy.yaml` together with your account SID: - -``` -authToken= -accountSid= -``` diff --git a/docs/docs/sources/twilio-sources.mdx b/docs/docs/sources/twilio-sources.mdx new file mode 100644 index 0000000000..f2647f2c64 --- /dev/null +++ b/docs/docs/sources/twilio-sources.mdx @@ -0,0 +1,70 @@ +import useBaseUrl from '@docusaurus/useBaseUrl'; +import SuccessBox from '@site/src/components/SuccessBox'; + +### Step 1: Find the authToken and accountSid + +Once you have a Twilio account, log in, and find your [Twilio auth +token account SID](https://support.twilio.com/hc/en-us/articles/223136027-Auth-Tokens-and-How-to-Change-Them) +on your Twilio dashboard (underlined in red in the screenshot below). + +``` +https://www.twilio.com/console +``` + +Twilio token + +Copy and paste your `Auth token` and `Account Sid` as strings below `sources/twilio` in `infrastructure/airy.yaml`. + +Make sure the `Auth token` and `Account Sid` are contained within a pair of either single quotation marks '' or double quotation marks ""; +paste the tokens as they are and do not add or remove additional spaces. + +``` +twilio: +authToken= +accountSid= +``` + +:::note + +Refer to the [Configuration +Docs](/getting-started/installation/configuration.md#components) on how to input +these values. + +Refer to the [test](getting-started/installation/minikube.md#connect-sources) +guide or the +[production](getting-started/installation/production.md#connect-sources) one to +set these variables in your Airy Core instance. + +::: + +### Step 2: Configure the webhook integration + +After editing the `airy.yaml` file, create an [Airy Core instance](/getting-started/installation/Minikube). + +You are now ready to configure the Webhook. + +On the Twilio dashboard, open the left menu sidebar and go to the Phone numbers section. + +Select the phone number that you want to use and copy and paste your Twilio Webhook URL as the Webhook URL of your phone number (in 'Messaging', below 'A MESSAGE +COMES IN' - 'Webhook' should be selected from the dropdown). + +``` +https://www.twilio.com/console/phone-numbers +``` + +Twilio webhook + +
+
+ +:::note + +Your Twilio Webhook URL should have the following format: + +``` +https://RANDOM_STRING.tunnel.airy.co/twilio +``` + +Refer to [the section on public webhooks](/getting-started/installation/minikube#public-webhooks) for information about Webhook URLs. + +::: diff --git a/docs/docs/sources/whatsapp-twilio.md b/docs/docs/sources/whatsapp-twilio.md index b553a0ffa5..176f934944 100644 --- a/docs/docs/sources/whatsapp-twilio.md +++ b/docs/docs/sources/whatsapp-twilio.md @@ -4,6 +4,8 @@ sidebar_label: WhatsApp Business API --- import TLDR from "@site/src/components/TLDR"; +import SuccessBox from "@site/src/components/SuccessBox"; +import ButtonBox from "@site/src/components/ButtonBox"; @@ -14,15 +16,82 @@ Connect with **2 Billion WhatsApp users** via messages and templates. The Twilio WhatsApp source provides a channel for sending and receiving WhatsApp messages using the [Twilio API](https://www.twilio.com/). -:::note +:::tip What you will learn -This document assumes that you have a Twilio account connected to -[WhatsApp](https://www.twilio.com/whatsapp). +- The required steps to configure the Twilio WhatsApp source +- How to connect a Twilio WhatsApp source to Airy Core ::: ## Configuration -import TwilioSource from './twilio-source.mdx' +import TwilioConfig from './twilio-config.mdx' - + + +Then, you need to request access to enable your Twilio number for WhatsApp: complete the request form on [Twilio's webiste](https://www.twilio.com/whatsapp/request-access). To request access, you +need to have a Facebook Business Manager ID: read [Twilio's documentation](https://www.twilio.com/docs/whatsapp/api) for more information on the request process. + +import TwilioSources from './twilio-sources.mdx' + + + + + +Success! You are now ready to connect a Twilio.WhatsApp source to your Airy Core instance 🎉 + + + +### Connect a Twilio provider to your instance + +There are 2 options to connect a Twilio.WhatsApp source to your instance: + +- you can connect the source via an API request (using curl or platforms such as Postman) +- you can connect the source via the UI + +We cover both options in this document. + +## Connect a Twilio.WhatsApp source via API request + +You connect connect a Twilio.WhatsApp source by sending a request to the Channels endpoint. + + } +title='Channels endpoint' +description='Connect a Twilio.WhatsApp source to your Airy Core instance through the Channels endpoint' +link='api/endpoints/channels#whatsapp' +/> + +
+ + + +import ConnectTwilioWhatsApp from '../api/endpoints/connect-twilioWhatsApp.mdx' + +## Connect a Twilio.WhatsApp source via the UI + +You can connect a Twilio.WhatsApp source via your Airy Core instance UI. + +On your instance's Airy Core UI, click on 'Channels' on the left sidebar menu and select the WhatsApp channel. Add your Twilio phone number in the Twilio Phone Number field. You can optionally add a name and an image. + +``` +http://localhost:8080/ui/channels +``` + +Your twilio.whatsApp channel will appear as connected in the UI. + +## Send and receive messages with the Inbox UI + +After connecting the source to your instance, it's time to create a conversation between your +Airy Core instance and the Twilio.WhatsApp source. + +Send a text message (via WhatsApp) from a mobile phone to the Twilio phone number you have used. +This will create a conversation: a Twilio.WhatsApp conversation will appear in the UI with the text message you have sent. + +import DebuggingTwilio from './debugging-twilio.mdx' + + + +import InboxMessages from './inbox-messages.mdx' + + diff --git a/docs/sidebars.js b/docs/sidebars.js index 6ac684e00d..386709ae20 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -3,16 +3,17 @@ module.exports = { { '🚀 Getting Started': [ 'getting-started/introduction', + 'getting-started/components', { Installation: [ 'getting-started/installation/introduction', - 'getting-started/installation/vagrant', + 'getting-started/installation/minikube', 'getting-started/installation/production', 'getting-started/installation/configuration', ], }, { - 'Command Line Interface': ['cli/introduction', 'cli/installation', 'cli/reference'], + 'Command Line Interface': ['cli/installation', 'cli/reference'], }, 'getting-started/quickstart', 'getting-started/troubleshooting', @@ -59,11 +60,7 @@ module.exports = { ], }, { - '✨ Apps': [ - { - UI: ['apps/ui/introduction', 'apps/ui/quickstart', 'apps/ui/inbox', 'apps/ui/tags', 'apps/ui/components'], - }, - ], + '💎 UI': ['apps/ui/introduction', 'apps/ui/quickstart', 'apps/ui/inbox', 'apps/ui/tags', 'apps/ui/components'], }, { '🛠️ Integrations': [ diff --git a/docs/src/components/ButtonBox/index.js b/docs/src/components/ButtonBox/index.js index e78d7638f0..71029227a5 100644 --- a/docs/src/components/ButtonBox/index.js +++ b/docs/src/components/ButtonBox/index.js @@ -15,7 +15,16 @@ const adjust = (color, amount) => { ); }; -const ButtonBox = ({children, icon, title, description, link, customizedBackgroundColor, customizedHoverColor}) => { +const ButtonBox = ({ + children, + icon, + iconInvertible, + title, + description, + link, + customizedBackgroundColor, + customizedHoverColor, +}) => { const {isDarkTheme} = useThemeContext(); if (customizedBackgroundColor) { @@ -31,7 +40,7 @@ const ButtonBox = ({children, icon, title, description, link, customizedBackgrou to={useBaseUrl(link)} className={isDarkTheme ? styles.containerDark : styles.containerLight} style={{backgroundColor: customizedBackgroundColor, boxShadow: `0px 0px 0px 4px ${customizedHoverColor}`}}> - {icon && icon()} + {icon}

{title}

diff --git a/docs/src/components/ButtonBox/styles.module.css b/docs/src/components/ButtonBox/styles.module.css index b5f98710aa..0082f86c25 100644 --- a/docs/src/components/ButtonBox/styles.module.css +++ b/docs/src/components/ButtonBox/styles.module.css @@ -69,3 +69,7 @@ margin-right: 12px; fill: white; } + +.invertedIcon { + filter: invert(100%); +} diff --git a/docs/src/components/Script/index.js b/docs/src/components/Script/index.js new file mode 100644 index 0000000000..2496317fed --- /dev/null +++ b/docs/src/components/Script/index.js @@ -0,0 +1,24 @@ +import React, {useEffect} from 'react'; + +export default ({src, id, ...props}) => { + const anchorId = 'script-anchor-' + id; + useEffect(() => { + const script = document.createElement('script'); + + script.src = src; + script.async = true; + script.id = id; + + Object.keys(props).forEach(key => { + script.setAttribute(key, props[key]); + }); + + document.getElementById(anchorId).appendChild(script); + + return () => { + document.getElementById(anchorId).removeChild(script); + }; + }, [src, id]); + + return

; +}; diff --git a/docs/static/icons/githubIcon.svg b/docs/static/icons/githubIcon.svg new file mode 100644 index 0000000000..d452f377ca --- /dev/null +++ b/docs/static/icons/githubIcon.svg @@ -0,0 +1,3 @@ + + GitHub icon + \ No newline at end of file diff --git a/docs/static/icons/minikube.svg b/docs/static/icons/minikube.svg new file mode 100644 index 0000000000..070a783e08 --- /dev/null +++ b/docs/static/icons/minikube.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/static/icons/vagrant.svg b/docs/static/icons/vagrant.svg deleted file mode 100644 index 8879caff3e..0000000000 --- a/docs/static/icons/vagrant.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/static/img/apps/ui/FacebookButtonTemplate.png b/docs/static/img/apps/ui/FacebookButtonTemplate.png new file mode 100644 index 0000000000..52a34aaff6 Binary files /dev/null and b/docs/static/img/apps/ui/FacebookButtonTemplate.png differ diff --git a/docs/static/img/apps/ui/FacebookTemplatePayload.png b/docs/static/img/apps/ui/FacebookTemplatePayload.png new file mode 100644 index 0000000000..fb97ec61bc Binary files /dev/null and b/docs/static/img/apps/ui/FacebookTemplatePayload.png differ diff --git a/docs/static/img/apps/ui/FilteringInbox.gif b/docs/static/img/apps/ui/FilteringInbox.gif new file mode 100644 index 0000000000..d8eca49d05 Binary files /dev/null and b/docs/static/img/apps/ui/FilteringInbox.gif differ diff --git a/docs/static/img/apps/ui/GoogleRichCardPayload.png b/docs/static/img/apps/ui/GoogleRichCardPayload.png new file mode 100644 index 0000000000..4c4b35030a Binary files /dev/null and b/docs/static/img/apps/ui/GoogleRichCardPayload.png differ diff --git a/docs/static/img/apps/ui/RichCardCarousel.gif b/docs/static/img/apps/ui/RichCardCarousel.gif new file mode 100644 index 0000000000..e24a530b9a Binary files /dev/null and b/docs/static/img/apps/ui/RichCardCarousel.gif differ diff --git a/docs/static/img/apps/ui/RichCardCarouselExample.png b/docs/static/img/apps/ui/RichCardCarouselExample.png new file mode 100644 index 0000000000..b2a5f95d6f Binary files /dev/null and b/docs/static/img/apps/ui/RichCardCarouselExample.png differ diff --git a/docs/static/img/apps/ui/create_tags.gif b/docs/static/img/apps/ui/create_tags.gif new file mode 100644 index 0000000000..20164b1c26 Binary files /dev/null and b/docs/static/img/apps/ui/create_tags.gif differ diff --git a/docs/static/img/apps/ui/demotags.gif b/docs/static/img/apps/ui/demotags.gif deleted file mode 100644 index b7c99bb4e6..0000000000 Binary files a/docs/static/img/apps/ui/demotags.gif and /dev/null differ diff --git a/docs/static/img/apps/ui/login.gif b/docs/static/img/apps/ui/login.gif index 5ac41431bd..cd86e5ac67 100644 Binary files a/docs/static/img/apps/ui/login.gif and b/docs/static/img/apps/ui/login.gif differ diff --git a/docs/static/img/apps/ui/storybookExample.png b/docs/static/img/apps/ui/storybookExample.png new file mode 100644 index 0000000000..53b81c3d93 Binary files /dev/null and b/docs/static/img/apps/ui/storybookExample.png differ diff --git a/docs/static/img/getting-started/quickstart/chatplugin.gif b/docs/static/img/getting-started/quickstart/chatplugin.gif index 8497c3a427..f353e1bb06 100644 Binary files a/docs/static/img/getting-started/quickstart/chatplugin.gif and b/docs/static/img/getting-started/quickstart/chatplugin.gif differ diff --git a/docs/static/img/sources/chatplugin/chatplugin.gif b/docs/static/img/sources/chatplugin/chatplugin.gif index 8497c3a427..f353e1bb06 100644 Binary files a/docs/static/img/sources/chatplugin/chatplugin.gif and b/docs/static/img/sources/chatplugin/chatplugin.gif differ diff --git a/docs/static/img/sources/twilio/twilio_debug.png b/docs/static/img/sources/twilio/twilio_debug.png new file mode 100644 index 0000000000..c2397354eb Binary files /dev/null and b/docs/static/img/sources/twilio/twilio_debug.png differ diff --git a/docs/static/img/sources/twilio/twilio_token.png b/docs/static/img/sources/twilio/twilio_token.png new file mode 100644 index 0000000000..d484227aa8 Binary files /dev/null and b/docs/static/img/sources/twilio/twilio_token.png differ diff --git a/docs/static/img/sources/twilio/twilio_ui.png b/docs/static/img/sources/twilio/twilio_ui.png new file mode 100644 index 0000000000..b23415f5c1 Binary files /dev/null and b/docs/static/img/sources/twilio/twilio_ui.png differ diff --git a/docs/static/img/sources/twilio/twilio_webhook.png b/docs/static/img/sources/twilio/twilio_webhook.png new file mode 100644 index 0000000000..1389907356 Binary files /dev/null and b/docs/static/img/sources/twilio/twilio_webhook.png differ diff --git a/docs/static/img/sources/twilioSMS/twilioSMS_ui.png b/docs/static/img/sources/twilioSMS/twilioSMS_ui.png new file mode 100644 index 0000000000..88abf755ec Binary files /dev/null and b/docs/static/img/sources/twilioSMS/twilioSMS_ui.png differ diff --git a/frontend/assets/images/icons/sms_avatar.svg b/frontend/assets/images/icons/sms_avatar.svg index aadff40f69..d381c759e0 100644 --- a/frontend/assets/images/icons/sms_avatar.svg +++ b/frontend/assets/images/icons/sms_avatar.svg @@ -3,7 +3,7 @@ Group 16 - + diff --git a/frontend/chat-plugin/README.md b/frontend/chat-plugin/README.md index f4b1ddb745..e485668202 100644 --- a/frontend/chat-plugin/README.md +++ b/frontend/chat-plugin/README.md @@ -4,8 +4,8 @@ This app demos a minimal frontend that allows contacts to communicate with the [ ## Develop -Run with [Bazelisk](https://github.com/bazelbuild/bazelisk) +Requires [Bazelisk](https://github.com/bazelbuild/bazelisk) and a running [minikube provider](/docs/docs/getting-started/installation/minikube.md) Core instance. ```bash -ibazel run //frontend/chat-plugin:bundle_server +./scripts/web-dev.sh //frontend/chat-plugin:bundle_server ``` diff --git a/frontend/chat-plugin/example.html b/frontend/chat-plugin/example.html index 1d4ec9f632..300a8f006c 100644 --- a/frontend/chat-plugin/example.html +++ b/frontend/chat-plugin/example.html @@ -10,7 +10,7 @@ (function(w, d, s, n) { w[n] = w[n] || {}; w[n].channelId = search.get('channel_id'); - w[n].host = '{{API_HOST}}'; + w[n].host = '{*API_HOST*}'; w[n].welcomeMessage = { fallback: 'Hello!\n\nWelcome to Airy!', richCard: { @@ -41,7 +41,7 @@ var f = d.getElementsByTagName(s)[0], j = d.createElement(s); j.async = true; - j.src = '//' + w[n].host + '/chatplugin/ui/s.js'; + j.src = w[n].host + '/chatplugin/ui/s.js'; f.parentNode.insertBefore(j, f); })(window, document, 'script', 'airy'); diff --git a/frontend/chat-plugin/handles/BUILD b/frontend/chat-plugin/handles/BUILD index e75c7a997b..c11c75ee58 100644 --- a/frontend/chat-plugin/handles/BUILD +++ b/frontend/chat-plugin/handles/BUILD @@ -1,4 +1,7 @@ load("@com_github_airyhq_bazel_tools//web:typescript.bzl", "ts_web_library") +load("@com_github_airyhq_bazel_tools//lint:buildifier.bzl", "check_pkg") + +check_pkg(name = "buildifier") package(default_visibility = ["//visibility:public"]) diff --git a/frontend/chat-plugin/src/api/index.tsx b/frontend/chat-plugin/src/api/index.tsx index 0185ffb45d..68ce62becd 100644 --- a/frontend/chat-plugin/src/api/index.tsx +++ b/frontend/chat-plugin/src/api/index.tsx @@ -8,10 +8,10 @@ declare const window: { }; }; -const API_HOST = window.airy ? window.airy.host : 'airy.core'; +const API_HOST = window.airy ? window.airy.host : process.env.API_HOST; export const sendMessage = (message: TextContent | SuggestionResponse, token: string) => { - return fetch(`//${API_HOST}/chatplugin.send`, { + return fetch(`${API_HOST}/chatplugin.send`, { method: 'POST', body: JSON.stringify(convertToBody(message)), headers: { @@ -39,7 +39,7 @@ const convertToBody = (message: TextContent | SuggestionResponse) => { }; export const getResumeToken = async (channelId: string, authToken: string) => { - const resumeChat = await fetch(`//${API_HOST}/chatplugin.resumeToken`, { + const resumeChat = await fetch(`${API_HOST}/chatplugin.resumeToken`, { method: 'POST', body: JSON.stringify({}), headers: { @@ -53,7 +53,7 @@ export const getResumeToken = async (channelId: string, authToken: string) => { export const start = async (channelId: string, resumeToken: string) => { try { - const response = await fetch(`//${API_HOST}/chatplugin.authenticate`, { + const response = await fetch(`${API_HOST}/chatplugin.authenticate`, { method: 'POST', body: JSON.stringify({ channel_id: channelId, diff --git a/frontend/chat-plugin/src/components/chat/index.tsx b/frontend/chat-plugin/src/components/chat/index.tsx index 415a4b1443..cbcf6d7a98 100644 --- a/frontend/chat-plugin/src/components/chat/index.tsx +++ b/frontend/chat-plugin/src/components/chat/index.tsx @@ -160,11 +160,14 @@ const Chat = (props: Props) => { props.airyMessageProp ? () => props.airyMessageProp(ctrl) : () => ( - + diff --git a/frontend/chat-plugin/src/websocket/index.ts b/frontend/chat-plugin/src/websocket/index.ts index 60c027525e..f796c5c3f3 100644 --- a/frontend/chat-plugin/src/websocket/index.ts +++ b/frontend/chat-plugin/src/websocket/index.ts @@ -17,7 +17,8 @@ declare global { } } -const API_HOST = window.airy ? window.airy.host : 'airy.core'; +const API_HOST = window.airy ? window.airy.host : process.env.API_HOST; +const host = new URL(API_HOST).host; // https: -> wss: and http: -> ws: const protocol = location.protocol.replace('http', 'ws'); @@ -53,7 +54,7 @@ class WebSocket { this.token = token; this.client = new Client({ - brokerURL: `${protocol}//${API_HOST}/ws.chatplugin`, + brokerURL: `${protocol}//${host}/ws.chatplugin`, connectHeaders: { Authorization: `Bearer ${token}`, }, diff --git a/frontend/ui/BUILD b/frontend/ui/BUILD index f9165549c1..f1465aeb06 100644 --- a/frontend/ui/BUILD +++ b/frontend/ui/BUILD @@ -33,9 +33,10 @@ ts_web_library( "@npm//react-window", "@npm//react-window-infinite-loader", "@npm//redux", - "@npm//redux-starter-kit", + "@npm//@reduxjs/toolkit", "@npm//reselect", "@npm//typesafe-actions", + "@npm//camelcase-keys", ], ) diff --git a/frontend/ui/README.md b/frontend/ui/README.md index 2e4faa148e..24c1e1d114 100644 --- a/frontend/ui/README.md +++ b/frontend/ui/README.md @@ -23,31 +23,36 @@ The Airy UI is a fully featured user interactive frontend project that showcases ### Building Airy Demo UI -You can run the Airy Demo UI locally by running the following commands: +You can run the backend required for development of the Airy Demo UI locally by installing Airy Core using the +[minikube provider](/docs/docs/getting-started/installation/minikube.md): + +To ensure that you develop against the latest state of the `create` command you can build and run the executable +from the repository: ``` -$ git clone https://github.com/airyhq/airy -$ cd airy -$ ./scripts/bootstrap.sh (Takes a few minutes) +$ bazel run //infrastructure/cli -- create --provider=minikube ``` -When the bootstrap process finishes, open another terminal and run `$ ibazel run //frontend/demo:bundle_server` +When the bootstrap process finishes, open another terminal and run `$ ./scripts/web-dev.sh //frontend/ui:bundle_server` Then open `http://localhost:8080/` in a web browser to access the Airy Demo UI -### Installation - -The bootstrap installation requires [Vagrant](https://www.vagrantup.com/downloads) and [VirtualBox](https://www.virtualbox.org/wiki/Downloads). If they are not -found, the script `$ ./scripts/bootstrap.sh` will attempt to install them for you. Check out our [test deployment guide](/docs/docs/getting-started/deployment/test-environment.md) for detailed information. - ### Authentication In order to communicate with our API endpoints, you need a valid [JWT](https://jwt.io/) token. To get a valid token you first need to signup using the signup [endpoint](#endpoints) and then login using the login [endpoint](#endpoints). ### Endpoints -To communicate with our signup endpoint and register your email, open another terminal and type in the terminal `curl -X POST -H 'Content-Type: application/json' -d '{"first_name": "your_name","last_name": "your_last_name","password": "your_password","email": "your_email@airy.co"}' http://airy.core/users.signup` +To call the signup endpoint and register your email, open another terminal and type in the terminal -To sign in, type in the terminal `token=$(echo $(curl -H 'Content-Type: application/json' -d \"{ \\"email\":\"your_email@airy.co\",\\"password\":\"your_last_name\" \}" airy.core/users.login) | jq -r '.token')` +```sh +curl -X POST -H 'Content-Type: application/json' -d '{"first_name": "your_name","last_name": "your_last_name","password": "your_password","email": "your_email@airy.co"}' airy.core/users.signup +``` + +To sign in, type in the terminal + +```sh +token=$(echo $(curl -H 'Content-Type: application/json' -d \"{ \\"email\":\"your_email@airy.co\",\\"password\":\"your_last_name\" \}" airy.core/users.login) | jq -r '.token') +``` Aside from Curl, [PostMan](https://www.postman.com/downloads/) and other API testing tools could also be used to access the endpoints. @@ -56,8 +61,8 @@ Aside from Curl, [PostMan](https://www.postman.com/downloads/) and other API tes To start the app in development mode, run these commands: ``` -yarn -yarn ibazel run //frontend/ui:bundle_server +$ yarn +$ ./scripts/web-dev.sh //frontend/ui:bundle_server ``` After it started, open a web browser to [`localhost:8080`](http://localhost:8080). Login with the user you created above. diff --git a/frontend/ui/handles/BUILD b/frontend/ui/handles/BUILD index 334ea614f1..7f2b72070f 100644 --- a/frontend/ui/handles/BUILD +++ b/frontend/ui/handles/BUILD @@ -1,4 +1,7 @@ load("@com_github_airyhq_bazel_tools//web:typescript.bzl", "ts_web_library") +load("@com_github_airyhq_bazel_tools//lint:buildifier.bzl", "check_pkg") + +check_pkg(name = "buildifier") package(default_visibility = ["//visibility:public"]) diff --git a/frontend/ui/handles/index.ts b/frontend/ui/handles/index.ts index cedc6489af..2faca55454 100644 --- a/frontend/ui/handles/index.ts +++ b/frontend/ui/handles/index.ts @@ -2,3 +2,35 @@ export const cyMessageList = 'messageList'; export const cyInputBar = 'inputBar'; export const cyMessageTextArea = 'messageTextArea'; export const cyMessageSendButton = 'messageSendButton'; +export const cySearchButton = 'searchButton'; +export const cySearchField = 'searchField'; +export const cySearchFieldBackButton = 'searchFieldBackButton'; +export const cyConversationList = 'conversationList'; +export const cyShowTagsDialog = 'showTagsDialog'; +export const cyTagsDialogInput = 'tagsDialogInput'; +export const cyTagsDialogButton = 'tagsDialogButton'; +export const cyTagsDialogColorSelectorBlue = 'tagsDialogColorSelectorBlue'; +export const cyTagsDialogColorSelectorRed = 'tagsDialogColorSelectorRed'; +export const cyTagsDialogColorSelectorGreen = 'tagsDialogColorSelectorGreen'; +export const cyTagsDialogColorSelectorPurple = 'tagsDialogColorSelectorPurple'; +export const cyTagsSearchField = 'tagsSearchField'; +export const cyTagsTable = 'tagsTable'; +export const cyTagsTableRowDisplayDeleteModal = 'tagsTableRowDisplayDeleteModal'; +export const cyTagsTableRowDisplayDeleteModalInput = 'tagsTableRowDisplayDeleteModalInput'; +export const cyTagsTableRowDisplayDeleteModalButton = 'tagsTableRowDisplayDeleteModalButton'; + +export const cyChannelsChatPluginAddButton = 'channelsChatPluginAddButton'; +export const cyChannelsFacebookAddButton = 'channelsFacebookAddButton'; +export const cyChannelsTwilioSmsAddButton = 'channelsTwilioSmsAddButton'; +export const cyChannelsTwilioWhatsappAddButton = 'channelsTwilioWhatsappAddButton'; +export const cyChannelsGoogleAddButton = 'channelsGoogleAddButton'; +export const cyChannelsChatPluginList = 'channelsChatPluginList'; +export const cyChannelsFacebookList = 'channelsFacebookList'; +export const cyChannelsTwilioSmsList = 'channelsTwilioSmsList'; +export const cyChannelsTwilioWhatsappList = 'channelsTwilioWhatsappList'; +export const cyChannelsGoogleList = 'channelsGoogleList'; + +export const cyChannelsChatPluginConnectButton = 'channelsChatPluginConnectButton'; +export const cyChannelsChatPluginFormNameInput = 'channelsChatPluginFormNameInput'; +export const cyChannelsChatPluginFormSubmitButton = 'channelsChatPluginFormSubmitButton'; +export const cyChannelsFormBackButton = 'channelsFormBackButton'; diff --git a/frontend/ui/index.html b/frontend/ui/index.html index 7a09e9d882..91f0547b8f 100644 --- a/frontend/ui/index.html +++ b/frontend/ui/index.html @@ -14,7 +14,7 @@ diff --git a/frontend/ui/src/InitializeAiryApi.ts b/frontend/ui/src/InitializeAiryApi.ts index 654e5c4049..35b9b5afed 100644 --- a/frontend/ui/src/InitializeAiryApi.ts +++ b/frontend/ui/src/InitializeAiryApi.ts @@ -6,7 +6,7 @@ import {logoutUser} from './actions/user'; const authToken = getAuthToken(); -export const HttpClientInstance = new HttpClient(authToken, `//${env.API_HOST}`, error => { +export const HttpClientInstance = new HttpClient(env.API_HOST, authToken, error => { console.error('Unauthorized request, logging out user'); console.error(error); diff --git a/frontend/ui/src/actions/templates/index.ts b/frontend/ui/src/actions/templates/index.ts index 94a68f360b..ad134273a3 100644 --- a/frontend/ui/src/actions/templates/index.ts +++ b/frontend/ui/src/actions/templates/index.ts @@ -6,14 +6,15 @@ import {HttpClientInstance} from '../../InitializeAiryApi'; const LIST_TEMPLATES = 'LIST_TEMPLATES'; -export const listTemplatesAction = createAction(LIST_TEMPLATES, (templates: Template[]) => templates)(); +export const listTemplatesAction = createAction(LIST_TEMPLATES, (source: string, templates: Template[]) => ({ + source, + templates, +}))<{source: string; templates: Template[]}>(); export function listTemplates(requestPayload: ListTemplatesRequestPayload) { return function (dispatch: Dispatch) { return HttpClientInstance.listTemplates(requestPayload).then((response: Template[]) => { - if (response.length > 0) { - dispatch(listTemplatesAction(response)); - } + dispatch(listTemplatesAction(requestPayload.source, response)); return Promise.resolve(true); }); diff --git a/frontend/ui/src/components/AiryWebsocket/index.tsx b/frontend/ui/src/components/AiryWebsocket/index.tsx index 3229243e5c..7b0bb75922 100644 --- a/frontend/ui/src/components/AiryWebsocket/index.tsx +++ b/frontend/ui/src/components/AiryWebsocket/index.tsx @@ -2,6 +2,7 @@ import React, {useEffect, useState} from 'react'; import _, {connect, ConnectedProps} from 'react-redux'; import {WebSocketClient} from 'websocketclient'; import {Message, Channel, MetadataEvent} from 'httpclient'; +import camelcaseKeys from 'camelcase-keys'; import {env} from '../../env'; import {StateModel} from '../../reducers'; @@ -28,7 +29,8 @@ const mapDispatchToProps = dispatch => ({ addMessages: (conversationId: string, messages: Message[]) => dispatch(addMessagesAction({conversationId, messages})), onChannel: (channel: Channel) => dispatch(setChannelAction(channel)), getConversationInfo: (conversationId: string) => dispatch(getConversationInfo(conversationId)), - onMetadata: (metadataEvent: MetadataEvent) => dispatch(setMetadataAction(metadataEvent)), + onMetadata: (metadataEvent: MetadataEvent) => + dispatch(camelcaseKeys(setMetadataAction(metadataEvent), {deep: true, stopPaths: ['metadata.user_data']})), }); const connector = connect(mapStateToProps, mapDispatchToProps); @@ -53,17 +55,13 @@ const AiryWebSocket: React.FC = props => { } if (user.token) { setWebSocketClient( - new WebSocketClient( - user.token, - { - onMessage: (conversationId: string, _channelId: string, message: Message) => { - onMessage(conversationId, message); - }, - onChannel, - onMetadata, + new WebSocketClient(env.API_HOST, user.token, { + onMessage: (conversationId: string, _channelId: string, message: Message) => { + onMessage(conversationId, message); }, - env.API_HOST - ) + onChannel, + onMetadata, + }) ); } }; diff --git a/frontend/ui/src/components/AvatarImage/index.module.scss b/frontend/ui/src/components/AvatarImage/index.module.scss deleted file mode 100644 index 49c278ac39..0000000000 --- a/frontend/ui/src/components/AvatarImage/index.module.scss +++ /dev/null @@ -1,9 +0,0 @@ -.avatar { - display: flex; -} - -.avatarImage { - border-radius: 50%; - width: 100%; - height: 100%; -} diff --git a/frontend/ui/src/components/AvatarImage/index.tsx b/frontend/ui/src/components/AvatarImage/index.tsx deleted file mode 100644 index 5be93c6104..0000000000 --- a/frontend/ui/src/components/AvatarImage/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; - -import {Contact} from 'httpclient'; -import styles from './index.module.scss'; - -const fallbackAvatar = 'https://s3.amazonaws.com/assets.airy.co/unknown.png'; - -type AvatarProps = { - contact: Contact; -}; - -const AvatarImage = (props: AvatarProps) => { - const {contact} = props; - - return ( -
- -
- ); -}; - -export default AvatarImage; diff --git a/frontend/ui/src/components/ChannelAvatar/index.module.scss b/frontend/ui/src/components/ChannelAvatar/index.module.scss new file mode 100644 index 0000000000..1ef72c3c8a --- /dev/null +++ b/frontend/ui/src/components/ChannelAvatar/index.module.scss @@ -0,0 +1,16 @@ +.image { + display: flex; + align-self: center; + + svg { + height: 100%; + width: 100%; + border-radius: 50%; + } + + img { + height: 100%; + width: 100%; + border-radius: 50%; + } +} diff --git a/frontend/ui/src/components/ChannelAvatar/index.tsx b/frontend/ui/src/components/ChannelAvatar/index.tsx new file mode 100644 index 0000000000..8664093988 --- /dev/null +++ b/frontend/ui/src/components/ChannelAvatar/index.tsx @@ -0,0 +1,56 @@ +import React, {CSSProperties, SyntheticEvent} from 'react'; +import {ReactComponent as GoogleAvatar} from 'assets/images/icons/google_avatar.svg'; +import {ReactComponent as WhatsappAvatar} from 'assets/images/icons/whatsapp_avatar.svg'; +import {ReactComponent as SmsAvatar} from 'assets/images/icons/sms_avatar.svg'; +import {ReactComponent as FacebookAvatar} from 'assets/images/icons/messenger_avatar.svg'; +import {ReactComponent as AiryAvatar} from 'assets/images/icons/airy_avatar.svg'; +import {Channel, Source} from 'httpclient'; +import styles from './index.module.scss'; + +type ChannelAvatarProps = { + channel: Channel; + style?: CSSProperties; + imageUrl?: string; +}; + +const fallbackImageUrl = (event: SyntheticEvent, source: string) => { + event.currentTarget.src = `https://s3.amazonaws.com/assets.airy.co/${source}_avatar.svg`; + event.currentTarget.alt = `${source} fallback image`; +}; + +const ChannelAvatar = (props: ChannelAvatarProps) => { + const {channel, imageUrl, style} = props; + + const getCustomLogo = (channel: Channel) => { + return ( + ) => fallbackImageUrl(event, channel.source)} + src={channel.metadata.imageUrl || imageUrl} + alt={channel.metadata.name || 'SourceLogo'} + /> + ); + }; + + const getChannelAvatar = (channel: Channel) => { + switch (channel.source) { + case Source.facebook: + return ; + case Source.google: + return ; + case Source.twilioSMS: + return ; + case Source.twilioWhatsapp: + return ; + default: + return ; + } + }; + + return ( +
+ {channel.metadata?.imageUrl || imageUrl ? getCustomLogo(channel) : getChannelAvatar(channel)} +
+ ); +}; + +export default ChannelAvatar; diff --git a/frontend/ui/src/components/ColorSelector.tsx b/frontend/ui/src/components/ColorSelector.tsx index 8d58a63824..14443b5910 100644 --- a/frontend/ui/src/components/ColorSelector.tsx +++ b/frontend/ui/src/components/ColorSelector.tsx @@ -5,6 +5,13 @@ import {Settings} from '../reducers/data/settings'; import styles from './ColorSelector.module.scss'; +import { + cyTagsDialogColorSelectorBlue, + cyTagsDialogColorSelectorRed, + cyTagsDialogColorSelectorGreen, + cyTagsDialogColorSelectorPurple, +} from 'handles'; + type ColorSelectorProps = { handleUpdate: (event: React.ChangeEvent) => void; color: string; @@ -20,6 +27,10 @@ const ColorSelector = ({handleUpdate, color, editing, id, settings}: ColorSelect const getColorValue = useCallback((color: string) => (settings && settings.colors[color].default) || '1578D4', [ settings, ]); + const dataCyTagsDialogColorSelectorBlue = cyTagsDialogColorSelectorBlue; + const dataCyTagsDialogColorSelectorRed = cyTagsDialogColorSelectorRed; + const dataCyTagsDialogColorSelectorGreen = cyTagsDialogColorSelectorGreen; + const dataCyTagsDialogColorSelectorPurple = cyTagsDialogColorSelectorPurple; return (
@@ -32,6 +43,7 @@ const ColorSelector = ({handleUpdate, color, editing, id, settings}: ColorSelect id={`color-blue-${id}`} name={`color-blue-${id}`} value="tag-blue" + data-cy={dataCyTagsDialogColorSelectorBlue} />