From 914eb6f0c2a374c250622466fc17ba16e5f14c1c Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Thu, 29 Apr 2021 16:11:40 +0530 Subject: [PATCH 1/7] Initial version that creates and gets notifications --- .../com/appsmith/server/constants/Url.java | 1 + .../controllers/NotificationController.java | 21 +++++++ .../com/appsmith/server/domains/Comment.java | 6 ++ .../server/domains/CommentNotification.java | 12 ++++ .../appsmith/server/domains/Notification.java | 24 ++++++++ .../appsmith/server/helpers/PolicyUtils.java | 16 +++++ .../CustomNotificationRepository.java | 6 ++ .../CustomNotificationRepositoryImpl.java | 13 ++++ .../repositories/NotificationRepository.java | 12 ++++ .../server/services/CommentServiceImpl.java | 26 +++++++- .../server/services/NotificationService.java | 7 +++ .../services/NotificationServiceImpl.java | 61 +++++++++++++++++++ 12 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/NotificationController.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/domains/CommentNotification.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Notification.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNotificationRepository.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNotificationRepositoryImpl.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/NotificationRepository.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/services/NotificationService.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/services/NotificationServiceImpl.java diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/Url.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/Url.java index 7d4d23540d..87d8446484 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/Url.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/Url.java @@ -29,4 +29,5 @@ public interface Url { String MARKETPLACE_ITEM_URL = BASE_URL + VERSION + "/items"; String ASSET_URL = BASE_URL + VERSION + "/assets"; String COMMENT_URL = BASE_URL + VERSION + "/comments"; + String NOTIFICATION_URL = BASE_URL + VERSION + "/notifications"; } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/NotificationController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/NotificationController.java new file mode 100644 index 0000000000..b7a00dec57 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/NotificationController.java @@ -0,0 +1,21 @@ +package com.appsmith.server.controllers; + +import com.appsmith.server.constants.Url; +import com.appsmith.server.domains.Notification; +import com.appsmith.server.services.NotificationService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequestMapping(Url.NOTIFICATION_URL) +public class NotificationController extends BaseController { + + @Autowired + public NotificationController(NotificationService service) { + super(service); + } + +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Comment.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Comment.java index dc67d106f4..541b026424 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Comment.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Comment.java @@ -32,6 +32,12 @@ public class Comment extends BaseDomain { Body body; + /** + * Indicates whether this comment is the leading comment in it's thread. Such a comment cannot be deleted. + */ + @JsonIgnore + Boolean leading; + @Data public static class Body { List blocks; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/CommentNotification.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/CommentNotification.java new file mode 100644 index 0000000000..0f68abbadc --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/CommentNotification.java @@ -0,0 +1,12 @@ +package com.appsmith.server.domains; + +import lombok.Data; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document +@Data +public class CommentNotification extends Notification { + + Comment comment; + +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Notification.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Notification.java new file mode 100644 index 0000000000..b972eda2b6 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Notification.java @@ -0,0 +1,24 @@ +package com.appsmith.server.domains; + +import com.appsmith.external.models.BaseDomain; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.data.mongodb.core.mapping.Document; + +@Data +@EqualsAndHashCode(callSuper = true) +@Document +public class Notification extends BaseDomain { + + // TODO: This class extends BaseDomain, so it has policies. Should we use information from policies instead of this field? + String forUsername; + + /** + * Read status for this notification. If it is `true`, then this notification is read. If `false` or `null`, it's unread. + */ + Boolean isRead; + + public void cleanForClient() { + } + +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/PolicyUtils.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/PolicyUtils.java index b67a6d967a..7c88e95432 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/PolicyUtils.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/PolicyUtils.java @@ -13,11 +13,13 @@ import com.appsmith.server.repositories.ApplicationRepository; import com.appsmith.server.repositories.DatasourceRepository; import com.appsmith.server.repositories.NewActionRepository; import com.appsmith.server.repositories.NewPageRepository; +import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -282,4 +284,18 @@ public class PolicyUtils { return false; } + + public Set findUsernamesWithPermission(Set policies, AclPermission permission) { + if (CollectionUtils.isNotEmpty(policies) && permission != null) { + final String permissionString = permission.getValue(); + for (Policy policy : policies) { + if (permissionString.equals(policy.getPermission())) { + return policy.getUsers(); + } + } + } + + return Collections.emptySet(); + } + } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNotificationRepository.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNotificationRepository.java new file mode 100644 index 0000000000..9f420f5cf7 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNotificationRepository.java @@ -0,0 +1,6 @@ +package com.appsmith.server.repositories; + +import com.appsmith.server.domains.Notification; + +public interface CustomNotificationRepository extends AppsmithRepository { +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNotificationRepositoryImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNotificationRepositoryImpl.java new file mode 100644 index 0000000000..6c4b50c95c --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNotificationRepositoryImpl.java @@ -0,0 +1,13 @@ +package com.appsmith.server.repositories; + +import com.appsmith.server.domains.Notification; +import org.springframework.data.mongodb.core.ReactiveMongoOperations; +import org.springframework.data.mongodb.core.convert.MongoConverter; + +public class CustomNotificationRepositoryImpl extends BaseAppsmithRepositoryImpl implements CustomNotificationRepository { + + public CustomNotificationRepositoryImpl(ReactiveMongoOperations mongoOperations, MongoConverter mongoConverter) { + super(mongoOperations, mongoConverter); + } + +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/NotificationRepository.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/NotificationRepository.java new file mode 100644 index 0000000000..eb81b59bb5 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/NotificationRepository.java @@ -0,0 +1,12 @@ +package com.appsmith.server.repositories; + +import com.appsmith.server.domains.Notification; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Flux; + +@Repository +public interface NotificationRepository extends BaseRepository, CustomNotificationRepository { + + Flux findByForUsername(String userId); + +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CommentServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CommentServiceImpl.java index 17f372b4e4..a1dd4fe781 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CommentServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CommentServiceImpl.java @@ -5,10 +5,13 @@ import com.appsmith.server.acl.PolicyGenerator; import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.Application; import com.appsmith.server.domains.Comment; +import com.appsmith.server.domains.CommentNotification; import com.appsmith.server.domains.CommentThread; +import com.appsmith.server.domains.Notification; import com.appsmith.server.domains.User; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; +import com.appsmith.server.helpers.PolicyUtils; import com.appsmith.server.repositories.CommentRepository; import com.appsmith.server.repositories.CommentThreadRepository; import lombok.extern.slf4j.Slf4j; @@ -41,8 +44,10 @@ public class CommentServiceImpl extends BaseService { + final Set usernames = policyUtils.findUsernamesWithPermission( + savedComment.getPolicies(), AclPermission.READ_COMMENT); + + List> monos = new ArrayList<>(); + for (String username : usernames) { + final CommentNotification notification = new CommentNotification(); + notification.setComment(savedComment); + notification.setForUsername(username); + monos.add(notificationService.create(notification)); + } + + return Flux.concat(monos).then(Mono.just(savedComment)); }); } @@ -137,6 +160,7 @@ public class CommentServiceImpl extends BaseService> commentSaverMonos = new ArrayList<>(); if (!CollectionUtils.isEmpty(thread.getComments())) { + thread.getComments().get(0).setLeading(true); for (final Comment comment : thread.getComments()) { comment.setId(null); commentSaverMonos.add(create(thread.getId(), comment)); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NotificationService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NotificationService.java new file mode 100644 index 0000000000..cce58ac380 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NotificationService.java @@ -0,0 +1,7 @@ +package com.appsmith.server.services; + +import com.appsmith.server.domains.Notification; + +public interface NotificationService extends CrudService { + +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NotificationServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NotificationServiceImpl.java new file mode 100644 index 0000000000..614e68fddc --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NotificationServiceImpl.java @@ -0,0 +1,61 @@ +package com.appsmith.server.services; + +import com.appsmith.server.domains.Notification; +import com.appsmith.server.repositories.NotificationRepository; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.mongodb.core.ReactiveMongoTemplate; +import org.springframework.data.mongodb.core.convert.MongoConverter; +import org.springframework.stereotype.Service; +import org.springframework.util.MultiValueMap; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Scheduler; + +import javax.validation.Validator; + +@Slf4j +@Service +public class NotificationServiceImpl + extends BaseService + implements NotificationService { + + private final SessionUserService sessionUserService; + + public NotificationServiceImpl( + Scheduler scheduler, + Validator validator, + MongoConverter mongoConverter, + ReactiveMongoTemplate reactiveMongoTemplate, + NotificationRepository repository, + AnalyticsService analyticsService, + SessionUserService sessionUserService + ) { + super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService); + this.sessionUserService = sessionUserService; + } + + @Override + public Mono create(Notification notification) { + Mono notificationWithUsernameMono; + if (StringUtils.isEmpty(notification.getForUsername())) { + notificationWithUsernameMono = sessionUserService.getCurrentUser() + .map(user -> { + notification.setForUsername(user.getUsername()); + return notification; + }); + } else { + notificationWithUsernameMono = Mono.just(notification); + } + + return notificationWithUsernameMono + .flatMap(super::create); + } + + @Override + public Flux get(MultiValueMap params) { + return sessionUserService.getCurrentUser() + .flatMapMany(user -> repository.findByForUsername(user.getUsername())); + } + +} From 75479448ccf95e0cfe6fb8bb35f5b673c1bfc482 Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Tue, 18 May 2021 14:51:31 +0530 Subject: [PATCH 2/7] Watch notification collection in RTS --- app/rts/src/server.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/app/rts/src/server.ts b/app/rts/src/server.ts index 5e627d7298..27c772250c 100644 --- a/app/rts/src/server.ts +++ b/app/rts/src/server.ts @@ -233,6 +233,34 @@ async function watchMongoDB(io) { } }) + const notificationsStream = db.collection("notification").watch( + [ + // Prevent server-internal fields from being sent to the client. + { + $unset: [ + "deletedAt", + "deleted", + "_class", + ].map(f => "fullDocument." + f) + }, + ], + { fullDocument: "updateLookup" } + ); + + notificationsStream.on("change", async (event: mongodb.ChangeEventCR) => { + console.log("notification event", event) + const notification = event.fullDocument + + if (notification == null) { + // This happens when `event.operationType === "drop"`, when a notification is deleted. + console.error("Null document recieved for notification change event", event) + return + } + + const eventName = event.operationType + ":" + event.ns.coll + io.to("email:" + notification.forUsername).emit(eventName, { notification }) + }) + process.on("exit", () => { (commentChangeStream != null ? commentChangeStream.close() : Promise.bind(client).resolve()) .then(client.close.bind(client)) From 28cab61e5b5683e2514f7f868105944159ceda37 Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Tue, 18 May 2021 17:16:09 +0530 Subject: [PATCH 3/7] Separate notifications for comment threads --- .../server/domains/CommentNotification.java | 4 +- .../domains/CommentThreadNotification.java | 14 +++++ .../appsmith/server/domains/Notification.java | 3 +- .../server/services/CommentServiceImpl.java | 51 +++++++++++++++---- 4 files changed, 61 insertions(+), 11 deletions(-) create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/domains/CommentThreadNotification.java diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/CommentNotification.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/CommentNotification.java index 0f68abbadc..b2277fd173 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/CommentNotification.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/CommentNotification.java @@ -1,10 +1,12 @@ package com.appsmith.server.domains; import lombok.Data; +import lombok.EqualsAndHashCode; import org.springframework.data.mongodb.core.mapping.Document; -@Document @Data +@EqualsAndHashCode(callSuper = true) +@Document public class CommentNotification extends Notification { Comment comment; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/CommentThreadNotification.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/CommentThreadNotification.java new file mode 100644 index 0000000000..ab3834805c --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/CommentThreadNotification.java @@ -0,0 +1,14 @@ +package com.appsmith.server.domains; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.data.mongodb.core.mapping.Document; + +@Data +@EqualsAndHashCode(callSuper = true) +@Document +public class CommentThreadNotification extends Notification { + + CommentThread commentThread; + +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Notification.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Notification.java index b972eda2b6..79ffee5af1 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Notification.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Notification.java @@ -18,7 +18,8 @@ public class Notification extends BaseDomain { */ Boolean isRead; - public void cleanForClient() { + public String getType() { + return getClass().getSimpleName(); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CommentServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CommentServiceImpl.java index b1b1b4b50d..1935b125b0 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CommentServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CommentServiceImpl.java @@ -8,6 +8,7 @@ import com.appsmith.server.domains.Application; import com.appsmith.server.domains.Comment; import com.appsmith.server.domains.CommentNotification; import com.appsmith.server.domains.CommentThread; +import com.appsmith.server.domains.CommentThreadNotification; import com.appsmith.server.domains.Notification; import com.appsmith.server.domains.User; import com.appsmith.server.exceptions.AppsmithError; @@ -79,6 +80,10 @@ public class CommentServiceImpl extends BaseService create(String threadId, Comment comment) { + return create(threadId, comment, true); + } + + public Mono create(String threadId, Comment comment, boolean shouldCreateNotification) { if (StringUtils.isWhitespace(comment.getAuthorName())) { // Error: User can't explicitly set the author name. It will be the currently logged in user. return Mono.empty(); @@ -117,18 +122,26 @@ public class CommentServiceImpl extends BaseService { + .flatMap(tuple -> { + final User user = tuple.getT1(); + final Comment savedComment = tuple.getT2(); + final Set usernames = policyUtils.findUsernamesWithPermission( savedComment.getPolicies(), AclPermission.READ_COMMENT); List> monos = new ArrayList<>(); for (String username : usernames) { - final CommentNotification notification = new CommentNotification(); - notification.setComment(savedComment); - notification.setForUsername(username); - monos.add(notificationService.create(notification)); + if (!username.equals(user.getUsername())) { + final CommentNotification notification = new CommentNotification(); + notification.setComment(savedComment); + notification.setForUsername(username); + monos.add(notificationService.create(notification)); + } } return Flux.concat(monos).then(Mono.just(savedComment)); @@ -190,9 +203,11 @@ public class CommentServiceImpl extends BaseService { + .zipWith(sessionUserService.getCurrentUser()) + .flatMap(tuple -> { + final List comments = tuple.getT1(); + final User user = tuple.getT2(); + commentThread.setComments(comments); commentThread.setIsViewed(true); - return commentThread; + + final Set usernames = policyUtils.findUsernamesWithPermission( + commentThread.getPolicies(), AclPermission.READ_THREAD); + + List> monos = new ArrayList<>(); + for (String username : usernames) { + if (!username.equals(user.getUsername())) { + final CommentThreadNotification notification = new CommentThreadNotification(); + notification.setCommentThread(commentThread); + notification.setForUsername(username); + monos.add(notificationService.create(notification)); + } + } + + return Flux.concat(monos).then(Mono.just(commentThread)); }); } From 63db439183fd23766799272b6405f558bb81781d Mon Sep 17 00:00:00 2001 From: arunvjn <32433245+arunvjn@users.noreply.github.com> Date: Thu, 20 May 2021 16:51:10 +0530 Subject: [PATCH 4/7] Fixed r.match bug (#4593) --- app/client/src/sagas/ApiPaneSagas.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/app/client/src/sagas/ApiPaneSagas.ts b/app/client/src/sagas/ApiPaneSagas.ts index 8ea71706e0..29b8e690be 100644 --- a/app/client/src/sagas/ApiPaneSagas.ts +++ b/app/client/src/sagas/ApiPaneSagas.ts @@ -2,7 +2,6 @@ * Handles the Api pane ui state. It looks into the routing based on actions too * */ import get from "lodash/get"; -import isObject from "lodash/isObject"; import omit from "lodash/omit"; import cloneDeep from "lodash/cloneDeep"; import { all, select, put, takeEvery, call, take } from "redux-saga/effects"; @@ -79,14 +78,11 @@ function* syncApiParamsSaga( //Payload here contains the path and query params of a typical url like https://{domain}/{path}?{query_params} let value = actionPayload.payload; // Regular expression to find the query params group - if (isObject(value)) { - value = get(value, "datasourceConfiguration.url", ""); - } - const queryParamsRegEx = /(\/[\s\S]*?)(\?(?![^{]*})[\s\S]*)?$/; - value = (value.match(queryParamsRegEx) || [])[2] || ""; const padQueryParams = { key: "", value: "" }; + const queryParamsRegEx = /(\/[\s\S]*?)(\?(?![^{]*})[\s\S]*)?$/; PerformanceTracker.startTracking(PerformanceTransactionName.SYNC_PARAMS_SAGA); if (field === "actionConfiguration.path") { + value = (value.match(queryParamsRegEx) || [])[2] || ""; if (value.indexOf("?") > -1) { const paramsString = value.substr(value.indexOf("?") + 1); const params = paramsString.split("&").map((p) => { @@ -95,7 +91,7 @@ function* syncApiParamsSaga( firstEqualPos > -1 ? [p.substring(0, firstEqualPos), p.substring(firstEqualPos + 1)] : []; - return { key: keyValue[0], value: keyValue[1] || "" }; + return { key: keyValue[0] || "", value: keyValue[1] || "" }; }); if (params.length < 2) { while (params.length < 2) { From 8964aea9df8fa580a6fd9f3043dfb448806acacb Mon Sep 17 00:00:00 2001 From: Rishabh Saxena Date: Thu, 20 May 2021 17:33:08 +0530 Subject: [PATCH 5/7] [Feature] Comments feature updates (#4579) --- .../ApiPaneTests/API_Edit_spec.js | 5 +- .../ProductUpdates/ProductUpdates_spec.js | 2 +- .../manual_TestSuite/List_Widget_Spec.js | 92 ++-- .../manual_TestSuite/Text_Widget_Spec.js | 199 ++++---- app/client/cypress/support/commands.js | 12 +- app/client/package.json | 5 +- app/client/public/index.html | 5 + app/client/src/actions/commentActions.ts | 118 ++++- app/client/src/actions/tourActions.ts | 22 + app/client/src/actions/userActions.ts | 13 + app/client/src/api/CommentsAPI.tsx | 37 +- app/client/src/api/UserApi.tsx | 21 +- app/client/src/assets/icons/comments/chat.svg | 3 + .../comment-mode-unread-indicator.svg | 4 + .../assets/icons/comments/commentCursor.png | Bin 0 -> 1441 bytes .../assets/icons/comments/context-menu.svg | 5 + .../src/assets/icons/comments/down-arrow.svg | 4 + .../src/assets/icons/comments/edit-mode.svg | 3 + .../src/assets/icons/comments/emoji.svg | 4 +- .../src/assets/icons/comments/filter.svg | 3 + app/client/src/assets/icons/comments/link.svg | 4 +- app/client/src/assets/icons/comments/pen.svg | 3 + .../src/assets/icons/comments/pin_3.svg | 3 + .../src/assets/icons/comments/reaction-2.svg | 13 + .../src/assets/icons/comments/reaction.svg | 6 + .../src/assets/icons/comments/read-pin.svg | 3 + .../src/assets/icons/comments/unpin.svg | 3 + .../src/assets/icons/comments/unread-pin.svg | 3 + .../images/comments-onboarding/step-1.png | Bin 0 -> 37202 bytes .../images/comments-onboarding/step-2.png | Bin 0 -> 30390 bytes .../images/comments-onboarding/step-3.png | Bin 0 -> 40249 bytes .../images/comments-onboarding/step-4.png | Bin 0 -> 15261 bytes .../src/assets/images/profile-placeholder.svg | 3 + .../AppComments/AppCommentThreads.tsx | 77 +++- .../src/comments/AppComments/AppComments.tsx | 17 +- .../AppComments/AppCommentsFilterPopover.tsx | 128 ++++++ .../AppComments/AppCommentsHeader.tsx | 55 +-- .../AppComments/AppCommentsPlaceholder.tsx | 34 ++ .../src/comments/AppComments/Container.tsx | 12 +- .../src/comments/CommentCard/CommentCard.tsx | 433 +++++++++++++++--- .../CommentCard/CommentContextMenu.tsx | 106 +++-- .../CommentCard/ResolveCommentButton.tsx | 47 +- .../comments/CommentThread/CommentThread.tsx | 177 ++++--- .../comments/CommentThread/ScrollToLatest.tsx | 30 +- .../CommentsCarouselModal.tsx | 25 + .../FormDisplayImage.tsx | 50 ++ .../CommentsShowcaseCarousel/ProfileForm.tsx | 81 ++++ .../CommentsShowcaseCarousel/index.tsx | 173 +++++++ .../src/comments/ToggleCommentModeButton.tsx | 105 ----- .../inlineComments/AddCommentInput.tsx | 143 ++++-- .../inlineComments/InlineCommentPin.tsx | 256 ++++++++--- .../inlineComments/OverlayCommentsWrapper.tsx | 50 +- .../inlineComments/StyledComponents.tsx | 6 +- .../UnpublishedCommentThread.tsx | 143 +++--- .../comments/tour/AddCommentTourComponent.tsx | 22 + .../src/comments/tour/commentsTourSteps.ts | 25 + app/client/src/comments/utils.ts | 36 ++ app/client/src/components/ads/Checkbox.tsx | 7 +- .../src/components/ads/DisplayImageUpload.tsx | 175 +++++++ app/client/src/components/ads/EmojiPicker.tsx | 48 +- .../src/components/ads/EmojiReactions.tsx | 157 +++++++ app/client/src/components/ads/Icon.tsx | 74 ++- .../src/components/ads/MentionsInput.tsx | 79 +++- app/client/src/components/ads/Radio.tsx | 8 +- .../src/components/ads/ShowcaseCarousel.tsx | 117 +++++ app/client/src/components/ads/Tooltip.tsx | 7 +- .../components/ads/formFields/TextField.tsx | 5 +- .../ads/tour/TourTooltipWrapper.tsx | 57 +++ .../blueprint/ModalComponent.tsx | 24 +- app/client/src/constants/CommentConstants.tsx | 1 + app/client/src/constants/DefaultTheme.tsx | 100 +++- app/client/src/constants/Layers.tsx | 7 +- .../src/constants/ReduxActionConstants.tsx | 21 + app/client/src/constants/TourSteps.tsx | 8 + app/client/src/constants/messages.ts | 17 + .../entities/Comments/CommentsInterfaces.ts | 33 +- app/client/src/entities/Tour/index.ts | 3 + .../src/globalStyles/commentThreadPopovers.ts | 15 +- app/client/src/globalStyles/index.tsx | 4 + app/client/src/globalStyles/popover.ts | 10 +- app/client/src/globalStyles/portals.ts | 20 + app/client/src/globalStyles/uppy.ts | 11 + app/client/src/index.css | 9 - .../AppViewer/viewer/AppViewerHeader.tsx | 2 +- app/client/src/pages/Editor/EditorHeader.tsx | 4 +- app/client/src/pages/Editor/GlobalHotKeys.tsx | 14 + .../src/pages/Editor/ToggleModeButton.tsx | 182 ++++++++ app/client/src/pages/Editor/index.tsx | 4 + app/client/src/pages/common/ProfileImage.tsx | 33 +- app/client/src/pages/common/SubHeader.tsx | 3 +- app/client/src/reducers/index.tsx | 2 + .../commentsReducer/commentsReducer.test.ts | 25 +- .../commentsReducer/commentsReducer.ts | 146 ++++-- .../handleCreateNewCommentThreadSuccess.ts | 2 +- .../handleNewCommentThreadEvent.ts | 17 +- .../handleUpdateCommentEvent.ts | 21 + .../handleUpdateCommentThreadEvent.ts | 5 +- .../handleUpdateCommentThreadSuccess.ts | 2 + .../uiReducers/commentsReducer/interfaces.ts | 7 + .../commentsReducer/testFixtures.ts | 1 - app/client/src/reducers/uiReducers/index.tsx | 2 + .../src/reducers/uiReducers/tourReducer.ts | 40 ++ .../sagas/CommentSagas/handleCommentEvents.ts | 5 + app/client/src/sagas/CommentSagas/index.ts | 172 +++++-- app/client/src/sagas/InitSagas.ts | 13 + app/client/src/sagas/TourSagas.ts | 30 ++ app/client/src/sagas/WebsocketSagas.ts | 22 +- app/client/src/sagas/index.tsx | 2 + app/client/src/sagas/userSagas.tsx | 36 +- app/client/src/selectors/commentsSelectors.ts | 112 ++++- app/client/src/selectors/tourSelectors.tsx | 7 + .../src/utils/hooks/dragResizeHooks.tsx | 10 +- .../src/utils/hooks/useIsScrolledToBottom.tsx | 1 + .../utils/hooks/useProceedToNextTourStep.tsx | 22 + .../src/utils/hooks/useResizeObserver.tsx | 19 + app/client/src/utils/storage.ts | 22 + app/client/start-https.sh | 4 +- app/client/test/setup.ts | 2 + app/client/yarn.lock | 44 +- .../com/appsmith/server/domains/Comment.java | 13 +- .../server/domains/CommentThread.java | 3 +- .../server/services/CommentServiceImpl.java | 17 +- 122 files changed, 3951 insertions(+), 963 deletions(-) create mode 100644 app/client/src/actions/tourActions.ts create mode 100644 app/client/src/assets/icons/comments/chat.svg create mode 100644 app/client/src/assets/icons/comments/comment-mode-unread-indicator.svg create mode 100644 app/client/src/assets/icons/comments/commentCursor.png create mode 100644 app/client/src/assets/icons/comments/context-menu.svg create mode 100644 app/client/src/assets/icons/comments/down-arrow.svg create mode 100644 app/client/src/assets/icons/comments/edit-mode.svg create mode 100644 app/client/src/assets/icons/comments/filter.svg create mode 100644 app/client/src/assets/icons/comments/pen.svg create mode 100644 app/client/src/assets/icons/comments/pin_3.svg create mode 100644 app/client/src/assets/icons/comments/reaction-2.svg create mode 100644 app/client/src/assets/icons/comments/reaction.svg create mode 100644 app/client/src/assets/icons/comments/read-pin.svg create mode 100644 app/client/src/assets/icons/comments/unpin.svg create mode 100644 app/client/src/assets/icons/comments/unread-pin.svg create mode 100644 app/client/src/assets/images/comments-onboarding/step-1.png create mode 100644 app/client/src/assets/images/comments-onboarding/step-2.png create mode 100644 app/client/src/assets/images/comments-onboarding/step-3.png create mode 100644 app/client/src/assets/images/comments-onboarding/step-4.png create mode 100644 app/client/src/assets/images/profile-placeholder.svg create mode 100644 app/client/src/comments/AppComments/AppCommentsFilterPopover.tsx create mode 100644 app/client/src/comments/AppComments/AppCommentsPlaceholder.tsx create mode 100644 app/client/src/comments/CommentsShowcaseCarousel/CommentsCarouselModal.tsx create mode 100644 app/client/src/comments/CommentsShowcaseCarousel/FormDisplayImage.tsx create mode 100644 app/client/src/comments/CommentsShowcaseCarousel/ProfileForm.tsx create mode 100644 app/client/src/comments/CommentsShowcaseCarousel/index.tsx delete mode 100644 app/client/src/comments/ToggleCommentModeButton.tsx create mode 100644 app/client/src/comments/tour/AddCommentTourComponent.tsx create mode 100644 app/client/src/comments/tour/commentsTourSteps.ts create mode 100644 app/client/src/components/ads/DisplayImageUpload.tsx create mode 100644 app/client/src/components/ads/EmojiReactions.tsx create mode 100644 app/client/src/components/ads/ShowcaseCarousel.tsx create mode 100644 app/client/src/components/ads/tour/TourTooltipWrapper.tsx create mode 100644 app/client/src/constants/TourSteps.tsx create mode 100644 app/client/src/entities/Tour/index.ts create mode 100644 app/client/src/globalStyles/portals.ts create mode 100644 app/client/src/globalStyles/uppy.ts create mode 100644 app/client/src/pages/Editor/ToggleModeButton.tsx create mode 100644 app/client/src/reducers/uiReducers/commentsReducer/handleUpdateCommentEvent.ts create mode 100644 app/client/src/reducers/uiReducers/tourReducer.ts create mode 100644 app/client/src/sagas/TourSagas.ts create mode 100644 app/client/src/selectors/tourSelectors.tsx create mode 100644 app/client/src/utils/hooks/useProceedToNextTourStep.tsx create mode 100644 app/client/src/utils/hooks/useResizeObserver.tsx diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_Edit_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_Edit_spec.js index 918c4592da..94a026cb44 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_Edit_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_Edit_spec.js @@ -49,6 +49,9 @@ describe("API Panel Test Functionality", function() { "https://mock-api.appsmith.com/users", ); cy.enterDatasourceAndPath(testdata.baseUrl, testdata.methodWithQueryParam); - cy.ValidateQueryParams({ key: "q", value:"mimeType='application/vnd.google-apps.spreadsheet'" }); + cy.ValidateQueryParams({ + key: "q", + value: "mimeType='application/vnd.google-apps.spreadsheet'", + }); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ProductUpdates/ProductUpdates_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ProductUpdates/ProductUpdates_spec.js index 338970ff7b..ef124c354f 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ProductUpdates/ProductUpdates_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ProductUpdates/ProductUpdates_spec.js @@ -10,7 +10,7 @@ describe("Check for product updates button and modal", function() { .its("store") .invoke("getState") .then((state) => { - const { releaseItems, newReleasesCount } = state.ui.releases; + const { newReleasesCount, releaseItems } = state.ui.releases; if (Array.isArray(releaseItems) && releaseItems.length > 0) { cy.get("[data-cy=t--product-updates-btn]") .contains("What's New?") diff --git a/app/client/cypress/manual_TestSuite/List_Widget_Spec.js b/app/client/cypress/manual_TestSuite/List_Widget_Spec.js index 38cd930f04..720bd2ab87 100644 --- a/app/client/cypress/manual_TestSuite/List_Widget_Spec.js +++ b/app/client/cypress/manual_TestSuite/List_Widget_Spec.js @@ -2,104 +2,92 @@ const dsl = require("../../../fixtures/ListWidgetDsl.json"); describe("List Widget test ideas ", function() { it("List widget background colour and deploy ", function() { - // Drag and drop a List widget - // Open Property pane - // Scroll down to Styles - // Add background colour - // Add item background colour - // Ensure the colour are added appropriately - // Click on Deploy and ensure it is deployed appropriately - } - ) + // Drag and drop a List widget + // Open Property pane + // Scroll down to Styles + // Add background colour + // Add item background colour + // Ensure the colour are added appropriately + // Click on Deploy and ensure it is deployed appropriately + }); it("Adding large item Spacing for item card", function() { // Drag and drop a List widget // Open Property pane // Scroll down to Styles // Add large item spacing (>100) - // Ensure the cards get spaced appropriately - } - ) + // Ensure the cards get spaced appropriately + }); - it("Binding an API data to list widget ", function() { - //Add an API + it("Binding an API data to list widget ", function() { + //Add an API // Drag and drop a List widget // Open list Property pane // Bind the API to list widget // Add Input widget into the list widget // Bind the input widgte to the list widget - } - ) + }); - it("Copy Paste and Delete the List Widget ", function() { + it("Copy Paste and Delete the List Widget ", function() { // Drag and drop a List widget // Click on the property pane // Click on Copy the widget // Paste(cmd+v) the list widget // Click on the delete option of the Parent widget - } - ) + }); it("Renaming the widget from Property pane and Entity explorer ", function() { // Drag and drop a List widget // Click on the property pane // Click name of the widget // Rename the widget - // Navigate to the Entity Explorer + // Navigate to the Entity Explorer // Click on the Widget expands // Navigate to List widget (Double Click) - // Rename the widget - // Ensure the name of the widget is possible from both the place - } - ) + // Rename the widget + // Ensure the name of the widget is possible from both the place + }); - it("Verify the Pagination functionlaity within List Widget", function() { - // Drag and Drop list Widget + it("Verify the Pagination functionlaity within List Widget", function() { + // Drag and Drop list Widget // Click on page 2 // Ensure list widget will be redirected to page 2 - // Click on next button + // Click on next button // Ensure the list widget will be redirected to page 3 // Click on Previous button // Ensure the list widget will be redirected to page 2 // Mouse Hover on the next button // Ensure the tool tip message is appropriate - // Mouse Hover on the Previous button + // Mouse Hover on the Previous button // Ensure the tool tip message is appropriate - } - ) + }); - it("Add new item in the list widget array object", function(){ + it("Add new item in the list widget array object", function() { //Drag and drop list widget - //Click to open an property pane + //Click to open an property pane //Expand Genearl section - //Add the following new item + //Add the following new item //("id": 7,"num": 007",name": Charizard",img": "http://www.serebii.net/pokemongo/pokemon/006.png") //Ensure the new item gets added to the list widget without any error - //Check for the new page is added upon adding new items - } - ) + //Check for the new page is added upon adding new items + }); - it("Adding apt widget into the List widget", function(){ - //Drag and Drop List widget - //Expand the section 1 size in the list widget + it("Adding apt widget into the List widget", function() { + //Drag and Drop List widget + //Expand the section 1 size in the list widget //Ensure by exapdning section inside list widget the page size gets increased - //Drag and Drop button widget inside list widget + //Drag and Drop button widget inside list widget //Ensure Button widget can be placed inside list Widget //Drag and Drop Image widget inside list widget //Ensure Image widget can be placed inside list widget // Drag and drop the text widget inside the list widget // Ensure text widget can be place inside the list widget - } - ) + }); - it("Adding unapt widget to identify the error message", function(){ - //Drag and Drop List widget - //Expand the section 1 size in the list widget + it("Adding unapt widget to identify the error message", function() { + //Drag and Drop List widget + //Expand the section 1 size in the list widget //Drag and Drop widgets ie: Chart ,Date Picker radio button etc - // Ensure an understandable error message is displayed to user - } - ) - - - - }) \ No newline at end of file + // Ensure an understandable error message is displayed to user + }); +}); diff --git a/app/client/cypress/manual_TestSuite/Text_Widget_Spec.js b/app/client/cypress/manual_TestSuite/Text_Widget_Spec.js index 1aa118d32a..d180857d68 100644 --- a/app/client/cypress/manual_TestSuite/Text_Widget_Spec.js +++ b/app/client/cypress/manual_TestSuite/Text_Widget_Spec.js @@ -1,117 +1,106 @@ const homePage = require("../../../locators/Textwidget.json"); describe("Test Ideas to test different feature of text widget ", function() { - it("Add New Text widget along with BG and text colour ", function() { - // Navigate to application - // Drag and drop a Text Widget - // Navigate to Property Pane - // Add a text - // Scroll to BG colour and add a colour - // Next add a text colour - // Click on Deploy - } - ) + it("Add New Text widget along with BG and text colour ", function() { + // Navigate to application + // Drag and drop a Text Widget + // Navigate to Property Pane + // Add a text + // Scroll to BG colour and add a colour + // Next add a text colour + // Click on Deploy + }); - it("Enable Scroll feature with text colour ", function() { - // Navigate to application - // Drag and drop a Text Widget - // Add a long text in the "Label" - // Enable scroll option - // Navigate to Text colour and add a colour - // and ensure it is scrolling - // Click on deploy and check if it scrollable and colour selected is visible - } - ) + it("Enable Scroll feature with text colour ", function() { + // Navigate to application + // Drag and drop a Text Widget + // Add a long text in the "Label" + // Enable scroll option + // Navigate to Text colour and add a colour + // and ensure it is scrolling + // Click on deploy and check if it scrollable and colour selected is visible + }); - it("Adding text Size to the Text along with BG colour ", function() { - // Navigate to application - // Drag and drop a Text Widget - // Navigate to Property pane - // Add a medium text in the "Label" - // Increase the area of the Text Widget - // Navigate to BG colour and add a colour - // Naviaget to "Text Size" - // Select Paragarph option - // Ensure the text size varies accordingly - } - ) + it("Adding text Size to the Text along with BG colour ", function() { + // Navigate to application + // Drag and drop a Text Widget + // Navigate to Property pane + // Add a medium text in the "Label" + // Increase the area of the Text Widget + // Navigate to BG colour and add a colour + // Naviaget to "Text Size" + // Select Paragarph option + // Ensure the text size varies accordingly + }); - it("Adding Bold Font style and Centre Text Alignment ", function() { - // Navigate to application - // Drag and drop a Text Widget - // Navigate to Property pane - // Add a medium text in the "Label" - // Increase the area of the Text Widget - // Navigate to Font Style - // Make it Bold - // and Navigate to Alignment and make it centre - // Ensure the changes are visible to user - } - ) + it("Adding Bold Font style and Centre Text Alignment ", function() { + // Navigate to application + // Drag and drop a Text Widget + // Navigate to Property pane + // Add a medium text in the "Label" + // Increase the area of the Text Widget + // Navigate to Font Style + // Make it Bold + // and Navigate to Alignment and make it centre + // Ensure the changes are visible to user + }); - it("Adding Italic Font style and Text Alignment to exsisting text widget ", function() { - // Navigate to already exsisting Text widget - // Ensure the text is added - // Navigate to Property pane - // Navigate to Font Style - // Make it Italic font - // and Navigate to Alignment and make it Right - // Ensure the changes are visible to user - } - ) + it("Adding Italic Font style and Text Alignment to exsisting text widget ", function() { + // Navigate to already exsisting Text widget + // Ensure the text is added + // Navigate to Property pane + // Navigate to Font Style + // Make it Italic font + // and Navigate to Alignment and make it Right + // Ensure the changes are visible to user + }); - it("Expand and Contract text widget Property pane", function() { - // Navigate to already exsisting Text widget - // Navigate to Property pane - // Click on collapse option - // Observe that the property pane is contracted - // Now click again on the arrow - //and ensure it collapses - } - ) + it("Expand and Contract text widget Property pane", function() { + // Navigate to already exsisting Text widget + // Navigate to Property pane + // Click on collapse option + // Observe that the property pane is contracted + // Now click again on the arrow + //and ensure it collapses + }); - it("Copy and paste a text widget", function() { - // Navigate to already exsisting Text widget - // Ensure Clour and font feature exsists - // Copy and paste the widget - // Ensure the new widget retrives the feature exsisting from parent widget - } - ) + it("Copy and paste a text widget", function() { + // Navigate to already exsisting Text widget + // Ensure Clour and font feature exsists + // Copy and paste the widget + // Ensure the new widget retrives the feature exsisting from parent widget + }); - it("Rename and search a text widget", function() { - // Ensure there are multiple Text widget - // Navigate to Entity Explorer - // Search for "Text" keyword - // Click on one of the text widget - // Rename the text widget from the Entity explorer - // Clear the search keyword - // enter the new text widget name - // and observe the user is navigated to same text widget and properties of the widget does not change on renaming - } - ) + it("Rename and search a text widget", function() { + // Ensure there are multiple Text widget + // Navigate to Entity Explorer + // Search for "Text" keyword + // Click on one of the text widget + // Rename the text widget from the Entity explorer + // Clear the search keyword + // enter the new text widget name + // and observe the user is navigated to same text widget and properties of the widget does not change on renaming + }); - it("Search and delete a text widget", function() { - // Ensure there are multiple Text widget - // Navigate to Entity Explorer - // Search for "Text" keyword - // Click on one of the text widget - // Ensure user is navigated to Text widget - // Click on Delete option - // Ensure the Text widget is delete - // Click on Deploy adn ensure the Widget is delete - } - ) + it("Search and delete a text widget", function() { + // Ensure there are multiple Text widget + // Navigate to Entity Explorer + // Search for "Text" keyword + // Click on one of the text widget + // Ensure user is navigated to Text widget + // Click on Delete option + // Ensure the Text widget is delete + // Click on Deploy adn ensure the Widget is delete + }); - it("Search and delete a text widget", function() { - // Ensure there are multiple Text widget - // Navigate to Entity Explorer - // Search for "Text" keyword - // Click on one of the text widget - // Ensure user is navigated to Text widget - // Click on Delete option - // Ensure the Text widget is delete - // Click on Deploy adn ensure the Widget is delete - } - ) -} -) \ No newline at end of file + it("Search and delete a text widget", function() { + // Ensure there are multiple Text widget + // Navigate to Entity Explorer + // Search for "Text" keyword + // Click on one of the text widget + // Ensure user is navigated to Text widget + // Click on Delete option + // Ensure the Text widget is delete + // Click on Deploy adn ensure the Widget is delete + }); +}); diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index 5b8e20e933..48b6b2af7d 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -2347,8 +2347,12 @@ Cypress.Commands.add("assertPageSave", () => { Cypress.Commands.add("ValidateQueryParams", (param) => { cy.xpath(apiwidget.paramsTab) - .should("be.visible") - .click({ force: true }); - cy.xpath(apiwidget.paramKey).first().contains(param.key); - cy.xpath(apiwidget.paramValue).first().contains(param.value); + .should("be.visible") + .click({ force: true }); + cy.xpath(apiwidget.paramKey) + .first() + .contains(param.key); + cy.xpath(apiwidget.paramValue) + .first() + .contains(param.value); }); diff --git a/app/client/package.json b/app/client/package.json index 347bbb8a6a..9ba6903428 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -45,6 +45,7 @@ "@uppy/dashboard": "^1.16.0", "@uppy/file-input": "^1.4.22", "@uppy/google-drive": "^1.5.22", + "@uppy/image-editor": "^0.2.4", "@uppy/onedrive": "^1.1.22", "@uppy/react": "^1.11.2", "@uppy/url": "^1.5.16", @@ -62,7 +63,7 @@ "deep-diff": "^1.0.2", "downloadjs": "^1.4.7", "draft-js": "^0.11.7", - "emoji-picker-react": "^3.4.2", + "emoji-mart": "^3.0.1", "eslint": "^7.11.0", "fast-deep-equal": "^3.1.1", "fast-xml-parser": "^3.17.5", @@ -197,6 +198,7 @@ "@types/deep-diff": "^1.0.0", "@types/downloadjs": "^1.4.2", "@types/draft-js": "^0.11.1", + "@types/emoji-mart": "^3.0.4", "@types/jest": "^24.0.22", "@types/marked": "^1.2.2", "@types/react-beautiful-dnd": "^11.0.4", @@ -206,6 +208,7 @@ "@types/react-window": "^1.8.2", "@types/redux-form": "^8.1.9", "@types/redux-mock-store": "^1.0.2", + "@types/resize-observer-browser": "^0.1.5", "@types/styled-system": "^5.1.9", "@types/tern": "0.22.0", "@types/toposort": "^2.0.3", diff --git a/app/client/public/index.html b/app/client/public/index.html index 70250975eb..8079bbba08 100755 --- a/app/client/public/index.html +++ b/app/client/public/index.html @@ -33,6 +33,11 @@
+ +