diff --git a/.circleci/config.yml.old b/.circleci/config.yml.old deleted file mode 100644 index f9a4a19578d6a..0000000000000 --- a/.circleci/config.yml.old +++ /dev/null @@ -1,518 +0,0 @@ -defaults: &defaults - working_directory: ~/repo - -attach_workspace: &attach_workspace - at: /tmp - -test-install-dependencies: &test-install-dependencies - name: Install dependencies - command: | - wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add - - sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 9DA31620334BD75D9DCB49F368818C72E52529D4 - echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" | sudo tee /etc/apt/sources.list.d/google.list - echo "deb http://repo.mongodb.org/apt/debian stretch/mongodb-org/4.0 main" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.0.list - sudo apt-get update - sudo apt-get install -y mongodb-org-shell google-chrome-stable - -test-run: &test-run - name: Run Tests - command: | - for i in $(seq 1 5); do mongo rocketchat --eval 'db.dropDatabase()' && npm test && s=0 && break || s=$? && sleep 1; done; (exit $s) - -test-npm-install: &test-npm-install - name: NPM install - command: | - npm install - -test-store_artifacts: &test-store_artifacts - path: .screenshots/ - -test-configure-replicaset: &test-configure-replicaset - name: Configure Replica Set - command: | - mongo --eval 'rs.initiate({_id:"rs0", members: [{"_id":1, "host":"localhost:27017"}]})' - mongo --eval 'rs.status()' - -test-restore-npm-cache: &test-restore-npm-cache - keys: - - node-modules-cache-{{ checksum ".circleci/config.yml" }}-{{ checksum "package.json" }} - -test-save-npm-cache: &test-save-npm-cache - key: node-modules-cache-{{ checksum ".circleci/config.yml" }}-{{ checksum "package.json" }} - paths: - - ./node_modules - -test-docker-image: &test-docker-image - circleci/node:8.16-stretch-browsers - -test-with-oplog: &test-with-oplog - <<: *defaults - environment: - TEST_MODE: "true" - MONGO_URL: mongodb://localhost:27017/rocketchat - MONGO_OPLOG_URL: mongodb://localhost:27017/local - - steps: - - attach_workspace: *attach_workspace - - checkout - - run: *test-install-dependencies - - run: *test-configure-replicaset - - restore_cache: *test-restore-npm-cache - - run: *test-npm-install - - run: *test-run - - save_cache: *test-save-npm-cache - - store_artifacts: *test-store_artifacts - -version: 2 -jobs: - build: - <<: *defaults - docker: - - image: circleci/node:8.16-stretch - - image: mongo:3.4 - - steps: - - checkout - - - restore_cache: - keys: - - node-modules-cache-{{ checksum ".circleci/config.yml" }}-{{ checksum "package.json" }} - - - restore_cache: - keys: - - meteor-{{ checksum ".circleci/config.yml" }}-{{ checksum ".meteor/release" }} - - - run: - name: Install Meteor - command: | - # Restore bin from cache - set +e - METEOR_SYMLINK_TARGET=$(readlink ~/.meteor/meteor) - METEOR_TOOL_DIRECTORY=$(dirname "$METEOR_SYMLINK_TARGET") - set -e - LAUNCHER=$HOME/.meteor/$METEOR_TOOL_DIRECTORY/scripts/admin/launch-meteor - if [ -e $LAUNCHER ] - then - echo "Cached Meteor bin found, restoring it" - sudo cp "$LAUNCHER" "/usr/local/bin/meteor" - else - echo "No cached Meteor bin found." - fi - - # only install meteor if bin isn't found - command -v meteor >/dev/null 2>&1 || curl https://install.meteor.com | sed s/--progress-bar/-sL/g | /bin/sh - - - run: - name: Versions - command: | - npm --versions - node -v - meteor --version - meteor npm --versions - meteor node -v - git version - - - run: - name: Meteor npm install - command: | - # rm -rf node_modules - # rm -f package-lock.json - meteor npm install - - - run: - name: Lint - command: | - meteor npm run lint - - - run: - name: Unit Test - command: | - MONGO_URL=mongodb://localhost:27017 meteor npm run testunit - - - restore_cache: - keys: - - meteor-cache-{{ checksum ".circleci/config.yml" }}-{{ checksum ".meteor/versions" }} - - # To reduce memory need during actual build, build the packages solely first - - run: - name: Build a Meteor cache - command: | - # to do this we can clear the main files and it build the rest - echo "" > server/main.js - echo "" > client/main.js - meteor build --server-only --debug --directory /tmp/build-temp - git checkout -- server/main.js client/main.js - - - run: - name: Build Rocket.Chat - environment: - TOOL_NODE_FLAGS: --max_old_space_size=4096 - command: | - if [[ $CIRCLE_TAG ]] || [[ $CIRCLE_BRANCH == 'develop' ]]; then - meteor reset; - fi - - export CIRCLE_PR_NUMBER="${CIRCLE_PR_NUMBER:-${CIRCLE_PULL_REQUEST##*/}}" - if [[ -z $CIRCLE_PR_NUMBER ]]; then - meteor build --server-only --directory /tmp/build-test - else - export METEOR_PROFILE=1000 - meteor build --server-only --directory --debug /tmp/build-test - fi; - - - run: - name: Prepare build - command: | - mkdir /tmp/build/ - cd /tmp/build-test - tar czf /tmp/build/Rocket.Chat.tar.gz bundle - cd /tmp/build-test/bundle/programs/server - npm install - - - save_cache: - key: node-modules-cache-{{ checksum ".circleci/config.yml" }}-{{ checksum "package.json" }} - paths: - - ./node_modules - - - save_cache: - key: meteor-cache-{{ checksum ".circleci/config.yml" }}-{{ checksum ".meteor/versions" }} - paths: - - ./.meteor/local - - - save_cache: - key: meteor-{{ checksum ".circleci/config.yml" }}-{{ checksum ".meteor/release" }} - paths: - - ~/.meteor - - - persist_to_workspace: - root: /tmp/ - paths: - - build-test - - build - - - store_artifacts: - path: /tmp/build - - - test-with-oplog-mongo-3-4: - <<: *test-with-oplog - docker: - - image: *test-docker-image - - image: mongo:3.4 - command: [mongod, --noprealloc, --smallfiles, --replSet=rs0] - - test-with-oplog-mongo-3-6: - <<: *test-with-oplog - docker: - - image: *test-docker-image - - image: mongo:3.6 - command: [mongod, --noprealloc, --smallfiles, --replSet=rs0] - - test-with-oplog-mongo-4-0: - <<: *test-with-oplog - docker: - - image: *test-docker-image - - image: mongo:4.0 - command: [mongod, --noprealloc, --smallfiles, --replSet=rs0] - - deploy: - <<: *defaults - docker: - - image: circleci/node:8.16-stretch - - steps: - - attach_workspace: - at: /tmp - - - checkout - - - run: - name: Install AWS cli - command: | - if [[ $CIRCLE_PULL_REQUESTS ]]; then exit 0; fi; - - sudo apt-get -y -qq update - sudo apt-get -y -qq install python3.5-dev - curl -O https://bootstrap.pypa.io/get-pip.py - python3.5 get-pip.py --user - export PATH=~/.local/bin:$PATH - pip install awscli --upgrade --user - - - run: - name: Publish assets - command: | - if [[ $CIRCLE_PULL_REQUESTS ]]; then exit 0; fi; - - export PATH=~/.local/bin:$PATH - export CIRCLE_TAG=${CIRCLE_TAG:=} - - aws s3 cp s3://rocketchat/sign.key.gpg .circleci/sign.key.gpg - - source .circleci/setartname.sh - source .circleci/setdeploydir.sh - bash .circleci/setupsig.sh - bash .circleci/namefiles.sh - - aws s3 cp $ROCKET_DEPLOY_DIR/ s3://download.rocket.chat/build/ --recursive - - bash .circleci/update-releases.sh - bash .circleci/snap.sh - bash .circleci/redhat-registry.sh - - image-build: - <<: *defaults - - docker: - - image: docker:17.05.0-ce-git - - steps: - - attach_workspace: - at: /tmp - - - checkout - - - setup_remote_docker - - - run: - name: Build Docker image - command: | - cd /tmp/build - tar xzf Rocket.Chat.tar.gz - rm Rocket.Chat.tar.gz - - export CIRCLE_TAG=${CIRCLE_TAG:=} - if [[ $CIRCLE_TAG ]]; then - docker login -u $DOCKER_USER -p $DOCKER_PASS - - echo "Build official Docker image" - cp ~/repo/.docker/Dockerfile . - docker build -t rocketchat/rocket.chat:$CIRCLE_TAG . - docker push rocketchat/rocket.chat:$CIRCLE_TAG - - echo "Build preview Docker image" - cp ~/repo/.docker-mongo/Dockerfile . - cp ~/repo/.docker-mongo/entrypoint.sh . - docker build -t rocketchat/rocket.chat.preview:$CIRCLE_TAG . - docker push rocketchat/rocket.chat.preview:$CIRCLE_TAG - - if echo "$CIRCLE_TAG" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' ; then - docker tag rocketchat/rocket.chat:$CIRCLE_TAG rocketchat/rocket.chat:latest - docker push rocketchat/rocket.chat:latest - - docker tag rocketchat/rocket.chat.preview:$CIRCLE_TAG rocketchat/rocket.chat.preview:latest - docker push rocketchat/rocket.chat.preview:latest - elif echo "$CIRCLE_TAG" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$' ; then - docker tag rocketchat/rocket.chat:$CIRCLE_TAG rocketchat/rocket.chat:release-candidate - docker push rocketchat/rocket.chat:release-candidate - - docker tag rocketchat/rocket.chat.preview:$CIRCLE_TAG rocketchat/rocket.chat.preview:release-candidate - docker push rocketchat/rocket.chat.preview:release-candidate - fi - - exit 0 - fi; - - if [[ $CIRCLE_BRANCH == 'develop' ]]; then - docker login -u $DOCKER_USER -p $DOCKER_PASS - - echo "Build official Docker image" - cp ~/repo/.docker/Dockerfile . - docker build -t rocketchat/rocket.chat:develop . - docker push rocketchat/rocket.chat:develop - - echo "Build preview Docker image" - cp ~/repo/.docker-mongo/Dockerfile . - cp ~/repo/.docker-mongo/entrypoint.sh . - docker build -t rocketchat/rocket.chat.preview:develop . - docker push rocketchat/rocket.chat.preview:develop - - exit 0 - fi; - - pr-build: - <<: *defaults - docker: - - image: circleci/node:8.16-stretch - - steps: - - checkout - - - restore_cache: - keys: - - node-modules-cache-{{ checksum ".circleci/config.yml" }}-{{ checksum "package.json" }} - - - restore_cache: - keys: - - meteor-{{ checksum ".circleci/config.yml" }}-{{ checksum ".meteor/release" }} - - - run: - name: Install Meteor - command: | - # Restore bin from cache - set +e - METEOR_SYMLINK_TARGET=$(readlink ~/.meteor/meteor) - METEOR_TOOL_DIRECTORY=$(dirname "$METEOR_SYMLINK_TARGET") - set -e - LAUNCHER=$HOME/.meteor/$METEOR_TOOL_DIRECTORY/scripts/admin/launch-meteor - if [ -e $LAUNCHER ] - then - echo "Cached Meteor bin found, restoring it" - sudo cp "$LAUNCHER" "/usr/local/bin/meteor" - else - echo "No cached Meteor bin found." - fi - - # only install meteor if bin isn't found - command -v meteor >/dev/null 2>&1 || curl https://install.meteor.com | sed s/--progress-bar/-sL/g | /bin/sh - - - run: - name: Versions - command: | - npm --versions - node -v - meteor --version - meteor npm --versions - meteor node -v - git version - - - run: - name: Meteor npm install - command: | - # rm -rf node_modules - # rm -f package-lock.json - meteor npm install - - - restore_cache: - keys: - - meteor-cache-{{ checksum ".circleci/config.yml" }}-{{ checksum ".meteor/versions" } - - - run: - name: Build Rocket.Chat - environment: - TOOL_NODE_FLAGS: --max_old_space_size=3072 - command: | - meteor build --server-only /tmp/build-pr - - - - persist_to_workspace: - root: /tmp/ - paths: - - build-pr - - - store_artifacts: - path: /tmp/build-pr - - pr-image-build: - <<: *defaults - - docker: - - image: docker:17.05.0-ce-git - - steps: - - attach_workspace: - at: /tmp - - - checkout - - - setup_remote_docker - - - run: - name: Build Docker image for PRs - command: | - export CIRCLE_PR_NUMBER="${CIRCLE_PR_NUMBER:-${CIRCLE_PULL_REQUEST##*/}}" - if [[ -z $CIRCLE_PR_NUMBER ]]; then - exit 0 - fi; - - cd /tmp/build-pr - tar xzf repo.tar.gz - rm repo.tar.gz - - docker login -u $DOCKER_USER -p $DOCKER_PASS - - echo "Build official Docker image" - cp ~/repo/.docker/Dockerfile . - docker build -t rocketchat/rocket.chat:pr-$CIRCLE_PR_NUMBER . - docker push rocketchat/rocket.chat:pr-$CIRCLE_PR_NUMBER - - #echo "Build preview Docker image" - #cp ~/repo/.docker-mongo/Dockerfile . - #cp ~/repo/.docker-mongo/entrypoint.sh . - #docker build -t rocketchat/rocket.chat.preview:pr-$CIRCLE_PR_NUMBER . - #docker push rocketchat/rocket.chat.preview:pr-$CIRCLE_PR_NUMBER - -workflows: - version: 2 - build-and-test: - jobs: - - hold-all: - type: approval - filters: - branches: - only: develop - tags: - only: /^[0-9]+\.[0-9]+\.[0-9]+(?:-(?:rc|beta)\.[0-9]+)?$/ - - build: - requires: - - hold-all - filters: - tags: - only: /^[0-9]+\.[0-9]+\.[0-9]+(?:-(?:rc|beta)\.[0-9]+)?$/ - - test-with-oplog-mongo-3-4: &test-mongo - requires: - - build - filters: - tags: - only: /^[0-9]+\.[0-9]+\.[0-9]+(?:-(?:rc|beta)\.[0-9]+)?$/ - - test-with-oplog-mongo-3-6: &test-mongo-no-pr - requires: - - build - filters: - branches: - only: develop - tags: - only: /^[0-9]+\.[0-9]+\.[0-9]+(?:-(?:rc|beta)\.[0-9]+)?$/ - - test-with-oplog-mongo-4-0: *test-mongo - - deploy: - requires: - - test-with-oplog-mongo-3-4 - - test-with-oplog-mongo-3-6 - - test-with-oplog-mongo-4-0 - filters: - branches: - only: develop - tags: - only: /^[0-9]+\.[0-9]+\.[0-9]+(?:-(?:rc|beta)\.[0-9]+)?$/ - - image-build: - requires: - - deploy - filters: - branches: - only: develop - tags: - only: /^[0-9]+\.[0-9]+\.[0-9]+(?:-(?:rc|beta)\.[0-9]+)?$/ - - hold: - type: approval - requires: - - build - filters: - branches: - ignore: develop - tags: - only: /^[0-9]+\.[0-9]+\.[0-9]+(?:-(?:rc|beta)\.[0-9]+)?$/ - - pr-build: - requires: - - hold - filters: - branches: - ignore: develop - tags: - only: /^[0-9]+\.[0-9]+\.[0-9]+(?:-(?:rc|beta)\.[0-9]+)?$/ - - pr-image-build: - requires: - - pr-build - filters: - branches: - ignore: develop - tags: - only: /^[0-9]+\.[0-9]+\.[0-9]+(?:-(?:rc|beta)\.[0-9]+)?$/ diff --git a/.circleci/namefiles.sh b/.circleci/namefiles.sh deleted file mode 100644 index f2fd572105f3b..0000000000000 --- a/.circleci/namefiles.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -set -euvo pipefail -IFS=$'\n\t' - -FILENAME="$ROCKET_DEPLOY_DIR/rocket.chat-$ARTIFACT_NAME.tgz"; - -ln -s /tmp/build/Rocket.Chat.tar.gz "$FILENAME" -gpg --armor --detach-sign "$FILENAME" diff --git a/.circleci/redhat-registry.sh b/.circleci/redhat-registry.sh deleted file mode 100755 index a206af991c19b..0000000000000 --- a/.circleci/redhat-registry.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -set -euvo pipefail -IFS=$'\n\t' - -if [[ $CIRCLE_TAG ]]; then - curl -X POST \ - https://connect.redhat.com/api/v2/projects/$REDHAT_REGISTRY_PID/build \ - -H "Authorization: Bearer $REDHAT_REGISTRY_KEY" \ - -H 'Cache-Control: no-cache' \ - -H 'Content-Type: application/json' \ - -d '{"tag":"'$CIRCLE_TAG'"}' -fi diff --git a/.circleci/setartname.sh b/.circleci/setartname.sh deleted file mode 100644 index acfdb3e032e6d..0000000000000 --- a/.circleci/setartname.sh +++ /dev/null @@ -1,23 +0,0 @@ -if [[ $CIRCLE_TAG ]]; then - export ARTIFACT_NAME="$(npm run version --silent)" -else - export ARTIFACT_NAME="$(npm run version --silent).$CIRCLE_BUILD_NUM" -fi - -if [[ $CIRCLE_TAG =~ ^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+ ]]; then - SNAP_CHANNEL=candidate - RC_RELEASE=candidate - RC_VERSION=$CIRCLE_TAG -elif [[ $CIRCLE_TAG =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - SNAP_CHANNEL=stable - RC_RELEASE=stable - RC_VERSION=$CIRCLE_TAG -else - SNAP_CHANNEL=edge - RC_RELEASE=develop - RC_VERSION="$(npm run version --silent)" -fi - -export SNAP_CHANNEL -export RC_RELEASE -export RC_VERSION diff --git a/.circleci/setdeploydir.sh b/.circleci/setdeploydir.sh deleted file mode 100644 index 2c49e4a7027ae..0000000000000 --- a/.circleci/setdeploydir.sh +++ /dev/null @@ -1,2 +0,0 @@ -export ROCKET_DEPLOY_DIR="/tmp/deploy" -mkdir -p $ROCKET_DEPLOY_DIR diff --git a/.circleci/setupsig.sh b/.circleci/setupsig.sh deleted file mode 100644 index 7b8f3820d745a..0000000000000 --- a/.circleci/setupsig.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -set -euvo pipefail -IFS=$'\n\t' - -cp .circleci/sign.key.gpg /tmp -gpg --yes --batch --passphrase=$GPG_PASSWORD /tmp/sign.key.gpg -gpg --allow-secret-key-import --import /tmp/sign.key -rm /tmp/sign.key diff --git a/.circleci/snap.sh b/.circleci/snap.sh deleted file mode 100644 index afd047de0e15c..0000000000000 --- a/.circleci/snap.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash -set -euvo pipefail -IFS=$'\n\t' - -# Add launchpad to known hosts - -mkdir -p $HOME/.ssh -ssh-keyscan -t rsa -H git.launchpad.net >> $HOME/.ssh/known_hosts - -echo "Preparing to trigger a snap release for $SNAP_CHANNEL channel" - -cd $PWD/.snapcraft - -# We need some meta data so it'll actually commit. This could be useful to have for debugging later. -echo -e "Tag: $CIRCLE_TAG\r\nBranch: $CIRCLE_BRANCH\r\nBuild: $CIRCLE_BUILD_NUM\r\nCommit: $CIRCLE_SHA1" > buildinfo - -# Clone launchpad repo for the channel down. -git clone -b $SNAP_CHANNEL --depth 1 git+ssh://rocket.chat.buildmaster@git.launchpad.net/rocket.chat launchpad - -# Rarely will change, but just incase we copy it all -cp -r resources buildinfo snap launchpad/ -sed s/#{RC_VERSION}/$RC_VERSION/ snap/snapcraft.yaml > launchpad/snap/snapcraft.yaml -sed s/#{RC_VERSION}/$RC_VERSION/ resources/prepareRocketChat > launchpad/resources/prepareRocketChat - -cd launchpad -git add resources snap buildinfo - -# Set commit author details -git config user.email "buildmaster@rocket.chat" -git config user.name "CircleCI" - -# Another place where basic meta data will live for at a glance info -git commit -m "CircleCI Build: $CIRCLE_BUILD_NUM CircleCI Commit: $CIRCLE_SHA1" - -# Push up up to the branch of choice. -git push origin $SNAP_CHANNEL - -# Clean up -cd .. -rm -rf launchpad diff --git a/.circleci/update-releases.sh b/.circleci/update-releases.sh deleted file mode 100644 index 1f5f527c4d086..0000000000000 --- a/.circleci/update-releases.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -set -euvo pipefail -IFS=$'\n\t' - -curl -H "Content-Type: application/json" -H "X-Update-Token: $UPDATE_TOKEN" -d \ - "{\"commit\": \"$CIRCLE_SHA1\", \"tag\": \"$RC_VERSION\", \"branch\": \"$CIRCLE_BRANCH\", \"artifactName\": \"$ARTIFACT_NAME\", \"releaseType\": \"$RC_RELEASE\" }" \ - https://releases.rocket.chat/update - -# Makes build fail if the release isn't there -curl --fail https://releases.rocket.chat/$RC_VERSION/info diff --git a/.docker-mongo/Dockerfile b/.docker-mongo/Dockerfile index a625eaf99c648..672ed4da6ba0e 100644 --- a/.docker-mongo/Dockerfile +++ b/.docker-mongo/Dockerfile @@ -25,8 +25,9 @@ RUN set -x \ && mkdir -p /app/uploads \ && chown rocketchat:rocketchat /app/uploads -ADD . /app -ADD entrypoint.sh /app/bundle/ +# --chown requires Docker 17.12 and works only on Linux +ADD --chown=rocketchat:rocketchat . /app +ADD --chown=rocketchat:rocketchat entrypoint.sh /app/bundle/ RUN aptMark="$(apt-mark showmanual)" \ && apt-get install -y --no-install-recommends g++ make python ca-certificates \ @@ -42,8 +43,7 @@ RUN aptMark="$(apt-mark showmanual)" \ | sort -u \ | xargs -r apt-mark manual \ && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ - && npm cache clear --force \ - && chown -R rocketchat:rocketchat /app + && npm cache clear --force VOLUME /app/uploads diff --git a/.docker/Dockerfile b/.docker/Dockerfile index fbc23cceb3f3c..355b08eb0ab49 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -10,7 +10,8 @@ RUN groupadd -g 65533 -r rocketchat \ && apt-get update \ && apt-get install -y --no-install-recommends fontconfig -ADD . /app +# --chown requires Docker 17.12 and works only on Linux +ADD --chown=rocketchat:rocketchat . /app RUN aptMark="$(apt-mark showmanual)" \ && apt-get install -y --no-install-recommends g++ make python ca-certificates \ @@ -26,8 +27,7 @@ RUN aptMark="$(apt-mark showmanual)" \ | sort -u \ | xargs -r apt-mark manual \ && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ - && npm cache clear --force \ - && chown -R rocketchat:rocketchat /app + && npm cache clear --force USER rocketchat diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index 4732b0a23ca9c..91a1248b77f34 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ -FROM registry.access.redhat.com/rhscl/nodejs-8-rhel7 +FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 3.8.2 +ENV RC_VERSION 3.12.1 MAINTAINER buildmaster@rocket.chat @@ -13,9 +13,9 @@ LABEL name="Rocket.Chat" \ description="The Ultimate Open Source Web Chat Platform" \ run="docker run -d --name ${NAME} ${IMAGE}" - -# This is ugly... But for some reason npm and node aren't available at this stage. -ENV PATH /opt/rh/rh-nodejs8/root/usr/bin:/opt/app-root/src/node_modules/.bin/:/opt/app-root/src/.npm-global/bin/:/opt/app-root/src/bin:/opt/app-root/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +USER root +RUN dnf install -y python38 && rm -rf /var/cache /var/log/dnf* /var/log/yum.* +USER default RUN set -x \ && gpg --keyserver ha.pool.sks-keyservers.net --recv-keys 0E163286C20D07B9787EBE9FD7F9D0414FD08104 \ @@ -32,10 +32,6 @@ VOLUME /opt/app-root/src/uploads WORKDIR /opt/app-root/src/bundle -# Hack needed to force use of bundled library instead of system level outdated library -# https://github.com/lovell/sharp/issues/892 -ENV LD_PRELOAD=/opt/app-root/src/bundle/programs/server/npm/node_modules/sharp/vendor/lib/libz.so - ENV DEPLOY_METHOD=docker-redhat \ NODE_ENV=production \ MONGO_URL=mongodb://mongo:27017/rocketchat \ diff --git a/.eslintignore b/.eslintignore index 2a39e35ae2b25..5831f3fab824f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,11 +4,9 @@ packages/meteor-streams/ packages/meteor-timesync/ app/emoji-emojione/generateEmojiIndex.js app/favico/favico.js -app/katex/client/katex/katex.min.js packages/rocketchat-livechat/assets/rocketchat-livechat.min.js packages/rocketchat-livechat/assets/rocket-livechat.js app/theme/client/vendor/ -app/ui/client/lib/Modernizr.js public/packages/rocketchat_videobridge/client/public/external_api.js packages/tap-i18n/lib/tap_i18next/tap_i18next-1.7.3.js private/moment-locales/ @@ -19,3 +17,4 @@ public/workers/**/* imports/client/ !/.storybook/ ee/server/services/dist/** +!/.mocharc.js diff --git a/.eslintrc b/.eslintrc index d1d22fdb7fa5e..e486bf36405e9 100644 --- a/.eslintrc +++ b/.eslintrc @@ -83,7 +83,9 @@ "indent": "off", "no-extra-parens": "off", "no-spaced-func": "off", + "no-unused-vars": "off", "no-useless-constructor": "off", + "no-use-before-define": "off", "react/jsx-uses-react": "error", "react/jsx-uses-vars": "error", "react/jsx-no-undef": "error", @@ -99,6 +101,10 @@ "SwitchCase": 1 } ], + "@typescript-eslint/interface-name-prefix": [ + "error", + "always" + ], "@typescript-eslint/no-extra-parens": [ "error", "all", @@ -111,10 +117,9 @@ } ], "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/interface-name-prefix": [ - "error", - "always" - ] + "@typescript-eslint/no-unused-vars": ["error", { + "argsIgnorePattern": "^_" + }] }, "env": { "browser": true, diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b7ce1a7852599..24e70c8f66391 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,46 +1,38 @@ - - - - -## Proposed changes - + + + + +## Proposed changes (including videos or screenshots) + + + + ## Issue(s) -## How to test or reproduce +## Steps to test or reproduce -## Screenshots - -## Types of changes - - - -- [ ] Bugfix (non-breaking change which fixes an issue) -- [ ] Improvement (non-breaking change which improves a current function) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) -- [ ] Hotfix (a major bugfix that has to be merged asap) -- [ ] Documentation Update (if none of the other choices apply) - -## Checklist - - -- [ ] I have read the [CONTRIBUTING](https://github.com/RocketChat/Rocket.Chat/blob/develop/.github/CONTRIBUTING.md#contributing-to-rocketchat) doc -- [ ] I have signed the [CLA](https://cla-assistant.io/RocketChat/Rocket.Chat) -- [ ] Lint and unit tests pass locally with my changes -- [ ] I have added tests that prove my fix is effective or that my feature works (if applicable) -- [ ] I have added necessary documentation (if applicable) -- [ ] Any dependent changes have been merged and published in downstream modules - -## Changelog - - - - ## Further comments - diff --git a/.github/history-manual.json b/.github/history-manual.json index 67dcdc88ac9a9..da4c6418600d8 100644 --- a/.github/history-manual.json +++ b/.github/history-manual.json @@ -28,5 +28,40 @@ "userLogin": "rodrigok", "contributors": [], "description": "Requires the 2FA password fallback enforcement enabled to work" + }], + "3.7.3": [{ + "title": "[FIX] Exception on certain login cases including SAML", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }], + "3.8.3": [{ + "title": "[FIX] Exception on certain login cases including SAML", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }], + "3.9.1": [{ + "title": "[FIX] Exception on certain login cases including SAML", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }], + "3.10.5": [{ + "title": "[FIX] Security Hotfix", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }], + "3.12.0-rc.3": [{ + "title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] }] } diff --git a/.github/history.json b/.github/history.json index 3af0161cbdd6a..7ec96d1795206 100644 --- a/.github/history.json +++ b/.github/history.json @@ -51655,10 +51655,10 @@ } ] }, - "3.8.2": { + "3.9.0-rc.0": { "node_version": "12.18.4", "npm_version": "6.14.8", - "apps_engine_version": "1.19.0", + "apps_engine_version": "1.20.0-alpha.4149", "mongo_versions": [ "3.4", "3.6", @@ -51666,22 +51666,5019 @@ ], "pull_requests": [ { - "pr": "19684", - "title": "[FIX] Room avatar update event doesn't properly broadcast room id", + "pr": "19488", + "title": "[FIX] Outgoing integrations without trigger words or with multiple commas", "userLogin": "g-thome", - "milestone": "3.8.2", "contributors": [ - "g-thome" + "g-thome", + "ggazzo", + "sampaiodiego", + "web-flow" ] }, { - "pr": "19683", - "title": "[FIX] Server crash while reading settings for allowed and blocked email domain lists", + "pr": "19236", + "title": "[FIX] Role description not updating", + "userLogin": "dougfabris", + "milestone": "3.9.0", + "contributors": [ + "ggazzo", + null, + "dougfabris", + "web-flow", + "sampaiodiego" + ] + }, + { + "pr": "19642", + "title": "Improve REST endpoint to log user out from other clients", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "19638", + "title": "[FIX] Channel actions not working when reduce motion is active", + "userLogin": "MartinSchoeler", + "milestone": "3.9.0", + "contributors": [ + "MartinSchoeler", + "web-flow" + ] + }, + { + "pr": "19338", + "title": "[IMPROVE] SlackBridge threads performance improvement", + "userLogin": "antkaz", + "contributors": [ + "antkaz" + ] + }, + { + "pr": "19571", + "title": "[FIX] LDAP Unique Identifier Field can not use operational attributes", + "userLogin": "truongtx8", + "contributors": [ + "truongtx8", + "web-flow" + ] + }, + { + "pr": "19628", + "title": "[FIX] Settings may not update internal cache immediately", "userLogin": "g-thome", - "milestone": "3.8.2", "contributors": [ "g-thome" ] + }, + { + "pr": "11726", + "title": "[NEW] 2 Factor Authentication when using OAuth and SAML", + "userLogin": "Hudell", + "milestone": "3.9.0", + "contributors": [ + "Hudell", + "pierre-lehnen-rc", + "web-flow", + "MarcosSpessatto" + ] + }, + { + "pr": "19634", + "title": "React Room Container", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "19381", + "title": "[FIX] Allow username change if LDAP is enabled but their username is not linked to an LDAP field", + "userLogin": "robertfromont", + "description": "LDAP users can change their username if the LDAP_Username_Field setting is blank.", + "milestone": "3.9.0", + "contributors": [ + "robertfromont", + "web-flow" + ] + }, + { + "pr": "19599", + "title": "[FIX] Auto Translate", + "userLogin": "ggazzo", + "milestone": "3.9.0", + "contributors": [ + "ggazzo", + "web-flow", + "sampaiodiego" + ] + }, + { + "pr": "19620", + "title": "Manual LingoHub update", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "19596", + "title": "[FIX] Prevent headerRoom's click to open room/direct info", + "userLogin": "dougfabris", + "contributors": [ + "dougfabris" + ] + }, + { + "pr": "10391", + "title": "[NEW] Added setting to disable password changes for users who log in using SSO", + "userLogin": "Hudell", + "milestone": "3.9.0", + "contributors": [ + "Hudell", + "web-flow", + "pierre-lehnen-rc", + "sampaiodiego" + ] + }, + { + "pr": "19633", + "title": "[IMPROVE] Rewrite Auto-Translate as a React component ", + "userLogin": "tiagoevanp", + "contributors": [ + "tiagoevanp" + ] + }, + { + "pr": "19514", + "title": "[FIX] Size of embed Youtube on threads for small screens", + "userLogin": "tiagoevanp", + "milestone": "3.9.0", + "contributors": [ + "tiagoevanp", + "dougfabris", + "web-flow" + ] + }, + { + "pr": "19593", + "title": "[FIX] Issue with drag and drop", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "19639", + "title": "Update Apps-Engine version", + "userLogin": "d-gubert", + "milestone": "3.9.0", + "contributors": [ + "d-gubert" + ] + }, + { + "pr": "19500", + "title": "[IMPROVE][ENTERPRISE] UI/UX enhancements in Omnichannel Units page", + "userLogin": "rafaelblink", + "milestone": "3.9.0", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19400", + "title": "[FIX] Omnichannel Analytics page doesn't have field labels", + "userLogin": "rafaelblink", + "milestone": "3.9.0", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19487", + "title": "[FIX] Wrong margin of description field in Omnichannel Webhooks page", + "userLogin": "rafaelblink", + "milestone": "3.9.0", + "contributors": [ + "rafaelblink", + "web-flow", + "renatobecker" + ] + }, + { + "pr": "19485", + "title": "[IMPROVE] UI/UX enhancements in Omnichannel Triggers page", + "userLogin": "rafaelblink", + "milestone": "3.9.0", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19601", + "title": "[FIX] Column width was not following the design system in Omnichannel Departments page", + "userLogin": "rafaelblink", + "milestone": "3.9.0", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19512", + "title": "[IMPROVE][ENTERPRISE] UI/UX enhancements in Omnichannel Priorities page", + "userLogin": "rafaelblink", + "milestone": "3.9.0", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19493", + "title": "[FIX] Save button enabled by default in Omnichannel Business Hours Form", + "userLogin": "rafaelblink", + "milestone": "3.9.0", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19495", + "title": "[IMPROVE][ENTERPRISE] UI/UX enhancements in Omnichannel Monitors page", + "userLogin": "rafaelblink", + "milestone": "3.9.0", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19624", + "title": "[FIX] The width of list columns was not following the design system in Omnichannel Managers page", + "userLogin": "rafaelblink", + "milestone": "3.9.0", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19625", + "title": "[FIX] The width of list columns was not following the design system in Omnichannel Agents page", + "userLogin": "rafaelblink", + "milestone": "3.9.0", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19627", + "title": "Fix Docker preview image build", + "userLogin": "sampaiodiego", + "milestone": "3.8.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "19473", + "title": "[IMPROVES] Omnichannel - Custom Fields pages.", + "userLogin": "rafaelblink", + "milestone": "3.9.0", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19510", + "title": "[IMPROVE][ENTERPRISE] UI/UX enhancements in Omnichannel Tags page", + "userLogin": "rafaelblink", + "milestone": "3.9.0", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19616", + "title": "[FIX] Engagement dashboard on old Mongo versions", + "userLogin": "sampaiodiego", + "milestone": "3.8.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "19576", + "title": "[IMPROVE] Forward Omnichannel room to agent in another department", + "userLogin": "mrfigueiredo", + "milestone": "3.9.0", + "contributors": [ + "mrfigueiredo", + "web-flow", + "renatobecker" + ] + }, + { + "pr": "19570", + "title": "[FIX] Typo in custom oauth from environment variable", + "userLogin": "geekgonecrazy", + "milestone": "3.8.1", + "contributors": [ + "geekgonecrazy", + "web-flow" + ] + }, + { + "pr": "19383", + "title": "Bump xml-crypto from 1.5.3 to 2.0.0", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "19592", + "title": "LingoHub based on develop", + "userLogin": "lingohub[bot]", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "19387", + "title": "Bump bcrypt from 4.0.1 to 5.0.0 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "19590", + "title": "chore: Debounce sidebar list", + "userLogin": "gabriellsh", + "contributors": [ + "gabriellsh", + "ggazzo" + ] + }, + { + "pr": "19543", + "title": "Bump systeminformation from 4.27.3 to 4.30.1 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "19131", + "title": "LingoHub based on develop", + "userLogin": "lingohub[bot]", + "contributors": [ + "sampaiodiego", + null + ] + }, + { + "pr": "19579", + "title": "[IMPROVE] Add support to `replace` operation when using Change Stream", + "userLogin": "sampaiodiego", + "milestone": "3.9.0", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "19582", + "title": "Regression: Room Info maxAgeDefault variable", + "userLogin": "dougfabris", + "contributors": [ + "dougfabris", + "ggazzo" + ] + }, + { + "pr": "19581", + "title": "Regression: Room Info Edit action", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo", + "web-flow", + "dougfabris" + ] + }, + { + "pr": "19421", + "title": "[IMPROVE] UI/UX enhancements in department pages following the design system", + "userLogin": "rafaelblink", + "milestone": "3.9.0", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19511", + "title": "[IMPROVE] Rewrite Room Info", + "userLogin": "dougfabris", + "milestone": "3.9.0", + "contributors": [ + "dougfabris", + "ggazzo" + ] + }, + { + "pr": "19518", + "title": "[IMPROVE] KeyboardShortcuts as React component", + "userLogin": "tiagoevanp", + "milestone": "3.9.0", + "contributors": [ + "tiagoevanp", + "ggazzo", + "web-flow" + ] + }, + { + "pr": "19577", + "title": "[FIX] Regex was not working properly on visitors.search endpoint", + "userLogin": "rafaelblink", + "milestone": "3.9.0", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19575", + "title": "Regression: Fix wrong template on photoswipe", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "19410", + "title": "[FIX] UI/UX issues on Omnichannel Managers page", + "userLogin": "rafaelblink", + "milestone": "3.9.0", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19414", + "title": "[FIX] Input without label and email ordering missing on Omnichannel Agents page", + "userLogin": "rafaelblink", + "milestone": "3.9.0", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19569", + "title": "REGRESSION: Photoswipe not working ", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "19397", + "title": "[IMPROVE] UI/UX enhancements in Omnichannnel Current Chats page", + "userLogin": "rafaelblink", + "milestone": "3.9.0", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19564", + "title": "Regression: Fix Avatar x40", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "19491", + "title": "[IMPROVE] Remove Box props from Avatar component", + "userLogin": "tiagoevanp", + "contributors": [ + "tiagoevanp", + "ggazzo" + ] + }, + { + "pr": "19467", + "title": "[IMPROVE] Remove Box dependence from Tag and Badge components", + "userLogin": "tiagoevanp", + "contributors": [ + "tiagoevanp", + "ggazzo", + "web-flow" + ] + }, + { + "pr": "19561", + "title": "[FIX] IE11 - Update ui kit and fuselage bundle", + "userLogin": "MartinSchoeler", + "milestone": "3.8.1", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "16884", + "title": "[FIX] Unread count for all messages when mentioning an user", + "userLogin": "subham103", + "contributors": [ + "subham103", + "sampaiodiego" + ] + }, + { + "pr": "19509", + "title": "[FIX] Setup Wizard User Creation Locking up", + "userLogin": "MartinSchoeler", + "description": "[FIX] Setup Wizard User Creation Locking up", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "19458", + "title": "[FIX] Restore Message View Mode Preference", + "userLogin": "MartinSchoeler", + "description": "[FIX] Restore Message View Mode Preference", + "milestone": "3.9.0", + "contributors": [ + "MartinSchoeler", + "web-flow" + ] + }, + { + "pr": "19475", + "title": "[FIX] TOTP Being ignored when changing our own avatar", + "userLogin": "MartinSchoeler", + "description": "[FIX] TOTP Being ignored when changing our own avatar", + "milestone": "3.9.0", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "19450", + "title": "[FIX] Engagement dashboard: graphs adjustment", + "userLogin": "gabriellsh", + "contributors": [ + "gabriellsh" + ] + }, + { + "pr": "19533", + "title": "[IMPROVE] Bundle Size Client", + "userLogin": "ggazzo", + "description": "temporarily removes some codeblock languages\r\nMoved some libraries to dynamic imports\r\nRemoved some shared code not used on the client side", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "19534", + "title": "Merge master into develop & Set version to 3.9.0-develop", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + } + ] + }, + "3.9.0-rc.1": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.20.0-alpha.4149", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "19670", + "title": "Regression: Verticalbar size", + "userLogin": "ggazzo", + "contributors": [ + "gabriellsh" + ] + }, + { + "pr": "19668", + "title": "Regression: Collapsed messages container in safari", + "userLogin": "gabriellsh", + "contributors": [ + "gabriellsh" + ] + } + ] + }, + "3.9.0-rc.2": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.20.0-alpha.4149", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "19657", + "title": "Report DAU and MAU by role", + "userLogin": "rodrigok", + "milestone": "3.9.0", + "contributors": [ + "rodrigok" + ] + }, + { + "pr": "19679", + "title": "Regression: Fix multiple react blazed template rendering at the same time", + "userLogin": "ggazzo", + "milestone": "3.9.0", + "contributors": [ + "ggazzo", + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "19671", + "title": "Regression: Issues with Safari", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler", + "web-flow" + ] + } + ] + }, + "3.9.0-rc.3": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.20.0-alpha.4149", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "19685", + "title": "Regression: URL preview problem", + "userLogin": "tiagoevanp", + "milestone": "3.9.0", + "contributors": [ + "tiagoevanp" + ] + }, + { + "pr": "19659", + "title": "Regression: Fix LDAP 2FA not working when Login Fallback is off", + "userLogin": "pierre-lehnen-rc", + "milestone": "3.9.0", + "contributors": [ + "pierre-lehnen-rc", + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "19653", + "title": "Fix permission duplicated error on startup causing CI to halt", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego", + "g-thome" + ] + } + ] + }, + "3.9.0-rc.4": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.20.0", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "19691", + "title": "Regression: Fix Custom OAuth 2FA", + "userLogin": "pierre-lehnen-rc", + "contributors": [ + "pierre-lehnen-rc" + ] + }, + { + "pr": "19702", + "title": "Update Apps-Engine version", + "userLogin": "d-gubert", + "contributors": [ + "d-gubert" + ] + }, + { + "pr": "19700", + "title": "Improve performance of migration 211 (adding mostImportantRole to sessions)", + "userLogin": "rodrigok", + "milestone": "3.9.0", + "contributors": [ + "rodrigok" + ] + } + ] + }, + "3.8.2": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.19.0", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "19705", + "title": "Release 3.8.2", + "userLogin": "sampaiodiego", + "contributors": [ + "g-thome", + "sampaiodiego" + ] + }, + { + "pr": "19684", + "title": "[FIX] Room avatar update event doesn't properly broadcast room id", + "userLogin": "g-thome", + "milestone": "3.8.2", + "contributors": [ + "g-thome" + ] + }, + { + "pr": "19683", + "title": "[FIX] Server crash while reading settings for allowed and blocked email domain lists", + "userLogin": "g-thome", + "milestone": "3.8.2", + "contributors": [ + "g-thome" + ] + } + ] + }, + "3.9.0-rc.5": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.20.0", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "19698", + "title": "Regression: object-fit for image element and Box margin in AppAvatar component", + "userLogin": "tiagoevanp", + "contributors": [ + "ggazzo", + "tiagoevanp" + ] + }, + { + "pr": "19705", + "title": "Release 3.8.2", + "userLogin": "sampaiodiego", + "contributors": [ + "g-thome", + "sampaiodiego" + ] + }, + { + "pr": "19684", + "title": "[FIX] Room avatar update event doesn't properly broadcast room id", + "userLogin": "g-thome", + "milestone": "3.8.2", + "contributors": [ + "g-thome" + ] + }, + { + "pr": "19683", + "title": "[FIX] Server crash while reading settings for allowed and blocked email domain lists", + "userLogin": "g-thome", + "milestone": "3.8.2", + "contributors": [ + "g-thome" + ] + }, + { + "pr": "19683", + "title": "[FIX] Server crash while reading settings for allowed and blocked email domain lists", + "userLogin": "g-thome", + "milestone": "3.8.2", + "contributors": [ + "g-thome" + ] + }, + { + "pr": "19684", + "title": "[FIX] Room avatar update event doesn't properly broadcast room id", + "userLogin": "g-thome", + "milestone": "3.8.2", + "contributors": [ + "g-thome" + ] + } + ] + }, + "3.9.0": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.20.0", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "0.74.4": { + "node_version": "8.11.4", + "npm_version": "6.4.1", + "apps_engine_version": "1.4.0", + "mongo_versions": [], + "pull_requests": [] + }, + "1.3.4": { + "node_version": "8.11.4", + "npm_version": "6.4.1", + "apps_engine_version": "1.5.1", + "mongo_versions": [], + "pull_requests": [] + }, + "2.4.13": { + "node_version": "8.17.0", + "npm_version": "6.13.4", + "apps_engine_version": "1.11.2", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "3.7.3": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.18.0", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "3.8.3": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.19.0", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "3.9.1": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.20.0", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "19734", + "title": "[FIX] Image preview for image URLs on messages", + "userLogin": "g-thome", + "milestone": "3.9.1", + "contributors": [ + "g-thome" + ] + }, + { + "pr": "19746", + "title": "[FIX] Sidebar presence will now correctly update for Omnichannel rooms", + "userLogin": "alansikora", + "milestone": "3.9.1", + "contributors": [ + "alansikora" + ] + }, + { + "pr": "19749", + "title": "[FIX] Startup error when using MongoDB with a password containing special characters", + "userLogin": "sampaiodiego", + "milestone": "3.9.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "19725", + "title": "[FIX] Sidebar UI disappearing", + "userLogin": "gabriellsh", + "milestone": "3.9.1", + "contributors": [ + "gabriellsh" + ] + } + ] + }, + "3.9.2": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.21.0-alpha.4235", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "19763", + "title": "[FIX] Some apps were not correctly enabled during startup in HA environments", + "userLogin": "thassiov", + "milestone": "3.9.2", + "contributors": [ + "thassiov", + "web-flow", + "sampaiodiego" + ] + }, + { + "pr": "19862", + "title": "[FIX] Download my data with file uploads", + "userLogin": "sampaiodiego", + "milestone": "3.9.2", + "contributors": [ + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "19842", + "title": "[FIX] Forgot password endpoint return status", + "userLogin": "g-thome", + "milestone": "3.9.2", + "contributors": [ + "g-thome" + ] + }, + { + "pr": "19793", + "title": "[FIX][ENTERPRISE] Omnichannel Department form is not correctly storing the list of departments allowed for forwarding", + "userLogin": "rafaelblink", + "milestone": "3.9.2", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19762", + "title": "[FIX] 'Not Allowed' in message auditing", + "userLogin": "MartinSchoeler", + "milestone": "3.9.2", + "contributors": [ + "MartinSchoeler" + ] + } + ] + }, + "3.9.3": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.21.0-alpha.4235", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "19854", + "title": "[FIX] Problem with attachment render", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "19817", + "title": "[FIX] Issue with special message rendering", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + } + ] + }, + "1.3.5": { + "node_version": "8.11.4", + "npm_version": "6.4.1", + "apps_engine_version": "1.5.1", + "mongo_versions": [], + "pull_requests": [ + { + "pr": "19817", + "title": "[FIX] Issue with special message rendering", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "19854", + "title": "[FIX] Problem with attachment render", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + } + ] + }, + "2.4.14": { + "node_version": "8.17.0", + "npm_version": "6.13.4", + "apps_engine_version": "1.11.2", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "19854", + "title": "[FIX] Problem with attachment render", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "19817", + "title": "[FIX] Issue with special message rendering", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + } + ] + }, + "3.7.4": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.18.0", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "19854", + "title": "[FIX] Problem with attachment render", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "19817", + "title": "[FIX] Issue with special message rendering", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + } + ] + }, + "3.8.4": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.19.0", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "19854", + "title": "[FIX] Problem with attachment render", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "19817", + "title": "[FIX] Issue with special message rendering", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + } + ] + }, + "3.10.0-rc.0": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.21.0-alpha.4235", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "19931", + "title": "[NEW] Omnichannel Contact Center (Directory)", + "userLogin": "rafaelblink", + "milestone": "3.10.0", + "contributors": [ + "rafaelblink", + "web-flow", + "renatobecker" + ] + }, + { + "pr": "19902", + "title": "[FIX] Hightlights validation on Account Preferences page", + "userLogin": "aKn1ghtOut", + "description": "This PR fixes two issues in the account settings \"preferences\" panel.\r\nOnce set, the \"Highlighted Words\" setting cannot be reset to an empty string. This was fixed by changing the string validation from checking the length to checking the type of variable.\r\nSecondly, it tracks the changes to correctly identify if changes after the last \"save changes\" action have been made, using an \"updates\" state variable, instead of just comparing against the initialValue that does not change on clicking \"save changes\".", + "milestone": "3.10.0", + "contributors": [ + "aKn1ghtOut", + "dougfabris", + "web-flow" + ] + }, + { + "pr": "19924", + "title": "[NEW] User preference for audio notifications", + "userLogin": "gabriellsh", + "description": "![image](https://user-images.githubusercontent.com/40830821/102808922-dfe32b00-439f-11eb-9268-6d0cf69dc64c.png)", + "contributors": [ + "gabriellsh", + "sampaiodiego" + ] + }, + { + "pr": "19036", + "title": "[FIX] Update base image in Dockerfile.rhel", + "userLogin": "andykrohg", + "contributors": [ + "andykrohg" + ] + }, + { + "pr": "19925", + "title": "[NEW] REST endpoints to add and retrieve Enterprise licenses", + "userLogin": "g-thome", + "contributors": [ + "g-thome", + "sampaiodiego" + ] + }, + { + "pr": "19898", + "title": "[FIX] Admin Users screen sorting showing deactivated users in wrong order", + "userLogin": "alansikora", + "contributors": [ + "alansikora" + ] + }, + { + "pr": "19926", + "title": "[NEW] REST Endpoint `instances.get`", + "userLogin": "g-thome", + "description": "Returns an array of instances on the cluster.", + "contributors": [ + "g-thome", + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "19929", + "title": "Bump systeminformation from 4.30.1 to 4.33.0 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "19834", + "title": "[FIX] Group DMs title when user changes his/her name", + "userLogin": "g-thome", + "contributors": [ + "g-thome", + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "19928", + "title": "[IMPROVE] Show all screen when printing screen", + "userLogin": "gabriellsh", + "contributors": [ + "gabriellsh" + ] + }, + { + "pr": "19808", + "title": "Rewrite: Room Header", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo", + "gabriellsh", + "web-flow" + ] + }, + { + "pr": "19850", + "title": "Chore: Add watch.settings to events whitelist", + "userLogin": "alansikora", + "contributors": [ + "alansikora" + ] + }, + { + "pr": "19843", + "title": "Improve: Report Weekly Active Users to statistics", + "userLogin": "rodrigok", + "description": "Add the fields `uniqueUsersOfLastWeek`, `uniqueDevicesOfLastWeek` and `uniqueOSOfLastWeek` to the statistics report among the daily and monthly already reported.", + "milestone": "3.10.0", + "contributors": [ + "rodrigok" + ] + }, + { + "pr": "19912", + "title": "Chore: Fix Caddy download URL in Snaps", + "userLogin": "geekgonecrazy", + "contributors": [ + "geekgonecrazy", + "web-flow" + ] + }, + { + "pr": "19923", + "title": "[FIX] Issue with oembed", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "19922", + "title": "Language update from LingoHub 🤖 on 2020-12-21Z", + "userLogin": "lingohub[bot]", + "contributors": [ + null, + "sampaiodiego" + ] + }, + { + "pr": "19901", + "title": "Remove Heroku from readme", + "userLogin": "geekgonecrazy", + "contributors": [ + "geekgonecrazy", + "web-flow" + ] + }, + { + "pr": "19875", + "title": "[FIX] RoomForeword", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo", + "web-flow" + ] + }, + { + "pr": "19879", + "title": "[FIX] User Info 'Local Time' translation keyword", + "userLogin": "J4r3tt", + "milestone": "3.10.0", + "contributors": [ + "J4r3tt" + ] + }, + { + "pr": "19886", + "title": "[FIX] Issue with oembed", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "19892", + "title": "[NEW] Update Checker Description", + "userLogin": "MartinSchoeler", + "milestone": "3.10.0", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "19763", + "title": "[FIX] Some apps were not correctly enabled during startup in HA environments", + "userLogin": "thassiov", + "milestone": "3.9.2", + "contributors": [ + "thassiov", + "web-flow", + "sampaiodiego" + ] + }, + { + "pr": "19876", + "title": "Regression: Fix member list Actions", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "19874", + "title": "Regression: Fix Room Files for DMs", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "19580", + "title": "[IMPROVE] Rewrite Room Files as React Component", + "userLogin": "dougfabris", + "contributors": [ + "dougfabris", + "ggazzo", + "web-flow" + ] + }, + { + "pr": "19870", + "title": "[FIX] User email showing [object Object]", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "19867", + "title": "Regression: RoomMembers Permission", + "userLogin": "dougfabris", + "contributors": [ + "dougfabris" + ] + }, + { + "pr": "19862", + "title": "[FIX] Download my data with file uploads", + "userLogin": "sampaiodiego", + "milestone": "3.9.2", + "contributors": [ + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "19841", + "title": "[IMPROVE] Rewrite contextualbar RoomMembers as React Component ", + "userLogin": "dougfabris", + "contributors": [ + "dougfabris", + "ggazzo", + "web-flow" + ] + }, + { + "pr": "19863", + "title": "Chore: Change Youtube test to verify if has an iframe with max-width", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "19854", + "title": "[FIX] Problem with attachment render", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "19842", + "title": "[FIX] Forgot password endpoint return status", + "userLogin": "g-thome", + "milestone": "3.9.2", + "contributors": [ + "g-thome" + ] + }, + { + "pr": "19844", + "title": "Bump ini from 1.3.5 to 1.3.8 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "19768", + "title": "Chore: Update Pull Request template", + "userLogin": "rodrigok", + "description": "Improve the template of Pull Requests in order to make it clear reducing duplicated information and removing the visible checklists that were generating noise and misunderstanding with the PR progress.\r\n- Moved the checklists to inside comments\r\n- Merge the changelog and proposed changes sections to have a single source of description that goes to the changelog\r\n- Remove the screenshot section, they can be added inside the description\r\n- Changed the proposed changes title to incentivizing the usage of images and videos", + "contributors": [ + "rodrigok", + "web-flow" + ] + }, + { + "pr": "19598", + "title": "Chore: Remove extra parentheses from return type", + "userLogin": "ArnoSaine", + "contributors": [ + "ArnoSaine", + "web-flow" + ] + }, + { + "pr": "19831", + "title": "Regression: Failed autolinker and markdown rendering", + "userLogin": "tassoevan", + "milestone": "3.10.0", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "19825", + "title": "[FIX] Spotify oEmbed", + "userLogin": "tassoevan", + "milestone": "3.10.0", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "19654", + "title": "Message parsing and rendering - Phase 1", + "userLogin": "tassoevan", + "milestone": "3.10.0", + "contributors": [ + "tassoevan", + "web-flow" + ] + }, + { + "pr": "19817", + "title": "[FIX] Issue with special message rendering", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "19816", + "title": "Regression: UserInfoWithData endpoint variable", + "userLogin": "dougfabris", + "contributors": [ + "dougfabris" + ] + }, + { + "pr": "19793", + "title": "[FIX][ENTERPRISE] Omnichannel Department form is not correctly storing the list of departments allowed for forwarding", + "userLogin": "rafaelblink", + "milestone": "3.9.2", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19807", + "title": "Regression: User Info Context bar breaking.", + "userLogin": "gabriellsh", + "contributors": [ + "gabriellsh" + ] + }, + { + "pr": "19803", + "title": "[IMPROVE] Rewrite contextualbar RoomMembers - AddUsers as React Component", + "userLogin": "dougfabris", + "contributors": [ + "dougfabris", + "web-flow", + "ggazzo" + ] + }, + { + "pr": "19762", + "title": "[FIX] 'Not Allowed' in message auditing", + "userLogin": "MartinSchoeler", + "milestone": "3.9.2", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "19806", + "title": "Regression: fix broken members list", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "19805", + "title": "[FIX] Custom Avatar", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "19796", + "title": "Improve Docker container size by adding chown to ADD command", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "19787", + "title": "Regression: roomInfo folder structure", + "userLogin": "dougfabris", + "contributors": [ + "dougfabris", + "ggazzo" + ] + }, + { + "pr": "19764", + "title": "[IMPROVE] Replace useClipboard", + "userLogin": "dougfabris", + "contributors": [ + "dougfabris", + "ggazzo" + ] + }, + { + "pr": "19761", + "title": "Regression: contextualBar folder structure", + "userLogin": "dougfabris", + "contributors": [ + "dougfabris" + ] + }, + { + "pr": "19759", + "title": "[IMPROVE] Replace usePrefersReducedMotion", + "userLogin": "dougfabris", + "contributors": [ + "dougfabris" + ] + }, + { + "pr": "19734", + "title": "[FIX] Image preview for image URLs on messages", + "userLogin": "g-thome", + "milestone": "3.9.1", + "contributors": [ + "g-thome" + ] + }, + { + "pr": "19746", + "title": "[FIX] Sidebar presence will now correctly update for Omnichannel rooms", + "userLogin": "alansikora", + "milestone": "3.9.1", + "contributors": [ + "alansikora" + ] + }, + { + "pr": "19749", + "title": "[FIX] Startup error when using MongoDB with a password containing special characters", + "userLogin": "sampaiodiego", + "milestone": "3.9.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "19729", + "title": "[FIX] File Tab Order", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler", + "tassoevan", + "web-flow" + ] + }, + { + "pr": "19727", + "title": "[FIX] Emails not showing up in Admin/Users", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler", + "ggazzo" + ] + }, + { + "pr": "19748", + "title": "Regression: Add Members showing the wrong template", + "userLogin": "dougfabris", + "contributors": [ + "dougfabris", + "tassoevan", + "web-flow" + ] + }, + { + "pr": "19753", + "title": "Regression: \"My Account\" page doesn't load", + "userLogin": "g-thome", + "contributors": [ + "g-thome" + ] + }, + { + "pr": "19516", + "title": "[FIX] Add fallback message when show notification content is disabled", + "userLogin": "youssef-md", + "milestone": "3.10.0", + "contributors": [ + "youssef-md" + ] + }, + { + "pr": "19631", + "title": "Frontend folder structure", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "19736", + "title": "bump fuselage", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "19496", + "title": "[IMPROVE] Removed useEndpointDataExperimental hook usage", + "userLogin": "tassoevan", + "milestone": "3.10.0", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "19723", + "title": "Merge EE and Community translations and LingoHub manual sync", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "19725", + "title": "[FIX] Sidebar UI disappearing", + "userLogin": "gabriellsh", + "milestone": "3.9.1", + "contributors": [ + "gabriellsh" + ] + }, + { + "pr": "19672", + "title": "[IMPROVE] Rewrite NotificationPreferences to React component", + "userLogin": "tiagoevanp", + "milestone": "3.10.0", + "contributors": [ + "tiagoevanp", + "ggazzo", + "web-flow" + ] + }, + { + "pr": "19701", + "title": "[NEW] Custom scroll", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler", + "ggazzo", + "web-flow" + ] + }, + { + "pr": "19694", + "title": "[IMPROVE] Rewrite contextualbar RoomMembers - InviteUsers", + "userLogin": "dougfabris", + "contributors": [ + "dougfabris", + "ggazzo", + "web-flow" + ] + }, + { + "pr": "19674", + "title": "[IMPROVE] Rewrite contextualbar OTR panel", + "userLogin": "dougfabris", + "contributors": [ + "dougfabris", + "ggazzo", + "web-flow" + ] + }, + { + "pr": "19690", + "title": "[FIX] UIKit Modal not scrolling", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo", + "web-flow" + ] + }, + { + "pr": "19720", + "title": "Merge master into develop & Set version to 3.10.0-develop", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego", + "web-flow" + ] + } + ] + }, + "3.10.0-rc.1": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.21.0-alpha.4235", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "19948", + "title": "Regression: Omnichannel Custom Fields Form no longer working after refactoring", + "userLogin": "renatobecker", + "description": "The Omnichannel `Custom Fields` form is not working anymore after some refactorings on client-side.\r\nWhen the user clicks on `Custom Field` in the Omnichannel menu, a blank page appears.", + "milestone": "3.10.0", + "contributors": [ + "renatobecker" + ] + }, + { + "pr": "19941", + "title": "Regression: UserCard \"See full profile\" link broken", + "userLogin": "dougfabris", + "milestone": "3.10.0", + "contributors": [ + "dougfabris", + "ggazzo" + ] + }, + { + "pr": "19944", + "title": "Regression: Admin Sidebar Scroll", + "userLogin": "gabriellsh", + "milestone": "3.10.0", + "contributors": [ + "gabriellsh" + ] + } + ] + }, + "3.10.0-rc.2": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.21.0-alpha.4235", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "19950", + "title": "Regression: Fix sorting indicators on Admin Users page", + "userLogin": "alansikora", + "milestone": "3.10.0", + "contributors": [ + "alansikora" + ] + }, + { + "pr": "19978", + "title": "Regression: Fix oembed", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "19935", + "title": "[FIX] Status on searchlist", + "userLogin": "gabriellsh", + "milestone": "3.9.4", + "contributors": [ + "gabriellsh", + "tassoevan", + "web-flow" + ] + }, + { + "pr": "19830", + "title": "[FIX] Omnichannel Departments Canned Responses", + "userLogin": "gabriellsh", + "milestone": "3.9.4", + "contributors": [ + "gabriellsh", + "web-flow", + "renatobecker", + "dougfabris" + ] + }, + { + "pr": "19951", + "title": "Regression: Check permissions properly when fetching rooms in Omnichannel Directory", + "userLogin": "rafaelblink", + "milestone": "3.10.0", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19968", + "title": "Regression: Add missing translations on the Omnichannel Contact Center(Directory)", + "userLogin": "rafaelblink", + "milestone": "3.10.0", + "contributors": [ + "rafaelblink", + "renatobecker" + ] + }, + { + "pr": "19946", + "title": "Regression: Header Styles fixes", + "userLogin": "ggazzo", + "milestone": "3.10.0", + "contributors": [ + "ggazzo", + "tassoevan", + "web-flow" + ] + }, + { + "pr": "19945", + "title": "[FIX] Room scrolling to top after returns to a opened room", + "userLogin": "ggazzo", + "milestone": "3.9.4", + "contributors": [ + "ggazzo", + "tassoevan", + "web-flow" + ] + } + ] + }, + "3.10.0-rc.3": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.21.0-alpha.4235", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "19981", + "title": "Regression: polishing licenses endpoints ", + "userLogin": "g-thome", + "contributors": [ + "g-thome", + "sampaiodiego" + ] + }, + { + "pr": "19980", + "title": "Regression: Double Scrollbars on tables", + "userLogin": "gabriellsh", + "description": "Before:\r\n![image](https://user-images.githubusercontent.com/40830821/103242719-0ec84680-4936-11eb-87a7-68b6eea8de7b.png)\r\n\r\n\r\nAfter:\r\n![image](https://user-images.githubusercontent.com/40830821/103242680-ee988780-4935-11eb-99e2-a95de99f78f1.png)", + "contributors": [ + "gabriellsh", + "ggazzo" + ] + }, + { + "pr": "19955", + "title": "Regression: Add currently running instance to instances.get endpoint", + "userLogin": "g-thome", + "milestone": "3.10.0", + "contributors": [ + "g-thome", + "sampaiodiego" + ] + } + ] + }, + "3.10.0-rc.4": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.21.0-alpha.4235", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "3.10.0": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.21.0-alpha.4235", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "3.8.5": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.19.0", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "19978", + "title": "Regression: Fix oembed", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + } + ] + }, + "3.9.4": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.21.0-alpha.4235", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "19978", + "title": "Regression: Fix oembed", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "19830", + "title": "[FIX] Omnichannel Departments Canned Responses", + "userLogin": "gabriellsh", + "milestone": "3.9.4", + "contributors": [ + "gabriellsh", + "web-flow", + "renatobecker", + "dougfabris" + ] + }, + { + "pr": "19945", + "title": "[FIX] Room scrolling to top after returns to a opened room", + "userLogin": "ggazzo", + "milestone": "3.9.4", + "contributors": [ + "ggazzo", + "tassoevan", + "web-flow" + ] + }, + { + "pr": "19935", + "title": "[FIX] Status on searchlist", + "userLogin": "gabriellsh", + "milestone": "3.9.4", + "contributors": [ + "gabriellsh", + "tassoevan", + "web-flow" + ] + } + ] + }, + "3.10.1": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.21.0-alpha.4235", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "20073", + "title": "[FIX] Actions from User Info panel", + "userLogin": "Darshilp326", + "description": "Users can be removed from channels without any error message.", + "milestone": "3.10.1", + "contributors": [ + "Darshilp326" + ] + }, + { + "pr": "20114", + "title": "[FIX] Messages being updated when not required after user changes his profile", + "userLogin": "sampaiodiego", + "milestone": "3.10.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "19953", + "title": "[FIX][ENTERPRISE] Omnichannel custom fields not storing additional form values ", + "userLogin": "rafaelblink", + "milestone": "3.10.1", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "20089", + "title": "[FIX] Omnichannel rooms breaking after return to queue or forward", + "userLogin": "gabriellsh", + "milestone": "3.10.1", + "contributors": [ + "gabriellsh", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "20093", + "title": "[FIX] Omnichannel raw model importing meteor dependency", + "userLogin": "renatobecker", + "milestone": "3.10.1", + "contributors": [ + "renatobecker" + ] + }, + { + "pr": "20061", + "title": "[FIX] User Audio notification preference not being applied", + "userLogin": "sampaiodiego", + "milestone": "3.10.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "20003", + "title": "[FIX] OAuth users being asked to change password on second login", + "userLogin": "pierre-lehnen-rc", + "milestone": "3.10.1", + "contributors": [ + "pierre-lehnen-rc", + "sampaiodiego" + ] + }, + { + "pr": "20022", + "title": "[FIX] Omnichannel Agents unable to take new chats in the queue", + "userLogin": "rafaelblink", + "milestone": "3.10.1", + "contributors": [ + "rafaelblink", + "renatobecker" + ] + }, + { + "pr": "20007", + "title": "[FIX] Omnichannel Business Hours form is not being rendered", + "userLogin": "rafaelblink", + "milestone": "3.10.1", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19965", + "title": "[FIX] Agent information panel not rendering", + "userLogin": "rafaelblink", + "milestone": "3.10.1", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19997", + "title": "[FIX] Creation of Omnichannel rooms not working correctly through the Apps when the agent parameter is set", + "userLogin": "murtaza98", + "milestone": "3.10.1", + "contributors": [ + "murtaza98" + ] + } + ] + }, + "3.10.2": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.21.0-alpha.4235", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "20122", + "title": "[FIX] Tabbar is opened", + "userLogin": "ggazzo", + "milestone": "3.10.2", + "contributors": [ + "ggazzo", + "web-flow" + ] + } + ] + }, + "3.10.3": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.21.0-alpha.4235", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "20128", + "title": "[FIX] User registration updating wrong subscriptions", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + } + ] + }, + "3.10.4": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.21.0-alpha.4235", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "20200", + "title": "Chore: Change console.warning() to console.warn()", + "userLogin": "lucassartor", + "milestone": "3.10.4", + "contributors": [ + "lucassartor" + ] + }, + { + "pr": "20176", + "title": "[FIX] Room's list showing all rooms with same name", + "userLogin": "sampaiodiego", + "description": "Add a migration to fix the room's list for those who ran version 3.10.1 and got it scrambled when a new user was registered.", + "milestone": "3.10.4", + "contributors": [ + "sampaiodiego" + ] + } + ] + }, + "3.11.0-rc.0": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.22.0-alpha.4469", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "20311", + "title": "[FIX][ENTERPRISE] Auditing RoomAutocomplete", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo", + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "20221", + "title": "[NEW] Banner system and NPS", + "userLogin": "sampaiodiego", + "description": "More robust and scalable banner system for alerting users.", + "contributors": [ + "sampaiodiego", + "web-flow", + "tassoevan" + ] + }, + { + "pr": "20285", + "title": "[NEW] [Apps] IPreFileUpload event", + "userLogin": "lolimay", + "milestone": "3.11.0", + "contributors": [ + "ggazzo", + "lolimay", + "web-flow", + "d-gubert" + ] + }, + { + "pr": "20078", + "title": "[NEW][Apps] Apps Permission System", + "userLogin": "thassiov", + "contributors": [ + "thassiov", + "d-gubert", + "web-flow" + ] + }, + { + "pr": "20244", + "title": "[NEW][ENTERPRISE] Omnichannel Contact Manager as preferred agent for routing", + "userLogin": "murtaza98", + "description": "If the `Contact-Manager` is assigned to a Visitor, the chat will automatically get transferred to the respective Contact-Manager, provided the Contact-Manager is online. In-case the Contact-Manager is offline, the chat will be transferred to any other online agent.\r\nWe have provided a setting to control this auto-assignment feature\r\n![image](https://user-images.githubusercontent.com/34130764/104880961-8104d780-5986-11eb-9d87-82b99814b028.png)\r\n\r\nBehavior based-on Routing method\r\n\r\n1. Auto-selection, Load-Balancing, or External Service (`autoAssignAgent = true`)\r\n This is straightforward, \r\n - if the Contact-manager is online, the chat will be transferred to the Contact-Manger only\r\n - if the Contact-manager is offline, the chat will be transferred to any other online-agent based on the Routing system\r\n2. Manual-selection (`autoAssignAgent = false`)\r\n - If the Contact-Manager is online, the chat will appear in the Queue of Contact-Manager **ONLY**\r\n - If the Contact-Manager is offline, the chat will appear in the Queue of all related Agents/Manager ( like it's done right now )", + "milestone": "3.11.0", + "contributors": [ + "ggazzo", + "web-flow", + "renatobecker", + "murtaza98" + ] + }, + { + "pr": "20101", + "title": "[NEW] Email Inboxes for Omnichannel", + "userLogin": "rafaelblink", + "description": "With this new feature, email accounts will receive email messages(threads) which will be transformed into Omnichannel chats. It'll be possible to set up multiple email accounts, test the connection with email server(email provider) and define the behaviour of each account.\r\n\r\nhttps://user-images.githubusercontent.com/2493803/105430398-242d4980-5c32-11eb-835a-450c94837d23.mp4\r\n\r\n### New item on admin menu\r\n\r\n![image](https://user-images.githubusercontent.com/2493803/105428723-bc293400-5c2e-11eb-8c02-e8d36ea82726.png)\r\n\r\n\r\n### Send test email tooltip\r\n\r\n![image](https://user-images.githubusercontent.com/2493803/104366986-eaa16380-54f8-11eb-9ba7-831cfde2319c.png)\r\n\r\n\r\n### Inbox Info\r\n\r\n![image](https://user-images.githubusercontent.com/2493803/104366796-ab731280-54f8-11eb-9941-a3cc8eb610e1.png)\r\n\r\n### SMTP Info\r\n\r\n![image](https://user-images.githubusercontent.com/2493803/104366868-c47bc380-54f8-11eb-969e-ccc29070957c.png)\r\n\r\n### IMAP Info\r\n\r\n![image](https://user-images.githubusercontent.com/2493803/104366897-cd6c9500-54f8-11eb-80c4-97d5b0c002d5.png)\r\n\r\n### Messages\r\n\r\n![image](https://user-images.githubusercontent.com/2493803/105428971-45d90180-5c2f-11eb-992a-022a3df94471.png)", + "milestone": "3.11.0", + "contributors": [ + "rafaelblink", + "rodrigok" + ] + }, + { + "pr": "20201", + "title": "[NEW] Encrypted Discussions and new Encryption Permissions", + "userLogin": "pierre-lehnen-rc", + "contributors": [ + "pierre-lehnen-rc", + "gabriellsh", + "ggazzo" + ] + }, + { + "pr": "20246", + "title": "Language update from LingoHub 🤖 on 2021-01-18Z", + "userLogin": "lingohub[bot]", + "contributors": [ + null + ] + }, + { + "pr": "20306", + "title": "Regression: Unread superposing announcement.", + "userLogin": "gabriellsh", + "description": "### Before\r\n![image](https://user-images.githubusercontent.com/40830821/105412619-c2f67d80-5c13-11eb-8204-5932ea880c8a.png)\r\n\r\n\r\n### After\r\n![image](https://user-images.githubusercontent.com/40830821/105411176-d1439a00-5c11-11eb-8d1b-ea27c8485214.png)", + "contributors": [ + "gabriellsh" + ] + }, + { + "pr": "20290", + "title": "Regression: Announcement bar not showing properly Markdown content", + "userLogin": "dougfabris", + "description": "**Before**:\r\n![image](https://user-images.githubusercontent.com/27704687/105273746-a4907380-5b7a-11eb-8121-aff665251c44.png)\r\n\r\n**After**:\r\n![image](https://user-images.githubusercontent.com/27704687/105274050-2e404100-5b7b-11eb-93b2-b6282a7bed95.png)", + "milestone": "3.11.0", + "contributors": [ + "dougfabris", + "ggazzo", + "web-flow" + ] + }, + { + "pr": "20291", + "title": "Regression: Attachments", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "20272", + "title": "[FIX] Changed success message for adding custom sound.", + "userLogin": "Darshilp326", + "description": "https://user-images.githubusercontent.com/55157259/105151351-daf2d200-5b2b-11eb-8223-eae5d60f770d.mp4", + "contributors": [ + "Darshilp326" + ] + }, + { + "pr": "20259", + "title": "[FIX] Saving with blank email in edit user", + "userLogin": "RonLek", + "description": "Disallows showing a success popup when email field is made blank in Edit User and instead shows the relevant error popup.\r\n\r\n\r\nhttps://user-images.githubusercontent.com/28918901/104960749-dbd81680-59fa-11eb-9c7b-2b257936f894.mp4", + "contributors": [ + "RonLek" + ] + }, + { + "pr": "20287", + "title": "[FIX] Fields overflowing page", + "userLogin": "gabriellsh", + "description": "### Before\r\n![image](https://user-images.githubusercontent.com/40830821/105246952-c1b14c00-5b52-11eb-8671-cff88edf242d.png)\r\n\r\n### After\r\n![image](https://user-images.githubusercontent.com/40830821/105247125-0a690500-5b53-11eb-9f3c-d6a68108e336.png)", + "contributors": [ + "gabriellsh" + ] + }, + { + "pr": "20265", + "title": "[FIX] Jump to message", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo", + "web-flow" + ] + }, + { + "pr": "20280", + "title": "Regression: Lint warnings and some datepicker", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "20277", + "title": "[FIX] Room special name in prompts", + "userLogin": "aKn1ghtOut", + "description": "The \"Hide room\" and \"Leave Room\" confirmation prompts use the \"name\" key from the room info. When the setting \"\r\nAllow Special Characters in Room Names\" is enabled, the prompts show the normalized names instead of those that contain the special characters.\r\n\r\nChanged the value being used from name to fname, which always has the user-set name.\r\n\r\nPrevious:\r\n![Screenshot from 2021-01-20 15-52-29](https://user-images.githubusercontent.com/38764067/105161642-9b31e780-5b37-11eb-8b0c-ec4b1414c948.png)\r\n\r\nUpdated:\r\n![Screenshot from 2021-01-20 15-50-19](https://user-images.githubusercontent.com/38764067/105161627-966d3380-5b37-11eb-9812-3dd9352b4f95.png)", + "contributors": [ + "aKn1ghtOut" + ] + }, + { + "pr": "20267", + "title": "[FIX] Engagement dashboard graphs labels superposing each other", + "userLogin": "gabriellsh", + "description": "Now after a certain breakpoint, the graphs should stack vertically, and overlapping text rotated.\r\n\r\n![image](https://user-images.githubusercontent.com/40830821/105098926-93b40500-5a89-11eb-9a56-2fc3b1552914.png)", + "contributors": [ + "gabriellsh", + "web-flow" + ] + }, + { + "pr": "19996", + "title": "[FIX] Changed success message for ignoring member.", + "userLogin": "Darshilp326", + "description": "Different messages for ignoring/unignoring will be displayed.\r\n\r\nhttps://user-images.githubusercontent.com/55157259/103310307-4241c880-4a3d-11eb-8c6c-4c9b99d023db.mp4", + "contributors": [ + "Darshilp326", + "web-flow" + ] + }, + { + "pr": "20028", + "title": "[FIX] User info 'Full Name' translation keyword", + "userLogin": "Karting06", + "description": "Fix the `Full Name` translation keyword, so that it can be translated.", + "milestone": "3.11.0", + "contributors": [ + "Karting06" + ] + }, + { + "pr": "20029", + "title": "[FIX] ViewLogs title translation keyword", + "userLogin": "Karting06", + "description": "Fix `View Logs` title translation keyword to enable translation of the title", + "contributors": [ + "Karting06" + ] + }, + { + "pr": "20098", + "title": "[FIX] Fix error that occurs on changing archive status of room", + "userLogin": "aKn1ghtOut", + "description": "This PR fixes an issue that happens when you try to edit the info of a room, and save changes after changing the value of \"Archived\". The archive functionality is handled separately from other room settings. The archived key is not used in the saveRoomSettings method but was still being sent over. Hence, the request was being considered invalid. I deleted the \"archived\" key from the data being sent in the request, making the request valid again.", + "contributors": [ + "aKn1ghtOut", + "web-flow" + ] + }, + { + "pr": "20159", + "title": "[FIX] Remove duplicate blaze events call for EmojiActions from roomOld", + "userLogin": "aKn1ghtOut", + "description": "A few methods concerning Emojis are bound multiple times to the DOM using the Template events() call, once in the reactions init.js and the other time after they get exported from app/ui/client/views/app/lib/getCommonRoomEvents.js to whatever page binds all the functions. The getCommonRoomEvents methods are always bound, hence negating a need to bind in a lower-level component.", + "contributors": [ + "aKn1ghtOut", + "web-flow" + ] + }, + { + "pr": "20164", + "title": "[FIX] \"Open_thread\" English tooltip correction", + "userLogin": "aKn1ghtOut", + "description": "Remove unnecessary spaces from the translation key, and added English translation value for the key.", + "contributors": [ + "aKn1ghtOut" + ] + }, + { + "pr": "20172", + "title": "[IMPROVE] Rewrite Announcement as React component", + "userLogin": "dougfabris", + "milestone": "3.11.0", + "contributors": [ + "dougfabris", + "gabriellsh", + "ggazzo", + "web-flow" + ] + }, + { + "pr": "20199", + "title": "[FIX] Added Margin between status bullet and status label", + "userLogin": "yash-rajpal", + "description": "Added Margins between status bullet and status label", + "contributors": [ + "yash-rajpal" + ] + }, + { + "pr": "20220", + "title": "[FIX]Added success message on saving notification preference.", + "userLogin": "Darshilp326", + "description": "Added success message after saving notification preferences.\r\n\r\nhttps://user-images.githubusercontent.com/55157259/104774617-03ca3e80-579d-11eb-8fa4-990b108dd8d9.mp4", + "contributors": [ + "Darshilp326" + ] + }, + { + "pr": "20225", + "title": "[FIX] White screen after 2FA code entered", + "userLogin": "wggdeveloper", + "milestone": "3.10.5", + "contributors": [ + "wggdeveloper", + "web-flow" + ] + }, + { + "pr": "20228", + "title": "[FIX] Added context check for closing active tabbar for member-list", + "userLogin": "yash-rajpal", + "description": "When we click on a username and then click on see user's full profile, a tab gets active and shows us the user's profile, the problem occurs when the tab is still active and we try to see another user's profile. In this case, tabbar gets closed.\r\nTo resolve this, added context check for closing action of active tabbar.", + "contributors": [ + "yash-rajpal" + ] + }, + { + "pr": "20245", + "title": "[FIX] Incorrect translations ZN", + "userLogin": "moniang", + "milestone": "3.11.0", + "contributors": [ + "moniang" + ] + }, + { + "pr": "20255", + "title": "Regression: reactAttachments cpu", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "20196", + "title": "[FIX] Omnichannel - Contact Center form is not validating custom fields properly", + "userLogin": "rafaelblink", + "description": "The contact form is accepting undefined values in required custom fields when creating or editing contacts, and, the errror message isn't following Rocket.chat design system.\r\n\r\n### Before\r\n![image](https://user-images.githubusercontent.com/2493803/104522668-31688980-55dd-11eb-92c5-83f96073edc4.png)\r\n\r\n### After\r\n\r\n#### New\r\n![image](https://user-images.githubusercontent.com/2493803/104770494-68f74300-574f-11eb-94a3-c8fd73365308.png)\r\n\r\n\r\n#### Edit\r\n![image](https://user-images.githubusercontent.com/2493803/104770538-7b717c80-574f-11eb-829f-1ae304103369.png)", + "milestone": "3.11.0", + "contributors": [ + "rafaelblink", + "web-flow", + "renatobecker" + ] + }, + { + "pr": "20090", + "title": "[NEW][ENTERPRISE] Automatic transfer of unanswered conversations to another agent", + "userLogin": "murtaza98", + "milestone": "3.11.0", + "contributors": [ + "murtaza98", + "web-flow", + "renatobecker" + ] + }, + { + "pr": "20123", + "title": "Rewrite Message action links", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "20106", + "title": "Rewrite: Message Attachments", + "userLogin": "ggazzo", + "description": "![image](https://user-images.githubusercontent.com/5263975/104783709-69023d80-5765-11eb-968f-a2b93fdfb51e.png)", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "20222", + "title": "Regression: User Dropdown margin", + "userLogin": "gabriellsh", + "contributors": [ + "gabriellsh" + ] + }, + { + "pr": "20119", + "title": "Rewrite Broadcast", + "userLogin": "ggazzo", + "description": "![image](https://user-images.githubusercontent.com/5263975/104035912-7fcaf200-51b1-11eb-91df-228c23d97448.png)", + "contributors": [ + "ggazzo", + "gabriellsh", + "web-flow" + ] + }, + { + "pr": "20121", + "title": "[IMPROVE] Message Collection Hooks", + "userLogin": "tassoevan", + "description": "Integrating a list of messages into a React component imposes some challenges. Its content is provided by some REST API calls and live-updated by streamer events. To avoid too much coupling with React Hooks, the structures `RecordList`, `MessageList` and their derivatives are simple event emitters created and connected on components via some simple hooks, like `useThreadsList()` and `useRecordList()`.", + "milestone": "3.11.0", + "contributors": [ + "tassoevan", + "ggazzo", + "web-flow" + ] + }, + { + "pr": "19899", + "title": "[FIX] Invalid filters on the Omnichannel Analytics page", + "userLogin": "murtaza98", + "milestone": "3.11.0", + "contributors": [ + "murtaza98", + "web-flow", + "renatobecker" + ] + }, + { + "pr": "20180", + "title": "Regression: Info Page Icon style and usage graph breaking", + "userLogin": "gabriellsh", + "contributors": [ + "gabriellsh", + "ggazzo", + "web-flow" + ] + }, + { + "pr": "20200", + "title": "Chore: Change console.warning() to console.warn()", + "userLogin": "lucassartor", + "milestone": "3.10.4", + "contributors": [ + "lucassartor" + ] + }, + { + "pr": "20176", + "title": "[FIX] Room's list showing all rooms with same name", + "userLogin": "sampaiodiego", + "description": "Add a migration to fix the room's list for those who ran version 3.10.1 and got it scrambled when a new user was registered.", + "milestone": "3.10.4", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "20177", + "title": "Regression: Change sort icon", + "userLogin": "gabriellsh", + "description": "### Before\r\n![image](https://user-images.githubusercontent.com/40830821/104366414-1bcd6400-54f8-11eb-9fc7-c6f13f07a61e.png)\r\n\r\n### After\r\n![image](https://user-images.githubusercontent.com/40830821/104366542-4cad9900-54f8-11eb-83ca-acb99899515a.png)", + "contributors": [ + "gabriellsh" + ] + }, + { + "pr": "20181", + "title": "[FIX] Wrong userId when open own user profile", + "userLogin": "dougfabris", + "contributors": [ + "dougfabris" + ] + }, + { + "pr": "20124", + "title": "[FIX] Livechat.RegisterGuest method removing unset fields", + "userLogin": "renatobecker", + "description": "After changes made on https://github.com/RocketChat/Rocket.Chat/pull/19931, the `Livechat.RegisterGuest` method started removing properties from the visitor inappropriately. The properties that did not receive value were removed from the object.\r\nThose changes were made to support the new Contact Form, but now the form has its own method to deal with Contact data so those changes are no longer necessary.", + "milestone": "3.11.0", + "contributors": [ + "ggazzo", + "web-flow", + "renatobecker", + "rafaelblink" + ] + }, + { + "pr": "19900", + "title": "[IMPROVE] Rewrite Prune Messages as React component", + "userLogin": "dougfabris", + "milestone": "3.11.0", + "contributors": [ + "dougfabris", + "web-flow", + "ggazzo" + ] + }, + { + "pr": "20174", + "title": "[FIX] Change header's favorite icon to filled star", + "userLogin": "dougfabris", + "description": "### Before: \r\n![image](https://user-images.githubusercontent.com/27704687/104351819-a60bcd00-54e4-11eb-8b43-7d281a6e5dcb.png)\r\n\r\n### After:\r\n![image](https://user-images.githubusercontent.com/27704687/104351632-67761280-54e4-11eb-87ba-25b940494bb5.png)", + "contributors": [ + "dougfabris" + ] + }, + { + "pr": "19938", + "title": "[FIX] Initial values update on Account Preferences", + "userLogin": "dougfabris", + "milestone": "3.11.0", + "contributors": [ + "dougfabris", + "tassoevan", + "web-flow" + ] + }, + { + "pr": "19643", + "title": "[FIX] Unable to reset password by Email if upper case character is pr…", + "userLogin": "bhavayAnand9", + "milestone": "3.11.0", + "contributors": [ + "bhavayAnand9" + ] + }, + { + "pr": "18722", + "title": "[FIX] Video call message not translated", + "userLogin": "galshiff", + "description": "Fixed video call message not translated.", + "milestone": "3.11.0", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "19517", + "title": "[NEW] Server Info page", + "userLogin": "gabriellsh", + "milestone": "3.11.0", + "contributors": [ + "gabriellsh" + ] + }, + { + "pr": "20083", + "title": "[IMPROVE] Title for user avatar buttons", + "userLogin": "sushant52", + "description": "Made user avatar change buttons to be descriptive of what they do.", + "contributors": [ + "sushant52" + ] + }, + { + "pr": "20110", + "title": "[FIX] Admin User Info email verified status", + "userLogin": "bdelwood", + "milestone": "3.11.0", + "contributors": [ + "bdelwood", + "web-flow" + ] + }, + { + "pr": "20116", + "title": "[IMPROVE] Tooltip added for Kebab menu on chat header", + "userLogin": "yash-rajpal", + "description": "Added the missing Tooltip for kebab menu on chat header.\r\n![tooltip after](https://user-images.githubusercontent.com/58601732/104031406-b07f4b80-51f2-11eb-87a4-1e8da78a254f.gif)", + "contributors": [ + "yash-rajpal" + ] + }, + { + "pr": "20134", + "title": "[FIX] Translate keyword for 'Showing results of' in tables", + "userLogin": "Karting06", + "description": "Change translation keyword in order to allow the translation of `Showing results %s - %s of %s` in tables.", + "milestone": "3.11.0", + "contributors": [ + "Karting06" + ] + }, + { + "pr": "20021", + "title": "[FIX] Markdown added for Header Room topic", + "userLogin": "yash-rajpal", + "description": "With the new 3.10.0 version update the Links in topic section below room name were not working, for more info refer issue #20018", + "milestone": "3.11.0", + "contributors": [ + "yash-rajpal" + ] + }, + { + "pr": "20016", + "title": "[FIX] Status circle in profile section", + "userLogin": "yash-rajpal", + "description": "The Status Circle in status message text input is now centered vertically.", + "milestone": "3.11.0", + "contributors": [ + "yash-rajpal" + ] + }, + { + "pr": "19962", + "title": "[FIX] Normalize messages for users in endpoint chat.getStarredMessages", + "userLogin": "tiagoevanp", + "milestone": "3.11.0", + "contributors": [ + "tiagoevanp", + "tassoevan", + "web-flow" + ] + }, + { + "pr": "19942", + "title": "[FIX] minWidth in FileIcon to prevent layout to broke", + "userLogin": "dougfabris", + "description": "![image](https://user-images.githubusercontent.com/27704687/102934691-69b7f480-4483-11eb-995b-a8a9b72246aa.png)", + "milestone": "3.11.0", + "contributors": [ + "dougfabris", + "tassoevan", + "web-flow" + ] + }, + { + "pr": "19489", + "title": "[IMPROVE] Add extra SAML settings to update room subs and add private room subs.", + "userLogin": "tlskinneriv", + "description": "Added a SAML setting to support updating room subscriptions each time a user logs in via SAML.\r\nAdded a SAML setting to support including private rooms in SAML updated subscriptions (whether initial or on each logon).", + "milestone": "3.11.0", + "contributors": [ + "tlskinneriv", + "web-flow" + ] + }, + { + "pr": "20070", + "title": "[IMPROVE] Rewrite User Dropdown and Kebab menu.", + "userLogin": "gabriellsh", + "description": "![image](https://user-images.githubusercontent.com/40830821/103699786-3a74ad80-4f82-11eb-913e-2e09d5f7eac6.png)", + "contributors": [ + "gabriellsh", + "ggazzo", + "web-flow" + ] + }, + { + "pr": "20146", + "title": "Language update from LingoHub 🤖 on 2021-01-11Z", + "userLogin": "lingohub[bot]", + "contributors": [ + null, + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "20128", + "title": "[FIX] User registration updating wrong subscriptions", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "20117", + "title": "Rewrite Discussion Metric", + "userLogin": "ggazzo", + "description": "https://user-images.githubusercontent.com/5263975/104031909-23190880-51ac-11eb-93dd-5d4b5295886d.mp4", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "19777", + "title": "[IMPROVE] Don't use global search by default", + "userLogin": "ikyuchukov", + "description": "Global chat search is not set by default now.", + "contributors": [ + "i-kychukov", + "ikyuchukov", + "web-flow" + ] + }, + { + "pr": "20122", + "title": "[FIX] Tabbar is opened", + "userLogin": "ggazzo", + "milestone": "3.10.2", + "contributors": [ + "ggazzo", + "web-flow" + ] + }, + { + "pr": "20073", + "title": "[FIX] Actions from User Info panel", + "userLogin": "Darshilp326", + "description": "Users can be removed from channels without any error message.", + "milestone": "3.10.1", + "contributors": [ + "Darshilp326" + ] + }, + { + "pr": "20118", + "title": "Update password policy English translation", + "userLogin": "zdumitru", + "contributors": [ + "zdumitru", + "web-flow" + ] + }, + { + "pr": "20114", + "title": "[FIX] Messages being updated when not required after user changes his profile", + "userLogin": "sampaiodiego", + "milestone": "3.10.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "19953", + "title": "[FIX][ENTERPRISE] Omnichannel custom fields not storing additional form values ", + "userLogin": "rafaelblink", + "milestone": "3.10.1", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "20089", + "title": "[FIX] Omnichannel rooms breaking after return to queue or forward", + "userLogin": "gabriellsh", + "milestone": "3.10.1", + "contributors": [ + "gabriellsh", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19993", + "title": "[FIX] Meteor errors not translating for toast messages", + "userLogin": "gabriellsh", + "contributors": [ + "gabriellsh" + ] + }, + { + "pr": "19992", + "title": "[FIX] Profile picture changing with username", + "userLogin": "gabriellsh", + "description": "![bug avatar](https://user-images.githubusercontent.com/40830821/103305935-24e40e80-49eb-11eb-9e35-9bd4c167898a.gif)", + "contributors": [ + "gabriellsh" + ] + }, + { + "pr": "19937", + "title": "[FIX] Search list filter", + "userLogin": "gabriellsh", + "contributors": [ + "gabriellsh", + "tassoevan", + "web-flow" + ] + }, + { + "pr": "20045", + "title": "chore: Change return button", + "userLogin": "gabriellsh", + "contributors": [ + "gabriellsh", + "ggazzo", + "web-flow" + ] + }, + { + "pr": "20051", + "title": "Rewrite : Message Thread metrics", + "userLogin": "ggazzo", + "description": "![image](https://user-images.githubusercontent.com/5263975/103585504-e904e980-4ec1-11eb-8d8c-3113ac812ead.png)", + "contributors": [ + "ggazzo", + "web-flow" + ] + }, + { + "pr": "20093", + "title": "[FIX] Omnichannel raw model importing meteor dependency", + "userLogin": "renatobecker", + "milestone": "3.10.1", + "contributors": [ + "renatobecker" + ] + }, + { + "pr": "20061", + "title": "[FIX] User Audio notification preference not being applied", + "userLogin": "sampaiodiego", + "milestone": "3.10.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "20003", + "title": "[FIX] OAuth users being asked to change password on second login", + "userLogin": "pierre-lehnen-rc", + "milestone": "3.10.1", + "contributors": [ + "pierre-lehnen-rc", + "sampaiodiego" + ] + }, + { + "pr": "20055", + "title": "Bump axios from 0.18.0 to 0.18.1", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "19916", + "title": "Add translation of Edit Status in all languages", + "userLogin": "sushant52", + "description": "Closes [#19915](https://github.com/RocketChat/Rocket.Chat/issues/19915)\r\nThe profile options menu is well translated in many languages. However, Edit Status is the only button which is not well translated. With this change, the whole profile options will be properly translated in a lot of languages.", + "contributors": [ + "sushant52", + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "20047", + "title": "Chore: Recover and update Storybook", + "userLogin": "tassoevan", + "description": "It reenables Storybook's usage.", + "milestone": "3.11.0", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "20041", + "title": "Chore: Add tests for the api/licenses.* endpoints", + "userLogin": "lucassartor", + "description": "Adding api tests for the new `licenses.*` endpoints (`licenses.get` and `licenses.add`)", + "contributors": [ + "lucassartor" + ] + }, + { + "pr": "20034", + "title": "Language update from LingoHub 🤖 on 2021-01-04Z", + "userLogin": "lingohub[bot]", + "contributors": [ + null, + "sampaiodiego" + ] + }, + { + "pr": "20022", + "title": "[FIX] Omnichannel Agents unable to take new chats in the queue", + "userLogin": "rafaelblink", + "milestone": "3.10.1", + "contributors": [ + "rafaelblink", + "renatobecker" + ] + }, + { + "pr": "20007", + "title": "[FIX] Omnichannel Business Hours form is not being rendered", + "userLogin": "rafaelblink", + "milestone": "3.10.1", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "20013", + "title": "Language update from LingoHub 🤖 on 2020-12-30Z", + "userLogin": "lingohub[bot]", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "19998", + "title": "Chore: Fix i18n duplicated keys", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "19965", + "title": "[FIX] Agent information panel not rendering", + "userLogin": "rafaelblink", + "milestone": "3.10.1", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19997", + "title": "[FIX] Creation of Omnichannel rooms not working correctly through the Apps when the agent parameter is set", + "userLogin": "murtaza98", + "milestone": "3.10.1", + "contributors": [ + "murtaza98" + ] + }, + { + "pr": "19988", + "title": "Chore: add tests to api/instances.get endpoint ", + "userLogin": "lucassartor", + "contributors": [ + "lucassartor", + "web-flow" + ] + } + ] + }, + "3.8.6": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.19.0", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "3.8.7": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.19.0", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "3.8.8": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.19.0", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "3.9.5": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.21.0-alpha.4235", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "3.9.6": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.21.0-alpha.4235", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "3.9.7": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.21.0-alpha.4235", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "3.10.5": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.21.0-alpha.4235", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "3.11.0-rc.1": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.22.0-alpha.4469", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "20430", + "title": "Security sync", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "20343", + "title": "Regression: Fix Cron statistics TypeError", + "userLogin": "RonLek", + "contributors": [ + "RonLek" + ] + } + ] + }, + "3.11.0-rc.2": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.22.0-alpha.4469", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "20450", + "title": "Regression: Bio page not rendering", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "20435", + "title": "regression: Announcement link open in new tab", + "userLogin": "tiagoevanp", + "contributors": [ + "tiagoevanp", + "ggazzo", + "web-flow" + ] + } + ] + }, + "3.11.0-rc.3": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.22.0-alpha.4534", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "20457", + "title": "[FIX] Sidebar palette color broken on IE", + "userLogin": "dougfabris", + "description": "![image](https://user-images.githubusercontent.com/27704687/106056093-0a29b600-60cd-11eb-8038-eabbc0d8fb03.png)", + "milestone": "3.10.6", + "contributors": [ + "dougfabris" + ] + }, + { + "pr": "20490", + "title": "[FIX] RoomManager validation broken on IE", + "userLogin": "dougfabris", + "milestone": "3.10.6", + "contributors": [ + "dougfabris" + ] + }, + { + "pr": "20482", + "title": "Update Apps-Engine version", + "userLogin": "d-gubert", + "description": "Update Apps-Engine version with some fixes for the current RC cycle.", + "milestone": "3.11.0", + "contributors": [ + "d-gubert" + ] + } + ] + }, + "3.11.0-rc.4": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.22.0-alpha.4545", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "20506", + "title": "[FIX][Apps] Don't show the \"review permissions\" modal when there's none to review", + "userLogin": "thassiov", + "milestone": "3.11.0", + "contributors": [ + "thassiov", + "d-gubert", + "web-flow" + ] + }, + { + "pr": "20491", + "title": "Update Apps-Engine and permissions translations", + "userLogin": "d-gubert", + "description": "Update Apps-Engine version and apply changes in translations for the changed permissions. Please review the texts on the translation files to make sure they're clear.", + "milestone": "3.11.0", + "contributors": [ + "d-gubert", + "lolimay", + "thassiov", + "web-flow" + ] + }, + { + "pr": "20492", + "title": "Regression: Add tests to new banners REST endpoints", + "userLogin": "lucassartor", + "description": "Add tests for the new `banners.*` endpoints: `banners.getNew` and `banners.dismiss`.", + "contributors": [ + "lucassartor", + "web-flow" + ] + }, + { + "pr": "20509", + "title": "[IMPROVE] Autofocus on directory", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler", + "web-flow" + ] + }, + { + "pr": "20510", + "title": "Update \"Industry\" setting", + "userLogin": "gabriellsh", + "contributors": [ + "gabriellsh" + ] + }, + { + "pr": "20495", + "title": "Regression: Fix duplicate email messages in multiple instances", + "userLogin": "renatobecker", + "milestone": "3.11.0", + "contributors": [ + "ggazzo", + "web-flow", + "renatobecker" + ] + } + ] + }, + "3.11.0-rc.5": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.22.0-alpha.4545", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "20517", + "title": "Regression: Fix banners sync data types", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "20433", + "title": "Regression: Fixed update room avatar issue.", + "userLogin": "Darshilp326", + "description": "Users can now update their room avatar without any error.\r\n\r\nhttps://user-images.githubusercontent.com/55157259/105951602-560d3880-6096-11eb-97a5-b5eb9a28b58d.mp4", + "milestone": "3.11.0", + "contributors": [ + "Darshilp326", + "d-gubert", + "web-flow" + ] + }, + { + "pr": "20434", + "title": "Regression: ESLint Warning - explicit-function-return-type", + "userLogin": "aditya-mitra", + "description": "Added explicit Return Type (Promise) on the function to fix eslint warning (`explicit-function-return-type`)", + "contributors": [ + "aditya-mitra", + "web-flow" + ] + } + ] + }, + "3.11.0-rc.6": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.22.1", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "20531", + "title": "Regression: Set image sizes based on rotation", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego", + "ggazzo" + ] + }, + { + "pr": "20523", + "title": "Regression: Apps-Engine - Convert streams to buffers on file upload", + "userLogin": "d-gubert", + "description": "This is an implementation to accommodate the changes in API for the `IPreFileUpload` hook in the Apps-Engine. Explanation on the reasoning for it is here https://github.com/RocketChat/Rocket.Chat.Apps-engine/pull/376", + "milestone": "3.11.0", + "contributors": [ + "d-gubert", + "sampaiodiego" + ] + }, + { + "pr": "20516", + "title": "Regression: Room not scrolling to bottom", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo", + "d-gubert", + "web-flow" + ] + }, + { + "pr": "20514", + "title": "Regression: NPS", + "userLogin": "tassoevan", + "contributors": [ + "tassoevan", + "ggazzo", + "sampaiodiego" + ] + }, + { + "pr": "20511", + "title": "Regression: Fix e2e paused state", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "20393", + "title": "Regression: Custom field labels are not displayed properly on Omnichannel Contact Profile form", + "userLogin": "rafaelblink", + "description": "### Before\r\n![image](https://user-images.githubusercontent.com/2493803/105780399-20116c80-5f4f-11eb-9620-0901472e453b.png)\r\n\r\n![image](https://user-images.githubusercontent.com/2493803/105780420-2e5f8880-5f4f-11eb-8e93-8115ebc685be.png)\r\n\r\n### After\r\n\r\n![image](https://user-images.githubusercontent.com/2493803/105780832-1ccab080-5f50-11eb-8042-188dd0c41904.png)\r\n\r\n![image](https://user-images.githubusercontent.com/2493803/105780911-500d3f80-5f50-11eb-96e0-7df3f179dbd5.png)", + "milestone": "3.11.0", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + } + ] + }, + "3.11.0-rc.7": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.22.1", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "3.11.0": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.22.1", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "3.11.1": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.22.2", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "20653", + "title": "[FIX] Livechat bridge permission checkers", + "userLogin": "d-gubert", + "description": "Update to latest patch version of the Apps-Engine with a fix for the Livechat bridge, as seen in https://github.com/RocketChat/Rocket.Chat.Apps-engine/pull/379", + "milestone": "3.11.1", + "contributors": [ + "d-gubert", + "lolimay", + "web-flow" + ] + }, + { + "pr": "20662", + "title": "[FIX] Omnichannel Routing System not assigning chats to Bot agents", + "userLogin": "renatobecker", + "description": "The `Omnichannel Routing System` is no longer assigning chats to `bot` agents when the `bot` agent is the default agent of the inquiry.", + "milestone": "3.11.1", + "contributors": [ + "renatobecker", + "web-flow" + ] + }, + { + "pr": "20585", + "title": "[FIX] Attachment download from title fixed", + "userLogin": "yash-rajpal", + "description": "Added target = '_self' to attachment link, this seems to fix the problem, without this attribute, error page is displayed.", + "milestone": "3.11.1", + "contributors": [ + "yash-rajpal", + "tiagoevanp", + "web-flow" + ] + }, + { + "pr": "20654", + "title": "[FIX] Gif images aspect ratio on preview", + "userLogin": "tiagoevanp", + "milestone": "3.11.1", + "contributors": [ + "tiagoevanp" + ] + }, + { + "pr": "20611", + "title": "[FIX] Update NPS banner when changing score", + "userLogin": "sampaiodiego", + "milestone": "3.11.1", + "contributors": [ + "sampaiodiego" + ] + } + ] + }, + "3.12.0-rc.0": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.23.0-alpha.4655", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "20838", + "title": "[NEW] Cloud Workspace bridge", + "userLogin": "d-gubert", + "description": "Adds the new CloudWorkspace functionality.\r\n\r\nIt allows apps to request the access token for the workspace it's installed on, so it can perform actions with other Rocket.Chat services, such as the Omni Gateway.\r\n\r\nhttps://github.com/RocketChat/Rocket.Chat.Apps-engine/pull/382", + "milestone": "3.12.0", + "contributors": [ + "d-gubert", + "geekgonecrazy" + ] + }, + { + "pr": "20832", + "title": "[NEW] Statistics about language usage", + "userLogin": "g-thome", + "description": "track what languages get picked the most as preferred ui language.", + "contributors": [ + "g-thome", + "pierre-lehnen-rc", + "sampaiodiego" + ] + }, + { + "pr": "20014", + "title": "[FIX] Custom OAuth provider creation from env vars", + "userLogin": "pierreozoux", + "contributors": [ + "pierreozoux", + "web-flow" + ] + }, + { + "pr": "20843", + "title": "Bump Livechat Widget", + "userLogin": "renatobecker", + "description": "Update Livechat version to `1.8.0` .", + "milestone": "3.12.0", + "contributors": [ + "renatobecker" + ] + }, + { + "pr": "20738", + "title": "Improve: Add more API tests", + "userLogin": "r0zbot", + "description": "Add end-to-end tests for untested endpoints.", + "contributors": [ + "r0zbot", + "web-flow", + "pierre-lehnen-rc" + ] + }, + { + "pr": "20834", + "title": "[FIX] Atlassian Crowd login with 2FA enabled", + "userLogin": "pierre-lehnen-rc", + "contributors": [ + "pierre-lehnen-rc" + ] + }, + { + "pr": "20840", + "title": "[FIX] CAS login failing due to TOTP requirement", + "userLogin": "pierre-lehnen-rc", + "contributors": [ + "pierre-lehnen-rc" + ] + }, + { + "pr": "20696", + "title": "[FIX] CORS config not accepting multiple origins", + "userLogin": "g-thome", + "description": "always include only one value in access-control-allow-origin", + "contributors": [ + "g-thome", + "sampaiodiego" + ] + }, + { + "pr": "20833", + "title": "[FIX] height prop on departments agents table", + "userLogin": "dougfabris", + "description": "![image](https://user-images.githubusercontent.com/27704687/108572412-fbf83f80-72f0-11eb-801a-5f659000325d.png)", + "milestone": "3.12.0", + "contributors": [ + "dougfabris" + ] + }, + { + "pr": "20815", + "title": "[FIX] Quoted messages from message links when user has no permission ", + "userLogin": "KevLehman", + "contributors": [ + "KevLehman", + "web-flow", + "sampaiodiego" + ] + }, + { + "pr": "20830", + "title": "[FIX] Omnichannel agents are unable to access the chat queue on the sidebar", + "userLogin": "rafaelblink", + "milestone": "3.12.0", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "20216", + "title": "[FIX] Several Slack Importer issues", + "userLogin": "pierre-lehnen-rc", + "description": "- Fix: Slack Importer crashes when importing a large users.json file\r\n- Fix: Slack importer crashes when messages have invalid mentions\r\n- Skip listing all users on the preparation screen when the user count is too large.\r\n- Split avatar download into a separate process.\r\n- Update room's last message when the import is complete.\r\n- Prevent invalid or duplicated channel names\r\n- Improve message error handling.\r\n- Reduce max allowed BSON size to avoid possible issues in some servers.\r\n- Improve handling of very large channel files.", + "milestone": "3.12.0", + "contributors": [ + "pierre-lehnen-rc", + "web-flow" + ] + }, + { + "pr": "20826", + "title": "Regression: Fix loadHistory method being called multiple times", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "20772", + "title": "[FIX] Adding the accidentally deleted tag template, used by other templates", + "userLogin": "yash-rajpal", + "description": "Adding back accidentally deleted tag Template.", + "contributors": [ + "yash-rajpal" + ] + }, + { + "pr": "20829", + "title": "Regression: Fix notification worker not firing", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "20800", + "title": "[FIX] Remove warning problems from console", + "userLogin": "tiagoevanp", + "contributors": [ + "tiagoevanp", + "tassoevan", + "web-flow" + ] + }, + { + "pr": "20739", + "title": "[FIX] Missing height on departments agents table", + "userLogin": "dougfabris", + "description": "![image](https://user-images.githubusercontent.com/27704687/107807002-510ee100-6d46-11eb-86e9-d65da7ab4129.png)", + "milestone": "3.12.0", + "contributors": [ + "dougfabris" + ] + }, + { + "pr": "20744", + "title": "[FIX] Missing setting to control when to send the ReplyTo field in email notifications", + "userLogin": "matheusbsilva137", + "description": "- Add a new setting (\"Add Reply-To header\") in the Email settings' page to control when the Reply-To header is used in e-mail notifications;\r\n- The new setting is turned off (`false` value) by default.", + "contributors": [ + "matheusbsilva137", + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "20827", + "title": "[IMPROVE] Make message field required in Omnichannel Triggers form", + "userLogin": "rafaelblink", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "20814", + "title": "[IMPROVE] New chat started system message for Omnichannel conversations", + "userLogin": "rafaelblink", + "contributors": [ + "rafaelblink", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "20549", + "title": "[NEW] Button to unset Slackbridge's importIds", + "userLogin": "pierre-lehnen-rc", + "contributors": [ + "pierre-lehnen-rc", + "web-flow" + ] + }, + { + "pr": "20740", + "title": "[FIX] External systems not being able to change Omnichannel Inquiry priorities ", + "userLogin": "renatobecker", + "description": "Due to a wrong property name, external applications were not able to change the priority of Omnichannel Inquires.", + "milestone": "3.11.2", + "contributors": [ + "renatobecker" + ] + }, + { + "pr": "20727", + "title": "[FIX] Room owner not being able to override global retention policy", + "userLogin": "g-thome", + "description": "use correct permissions to check if room owner can override global retention policy", + "milestone": "3.11.2", + "contributors": [ + "g-thome" + ] + }, + { + "pr": "20725", + "title": "[FIX] Threads Issues", + "userLogin": "ggazzo", + "milestone": "3.11.2", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "20447", + "title": "[IMPROVE] Add symbol to indicate apps' required settings in the UI", + "userLogin": "matheusbsilva137", + "description": "- Apps are able to define **required** settings. These settings should not be left blank by the user and an error will be thrown and shown in the interface if an user attempts to save changes in the app details page leaving any required fields blank;\r\n![prt_screen_required_app_settings_warning](https://user-images.githubusercontent.com/36537004/106032964-e73cd900-60af-11eb-8eab-c11fd651b593.png)\r\n\r\n - A sign (*) is added to the label of app settings' fields that are required so as to highlight the fields which must not be left blank.\r\n![prt_screen_required_app_settings](https://user-images.githubusercontent.com/36537004/106014879-ae473900-609c-11eb-9b9e-95de7bbf20a5.png)", + "contributors": [ + "matheusbsilva137", + "web-flow" + ] + }, + { + "pr": "20704", + "title": "[FIX] E2E issues", + "userLogin": "ggazzo", + "milestone": "3.12.0", + "contributors": [ + "ggazzo", + "tassoevan", + "web-flow" + ] + }, + { + "pr": "20793", + "title": "[IMPROVE] Customize announcement", + "userLogin": "im-adithya", + "description": "Included new variables in customizable ones", + "contributors": [ + "im-adithya", + "web-flow", + "dougfabris" + ] + }, + { + "pr": "20757", + "title": "Language update from LingoHub 🤖 on 2021-02-15Z", + "userLogin": "lingohub[bot]", + "contributors": [ + null, + "sampaiodiego" + ] + }, + { + "pr": "20573", + "title": "[IMPROVE] Selector for default custom oauth key field", + "userLogin": "paulobernardoaf", + "milestone": "3.12.0", + "contributors": [ + "paulobernardoaf", + "web-flow" + ] + }, + { + "pr": "20663", + "title": "[FIX] Event emitter warning", + "userLogin": "sampaiodiego", + "milestone": "3.12.0", + "contributors": [ + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "20666", + "title": "[FIX] Increasing unread counter twice for new threads in DMs or with mentions", + "userLogin": "KevLehman", + "description": "- Unread messages count won't be incremented when the message sent is on a thread (thread count is treated different)", + "contributors": [ + null + ] + }, + { + "pr": "20801", + "title": "[FIX] Message payload from `__my_messages__` stream", + "userLogin": "ggazzo", + "milestone": "3.12.0", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "20799", + "title": "Mixed client and server code on Storybook", + "userLogin": "tassoevan", + "description": "For Storybook to work, we've mocked all modules under `**/server/`, thus making them suitable to hold all code that refers Node.js modules. This implies some duplication, between `client/` and `server/` modules, mediated by modules under `libs/`.", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "20606", + "title": "[FIX] Default Attachments - Show Full Attachment.Text with Markdown", + "userLogin": "aditya-mitra", + "description": "Removed truncating of text in `Attachment.Text`. \r\nAdded `Attachment.Text` to be parsed to markdown by default.\r\n\r\n### Earlier\r\n![earlier](https://user-images.githubusercontent.com/55396651/106910781-92d8cf80-6727-11eb-82ec-818df7544ff0.png)\r\n\r\n### Now\r\n\r\n![now](https://user-images.githubusercontent.com/55396651/106910840-a126eb80-6727-11eb-8bd6-d86383dd9181.png)", + "contributors": [ + "aditya-mitra", + "ggazzo", + "web-flow" + ] + }, + { + "pr": "19954", + "title": "[IMPROVE] Added auto-focus for better user-experience.", + "userLogin": "Darshilp326", + "contributors": [ + "Darshilp326", + "MartinSchoeler", + "web-flow" + ] + }, + { + "pr": "20185", + "title": "[FIX] Remove duplicate getCommonRoomEvents() event binding for starredMessages", + "userLogin": "aKn1ghtOut", + "description": "The getCommonRoomEvents() returned functions were bound to the starredMessages template twice. This was causing some bugs, as detailed in the Issue mentioned below.\r\nI removed the top events call that only bound the getCommonRoomEvents(). Therefore, only one call for the same is left, which is at the end of the file. Having the events bound just once removes the bugs mentioned.", + "contributors": [ + "aKn1ghtOut" + ] + }, + { + "pr": "19645", + "title": "[FIX] star icon was visible after unstarring a message", + "userLogin": "bhavayAnand9", + "contributors": [ + "bhavayAnand9", + "sampaiodiego", + "web-flow", + "MartinSchoeler" + ] + }, + { + "pr": "20785", + "title": "[FIX] Admin cannot clear user details like bio or nickname", + "userLogin": "yash-rajpal", + "description": "When the API users.update is called to update user data, it passes data to saveUser function. Here before saving data like bio or nickname we are checking if they are available or not. If data is available then we are saving it, but we are not doing anything when data isn't available.\r\n\r\nSo unsetting data if data isn't available to save. Will also fix bio and other fields. :)", + "contributors": [ + "yash-rajpal" + ] + }, + { + "pr": "20618", + "title": "[FIX] Default Attachments - Remove Extra Margin in Field Attachments", + "userLogin": "aditya-mitra", + "description": "A large amount of unnecessary margin which existed in the **Field Attachments inside the `DefaultAttachments`** has been fixed.\r\n\r\n### Earlier\r\n\r\n![earlier](https://user-images.githubusercontent.com/55396651/107056792-ba4b9d00-67f8-11eb-9153-05281416cddb.png)\r\n\r\n### Now\r\n\r\n![now](https://user-images.githubusercontent.com/55396651/107057196-3219c780-67f9-11eb-84db-e4a0addfc168.png)", + "contributors": [ + "aditya-mitra" + ] + }, + { + "pr": "20408", + "title": "[FIX] Selected messages don't get unselected", + "userLogin": "im-adithya", + "description": "https://user-images.githubusercontent.com/64399555/105844776-c157fb80-5fff-11eb-90cc-94e9f69649b6.mp4", + "contributors": [ + "im-adithya", + "web-flow", + "gabriellsh" + ] + }, + { + "pr": "20750", + "title": "[IMPROVE] Better Presentation of Blockquotes", + "userLogin": "aditya-mitra", + "description": "Changed the values of `margin-top` and `margin-bottom` for *first* and *last* childs in blockquotes to increase readability.\r\n\r\n### Before\r\n\r\n![before](https://user-images.githubusercontent.com/55396651/107858662-3e3a0080-6e5b-11eb-8274-9bd956807235.png)\r\n\r\n### Now\r\n\r\n![now](https://user-images.githubusercontent.com/55396651/107858471-480f3400-6e5a-11eb-9ccb-3f1be2fed0a4.png)", + "contributors": [ + "aditya-mitra" + ] + }, + { + "pr": "17968", + "title": "[FIX] Incorrect display of \"Reply in Direct Message\" in MessageAction", + "userLogin": "abrom", + "description": "[FIX] Incorrect display of \"Reply in Direct Message\" in MessageAction", + "milestone": "3.10.0", + "contributors": [ + "abrom", + "MartinSchoeler", + "web-flow" + ] + }, + { + "pr": "20737", + "title": "[FIX] Save user password and email from My Account", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "20745", + "title": "[FIX] Don't ask again not rendering", + "userLogin": "gabriellsh", + "milestone": "3.12.0", + "contributors": [ + "gabriellsh", + "web-flow" + ] + }, + { + "pr": "20390", + "title": "[FIX] Retry icon comes out of the div", + "userLogin": "im-adithya", + "description": "Changed the height of the div container.", + "contributors": [ + "im-adithya", + "web-flow", + "tiagoevanp" + ] + }, + { + "pr": "20798", + "title": "[FIX] Regenerate token modal on top of 2FA modal", + "userLogin": "gabriellsh", + "milestone": "3.12.0", + "contributors": [ + "gabriellsh" + ] + }, + { + "pr": "20366", + "title": "[IMPROVE] Check Livechat message length through REST API endpoint", + "userLogin": "yash-rajpal", + "description": "Added checks for message length for livechat message api, it shouldn't exceed specified character limit.", + "milestone": "3.12.0", + "contributors": [ + "yash-rajpal", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "20607", + "title": "Chore: Disable Sessions Aggregates tests locally", + "userLogin": "KevLehman", + "description": "Disable Session aggregates tests in local environments\r\nFor context, refer to: #20161", + "contributors": [ + null, + "KevLehman" + ] + }, + { + "pr": "20735", + "title": "Exclude user's own password from /me endpoint", + "userLogin": "KevLehman", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "20403", + "title": "[FIX] Added check for view admin permission page", + "userLogin": "yash-rajpal", + "description": "Admin Permission page was visible to all, if you add admin/permissions after the base url. This should not be visible to all user, only people with certain permissions should be able to see this page.\r\nI am also able to see permissions page for open workspace of Rocket chat.\r\n![image](https://user-images.githubusercontent.com/58601732/105829728-bfd00880-5fea-11eb-9121-6c53a752f140.png)", + "contributors": [ + "yash-rajpal", + "gabriellsh", + "web-flow" + ] + }, + { + "pr": "20726", + "title": "[FIX] Mark messages inside a thread as unread", + "userLogin": "im-adithya", + "description": "Added threads to mark unread action button.", + "contributors": [ + "im-adithya", + "web-flow" + ] + }, + { + "pr": "20733", + "title": "[IMPROVE] Update rc-scrollbars", + "userLogin": "tiagoevanp", + "contributors": [ + "tiagoevanp" + ] + }, + { + "pr": "20722", + "title": "[FIX] OAuth Login not working on Firefox", + "userLogin": "gabriellsh", + "milestone": "3.12.0", + "contributors": [ + "gabriellsh", + "ggazzo", + "web-flow" + ] + }, + { + "pr": "20706", + "title": "Chore: Push correct Docker tag of service images", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "20720", + "title": "[FIX] Sending user to home after logging in from resume token query param", + "userLogin": "sampaiodiego", + "description": "Do not redirect to `/home` anymore after logging in with `resumeToken`.", + "milestone": "3.12.0", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "20670", + "title": "[FIX] New Integration page was not being displayed", + "userLogin": "yash-rajpal", + "milestone": "3.12.0", + "contributors": [ + "yash-rajpal", + "MartinSchoeler", + "web-flow" + ] + }, + { + "pr": "20713", + "title": "[FIX] Icon for OTR messages", + "userLogin": "tiagoevanp", + "contributors": [ + "tiagoevanp" + ] + }, + { + "pr": "20709", + "title": "Chore: update RC with the latest fuselage-polyfills", + "userLogin": "dougfabris", + "contributors": [ + "dougfabris" + ] + }, + { + "pr": "20605", + "title": "[FIX] Notification worker stopping on error", + "userLogin": "sampaiodiego", + "milestone": "3.12.0", + "contributors": [ + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "20456", + "title": "[FIX] Add tooltips to Thread header buttons", + "userLogin": "aKn1ghtOut", + "description": "Added tooltips to \"Expand\" and \"Follow Message\"/\"Unfollow Message\" in ThreadView for coherency.", + "milestone": "3.12.0", + "contributors": [ + "aKn1ghtOut" + ] + }, + { + "pr": "20680", + "title": "[FIX] Room's last message's update date format on IE", + "userLogin": "dougfabris", + "description": "The proposed change fixes a bug when updates the cached records on Internet Explorer and it breaks the sidebar as shown on the screenshot below:\r\n\r\n![image](https://user-images.githubusercontent.com/27704687/107578007-f2285b00-6bd1-11eb-9250-1e76ae67f9c9.png)", + "contributors": [ + "dougfabris", + "web-flow" + ] + }, + { + "pr": "20661", + "title": "Added toast message after deleting file.", + "userLogin": "Darshilp326", + "description": "https://user-images.githubusercontent.com/55157259/107410849-d1a9c380-6b33-11eb-8d10-3d225dc7a9db.mp4", + "contributors": [ + "Darshilp326" + ] + }, + { + "pr": "20545", + "title": "Chore: Remove node-sprite-generator dependency", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "20679", + "title": "[FIX] Hide system messages not working on second save", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "20662", + "title": "[FIX] Omnichannel Routing System not assigning chats to Bot agents", + "userLogin": "renatobecker", + "description": "The `Omnichannel Routing System` is no longer assigning chats to `bot` agents when the `bot` agent is the default agent of the inquiry.", + "milestone": "3.11.1", + "contributors": [ + "renatobecker", + "web-flow" + ] + }, + { + "pr": "19778", + "title": "[IMPROVE] Rewrite Call as React component ", + "userLogin": "tiagoevanp", + "milestone": "3.11.0", + "contributors": [ + "tiagoevanp", + "ggazzo", + "tassoevan" + ] + }, + { + "pr": "20665", + "title": "[FIX] Server-side marked parsing", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler", + "web-flow" + ] + }, + { + "pr": "20653", + "title": "[FIX] Livechat bridge permission checkers", + "userLogin": "d-gubert", + "description": "Update to latest patch version of the Apps-Engine with a fix for the Livechat bridge, as seen in https://github.com/RocketChat/Rocket.Chat.Apps-engine/pull/379", + "milestone": "3.11.1", + "contributors": [ + "d-gubert", + "lolimay", + "web-flow" + ] + }, + { + "pr": "20481", + "title": "[FIX] Users autocomplete showing duplicated results", + "userLogin": "Darshilp326", + "description": "Added new query for outside room users so that room members are not shown twice.\r\n\r\nhttps://user-images.githubusercontent.com/55157259/106174582-33c10b00-61bb-11eb-9716-377ef7bba34e.mp4", + "contributors": [ + "Darshilp326" + ] + }, + { + "pr": "20585", + "title": "[FIX] Attachment download from title fixed", + "userLogin": "yash-rajpal", + "description": "Added target = '_self' to attachment link, this seems to fix the problem, without this attribute, error page is displayed.", + "milestone": "3.11.1", + "contributors": [ + "yash-rajpal", + "tiagoevanp", + "web-flow" + ] + }, + { + "pr": "19934", + "title": "[IMPROVE] Adds tooltip for sidebar header icons", + "userLogin": "RonLek", + "description": "Previously the header icons in the sidebar didn't show a tooltip when hovered over. This PR fixes that.\r\n\r\n![Screenshot from 2020-12-22 15-17-41](https://user-images.githubusercontent.com/28918901/102874804-f2756700-4468-11eb-8324-b7f3194e62fe.png)", + "milestone": "3.11.0", + "contributors": [ + "RonLek" + ] + }, + { + "pr": "20617", + "title": "Rewrite: CreateChannel modal component", + "userLogin": "tiagoevanp", + "description": "![image](https://user-images.githubusercontent.com/17487063/107058434-5f438700-67b3-11eb-8cf2-1ad3d5008aa8.png)", + "contributors": [ + "tiagoevanp", + "MartinSchoeler", + "web-flow" + ] + }, + { + "pr": "20654", + "title": "[FIX] Gif images aspect ratio on preview", + "userLogin": "tiagoevanp", + "milestone": "3.11.1", + "contributors": [ + "tiagoevanp" + ] + }, + { + "pr": "20237", + "title": "[FIX] - Cancel button on Room Notification don't close contextualBar", + "userLogin": "dougfabris", + "milestone": "3.12.0", + "contributors": [ + "dougfabris", + "ggazzo", + "web-flow" + ] + }, + { + "pr": "20651", + "title": "[FIX] Links not opening in new tabs", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "20649", + "title": "[FIX] Room Scroll to Bottom", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "20652", + "title": "Regression: Discussions inside direct messages not rendering", + "userLogin": "gabriellsh", + "contributors": [ + "gabriellsh" + ] + }, + { + "pr": "20381", + "title": "[FIX] Announcement with multiple lines fixed.", + "userLogin": "yash-rajpal", + "description": "Announcements with multiple lines used to break UI for announcements bar. Fixed it by replacing all break lines in announcement with empty space (\" \") . The announcement modal would work as usual and show all break lines.", + "contributors": [ + "yash-rajpal" + ] + }, + { + "pr": "20329", + "title": "[FIX] Fix Empty highlighted words field", + "userLogin": "yash-rajpal", + "description": "Able to Empty the highlighted text field in preferences", + "contributors": [ + "yash-rajpal" + ] + }, + { + "pr": "20592", + "title": "[FIX] OTR issue", + "userLogin": "ggazzo", + "description": "Since the users are not being stored at the user collection anymore (thats a good thing actually), there is no such record to to fetch and show the username.", + "milestone": "3.10.6", + "contributors": [ + "ggazzo", + "web-flow" + ] + }, + { + "pr": "20611", + "title": "[FIX] Update NPS banner when changing score", + "userLogin": "sampaiodiego", + "milestone": "3.11.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "20625", + "title": "Remove `uiKitText` reference", + "userLogin": "tassoevan", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "20624", + "title": "[FIX] List of Omnichannel triggers is not listing data", + "userLogin": "rafaelblink", + "description": "### Before\r\n![image](https://user-images.githubusercontent.com/2493803/107095379-7308e080-67e7-11eb-8251-7e7ff891087a.png)\r\n\r\n\r\n### After\r\n![image](https://user-images.githubusercontent.com/2493803/107095261-3b019d80-67e7-11eb-8425-8612b03ac50a.png)", + "milestone": "3.12.0", + "contributors": [ + "rafaelblink" + ] + }, + { + "pr": "20616", + "title": "Regression: Header Styles", + "userLogin": "gabriellsh", + "contributors": [ + "gabriellsh", + "web-flow" + ] + }, + { + "pr": "20613", + "title": "[FIX] Regular status mutating custom status", + "userLogin": "gabriellsh", + "contributors": [ + "gabriellsh" + ] + }, + { + "pr": "20484", + "title": "[FIX] Channel mentions showing user subscribed channels twice", + "userLogin": "Darshilp326", + "description": "Channel mention shows user subscribed channels twice.\r\n\r\nhttps://user-images.githubusercontent.com/55157259/106183033-b353d780-61c5-11eb-8aab-1dbb62b02ff8.mp4", + "contributors": [ + "Darshilp326" + ] + }, + { + "pr": "20612", + "title": "[IMPROVE] Change header based on room type", + "userLogin": "dougfabris", + "description": "It brings more flexibility, allowing us to use different hooks and different components for each header", + "milestone": "3.12.0", + "contributors": [ + "dougfabris", + "gabriellsh", + "web-flow", + "ggazzo" + ] + }, + { + "pr": "20609", + "title": "[NEW] Header with Breadcrumbs", + "userLogin": "dougfabris", + "description": "![image](https://user-images.githubusercontent.com/27704687/106945019-1386d400-6706-11eb-90db-c12b50f260d5.png)", + "milestone": "3.12.0", + "contributors": [ + "dougfabris", + "gabriellsh", + "web-flow", + "ggazzo" + ] + }, + { + "pr": "20250", + "title": "Chore: Change error message when marking empty chat as unread", + "userLogin": "lucassartor", + "contributors": [ + "lucassartor" + ] + }, + { + "pr": "20519", + "title": "Chore: Improve performance of messages’ watcher", + "userLogin": "rodrigok", + "milestone": "3.12.0", + "contributors": [ + "rodrigok" + ] + }, + { + "pr": "20550", + "title": "RoomFiles hook", + "userLogin": "tiagoevanp", + "contributors": [ + "tiagoevanp", + "ggazzo", + "web-flow" + ] + }, + { + "pr": "20586", + "title": "[FIX] ESLint Warning - react-hooks/exhaustive-deps", + "userLogin": "aditya-mitra", + "description": "Added the required dep (`label`) in `useMemo` to fix eslint warning `react-hooks/exhaustive-deps`.", + "contributors": [ + "aditya-mitra" + ] + }, + { + "pr": "20584", + "title": "[NEW] useUserData Hook", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "20320", + "title": "[FIX] Filters are not being applied correctly in Omnichannel Current Chats list", + "userLogin": "rafaelblink", + "description": "### Before\r\n![image](https://user-images.githubusercontent.com/2493803/105537672-082cb500-5cd1-11eb-8f1b-1726ba60420a.png)\r\n\r\n### After\r\n![image](https://user-images.githubusercontent.com/2493803/105537773-2d212800-5cd1-11eb-8746-048deb9502d9.png)\r\n\r\n![image](https://user-images.githubusercontent.com/2493803/106494728-88090b00-6499-11eb-922e-5386107e2389.png)\r\n\r\n![image](https://user-images.githubusercontent.com/2493803/106494751-90f9dc80-6499-11eb-901b-5e4dbdc678ba.png)", + "milestone": "3.12.0", + "contributors": [ + "rafaelblink", + "web-flow", + "renatobecker" + ] + }, + { + "pr": "20297", + "title": "[FIX] Add debouncing to add users search field.", + "userLogin": "Darshilp326", + "description": "BEFORE\r\n\r\nhttps://user-images.githubusercontent.com/55157259/105350722-98a3c080-5c11-11eb-82f3-d9a62a4fa50b.mp4\r\n\r\n\r\nAFTER\r\n\r\nhttps://user-images.githubusercontent.com/55157259/105350757-a2c5bf00-5c11-11eb-91db-25c0b9e01a28.mp4", + "contributors": [ + "Darshilp326", + "dougfabris", + "web-flow" + ] + }, + { + "pr": "20356", + "title": "[FIX] Changed password input field for password access in edit room info.", + "userLogin": "Darshilp326", + "description": "Password field would be secured with asterisks in edit room info\r\n\r\nhttps://user-images.githubusercontent.com/55157259/105641758-cad04f00-5eab-11eb-90de-0c91263edd55.mp4\r\n\r\n.", + "contributors": [ + "Darshilp326" + ] + }, + { + "pr": "20179", + "title": "[FIX] Remove duplicate getCommonRoomEvents() event binding for pinnedMessages", + "userLogin": "aKn1ghtOut", + "description": "The getCommonRoomEvents() returned functions were bound to the pinnedMessages template twice. This was causing some bugs, as detailed in the Issue mentioned below.", + "contributors": [ + "aKn1ghtOut", + "web-flow" + ] + }, + { + "pr": "20341", + "title": "[FIX] User statuses in admin user info panel", + "userLogin": "RonLek", + "description": "Modifies user statuses in admin info panel based on their actual status instead of their `statusConnection`. This enables correct and consistent change in user statuses. \r\nAlso, bot users having status as online were classified as offline, with this change they are now correctly classified based on their corresponding statuses.\r\n\r\nhttps://user-images.githubusercontent.com/28918901/105624438-b8bcc500-5e47-11eb-8d1e-3a4180da1304.mp4", + "contributors": [ + "RonLek" + ] + }, + { + "pr": "20193", + "title": "[FIX] Blank Personal Access Token Bug", + "userLogin": "RonLek", + "description": "Adds error when personal access token is blank thereby disallowing the creation of one.\r\n\r\nhttps://user-images.githubusercontent.com/28918901/104483631-5adde100-55ee-11eb-9938-64146bce127e.mp4", + "contributors": [ + "RonLek", + "web-flow" + ] + }, + { + "pr": "20339", + "title": "[FIX] Feedback on bulk invite", + "userLogin": "aKn1ghtOut", + "description": "Resolved structure where no response was being received. Changed from callback to async/await.\r\nAdded error in case of empty submission, or if no valid emails were found.\r\n\r\nhttps://user-images.githubusercontent.com/38764067/105613964-dfe5a900-5deb-11eb-80f2-21fc8dee57c0.mp4", + "contributors": [ + "aKn1ghtOut" + ] + }, + { + "pr": "20337", + "title": "[IMPROVE] Added disable button check for send invite button", + "userLogin": "yash-rajpal", + "description": "Added Disable check for send invite button. If the text field is empty button would be disabled, and after any valid email is filled, button would get enabled", + "contributors": [ + "yash-rajpal" + ] + }, + { + "pr": "20358", + "title": "[FIX]Selected hide system messages would now be viewed in vertical bar.", + "userLogin": "Darshilp326", + "description": "All selected hide system messages are now in vertical Bar.\r\n\r\nhttps://user-images.githubusercontent.com/55157259/105642624-d5411780-5eb0-11eb-8848-93e4b02629cb.mp4", + "contributors": [ + "Darshilp326" + ] + }, + { + "pr": "20426", + "title": "[FIX] Typo in Message Character Limit", + "userLogin": "aditya-mitra", + "description": "Changed the spelling of *Characther* to *Character*", + "contributors": [ + "aditya-mitra" + ] + }, + { + "pr": "20444", + "title": "[FIX] Unset tshow on deleted messages", + "userLogin": "aKn1ghtOut", + "description": "When setting 'Message_ShowDeletedStatus' is set to true, deleting a message with `tshow: true` causes a bug on the frontend. This issue should, however, never be logically possible as a 'removed' message should not have tshow anyway. Hence, this PR unsets that when the message is set to \"Message Removed\".", + "contributors": [ + "aKn1ghtOut" + ] + }, + { + "pr": "20305", + "title": "[FIX] Added Bio Structure for UserCard, rendering Skeleton View on loading Instead of [Object][Object] ", + "userLogin": "yash-rajpal", + "description": "Added Bio Structure for rendering Skeleton View on loading UserCard.", + "milestone": "3.11.0", + "contributors": [ + "yash-rajpal" + ] + }, + { + "pr": "20392", + "title": "[IMPROVE] Replace react-window for react-virtuoso package", + "userLogin": "tiagoevanp", + "description": "Remove:\r\n- react-window\r\n- react-window-infinite-loader\r\n- simplebar-react\r\n\r\nInclude:\r\n- react-virtuoso\r\n- rc-scrollbars", + "milestone": "3.12.0", + "contributors": [ + "tiagoevanp", + "ggazzo", + "tassoevan", + "web-flow" + ] + }, + { + "pr": "20470", + "title": "[IMPROVE] Added Markdown links to custom status.", + "userLogin": "yash-rajpal", + "description": "Added markdown links to user's custom status.", + "contributors": [ + "yash-rajpal" + ] + }, + { + "pr": "20473", + "title": "[IMPROVE] Added key prop, removing unwanted warnings", + "userLogin": "yash-rajpal", + "description": "Removes warnings listed on the issue", + "contributors": [ + "yash-rajpal" + ] + }, + { + "pr": "20498", + "title": "[FIX] Removed tooltip in kebab menu options.", + "userLogin": "Darshilp326", + "description": "Removed tooltip as it was not needed.\r\n\r\nhttps://user-images.githubusercontent.com/55157259/106246146-a53ca000-6233-11eb-9874-cbd1b4331bc0.mp4", + "contributors": [ + "Darshilp326" + ] + }, + { + "pr": "20308", + "title": "[IMPROVE] Add visual validation on users admin forms", + "userLogin": "dougfabris", + "contributors": [ + "dougfabris", + "gabriellsh" + ] + }, + { + "pr": "20508", + "title": "Wrong method used while starring", + "userLogin": "im-adithya", + "description": "Changed the method from pinMessage to starMessage", + "contributors": [ + "im-adithya", + "web-flow" + ] + }, + { + "pr": "20046", + "title": "Chore: Try building micro services early on CI", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego", + "renatobecker", + "web-flow", + "rodrigok" + ] + }, + { + "pr": "20533", + "title": "Merge master into develop & Set version to 3.12.0-develop", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego", + "web-flow" + ] + } + ] + }, + "3.12.0-rc.1": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.23.0-alpha.4655", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "20871", + "title": "Regression: Fix scopes not being provided to getWorkspaceAccessToken", + "userLogin": "geekgonecrazy", + "milestone": "3.12.0", + "contributors": [ + "geekgonecrazy" + ] + }, + { + "pr": "20869", + "title": "Regression: Keep user custom status after change presence", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "20767", + "title": "[FIX] Markdown prop variants", + "userLogin": "dougfabris", + "description": "A new prop variants on Markdown component: **inline** and **inlineWithoutBreaks**", + "contributors": [ + "dougfabris", + "web-flow", + "ggazzo" + ] + }, + { + "pr": "20868", + "title": "[FIX] Open Visitor Info when omnichannel chat was open", + "userLogin": "tiagoevanp", + "contributors": [ + "tiagoevanp" + ] + }, + { + "pr": "20860", + "title": "Regression: Prevent Message Attachment rendering", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "20820", + "title": "[FIX] Download buttons on desktop app and CDN being ignored", + "userLogin": "ggazzo", + "milestone": "3.12.0", + "contributors": [ + "ggazzo" + ] + } + ] + }, + "3.12.0-rc.2": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.23.0", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "20912", + "title": "[FIX] Admin Panel pages not visible in Safari", + "userLogin": "tiagoevanp", + "milestone": "3.12.0", + "contributors": [ + "tiagoevanp" + ] + }, + { + "pr": "20921", + "title": "Update Apps-Engine version", + "userLogin": "d-gubert", + "description": "Update the Apps-Engine to latest version for the release.", + "milestone": "3.12.0", + "contributors": [ + "d-gubert" + ] + }, + { + "pr": "20922", + "title": "Regression: Messages not being encrypted E2E", + "userLogin": "ggazzo", + "milestone": "3.11.2", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "20852", + "title": "Fix: Add network observe plug to snap", + "userLogin": "geekgonecrazy", + "contributors": [ + "geekgonecrazy", + "web-flow", + "sampaiodiego" + ] + }, + { + "pr": "20853", + "title": "Language update from LingoHub 🤖 on 2021-02-22Z", + "userLogin": "lingohub[bot]", + "contributors": [ + null + ] + }, + { + "pr": "20819", + "title": "Added types to Emitters", + "userLogin": "ggazzo", + "milestone": "3.12.0", + "contributors": [ + "ggazzo" + ] + } + ] + }, + "3.12.0-rc.3": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.23.0", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "3.12.0-rc.4": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.23.0", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "3.12.0": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.23.0", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "3.10.6": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.21.0-alpha.4235", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "3.11.2": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.22.2", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "20727", + "title": "[FIX] Room owner not being able to override global retention policy", + "userLogin": "g-thome", + "description": "use correct permissions to check if room owner can override global retention policy", + "milestone": "3.11.2", + "contributors": [ + "g-thome" + ] + }, + { + "pr": "20860", + "title": "[FIX] Prevent Message Attachment rendering", + "userLogin": "ggazzo", + "milestone": "3.11.2", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "20740", + "title": "[FIX] External systems not being able to change Omnichannel Inquiry priorities ", + "userLogin": "renatobecker", + "description": "Due to a wrong property name, external applications were not able to change the priority of Omnichannel Inquires.", + "milestone": "3.11.2", + "contributors": [ + "renatobecker" + ] + } + ] + }, + "3.12.1": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.23.0", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "21020", + "title": "[FIX] Missing spaces on attachment ", + "userLogin": "dougfabris", + "milestone": "3.12.1", + "contributors": [ + "dougfabris", + "tassoevan", + "web-flow" + ] + }, + { + "pr": "21004", + "title": "[IMPROVE] Close Call contextual bar after starting jitsi call.", + "userLogin": "yash-rajpal", + "description": "After jitsi call is started, if the call is started in a new window then we should close contextual tab bar.\r\nSo, when 'YES' is pressed on modal, we call handleClose function if openNewWindow is true, as call doesn't starts on tab bar, it starts on new window.", + "milestone": "3.12.1", + "contributors": [ + "yash-rajpal", + "tassoevan", + "web-flow" + ] + }, + { + "pr": "20973", + "title": "[FIX] Stopping Jitsi reload", + "userLogin": "yash-rajpal", + "description": "The Function where Jitsi call is started gets called many times due to `room.usernames` dep of useMemo, this dep triggers reloading of this function many times.\r\nSo removing this dep from useMemo dependencies", + "milestone": "3.12.1", + "contributors": [ + "yash-rajpal", + "tassoevan", + "web-flow" + ] } ] } diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 272c40ac57d51..8bb1d069b23a0 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -31,7 +31,7 @@ jobs: cat $GITHUB_EVENT_PATH - name: Use Node.js 12.18.4 - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 with: node-version: "12.18.4" @@ -51,7 +51,7 @@ jobs: - name: Cache cypress id: cache-cypress - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: /home/runner/.cache/Cypress key: ${{ runner.OS }}-cache-cypress-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('.github/workflows/build_and_test.yml') }} @@ -59,19 +59,19 @@ jobs: - name: Cache node modules if: steps.cache-cypress.outputs.cache-hit == 'true' id: cache-nodemodules - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: node_modules key: ${{ runner.OS }}-node_modules-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('.github/workflows/build_and_test.yml') }} - name: Cache meteor local - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ./.meteor/local key: ${{ runner.OS }}-meteor_cache-${{ hashFiles('.meteor/versions') }}-${{ hashFiles('.github/workflows/build_and_test.yml') }} - name: Cache meteor - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ~/.meteor key: ${{ runner.OS }}-meteor-${{ hashFiles('.meteor/release') }}-${{ hashFiles('.github/workflows/build_and_test.yml') }} @@ -122,6 +122,12 @@ jobs: - run: meteor npm run typecheck + - name: Build Storybook to sanity check components + run: npm run build-storybook ; rm -rf ./storybook-static + env: + NODE_OPTIONS: --max_old_space_size=8192 + + # To reduce memory need during actual build, build the packages solely first # - name: Build a Meteor cache # run: | @@ -137,6 +143,13 @@ jobs: run: | meteor reset + - name: Try building micro services + run: | + cd ./ee/server/services + npm i + npm run build + rm -rf dist/ + - name: Build Rocket.Chat From Pull Request if: startsWith(github.ref, 'refs/pull/') == true env: @@ -160,13 +173,13 @@ jobs: tar czf Rocket.Chat.test.tar.gz ./build-test - name: Store build for tests - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 with: name: build-test path: /tmp/Rocket.Chat.test.tar.gz - name: Store build - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 with: name: build path: /tmp/build @@ -187,7 +200,7 @@ jobs: mongoDBVersion: ${{ matrix.mongodb-version }} --noprealloc --smallfiles --replSet=rs0 - name: Restore build for tests - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v2 with: name: build-test path: /tmp @@ -199,7 +212,7 @@ jobs: cd - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} @@ -216,7 +229,7 @@ jobs: - name: Cache cypress id: cache-cypress - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: /home/runner/.cache/Cypress key: ${{ runner.OS }}-cache-cypress-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('.github/workflows/build_and_test.yml') }} @@ -224,7 +237,7 @@ jobs: - name: Cache node modules if: steps.cache-cypress.outputs.cache-hit == 'true' id: cache-nodemodules - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: node_modules key: ${{ runner.OS }}-build-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('.github/workflows/build_and_test.yml') }} @@ -282,25 +295,25 @@ jobs: - name: Cache node modules id: cache-nodemodules - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: node_modules key: ${{ runner.OS }}-node_modules-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('.github/workflows/build_and_test.yml') }} - name: Cache meteor local - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ./.meteor/local key: ${{ runner.OS }}-meteor_cache-${{ hashFiles('.meteor/versions') }}-${{ hashFiles('.github/workflows/build_and_test.yml') }} - name: Cache meteor - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ~/.meteor key: ${{ runner.OS }}-meteor-${{ hashFiles('.meteor/release') }}-${{ hashFiles('.github/workflows/build_and_test.yml') }} - name: Use Node.js 12.18.4 - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 with: node-version: "12.18.4" @@ -331,7 +344,6 @@ jobs: meteor npm --versions meteor node -v git version - echo $GITHUB_REF - name: npm install if: steps.cache-nodemodules.outputs.cache-hit != 'true' @@ -353,74 +365,37 @@ jobs: meteor build --server-only --directory /tmp/build-pr - name: Build Docker image for PRs + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: pr-${{ github.event.number }} + DOCKER_USER: ${{ secrets.DOCKER_USER }} + DOCKER_PASS: ${{ secrets.DOCKER_PASS }} run: | cd /tmp/build-pr - LOWERCASE_REPOSITORY=$(echo "${{ github.repository_owner }}" | tr "[:upper:]" "[:lower:]") - IMAGE_NAME="ghcr.io/${LOWERCASE_REPOSITORY}/rocket.chat:pr-${{ github.event.number }}" + docker login -u "${DOCKER_USER}" -p "${DOCKER_PASS}" - echo "Build official Docker image ${IMAGE_NAME}" + cp $GITHUB_WORKSPACE/.docker/Dockerfile . - docker build -f $GITHUB_WORKSPACE/.docker/Dockerfile -t $IMAGE_NAME . - docker push $IMAGE_NAME + export LOWERCASE_REPOSITORY=$(echo "$GITHUB_REPOSITORY" | tr "[:upper:]" "[:lower:]") - deploy: - runs-on: ubuntu-latest - if: github.event_name == 'release' || github.ref == 'refs/heads/develop' - needs: test + export IMAGE_NAME="assistify/chat:${VERSION}" - steps: - - uses: actions/checkout@v2 - - - name: Restore build - uses: actions/download-artifact@v1 - with: - name: build - path: /tmp/build - - - name: Publish assets - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }} - REDHAT_REGISTRY_PID: ${{ secrets.REDHAT_REGISTRY_PID }} - REDHAT_REGISTRY_KEY: ${{ secrets.REDHAT_REGISTRY_KEY }} - UPDATE_TOKEN: ${{ secrets.UPDATE_TOKEN }} - run: | - if [[ '${{ github.event_name }}' = 'release' ]]; then - export CIRCLE_TAG="${GITHUB_REF#*tags/}" - export CIRCLE_BRANCH="" - else - export CIRCLE_TAG="" - export CIRCLE_BRANCH="${GITHUB_REF#*heads/}" - fi; - - export CIRCLE_SHA1=$GITHUB_SHA - export CIRCLE_BUILD_NUM=$GITHUB_SHA - - aws s3 cp s3://rocketchat/sign.key.gpg .circleci/sign.key.gpg - - source .circleci/setartname.sh - source .circleci/setdeploydir.sh - bash .circleci/setupsig.sh - bash .circleci/namefiles.sh - - aws s3 cp $ROCKET_DEPLOY_DIR/ s3://download.rocket.chat/build/ --recursive + echo "Build official Docker image ${IMAGE_NAME}" - bash .circleci/update-releases.sh - # bash .circleci/snap.sh - bash .circleci/redhat-registry.sh + docker build -f $GITHUB_WORKSPACE/.docker/Dockerfile -t $IMAGE_NAME . + docker push $IMAGE_NAME image-build: runs-on: ubuntu-latest - needs: deploy + needs: test strategy: matrix: - release: ["official", "preview"] + release: ["official"] env: - IMAGE: "rocketchat/rocket.chat" + IMAGE: "assistify/chat" steps: - uses: actions/checkout@v2 @@ -432,7 +407,7 @@ jobs: password: ${{ secrets.DOCKER_PASS }} - name: Restore build - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v2 with: name: build path: /tmp/build @@ -458,22 +433,22 @@ jobs: if: github.event_name == 'release' run: | cd /tmp/build - CIRCLE_TAG="${GITHUB_REF#*tags/}" + GIT_TAG="${GITHUB_REF#*tags/}" if [[ '${{ matrix.release }}' = 'preview' ]]; then IMAGE="${IMAGE}.preview" fi; - docker build -t ${IMAGE}:$CIRCLE_TAG . - docker push ${IMAGE}:$CIRCLE_TAG + docker build -t ${IMAGE}:$GIT_TAG . + docker push ${IMAGE}:$GIT_TAG - if echo "$CIRCLE_TAG" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' ; then + if echo "$GIT_TAG" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' ; then RELEASE="latest" - elif echo "$CIRCLE_TAG" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$' ; then + elif echo "$GIT_TAG" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$' ; then RELEASE="release-candidate" fi - docker tag ${IMAGE}:$CIRCLE_TAG ${IMAGE}:${RELEASE} + docker tag ${IMAGE}:$GIT_TAG ${IMAGE}:${RELEASE} docker push ${IMAGE}:${RELEASE} - name: Build Docker image for develop @@ -488,43 +463,3 @@ jobs: docker build -t ${IMAGE}:develop . docker push ${IMAGE}:develop - services-image-build: - runs-on: ubuntu-latest - needs: deploy - - strategy: - matrix: - service: ["account", "authorization", "ddp-streamer", "presence", "stream-hub"] - - steps: - - uses: actions/checkout@v2 - - - name: Use Node.js 12.18.4 - uses: actions/setup-node@v1 - with: - node-version: "12.18.4" - - - name: Login to DockerHub - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKER_USER }} - password: ${{ secrets.DOCKER_PASS }} - - - name: Build Docker images - run: | - # defines image tag - if [[ $GITHUB_REF == refs/tags/* ]]; then IMAGE_TAG="${GITHUB_REF#refs/tags/}"; else IMAGE_TAG="${GITHUB_REF#refs/heads/}"; fi - - # first install repo dependencies - npm i - - # then micro services dependencies - cd ./ee/server/services - npm i - npm run build - - echo "Building Docker image for service: ${{ matrix.service }}:${IMAGE_TAG}" - - docker build --build-arg SERVICE=${{ matrix.service }} -t rocketchat/${{ matrix.service }}-service:${IMAGE_TAG} . - - docker push rocketchat/${{ matrix.service }}-service diff --git a/.gitignore b/.gitignore index f68d665bf8e81..8d5cc726ac807 100644 --- a/.gitignore +++ b/.gitignore @@ -79,4 +79,6 @@ tests/end-to-end/temporary_staged_test .screenshots /private/livechat /storybook-static -/tests/cypress/screenshots \ No newline at end of file +/tests/cypress/screenshots +coverage +.nyc_output diff --git a/.meteorignore b/.meteorignore index 35dd55fee165e..6453c2f01e3d1 100644 --- a/.meteorignore +++ b/.meteorignore @@ -1 +1,2 @@ ee/server/services +coverage diff --git a/mocha.opts.js b/.mocharc.js similarity index 72% rename from mocha.opts.js rename to .mocharc.js index bb4d43909e563..bd3bd56e3c0ed 100644 --- a/mocha.opts.js +++ b/.mocharc.js @@ -3,15 +3,15 @@ module.exports = { require: [ 'ts-node/register', - 'babel-mocha-es6-compiler', - 'babel-polyfill', + '@babel/register', ], reporter: 'spec', ui: 'bdd', - extension: 'js,ts', + extension: ['js', 'ts'], spec: [ 'app/**/*.tests.js', 'app/**/*.tests.ts', + 'server/**/*.tests.ts', 'client/**/*.spec.ts', ], }; diff --git a/.scripts/check-i18n.js b/.scripts/check-i18n.js index f6707402ca177..68da0fbe91a70 100644 --- a/.scripts/check-i18n.js +++ b/.scripts/check-i18n.js @@ -1,90 +1,113 @@ const fs = require('fs'); +const path = require('path'); const fg = require('fast-glob'); -const checkFiles = async (path, source) => { - const sourceFile = JSON.parse(fs.readFileSync(`${ path }${ source }`, 'utf8')); +const regexVar = /__[a-zA-Z_]+__/g; - const regexVar = /__([a-zA-Z_]+?)__/g; +const validateKeys = (json, usedKeys) => + usedKeys + .filter(({ key }) => typeof json[key] !== 'undefined') + .reduce((prev, cur) => { + const { key, replaces } = cur; - const usedKeys = Object.entries(sourceFile) - .filter(([, value]) => regexVar.exec(value)) - .map(([key, value]) => { - const replaces = value.match(regexVar); - return { - key, - replaces, - }; - }); + const miss = replaces.filter((replace) => json[key] && json[key].indexOf(replace) === -1); - const validateKeys = (json) => - usedKeys - .filter(({ key }) => typeof json[key] !== 'undefined') - .reduce((prev, cur) => { - const { key, replaces } = cur; + if (miss.length > 0) { + prev.push({ key, miss }); + } - const miss = replaces.filter((replace) => json[key].indexOf(replace) === -1); + return prev; + }, []); - if (miss.length > 0) { - prev.push({ key, miss }); - } +const removeMissingKeys = (i18nFiles, usedKeys) => { + i18nFiles.forEach((file) => { + const json = JSON.parse(fs.readFileSync(file, 'utf8')); + if (Object.keys(json).length === 0) { + return; + } - return prev; - }, []); + validateKeys(json, usedKeys) + .forEach(({ key }) => { + json[key] = null; + }); - const i18nFiles = await fg([`${ path }/**/*.i18n.json`]); + fs.writeFileSync(file, JSON.stringify(json, null, 2)); + }); +}; - // const getInvalidKeys = (json) => - // usedKeys - // .filter(({ key }) => typeof json[key] !== 'undefined') - // .filter(({ key, replaces }) => { - // const miss = replaces.filter((replace) => json[key].indexOf(replace) === -1); +const checkUniqueKeys = (content, json, filename) => { + const matchKeys = content.matchAll(/^\s+"([^"]+)"/mg); - // return miss.length > 0; - // }) - // .map(({ key }) => key); + const allKeys = [...matchKeys]; - // const removeMissingKeys = () => { - // const allKeys = Object.keys(sourceFile); - // i18nFiles.forEach((file) => { - // const json = JSON.parse(fs.readFileSync(file, 'utf8')); + if (allKeys.length !== Object.keys(json).length) { + throw new Error(`Duplicated keys found on file ${ filename }`); + } +}; - // const invalidKeys = getInvalidKeys(json); +const validate = (i18nFiles, usedKeys) => { + const totalErrors = i18nFiles + .reduce((errors, file) => { + const content = fs.readFileSync(file, 'utf8'); + const json = JSON.parse(content); - // const validKeys = allKeys.filter((key) => !invalidKeys.includes(key)); - // // console.log('validKeys', file, validKeys); + checkUniqueKeys(content, json, file); - // fs.writeFileSync(file, JSON.stringify(json, validKeys, 2)); - // }); - // }; + // console.log('json, usedKeys2', json, usedKeys); - let totalErrors = 0; - i18nFiles.filter((file) => { - const json = JSON.parse(fs.readFileSync(file, 'utf8')); + const result = validateKeys(json, usedKeys); - const result = validateKeys(json); + if (result.length === 0) { + return errors; + } - if (result.length === 0) { - return true; - } + console.log('\n## File', file, `(${ result.length } errors)`); - totalErrors += result.length; + result.forEach(({ key, miss }) => { + console.log('\n- Key:', key, '\n Missing variables:', miss.join(', ')); + }); - console.log('\n## File', file, `(${ result.length } errors)`); + return errors + result.length; + }, 0); - result.forEach(({ key, miss }) => { - console.log('\n- Key:', key, '\n Missing variables:', miss.join(', ')); + if (totalErrors > 0) { + throw new Error(`\n${ totalErrors } errors found`); + } +}; + +const checkFiles = async (sourcePath, sourceFile, fix = false) => { + const content = fs.readFileSync(path.join(sourcePath, sourceFile), 'utf8'); + const sourceContent = JSON.parse(content); + + checkUniqueKeys(content, sourceContent, sourceFile); + + const usedKeys = Object.entries(sourceContent) + .map(([key, value]) => { + const replaces = value.match(regexVar); + return { + key, + replaces, + }; }); - return false; - }); + const keysWithInterpolation = usedKeys + .filter(({ replaces }) => !!replaces); - if (totalErrors > 0) { - console.error(`\n${ totalErrors } errors found`); - process.exit(1); + const i18nFiles = await fg([`${ sourcePath }/**/*.i18n.json`]); + + if (fix) { + return removeMissingKeys(i18nFiles, keysWithInterpolation); } - process.exit(0); + validate(i18nFiles, keysWithInterpolation); }; -checkFiles('./packages/rocketchat-i18n', '/i18n/en.i18n.json'); +(async () => { + try { + await checkFiles('./packages/rocketchat-i18n/i18n', 'en.i18n.json', process.argv[2] === '--fix'); + } catch (e) { + console.error(e); + process.exit(1); + } +})(); diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index 08310b40ea3ff..6466519388021 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/3.8.2/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/3.12.1/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/resources/preparecaddy b/.snapcraft/resources/preparecaddy index 2bb1df13baa91..303020c803e10 100755 --- a/.snapcraft/resources/preparecaddy +++ b/.snapcraft/resources/preparecaddy @@ -32,7 +32,7 @@ caddy_arch=_linux_$caddy_arch echo "Downloading Caddy for $caddy_os/$caddy_arch$caddy_arm..." caddy_file="caddy_linux_$caddy_arch${caddy_arm}_custom$caddy_dl_ext" -caddy_url="https://github.com/mholt/caddy/releases/download/$caddy_version/caddy_$caddy_version$caddy_arch$caddy_arm.tar.gz" +caddy_url="https://github.com/caddyserver/caddy/releases/download/$caddy_version/caddy_$caddy_version$caddy_arch$caddy_arm.tar.gz" echo "$caddy_url" curl -L "$caddy_url" -o "$caddy_file" diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index db020b811ceae..52398f6de5557 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 3.8.2 +version: 3.8.8 summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict @@ -20,7 +20,7 @@ apps: rocketchat-mongo: command: startmongo daemon: simple - plugs: [network, network-bind] + plugs: [network, network-bind, network-observe] rocketchat-caddy: command: env LC_ALL=C caddy -conf=$SNAP_DATA/Caddyfile daemon: simple diff --git a/.storybook/logo.svg b/.storybook/logo.svg new file mode 100644 index 0000000000000..6ae18fa4b93eb --- /dev/null +++ b/.storybook/logo.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + diff --git a/.storybook/main.js b/.storybook/main.js index c1730b0e818e5..ecfbbfab389ff 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -5,8 +5,6 @@ module.exports = { '../ee/**/*.stories.js', ], addons: [ - '@storybook/addon-actions', - '@storybook/addon-knobs', - '@storybook/addon-viewport', + '@storybook/addon-essentials', ], }; diff --git a/.storybook/manager.js b/.storybook/manager.js new file mode 100644 index 0000000000000..6e9aadd1ffd34 --- /dev/null +++ b/.storybook/manager.js @@ -0,0 +1,17 @@ +import colorTokens from '@rocket.chat/fuselage-tokens/colors'; +import { addons } from '@storybook/addons'; +import { create } from '@storybook/theming/create'; + +import manifest from '../package.json'; +import logo from './logo.svg'; + +addons.setConfig({ + theme: create({ + base: 'light', + brandTitle: manifest.name, + brandImage: logo, + brandUrl: manifest.homepage, + colorPrimary: colorTokens.n500, + colorSecondary: colorTokens.b500, + }), +}); diff --git a/.storybook/preview.js b/.storybook/preview.js index 39bdd05fd4839..b82a2b1e56b70 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -1,13 +1,24 @@ -import { withKnobs } from '@storybook/addon-knobs'; +import { DocsPage, DocsContainer } from '@storybook/addon-docs/blocks'; import { addDecorator, addParameters } from '@storybook/react'; import { rocketChatDecorator } from './decorators'; addDecorator(rocketChatDecorator); -addDecorator(withKnobs); addParameters({ + backgrounds: { + grid: { + cellSize: 4, + cellAmount: 4, + opacity: 0.5, + }, + }, + docs: { + container: DocsContainer, + page: DocsPage, + }, options: { - showRoots: true, + storySort: ([, a], [, b]) => + a.kind.localeCompare(b.kind), }, }); diff --git a/.stylelintignore b/.stylelintignore index 4fc5d8a09035e..4bd9c6d458d65 100644 --- a/.stylelintignore +++ b/.stylelintignore @@ -1,4 +1,3 @@ app/theme/client/vendor/fontello/css/fontello.css app/meteor-autocomplete/client/autocomplete.css -app/katex/katex.min.css app/emoji-emojione/client/*.css diff --git a/HISTORY.md b/HISTORY.md index 85a10453494da..69283be91c3f2 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,1933 @@ +# 3.12.1 +`2021-03-08 · 1 🚀 · 2 🐛 · 3 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.23.0` + +### 🚀 Improvements + + +- Close Call contextual bar after starting jitsi call. ([#21004](https://github.com/RocketChat/Rocket.Chat/pull/21004) by [@yash-rajpal](https://github.com/yash-rajpal)) + + After jitsi call is started, if the call is started in a new window then we should close contextual tab bar. + So, when 'YES' is pressed on modal, we call handleClose function if openNewWindow is true, as call doesn't starts on tab bar, it starts on new window. + +### 🐛 Bug fixes + + +- Missing spaces on attachment ([#21020](https://github.com/RocketChat/Rocket.Chat/pull/21020)) + +- Stopping Jitsi reload ([#20973](https://github.com/RocketChat/Rocket.Chat/pull/20973) by [@yash-rajpal](https://github.com/yash-rajpal)) + + The Function where Jitsi call is started gets called many times due to `room.usernames` dep of useMemo, this dep triggers reloading of this function many times. + So removing this dep from useMemo dependencies + +### 👩‍💻👨‍💻 Contributors 😍 + +- [@yash-rajpal](https://github.com/yash-rajpal) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@dougfabris](https://github.com/dougfabris) +- [@tassoevan](https://github.com/tassoevan) + +# 3.12.0 +`2021-02-28 · 5 🎉 · 17 🚀 · 74 🐛 · 30 🔍 · 29 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.23.0` + +### 🎉 New features + + +- Button to unset Slackbridge's importIds ([#20549](https://github.com/RocketChat/Rocket.Chat/pull/20549)) + +- Cloud Workspace bridge ([#20838](https://github.com/RocketChat/Rocket.Chat/pull/20838)) + + Adds the new CloudWorkspace functionality. + + It allows apps to request the access token for the workspace it's installed on, so it can perform actions with other Rocket.Chat services, such as the Omni Gateway. + + https://github.com/RocketChat/Rocket.Chat.Apps-engine/pull/382 + +- Header with Breadcrumbs ([#20609](https://github.com/RocketChat/Rocket.Chat/pull/20609)) + + ![image](https://user-images.githubusercontent.com/27704687/106945019-1386d400-6706-11eb-90db-c12b50f260d5.png) + +- Statistics about language usage ([#20832](https://github.com/RocketChat/Rocket.Chat/pull/20832)) + + track what languages get picked the most as preferred ui language. + +- useUserData Hook ([#20584](https://github.com/RocketChat/Rocket.Chat/pull/20584)) + +### 🚀 Improvements + + +- Add symbol to indicate apps' required settings in the UI ([#20447](https://github.com/RocketChat/Rocket.Chat/pull/20447)) + + - Apps are able to define **required** settings. These settings should not be left blank by the user and an error will be thrown and shown in the interface if an user attempts to save changes in the app details page leaving any required fields blank; + ![prt_screen_required_app_settings_warning](https://user-images.githubusercontent.com/36537004/106032964-e73cd900-60af-11eb-8eab-c11fd651b593.png) + + - A sign (*) is added to the label of app settings' fields that are required so as to highlight the fields which must not be left blank. + ![prt_screen_required_app_settings](https://user-images.githubusercontent.com/36537004/106014879-ae473900-609c-11eb-9b9e-95de7bbf20a5.png) + +- Add visual validation on users admin forms ([#20308](https://github.com/RocketChat/Rocket.Chat/pull/20308)) + +- Added auto-focus for better user-experience. ([#19954](https://github.com/RocketChat/Rocket.Chat/pull/19954) by [@Darshilp326](https://github.com/Darshilp326)) + +- Added disable button check for send invite button ([#20337](https://github.com/RocketChat/Rocket.Chat/pull/20337) by [@yash-rajpal](https://github.com/yash-rajpal)) + + Added Disable check for send invite button. If the text field is empty button would be disabled, and after any valid email is filled, button would get enabled + +- Added key prop, removing unwanted warnings ([#20473](https://github.com/RocketChat/Rocket.Chat/pull/20473) by [@yash-rajpal](https://github.com/yash-rajpal)) + + Removes warnings listed on the issue + +- Added Markdown links to custom status. ([#20470](https://github.com/RocketChat/Rocket.Chat/pull/20470) by [@yash-rajpal](https://github.com/yash-rajpal)) + + Added markdown links to user's custom status. + +- Adds tooltip for sidebar header icons ([#19934](https://github.com/RocketChat/Rocket.Chat/pull/19934) by [@RonLek](https://github.com/RonLek)) + + Previously the header icons in the sidebar didn't show a tooltip when hovered over. This PR fixes that. + + ![Screenshot from 2020-12-22 15-17-41](https://user-images.githubusercontent.com/28918901/102874804-f2756700-4468-11eb-8324-b7f3194e62fe.png) + +- Better Presentation of Blockquotes ([#20750](https://github.com/RocketChat/Rocket.Chat/pull/20750) by [@aditya-mitra](https://github.com/aditya-mitra)) + + Changed the values of `margin-top` and `margin-bottom` for *first* and *last* childs in blockquotes to increase readability. + + ### Before + + ![before](https://user-images.githubusercontent.com/55396651/107858662-3e3a0080-6e5b-11eb-8274-9bd956807235.png) + + ### Now + + ![now](https://user-images.githubusercontent.com/55396651/107858471-480f3400-6e5a-11eb-9ccb-3f1be2fed0a4.png) + +- Change header based on room type ([#20612](https://github.com/RocketChat/Rocket.Chat/pull/20612)) + + It brings more flexibility, allowing us to use different hooks and different components for each header + +- Check Livechat message length through REST API endpoint ([#20366](https://github.com/RocketChat/Rocket.Chat/pull/20366) by [@yash-rajpal](https://github.com/yash-rajpal)) + + Added checks for message length for livechat message api, it shouldn't exceed specified character limit. + +- Customize announcement ([#20793](https://github.com/RocketChat/Rocket.Chat/pull/20793) by [@im-adithya](https://github.com/im-adithya)) + + Included new variables in customizable ones + +- Make message field required in Omnichannel Triggers form ([#20827](https://github.com/RocketChat/Rocket.Chat/pull/20827)) + +- New chat started system message for Omnichannel conversations ([#20814](https://github.com/RocketChat/Rocket.Chat/pull/20814)) + +- Replace react-window for react-virtuoso package ([#20392](https://github.com/RocketChat/Rocket.Chat/pull/20392)) + + Remove: + + - react-window + + - react-window-infinite-loader + + - simplebar-react + + Include: + + - react-virtuoso + + - rc-scrollbars + +- Rewrite Call as React component ([#19778](https://github.com/RocketChat/Rocket.Chat/pull/19778)) + +- Selector for default custom oauth key field ([#20573](https://github.com/RocketChat/Rocket.Chat/pull/20573) by [@paulobernardoaf](https://github.com/paulobernardoaf)) + +- Update rc-scrollbars ([#20733](https://github.com/RocketChat/Rocket.Chat/pull/20733)) + +### 🐛 Bug fixes + + +- - Cancel button on Room Notification don't close contextualBar ([#20237](https://github.com/RocketChat/Rocket.Chat/pull/20237)) + +- Add debouncing to add users search field. ([#20297](https://github.com/RocketChat/Rocket.Chat/pull/20297) by [@Darshilp326](https://github.com/Darshilp326)) + + BEFORE + + https://user-images.githubusercontent.com/55157259/105350722-98a3c080-5c11-11eb-82f3-d9a62a4fa50b.mp4 + + + AFTER + + https://user-images.githubusercontent.com/55157259/105350757-a2c5bf00-5c11-11eb-91db-25c0b9e01a28.mp4 + +- Add tooltips to Thread header buttons ([#20456](https://github.com/RocketChat/Rocket.Chat/pull/20456) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) + + Added tooltips to "Expand" and "Follow Message"/"Unfollow Message" in ThreadView for coherency. + +- Added Bio Structure for UserCard, rendering Skeleton View on loading Instead of [Object][Object] ([#20305](https://github.com/RocketChat/Rocket.Chat/pull/20305) by [@yash-rajpal](https://github.com/yash-rajpal)) + + Added Bio Structure for rendering Skeleton View on loading UserCard. + +- Added check for view admin permission page ([#20403](https://github.com/RocketChat/Rocket.Chat/pull/20403) by [@yash-rajpal](https://github.com/yash-rajpal)) + + Admin Permission page was visible to all, if you add admin/permissions after the base url. This should not be visible to all user, only people with certain permissions should be able to see this page. + I am also able to see permissions page for open workspace of Rocket chat. + ![image](https://user-images.githubusercontent.com/58601732/105829728-bfd00880-5fea-11eb-9121-6c53a752f140.png) + +- Adding the accidentally deleted tag template, used by other templates ([#20772](https://github.com/RocketChat/Rocket.Chat/pull/20772) by [@yash-rajpal](https://github.com/yash-rajpal)) + + Adding back accidentally deleted tag Template. + +- Admin cannot clear user details like bio or nickname ([#20785](https://github.com/RocketChat/Rocket.Chat/pull/20785) by [@yash-rajpal](https://github.com/yash-rajpal)) + + When the API users.update is called to update user data, it passes data to saveUser function. Here before saving data like bio or nickname we are checking if they are available or not. If data is available then we are saving it, but we are not doing anything when data isn't available. + + So unsetting data if data isn't available to save. Will also fix bio and other fields. :) + +- Admin Panel pages not visible in Safari ([#20912](https://github.com/RocketChat/Rocket.Chat/pull/20912)) + +- Announcement with multiple lines fixed. ([#20381](https://github.com/RocketChat/Rocket.Chat/pull/20381) by [@yash-rajpal](https://github.com/yash-rajpal)) + + Announcements with multiple lines used to break UI for announcements bar. Fixed it by replacing all break lines in announcement with empty space (" ") . The announcement modal would work as usual and show all break lines. + +- Atlassian Crowd login with 2FA enabled ([#20834](https://github.com/RocketChat/Rocket.Chat/pull/20834)) + +- Attachment download from title fixed ([#20585](https://github.com/RocketChat/Rocket.Chat/pull/20585) by [@yash-rajpal](https://github.com/yash-rajpal)) + + Added target = '_self' to attachment link, this seems to fix the problem, without this attribute, error page is displayed. + +- Blank Personal Access Token Bug ([#20193](https://github.com/RocketChat/Rocket.Chat/pull/20193) by [@RonLek](https://github.com/RonLek)) + + Adds error when personal access token is blank thereby disallowing the creation of one. + + https://user-images.githubusercontent.com/28918901/104483631-5adde100-55ee-11eb-9938-64146bce127e.mp4 + +- CAS login failing due to TOTP requirement ([#20840](https://github.com/RocketChat/Rocket.Chat/pull/20840)) + +- Changed password input field for password access in edit room info. ([#20356](https://github.com/RocketChat/Rocket.Chat/pull/20356) by [@Darshilp326](https://github.com/Darshilp326)) + + Password field would be secured with asterisks in edit room info + + https://user-images.githubusercontent.com/55157259/105641758-cad04f00-5eab-11eb-90de-0c91263edd55.mp4 + + . + +- Channel mentions showing user subscribed channels twice ([#20484](https://github.com/RocketChat/Rocket.Chat/pull/20484) by [@Darshilp326](https://github.com/Darshilp326)) + + Channel mention shows user subscribed channels twice. + + https://user-images.githubusercontent.com/55157259/106183033-b353d780-61c5-11eb-8aab-1dbb62b02ff8.mp4 + +- CORS config not accepting multiple origins ([#20696](https://github.com/RocketChat/Rocket.Chat/pull/20696)) + + always include only one value in access-control-allow-origin + +- Custom OAuth provider creation from env vars ([#20014](https://github.com/RocketChat/Rocket.Chat/pull/20014) by [@pierreozoux](https://github.com/pierreozoux)) + +- Default Attachments - Remove Extra Margin in Field Attachments ([#20618](https://github.com/RocketChat/Rocket.Chat/pull/20618) by [@aditya-mitra](https://github.com/aditya-mitra)) + + A large amount of unnecessary margin which existed in the **Field Attachments inside the `DefaultAttachments`** has been fixed. + + ### Earlier + + ![earlier](https://user-images.githubusercontent.com/55396651/107056792-ba4b9d00-67f8-11eb-9153-05281416cddb.png) + + ### Now + + ![now](https://user-images.githubusercontent.com/55396651/107057196-3219c780-67f9-11eb-84db-e4a0addfc168.png) + +- Default Attachments - Show Full Attachment.Text with Markdown ([#20606](https://github.com/RocketChat/Rocket.Chat/pull/20606) by [@aditya-mitra](https://github.com/aditya-mitra)) + + Removed truncating of text in `Attachment.Text`. + Added `Attachment.Text` to be parsed to markdown by default. + + ### Earlier + ![earlier](https://user-images.githubusercontent.com/55396651/106910781-92d8cf80-6727-11eb-82ec-818df7544ff0.png) + + ### Now + + ![now](https://user-images.githubusercontent.com/55396651/106910840-a126eb80-6727-11eb-8bd6-d86383dd9181.png) + +- Don't ask again not rendering ([#20745](https://github.com/RocketChat/Rocket.Chat/pull/20745)) + +- Download buttons on desktop app and CDN being ignored ([#20820](https://github.com/RocketChat/Rocket.Chat/pull/20820)) + +- E2E issues ([#20704](https://github.com/RocketChat/Rocket.Chat/pull/20704)) + +- ESLint Warning - react-hooks/exhaustive-deps ([#20586](https://github.com/RocketChat/Rocket.Chat/pull/20586) by [@aditya-mitra](https://github.com/aditya-mitra)) + + Added the required dep (`label`) in `useMemo` to fix eslint warning `react-hooks/exhaustive-deps`. + +- Event emitter warning ([#20663](https://github.com/RocketChat/Rocket.Chat/pull/20663)) + +- External systems not being able to change Omnichannel Inquiry priorities ([#20740](https://github.com/RocketChat/Rocket.Chat/pull/20740)) + + Due to a wrong property name, external applications were not able to change the priority of Omnichannel Inquires. + +- Feedback on bulk invite ([#20339](https://github.com/RocketChat/Rocket.Chat/pull/20339) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) + + Resolved structure where no response was being received. Changed from callback to async/await. + Added error in case of empty submission, or if no valid emails were found. + + https://user-images.githubusercontent.com/38764067/105613964-dfe5a900-5deb-11eb-80f2-21fc8dee57c0.mp4 + +- Filters are not being applied correctly in Omnichannel Current Chats list ([#20320](https://github.com/RocketChat/Rocket.Chat/pull/20320)) + + ### Before + ![image](https://user-images.githubusercontent.com/2493803/105537672-082cb500-5cd1-11eb-8f1b-1726ba60420a.png) + + ### After + ![image](https://user-images.githubusercontent.com/2493803/105537773-2d212800-5cd1-11eb-8746-048deb9502d9.png) + + ![image](https://user-images.githubusercontent.com/2493803/106494728-88090b00-6499-11eb-922e-5386107e2389.png) + + ![image](https://user-images.githubusercontent.com/2493803/106494751-90f9dc80-6499-11eb-901b-5e4dbdc678ba.png) + +- Fix Empty highlighted words field ([#20329](https://github.com/RocketChat/Rocket.Chat/pull/20329) by [@yash-rajpal](https://github.com/yash-rajpal)) + + Able to Empty the highlighted text field in preferences + +- Gif images aspect ratio on preview ([#20654](https://github.com/RocketChat/Rocket.Chat/pull/20654)) + +- height prop on departments agents table ([#20833](https://github.com/RocketChat/Rocket.Chat/pull/20833)) + + ![image](https://user-images.githubusercontent.com/27704687/108572412-fbf83f80-72f0-11eb-801a-5f659000325d.png) + +- Hide system messages not working on second save ([#20679](https://github.com/RocketChat/Rocket.Chat/pull/20679)) + +- Icon for OTR messages ([#20713](https://github.com/RocketChat/Rocket.Chat/pull/20713)) + +- Incorrect display of "Reply in Direct Message" in MessageAction ([#17968](https://github.com/RocketChat/Rocket.Chat/pull/17968) by [@abrom](https://github.com/abrom)) + + [FIX] Incorrect display of "Reply in Direct Message" in MessageAction + +- Increasing unread counter twice for new threads in DMs or with mentions ([#20666](https://github.com/RocketChat/Rocket.Chat/pull/20666)) + + - Unread messages count won't be incremented when the message sent is on a thread (thread count is treated different) + +- Links not opening in new tabs ([#20651](https://github.com/RocketChat/Rocket.Chat/pull/20651)) + +- List of Omnichannel triggers is not listing data ([#20624](https://github.com/RocketChat/Rocket.Chat/pull/20624)) + + ### Before + ![image](https://user-images.githubusercontent.com/2493803/107095379-7308e080-67e7-11eb-8251-7e7ff891087a.png) + + + ### After + ![image](https://user-images.githubusercontent.com/2493803/107095261-3b019d80-67e7-11eb-8425-8612b03ac50a.png) + +- Livechat bridge permission checkers ([#20653](https://github.com/RocketChat/Rocket.Chat/pull/20653)) + + Update to latest patch version of the Apps-Engine with a fix for the Livechat bridge, as seen in https://github.com/RocketChat/Rocket.Chat.Apps-engine/pull/379 + +- Mark messages inside a thread as unread ([#20726](https://github.com/RocketChat/Rocket.Chat/pull/20726) by [@im-adithya](https://github.com/im-adithya)) + + Added threads to mark unread action button. + +- Markdown prop variants ([#20767](https://github.com/RocketChat/Rocket.Chat/pull/20767)) + + A new prop variants on Markdown component: **inline** and **inlineWithoutBreaks** + +- Message payload from `__my_messages__` stream ([#20801](https://github.com/RocketChat/Rocket.Chat/pull/20801)) + +- Missing height on departments agents table ([#20739](https://github.com/RocketChat/Rocket.Chat/pull/20739)) + + ![image](https://user-images.githubusercontent.com/27704687/107807002-510ee100-6d46-11eb-86e9-d65da7ab4129.png) + +- Missing setting to control when to send the ReplyTo field in email notifications ([#20744](https://github.com/RocketChat/Rocket.Chat/pull/20744)) + + - Add a new setting ("Add Reply-To header") in the Email settings' page to control when the Reply-To header is used in e-mail notifications; + + - The new setting is turned off (`false` value) by default. + +- New Integration page was not being displayed ([#20670](https://github.com/RocketChat/Rocket.Chat/pull/20670) by [@yash-rajpal](https://github.com/yash-rajpal)) + +- Notification worker stopping on error ([#20605](https://github.com/RocketChat/Rocket.Chat/pull/20605)) + +- OAuth Login not working on Firefox ([#20722](https://github.com/RocketChat/Rocket.Chat/pull/20722)) + +- Omnichannel agents are unable to access the chat queue on the sidebar ([#20830](https://github.com/RocketChat/Rocket.Chat/pull/20830)) + +- Omnichannel Routing System not assigning chats to Bot agents ([#20662](https://github.com/RocketChat/Rocket.Chat/pull/20662)) + + The `Omnichannel Routing System` is no longer assigning chats to `bot` agents when the `bot` agent is the default agent of the inquiry. + +- Open Visitor Info when omnichannel chat was open ([#20868](https://github.com/RocketChat/Rocket.Chat/pull/20868)) + +- OTR issue ([#20592](https://github.com/RocketChat/Rocket.Chat/pull/20592)) + + Since the users are not being stored at the user collection anymore (thats a good thing actually), there is no such record to to fetch and show the username. + +- Quoted messages from message links when user has no permission ([#20815](https://github.com/RocketChat/Rocket.Chat/pull/20815)) + +- Regenerate token modal on top of 2FA modal ([#20798](https://github.com/RocketChat/Rocket.Chat/pull/20798)) + +- Regular status mutating custom status ([#20613](https://github.com/RocketChat/Rocket.Chat/pull/20613)) + +- Remove duplicate getCommonRoomEvents() event binding for pinnedMessages ([#20179](https://github.com/RocketChat/Rocket.Chat/pull/20179) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) + + The getCommonRoomEvents() returned functions were bound to the pinnedMessages template twice. This was causing some bugs, as detailed in the Issue mentioned below. + +- Remove duplicate getCommonRoomEvents() event binding for starredMessages ([#20185](https://github.com/RocketChat/Rocket.Chat/pull/20185) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) + + The getCommonRoomEvents() returned functions were bound to the starredMessages template twice. This was causing some bugs, as detailed in the Issue mentioned below. + I removed the top events call that only bound the getCommonRoomEvents(). Therefore, only one call for the same is left, which is at the end of the file. Having the events bound just once removes the bugs mentioned. + +- Remove warning problems from console ([#20800](https://github.com/RocketChat/Rocket.Chat/pull/20800)) + +- Removed tooltip in kebab menu options. ([#20498](https://github.com/RocketChat/Rocket.Chat/pull/20498) by [@Darshilp326](https://github.com/Darshilp326)) + + Removed tooltip as it was not needed. + + https://user-images.githubusercontent.com/55157259/106246146-a53ca000-6233-11eb-9874-cbd1b4331bc0.mp4 + +- Retry icon comes out of the div ([#20390](https://github.com/RocketChat/Rocket.Chat/pull/20390) by [@im-adithya](https://github.com/im-adithya)) + + Changed the height of the div container. + +- Room owner not being able to override global retention policy ([#20727](https://github.com/RocketChat/Rocket.Chat/pull/20727)) + + use correct permissions to check if room owner can override global retention policy + +- Room Scroll to Bottom ([#20649](https://github.com/RocketChat/Rocket.Chat/pull/20649)) + +- Room's last message's update date format on IE ([#20680](https://github.com/RocketChat/Rocket.Chat/pull/20680)) + + The proposed change fixes a bug when updates the cached records on Internet Explorer and it breaks the sidebar as shown on the screenshot below: + + ![image](https://user-images.githubusercontent.com/27704687/107578007-f2285b00-6bd1-11eb-9250-1e76ae67f9c9.png) + +- Save user password and email from My Account ([#20737](https://github.com/RocketChat/Rocket.Chat/pull/20737)) + +- Security Hotfix (https://docs.rocket.chat/guides/security/security-updates) + +- Selected hide system messages would now be viewed in vertical bar. ([#20358](https://github.com/RocketChat/Rocket.Chat/pull/20358) by [@Darshilp326](https://github.com/Darshilp326)) + + All selected hide system messages are now in vertical Bar. + + https://user-images.githubusercontent.com/55157259/105642624-d5411780-5eb0-11eb-8848-93e4b02629cb.mp4 + +- Selected messages don't get unselected ([#20408](https://github.com/RocketChat/Rocket.Chat/pull/20408) by [@im-adithya](https://github.com/im-adithya)) + + https://user-images.githubusercontent.com/64399555/105844776-c157fb80-5fff-11eb-90cc-94e9f69649b6.mp4 + +- Sending user to home after logging in from resume token query param ([#20720](https://github.com/RocketChat/Rocket.Chat/pull/20720)) + + Do not redirect to `/home` anymore after logging in with `resumeToken`. + +- Server-side marked parsing ([#20665](https://github.com/RocketChat/Rocket.Chat/pull/20665)) + +- Several Slack Importer issues ([#20216](https://github.com/RocketChat/Rocket.Chat/pull/20216)) + + - Fix: Slack Importer crashes when importing a large users.json file + + - Fix: Slack importer crashes when messages have invalid mentions + + - Skip listing all users on the preparation screen when the user count is too large. + + - Split avatar download into a separate process. + + - Update room's last message when the import is complete. + + - Prevent invalid or duplicated channel names + + - Improve message error handling. + + - Reduce max allowed BSON size to avoid possible issues in some servers. + + - Improve handling of very large channel files. + +- star icon was visible after unstarring a message ([#19645](https://github.com/RocketChat/Rocket.Chat/pull/19645) by [@bhavayAnand9](https://github.com/bhavayAnand9)) + +- Threads Issues ([#20725](https://github.com/RocketChat/Rocket.Chat/pull/20725)) + +- Typo in Message Character Limit ([#20426](https://github.com/RocketChat/Rocket.Chat/pull/20426) by [@aditya-mitra](https://github.com/aditya-mitra)) + + Changed the spelling of *Characther* to *Character* + +- Unset tshow on deleted messages ([#20444](https://github.com/RocketChat/Rocket.Chat/pull/20444) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) + + When setting 'Message_ShowDeletedStatus' is set to true, deleting a message with `tshow: true` causes a bug on the frontend. This issue should, however, never be logically possible as a 'removed' message should not have tshow anyway. Hence, this PR unsets that when the message is set to "Message Removed". + +- Update NPS banner when changing score ([#20611](https://github.com/RocketChat/Rocket.Chat/pull/20611)) + +- User statuses in admin user info panel ([#20341](https://github.com/RocketChat/Rocket.Chat/pull/20341) by [@RonLek](https://github.com/RonLek)) + + Modifies user statuses in admin info panel based on their actual status instead of their `statusConnection`. This enables correct and consistent change in user statuses. + Also, bot users having status as online were classified as offline, with this change they are now correctly classified based on their corresponding statuses. + + https://user-images.githubusercontent.com/28918901/105624438-b8bcc500-5e47-11eb-8d1e-3a4180da1304.mp4 + +- Users autocomplete showing duplicated results ([#20481](https://github.com/RocketChat/Rocket.Chat/pull/20481) by [@Darshilp326](https://github.com/Darshilp326)) + + Added new query for outside room users so that room members are not shown twice. + + https://user-images.githubusercontent.com/55157259/106174582-33c10b00-61bb-11eb-9716-377ef7bba34e.mp4 + +
+🔍 Minor changes + + +- Added toast message after deleting file. ([#20661](https://github.com/RocketChat/Rocket.Chat/pull/20661) by [@Darshilp326](https://github.com/Darshilp326)) + + https://user-images.githubusercontent.com/55157259/107410849-d1a9c380-6b33-11eb-8d10-3d225dc7a9db.mp4 + +- Added types to Emitters ([#20819](https://github.com/RocketChat/Rocket.Chat/pull/20819)) + +- Bump Livechat Widget ([#20843](https://github.com/RocketChat/Rocket.Chat/pull/20843)) + + Update Livechat version to `1.8.0` . + +- Chore: Change error message when marking empty chat as unread ([#20250](https://github.com/RocketChat/Rocket.Chat/pull/20250)) + +- Chore: Disable Sessions Aggregates tests locally ([#20607](https://github.com/RocketChat/Rocket.Chat/pull/20607)) + + Disable Session aggregates tests in local environments + For context, refer to: #20161 + +- Chore: Improve performance of messages’ watcher ([#20519](https://github.com/RocketChat/Rocket.Chat/pull/20519)) + +- Chore: Push correct Docker tag of service images ([#20706](https://github.com/RocketChat/Rocket.Chat/pull/20706)) + +- Chore: Remove node-sprite-generator dependency ([#20545](https://github.com/RocketChat/Rocket.Chat/pull/20545)) + +- Chore: Try building micro services early on CI ([#20046](https://github.com/RocketChat/Rocket.Chat/pull/20046)) + +- Chore: update RC with the latest fuselage-polyfills ([#20709](https://github.com/RocketChat/Rocket.Chat/pull/20709)) + +- Exclude user's own password from /me endpoint ([#20735](https://github.com/RocketChat/Rocket.Chat/pull/20735)) + +- Fix: Add network observe plug to snap ([#20852](https://github.com/RocketChat/Rocket.Chat/pull/20852)) + +- Improve: Add more API tests ([#20738](https://github.com/RocketChat/Rocket.Chat/pull/20738)) + + Add end-to-end tests for untested endpoints. + +- Language update from LingoHub 🤖 on 2021-02-15Z ([#20757](https://github.com/RocketChat/Rocket.Chat/pull/20757)) + +- Language update from LingoHub 🤖 on 2021-02-22Z ([#20853](https://github.com/RocketChat/Rocket.Chat/pull/20853)) + +- Merge master into develop & Set version to 3.12.0-develop ([#20533](https://github.com/RocketChat/Rocket.Chat/pull/20533)) + +- Mixed client and server code on Storybook ([#20799](https://github.com/RocketChat/Rocket.Chat/pull/20799)) + + For Storybook to work, we've mocked all modules under `**/server/`, thus making them suitable to hold all code that refers Node.js modules. This implies some duplication, between `client/` and `server/` modules, mediated by modules under `libs/`. + +- Regression: Discussions inside direct messages not rendering ([#20652](https://github.com/RocketChat/Rocket.Chat/pull/20652)) + +- Regression: Fix loadHistory method being called multiple times ([#20826](https://github.com/RocketChat/Rocket.Chat/pull/20826)) + +- Regression: Fix notification worker not firing ([#20829](https://github.com/RocketChat/Rocket.Chat/pull/20829)) + +- Regression: Fix scopes not being provided to getWorkspaceAccessToken ([#20871](https://github.com/RocketChat/Rocket.Chat/pull/20871)) + +- Regression: Header Styles ([#20616](https://github.com/RocketChat/Rocket.Chat/pull/20616)) + +- Regression: Keep user custom status after change presence ([#20869](https://github.com/RocketChat/Rocket.Chat/pull/20869)) + +- Regression: Messages not being encrypted E2E ([#20922](https://github.com/RocketChat/Rocket.Chat/pull/20922)) + +- Regression: Prevent Message Attachment rendering ([#20860](https://github.com/RocketChat/Rocket.Chat/pull/20860)) + +- Remove `uiKitText` reference ([#20625](https://github.com/RocketChat/Rocket.Chat/pull/20625)) + +- Rewrite: CreateChannel modal component ([#20617](https://github.com/RocketChat/Rocket.Chat/pull/20617)) + + ![image](https://user-images.githubusercontent.com/17487063/107058434-5f438700-67b3-11eb-8cf2-1ad3d5008aa8.png) + +- RoomFiles hook ([#20550](https://github.com/RocketChat/Rocket.Chat/pull/20550)) + +- Update Apps-Engine version ([#20921](https://github.com/RocketChat/Rocket.Chat/pull/20921)) + + Update the Apps-Engine to latest version for the release. + +- Wrong method used while starring ([#20508](https://github.com/RocketChat/Rocket.Chat/pull/20508) by [@im-adithya](https://github.com/im-adithya)) + + Changed the method from pinMessage to starMessage + +
+ +### 👩‍💻👨‍💻 Contributors 😍 + +- [@Darshilp326](https://github.com/Darshilp326) +- [@RonLek](https://github.com/RonLek) +- [@aKn1ghtOut](https://github.com/aKn1ghtOut) +- [@abrom](https://github.com/abrom) +- [@aditya-mitra](https://github.com/aditya-mitra) +- [@bhavayAnand9](https://github.com/bhavayAnand9) +- [@im-adithya](https://github.com/im-adithya) +- [@paulobernardoaf](https://github.com/paulobernardoaf) +- [@pierreozoux](https://github.com/pierreozoux) +- [@yash-rajpal](https://github.com/yash-rajpal) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@KevLehman](https://github.com/KevLehman) +- [@MartinSchoeler](https://github.com/MartinSchoeler) +- [@d-gubert](https://github.com/d-gubert) +- [@dougfabris](https://github.com/dougfabris) +- [@g-thome](https://github.com/g-thome) +- [@gabriellsh](https://github.com/gabriellsh) +- [@geekgonecrazy](https://github.com/geekgonecrazy) +- [@ggazzo](https://github.com/ggazzo) +- [@lolimay](https://github.com/lolimay) +- [@lucassartor](https://github.com/lucassartor) +- [@matheusbsilva137](https://github.com/matheusbsilva137) +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) +- [@r0zbot](https://github.com/r0zbot) +- [@rafaelblink](https://github.com/rafaelblink) +- [@renatobecker](https://github.com/renatobecker) +- [@rodrigok](https://github.com/rodrigok) +- [@sampaiodiego](https://github.com/sampaiodiego) +- [@tassoevan](https://github.com/tassoevan) +- [@tiagoevanp](https://github.com/tiagoevanp) + +# 3.11.2 +`2021-02-28 · 3 🐛 · 3 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.22.2` + +### 🐛 Bug fixes + + +- External systems not being able to change Omnichannel Inquiry priorities ([#20740](https://github.com/RocketChat/Rocket.Chat/pull/20740)) + + Due to a wrong property name, external applications were not able to change the priority of Omnichannel Inquires. + +- Prevent Message Attachment rendering ([#20860](https://github.com/RocketChat/Rocket.Chat/pull/20860)) + +- Room owner not being able to override global retention policy ([#20727](https://github.com/RocketChat/Rocket.Chat/pull/20727)) + + use correct permissions to check if room owner can override global retention policy + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@g-thome](https://github.com/g-thome) +- [@ggazzo](https://github.com/ggazzo) +- [@renatobecker](https://github.com/renatobecker) + +# 3.11.1 +`2021-02-10 · 5 🐛 · 6 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.22.2` + +### 🐛 Bug fixes + + +- Attachment download from title fixed ([#20585](https://github.com/RocketChat/Rocket.Chat/pull/20585) by [@yash-rajpal](https://github.com/yash-rajpal)) + + Added target = '_self' to attachment link, this seems to fix the problem, without this attribute, error page is displayed. + +- Gif images aspect ratio on preview ([#20654](https://github.com/RocketChat/Rocket.Chat/pull/20654)) + +- Livechat bridge permission checkers ([#20653](https://github.com/RocketChat/Rocket.Chat/pull/20653)) + + Update to latest patch version of the Apps-Engine with a fix for the Livechat bridge, as seen in https://github.com/RocketChat/Rocket.Chat.Apps-engine/pull/379 + +- Omnichannel Routing System not assigning chats to Bot agents ([#20662](https://github.com/RocketChat/Rocket.Chat/pull/20662)) + + The `Omnichannel Routing System` is no longer assigning chats to `bot` agents when the `bot` agent is the default agent of the inquiry. + +- Update NPS banner when changing score ([#20611](https://github.com/RocketChat/Rocket.Chat/pull/20611)) + +### 👩‍💻👨‍💻 Contributors 😍 + +- [@yash-rajpal](https://github.com/yash-rajpal) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@d-gubert](https://github.com/d-gubert) +- [@lolimay](https://github.com/lolimay) +- [@renatobecker](https://github.com/renatobecker) +- [@sampaiodiego](https://github.com/sampaiodiego) +- [@tiagoevanp](https://github.com/tiagoevanp) + +# 3.11.0 +`2021-01-31 · 8 🎉 · 9 🚀 · 52 🐛 · 44 🔍 · 32 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.22.1` + +### 🎉 New features + + +- **Apps:** Apps Permission System ([#20078](https://github.com/RocketChat/Rocket.Chat/pull/20078)) + +- **Apps:** IPreFileUpload event ([#20285](https://github.com/RocketChat/Rocket.Chat/pull/20285)) + +- **ENTERPRISE:** Automatic transfer of unanswered conversations to another agent ([#20090](https://github.com/RocketChat/Rocket.Chat/pull/20090)) + +- **ENTERPRISE:** Omnichannel Contact Manager as preferred agent for routing ([#20244](https://github.com/RocketChat/Rocket.Chat/pull/20244)) + + If the `Contact-Manager` is assigned to a Visitor, the chat will automatically get transferred to the respective Contact-Manager, provided the Contact-Manager is online. In-case the Contact-Manager is offline, the chat will be transferred to any other online agent. + We have provided a setting to control this auto-assignment feature + ![image](https://user-images.githubusercontent.com/34130764/104880961-8104d780-5986-11eb-9d87-82b99814b028.png) + + Behavior based-on Routing method + + + 1. Auto-selection, Load-Balancing, or External Service (`autoAssignAgent = true`) + This is straightforward, + - if the Contact-manager is online, the chat will be transferred to the Contact-Manger only + - if the Contact-manager is offline, the chat will be transferred to any other online-agent based on the Routing system + + 2. Manual-selection (`autoAssignAgent = false`) + - If the Contact-Manager is online, the chat will appear in the Queue of Contact-Manager **ONLY** + - If the Contact-Manager is offline, the chat will appear in the Queue of all related Agents/Manager ( like it's done right now ) + +- Banner system and NPS ([#20221](https://github.com/RocketChat/Rocket.Chat/pull/20221)) + + More robust and scalable banner system for alerting users. + +- Email Inboxes for Omnichannel ([#20101](https://github.com/RocketChat/Rocket.Chat/pull/20101)) + + With this new feature, email accounts will receive email messages(threads) which will be transformed into Omnichannel chats. It'll be possible to set up multiple email accounts, test the connection with email server(email provider) and define the behaviour of each account. + + https://user-images.githubusercontent.com/2493803/105430398-242d4980-5c32-11eb-835a-450c94837d23.mp4 + + ### New item on admin menu + + ![image](https://user-images.githubusercontent.com/2493803/105428723-bc293400-5c2e-11eb-8c02-e8d36ea82726.png) + + + ### Send test email tooltip + + ![image](https://user-images.githubusercontent.com/2493803/104366986-eaa16380-54f8-11eb-9ba7-831cfde2319c.png) + + + ### Inbox Info + + ![image](https://user-images.githubusercontent.com/2493803/104366796-ab731280-54f8-11eb-9941-a3cc8eb610e1.png) + + ### SMTP Info + + ![image](https://user-images.githubusercontent.com/2493803/104366868-c47bc380-54f8-11eb-969e-ccc29070957c.png) + + ### IMAP Info + + ![image](https://user-images.githubusercontent.com/2493803/104366897-cd6c9500-54f8-11eb-80c4-97d5b0c002d5.png) + + ### Messages + + ![image](https://user-images.githubusercontent.com/2493803/105428971-45d90180-5c2f-11eb-992a-022a3df94471.png) + +- Encrypted Discussions and new Encryption Permissions ([#20201](https://github.com/RocketChat/Rocket.Chat/pull/20201)) + +- Server Info page ([#19517](https://github.com/RocketChat/Rocket.Chat/pull/19517)) + +### 🚀 Improvements + + +- Add extra SAML settings to update room subs and add private room subs. ([#19489](https://github.com/RocketChat/Rocket.Chat/pull/19489) by [@tlskinneriv](https://github.com/tlskinneriv)) + + Added a SAML setting to support updating room subscriptions each time a user logs in via SAML. + Added a SAML setting to support including private rooms in SAML updated subscriptions (whether initial or on each logon). + +- Autofocus on directory ([#20509](https://github.com/RocketChat/Rocket.Chat/pull/20509)) + +- Don't use global search by default ([#19777](https://github.com/RocketChat/Rocket.Chat/pull/19777) by [@i-kychukov](https://github.com/i-kychukov) & [@ikyuchukov](https://github.com/ikyuchukov)) + + Global chat search is not set by default now. + +- Message Collection Hooks ([#20121](https://github.com/RocketChat/Rocket.Chat/pull/20121)) + + Integrating a list of messages into a React component imposes some challenges. Its content is provided by some REST API calls and live-updated by streamer events. To avoid too much coupling with React Hooks, the structures `RecordList`, `MessageList` and their derivatives are simple event emitters created and connected on components via some simple hooks, like `useThreadsList()` and `useRecordList()`. + +- Rewrite Announcement as React component ([#20172](https://github.com/RocketChat/Rocket.Chat/pull/20172)) + +- Rewrite Prune Messages as React component ([#19900](https://github.com/RocketChat/Rocket.Chat/pull/19900)) + +- Rewrite User Dropdown and Kebab menu. ([#20070](https://github.com/RocketChat/Rocket.Chat/pull/20070)) + + ![image](https://user-images.githubusercontent.com/40830821/103699786-3a74ad80-4f82-11eb-913e-2e09d5f7eac6.png) + +- Title for user avatar buttons ([#20083](https://github.com/RocketChat/Rocket.Chat/pull/20083) by [@sushant52](https://github.com/sushant52)) + + Made user avatar change buttons to be descriptive of what they do. + +- Tooltip added for Kebab menu on chat header ([#20116](https://github.com/RocketChat/Rocket.Chat/pull/20116) by [@yash-rajpal](https://github.com/yash-rajpal)) + + Added the missing Tooltip for kebab menu on chat header. + ![tooltip after](https://user-images.githubusercontent.com/58601732/104031406-b07f4b80-51f2-11eb-87a4-1e8da78a254f.gif) + +### 🐛 Bug fixes + + +- "Open_thread" English tooltip correction ([#20164](https://github.com/RocketChat/Rocket.Chat/pull/20164) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) + + Remove unnecessary spaces from the translation key, and added English translation value for the key. + +- **Apps:** Don't show the "review permissions" modal when there's none to review ([#20506](https://github.com/RocketChat/Rocket.Chat/pull/20506)) + +- **ENTERPRISE:** Auditing RoomAutocomplete ([#20311](https://github.com/RocketChat/Rocket.Chat/pull/20311)) + +- **ENTERPRISE:** Omnichannel custom fields not storing additional form values ([#19953](https://github.com/RocketChat/Rocket.Chat/pull/19953)) + +- Actions from User Info panel ([#20073](https://github.com/RocketChat/Rocket.Chat/pull/20073) by [@Darshilp326](https://github.com/Darshilp326)) + + Users can be removed from channels without any error message. + +- Added context check for closing active tabbar for member-list ([#20228](https://github.com/RocketChat/Rocket.Chat/pull/20228) by [@yash-rajpal](https://github.com/yash-rajpal)) + + When we click on a username and then click on see user's full profile, a tab gets active and shows us the user's profile, the problem occurs when the tab is still active and we try to see another user's profile. In this case, tabbar gets closed. + To resolve this, added context check for closing action of active tabbar. + +- Added Margin between status bullet and status label ([#20199](https://github.com/RocketChat/Rocket.Chat/pull/20199) by [@yash-rajpal](https://github.com/yash-rajpal)) + + Added Margins between status bullet and status label + +- Added success message on saving notification preference. ([#20220](https://github.com/RocketChat/Rocket.Chat/pull/20220) by [@Darshilp326](https://github.com/Darshilp326)) + + Added success message after saving notification preferences. + + https://user-images.githubusercontent.com/55157259/104774617-03ca3e80-579d-11eb-8fa4-990b108dd8d9.mp4 + +- Admin User Info email verified status ([#20110](https://github.com/RocketChat/Rocket.Chat/pull/20110) by [@bdelwood](https://github.com/bdelwood)) + +- Agent information panel not rendering ([#19965](https://github.com/RocketChat/Rocket.Chat/pull/19965)) + +- Change header's favorite icon to filled star ([#20174](https://github.com/RocketChat/Rocket.Chat/pull/20174)) + + ### Before: + ![image](https://user-images.githubusercontent.com/27704687/104351819-a60bcd00-54e4-11eb-8b43-7d281a6e5dcb.png) + + ### After: + ![image](https://user-images.githubusercontent.com/27704687/104351632-67761280-54e4-11eb-87ba-25b940494bb5.png) + +- Changed success message for adding custom sound. ([#20272](https://github.com/RocketChat/Rocket.Chat/pull/20272) by [@Darshilp326](https://github.com/Darshilp326)) + + https://user-images.githubusercontent.com/55157259/105151351-daf2d200-5b2b-11eb-8223-eae5d60f770d.mp4 + +- Changed success message for ignoring member. ([#19996](https://github.com/RocketChat/Rocket.Chat/pull/19996) by [@Darshilp326](https://github.com/Darshilp326)) + + Different messages for ignoring/unignoring will be displayed. + + https://user-images.githubusercontent.com/55157259/103310307-4241c880-4a3d-11eb-8c6c-4c9b99d023db.mp4 + +- Creation of Omnichannel rooms not working correctly through the Apps when the agent parameter is set ([#19997](https://github.com/RocketChat/Rocket.Chat/pull/19997)) + +- Engagement dashboard graphs labels superposing each other ([#20267](https://github.com/RocketChat/Rocket.Chat/pull/20267)) + + Now after a certain breakpoint, the graphs should stack vertically, and overlapping text rotated. + + ![image](https://user-images.githubusercontent.com/40830821/105098926-93b40500-5a89-11eb-9a56-2fc3b1552914.png) + +- Fields overflowing page ([#20287](https://github.com/RocketChat/Rocket.Chat/pull/20287)) + + ### Before + ![image](https://user-images.githubusercontent.com/40830821/105246952-c1b14c00-5b52-11eb-8671-cff88edf242d.png) + + ### After + ![image](https://user-images.githubusercontent.com/40830821/105247125-0a690500-5b53-11eb-9f3c-d6a68108e336.png) + +- Fix error that occurs on changing archive status of room ([#20098](https://github.com/RocketChat/Rocket.Chat/pull/20098) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) + + This PR fixes an issue that happens when you try to edit the info of a room, and save changes after changing the value of "Archived". The archive functionality is handled separately from other room settings. The archived key is not used in the saveRoomSettings method but was still being sent over. Hence, the request was being considered invalid. I deleted the "archived" key from the data being sent in the request, making the request valid again. + +- Incorrect translations ZN ([#20245](https://github.com/RocketChat/Rocket.Chat/pull/20245) by [@moniang](https://github.com/moniang)) + +- Initial values update on Account Preferences ([#19938](https://github.com/RocketChat/Rocket.Chat/pull/19938)) + +- Invalid filters on the Omnichannel Analytics page ([#19899](https://github.com/RocketChat/Rocket.Chat/pull/19899)) + +- Jump to message ([#20265](https://github.com/RocketChat/Rocket.Chat/pull/20265)) + +- Livechat.RegisterGuest method removing unset fields ([#20124](https://github.com/RocketChat/Rocket.Chat/pull/20124)) + + After changes made on https://github.com/RocketChat/Rocket.Chat/pull/19931, the `Livechat.RegisterGuest` method started removing properties from the visitor inappropriately. The properties that did not receive value were removed from the object. + Those changes were made to support the new Contact Form, but now the form has its own method to deal with Contact data so those changes are no longer necessary. + +- Markdown added for Header Room topic ([#20021](https://github.com/RocketChat/Rocket.Chat/pull/20021) by [@yash-rajpal](https://github.com/yash-rajpal)) + + With the new 3.10.0 version update the Links in topic section below room name were not working, for more info refer issue #20018 + +- Messages being updated when not required after user changes his profile ([#20114](https://github.com/RocketChat/Rocket.Chat/pull/20114)) + +- Meteor errors not translating for toast messages ([#19993](https://github.com/RocketChat/Rocket.Chat/pull/19993)) + +- minWidth in FileIcon to prevent layout to broke ([#19942](https://github.com/RocketChat/Rocket.Chat/pull/19942)) + + ![image](https://user-images.githubusercontent.com/27704687/102934691-69b7f480-4483-11eb-995b-a8a9b72246aa.png) + +- Normalize messages for users in endpoint chat.getStarredMessages ([#19962](https://github.com/RocketChat/Rocket.Chat/pull/19962)) + +- OAuth users being asked to change password on second login ([#20003](https://github.com/RocketChat/Rocket.Chat/pull/20003)) + +- Omnichannel - Contact Center form is not validating custom fields properly ([#20196](https://github.com/RocketChat/Rocket.Chat/pull/20196)) + + The contact form is accepting undefined values in required custom fields when creating or editing contacts, and, the errror message isn't following Rocket.chat design system. + + ### Before + ![image](https://user-images.githubusercontent.com/2493803/104522668-31688980-55dd-11eb-92c5-83f96073edc4.png) + + ### After + + #### New + ![image](https://user-images.githubusercontent.com/2493803/104770494-68f74300-574f-11eb-94a3-c8fd73365308.png) + + + #### Edit + ![image](https://user-images.githubusercontent.com/2493803/104770538-7b717c80-574f-11eb-829f-1ae304103369.png) + +- Omnichannel Agents unable to take new chats in the queue ([#20022](https://github.com/RocketChat/Rocket.Chat/pull/20022)) + +- Omnichannel Business Hours form is not being rendered ([#20007](https://github.com/RocketChat/Rocket.Chat/pull/20007)) + +- Omnichannel raw model importing meteor dependency ([#20093](https://github.com/RocketChat/Rocket.Chat/pull/20093)) + +- Omnichannel rooms breaking after return to queue or forward ([#20089](https://github.com/RocketChat/Rocket.Chat/pull/20089)) + +- Profile picture changing with username ([#19992](https://github.com/RocketChat/Rocket.Chat/pull/19992)) + + ![bug avatar](https://user-images.githubusercontent.com/40830821/103305935-24e40e80-49eb-11eb-9e35-9bd4c167898a.gif) + +- Remove duplicate blaze events call for EmojiActions from roomOld ([#20159](https://github.com/RocketChat/Rocket.Chat/pull/20159) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) + + A few methods concerning Emojis are bound multiple times to the DOM using the Template events() call, once in the reactions init.js and the other time after they get exported from app/ui/client/views/app/lib/getCommonRoomEvents.js to whatever page binds all the functions. The getCommonRoomEvents methods are always bound, hence negating a need to bind in a lower-level component. + +- Room special name in prompts ([#20277](https://github.com/RocketChat/Rocket.Chat/pull/20277) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) + + The "Hide room" and "Leave Room" confirmation prompts use the "name" key from the room info. When the setting " + Allow Special Characters in Room Names" is enabled, the prompts show the normalized names instead of those that contain the special characters. + + Changed the value being used from name to fname, which always has the user-set name. + + Previous: + ![Screenshot from 2021-01-20 15-52-29](https://user-images.githubusercontent.com/38764067/105161642-9b31e780-5b37-11eb-8b0c-ec4b1414c948.png) + + Updated: + ![Screenshot from 2021-01-20 15-50-19](https://user-images.githubusercontent.com/38764067/105161627-966d3380-5b37-11eb-9812-3dd9352b4f95.png) + +- Room's list showing all rooms with same name ([#20176](https://github.com/RocketChat/Rocket.Chat/pull/20176)) + + Add a migration to fix the room's list for those who ran version 3.10.1 and got it scrambled when a new user was registered. + +- RoomManager validation broken on IE ([#20490](https://github.com/RocketChat/Rocket.Chat/pull/20490)) + +- Saving with blank email in edit user ([#20259](https://github.com/RocketChat/Rocket.Chat/pull/20259) by [@RonLek](https://github.com/RonLek)) + + Disallows showing a success popup when email field is made blank in Edit User and instead shows the relevant error popup. + + + https://user-images.githubusercontent.com/28918901/104960749-dbd81680-59fa-11eb-9c7b-2b257936f894.mp4 + +- Search list filter ([#19937](https://github.com/RocketChat/Rocket.Chat/pull/19937)) + +- Sidebar palette color broken on IE ([#20457](https://github.com/RocketChat/Rocket.Chat/pull/20457)) + + ![image](https://user-images.githubusercontent.com/27704687/106056093-0a29b600-60cd-11eb-8038-eabbc0d8fb03.png) + +- Status circle in profile section ([#20016](https://github.com/RocketChat/Rocket.Chat/pull/20016) by [@yash-rajpal](https://github.com/yash-rajpal)) + + The Status Circle in status message text input is now centered vertically. + +- Tabbar is opened ([#20122](https://github.com/RocketChat/Rocket.Chat/pull/20122)) + +- Translate keyword for 'Showing results of' in tables ([#20134](https://github.com/RocketChat/Rocket.Chat/pull/20134) by [@Karting06](https://github.com/Karting06)) + + Change translation keyword in order to allow the translation of `Showing results %s - %s of %s` in tables. + +- Unable to reset password by Email if upper case character is pr… ([#19643](https://github.com/RocketChat/Rocket.Chat/pull/19643) by [@bhavayAnand9](https://github.com/bhavayAnand9)) + +- User Audio notification preference not being applied ([#20061](https://github.com/RocketChat/Rocket.Chat/pull/20061)) + +- User info 'Full Name' translation keyword ([#20028](https://github.com/RocketChat/Rocket.Chat/pull/20028) by [@Karting06](https://github.com/Karting06)) + + Fix the `Full Name` translation keyword, so that it can be translated. + +- User registration updating wrong subscriptions ([#20128](https://github.com/RocketChat/Rocket.Chat/pull/20128)) + +- Video call message not translated ([#18722](https://github.com/RocketChat/Rocket.Chat/pull/18722)) + + Fixed video call message not translated. + +- ViewLogs title translation keyword ([#20029](https://github.com/RocketChat/Rocket.Chat/pull/20029) by [@Karting06](https://github.com/Karting06)) + + Fix `View Logs` title translation keyword to enable translation of the title + +- White screen after 2FA code entered ([#20225](https://github.com/RocketChat/Rocket.Chat/pull/20225) by [@wggdeveloper](https://github.com/wggdeveloper)) + +- Wrong userId when open own user profile ([#20181](https://github.com/RocketChat/Rocket.Chat/pull/20181)) + +
+🔍 Minor changes + + +- Add translation of Edit Status in all languages ([#19916](https://github.com/RocketChat/Rocket.Chat/pull/19916) by [@sushant52](https://github.com/sushant52)) + + Closes [#19915](https://github.com/RocketChat/Rocket.Chat/issues/19915) + The profile options menu is well translated in many languages. However, Edit Status is the only button which is not well translated. With this change, the whole profile options will be properly translated in a lot of languages. + +- Bump axios from 0.18.0 to 0.18.1 ([#20055](https://github.com/RocketChat/Rocket.Chat/pull/20055) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Chore: Add tests for the api/licenses.* endpoints ([#20041](https://github.com/RocketChat/Rocket.Chat/pull/20041)) + + Adding api tests for the new `licenses.*` endpoints (`licenses.get` and `licenses.add`) + +- Chore: add tests to api/instances.get endpoint ([#19988](https://github.com/RocketChat/Rocket.Chat/pull/19988)) + +- Chore: Change console.warning() to console.warn() ([#20200](https://github.com/RocketChat/Rocket.Chat/pull/20200)) + +- chore: Change return button ([#20045](https://github.com/RocketChat/Rocket.Chat/pull/20045)) + +- Chore: Fix i18n duplicated keys ([#19998](https://github.com/RocketChat/Rocket.Chat/pull/19998)) + +- Chore: Recover and update Storybook ([#20047](https://github.com/RocketChat/Rocket.Chat/pull/20047)) + + It reenables Storybook's usage. + +- Language update from LingoHub 🤖 on 2020-12-30Z ([#20013](https://github.com/RocketChat/Rocket.Chat/pull/20013)) + +- Language update from LingoHub 🤖 on 2021-01-04Z ([#20034](https://github.com/RocketChat/Rocket.Chat/pull/20034)) + +- Language update from LingoHub 🤖 on 2021-01-11Z ([#20146](https://github.com/RocketChat/Rocket.Chat/pull/20146)) + +- Language update from LingoHub 🤖 on 2021-01-18Z ([#20246](https://github.com/RocketChat/Rocket.Chat/pull/20246)) + +- Regression: Add tests to new banners REST endpoints ([#20492](https://github.com/RocketChat/Rocket.Chat/pull/20492)) + + Add tests for the new `banners.*` endpoints: `banners.getNew` and `banners.dismiss`. + +- Regression: Announcement bar not showing properly Markdown content ([#20290](https://github.com/RocketChat/Rocket.Chat/pull/20290)) + + **Before**: + ![image](https://user-images.githubusercontent.com/27704687/105273746-a4907380-5b7a-11eb-8121-aff665251c44.png) + + **After**: + ![image](https://user-images.githubusercontent.com/27704687/105274050-2e404100-5b7b-11eb-93b2-b6282a7bed95.png) + +- regression: Announcement link open in new tab ([#20435](https://github.com/RocketChat/Rocket.Chat/pull/20435)) + +- Regression: Apps-Engine - Convert streams to buffers on file upload ([#20523](https://github.com/RocketChat/Rocket.Chat/pull/20523)) + + This is an implementation to accommodate the changes in API for the `IPreFileUpload` hook in the Apps-Engine. Explanation on the reasoning for it is here https://github.com/RocketChat/Rocket.Chat.Apps-engine/pull/376 + +- Regression: Attachments ([#20291](https://github.com/RocketChat/Rocket.Chat/pull/20291)) + +- Regression: Bio page not rendering ([#20450](https://github.com/RocketChat/Rocket.Chat/pull/20450)) + +- Regression: Change sort icon ([#20177](https://github.com/RocketChat/Rocket.Chat/pull/20177)) + + ### Before + ![image](https://user-images.githubusercontent.com/40830821/104366414-1bcd6400-54f8-11eb-9fc7-c6f13f07a61e.png) + + ### After + ![image](https://user-images.githubusercontent.com/40830821/104366542-4cad9900-54f8-11eb-83ca-acb99899515a.png) + +- Regression: Custom field labels are not displayed properly on Omnichannel Contact Profile form ([#20393](https://github.com/RocketChat/Rocket.Chat/pull/20393)) + + ### Before + ![image](https://user-images.githubusercontent.com/2493803/105780399-20116c80-5f4f-11eb-9620-0901472e453b.png) + + ![image](https://user-images.githubusercontent.com/2493803/105780420-2e5f8880-5f4f-11eb-8e93-8115ebc685be.png) + + ### After + + ![image](https://user-images.githubusercontent.com/2493803/105780832-1ccab080-5f50-11eb-8042-188dd0c41904.png) + + ![image](https://user-images.githubusercontent.com/2493803/105780911-500d3f80-5f50-11eb-96e0-7df3f179dbd5.png) + +- Regression: ESLint Warning - explicit-function-return-type ([#20434](https://github.com/RocketChat/Rocket.Chat/pull/20434) by [@aditya-mitra](https://github.com/aditya-mitra)) + + Added explicit Return Type (Promise) on the function to fix eslint warning (`explicit-function-return-type`) + +- Regression: Fix banners sync data types ([#20517](https://github.com/RocketChat/Rocket.Chat/pull/20517)) + +- Regression: Fix Cron statistics TypeError ([#20343](https://github.com/RocketChat/Rocket.Chat/pull/20343) by [@RonLek](https://github.com/RonLek)) + +- Regression: Fix duplicate email messages in multiple instances ([#20495](https://github.com/RocketChat/Rocket.Chat/pull/20495)) + +- Regression: Fix e2e paused state ([#20511](https://github.com/RocketChat/Rocket.Chat/pull/20511)) + +- Regression: Fixed update room avatar issue. ([#20433](https://github.com/RocketChat/Rocket.Chat/pull/20433) by [@Darshilp326](https://github.com/Darshilp326)) + + Users can now update their room avatar without any error. + + https://user-images.githubusercontent.com/55157259/105951602-560d3880-6096-11eb-97a5-b5eb9a28b58d.mp4 + +- Regression: Info Page Icon style and usage graph breaking ([#20180](https://github.com/RocketChat/Rocket.Chat/pull/20180)) + +- Regression: Lint warnings and some datepicker ([#20280](https://github.com/RocketChat/Rocket.Chat/pull/20280)) + +- Regression: NPS ([#20514](https://github.com/RocketChat/Rocket.Chat/pull/20514)) + +- Regression: reactAttachments cpu ([#20255](https://github.com/RocketChat/Rocket.Chat/pull/20255)) + +- Regression: Room not scrolling to bottom ([#20516](https://github.com/RocketChat/Rocket.Chat/pull/20516)) + +- Regression: Set image sizes based on rotation ([#20531](https://github.com/RocketChat/Rocket.Chat/pull/20531)) + +- Regression: Unread superposing announcement. ([#20306](https://github.com/RocketChat/Rocket.Chat/pull/20306)) + + ### Before + ![image](https://user-images.githubusercontent.com/40830821/105412619-c2f67d80-5c13-11eb-8204-5932ea880c8a.png) + + + ### After + ![image](https://user-images.githubusercontent.com/40830821/105411176-d1439a00-5c11-11eb-8d1b-ea27c8485214.png) + +- Regression: User Dropdown margin ([#20222](https://github.com/RocketChat/Rocket.Chat/pull/20222)) + +- Rewrite : Message Thread metrics ([#20051](https://github.com/RocketChat/Rocket.Chat/pull/20051)) + + ![image](https://user-images.githubusercontent.com/5263975/103585504-e904e980-4ec1-11eb-8d8c-3113ac812ead.png) + +- Rewrite Broadcast ([#20119](https://github.com/RocketChat/Rocket.Chat/pull/20119)) + + ![image](https://user-images.githubusercontent.com/5263975/104035912-7fcaf200-51b1-11eb-91df-228c23d97448.png) + +- Rewrite Discussion Metric ([#20117](https://github.com/RocketChat/Rocket.Chat/pull/20117)) + + https://user-images.githubusercontent.com/5263975/104031909-23190880-51ac-11eb-93dd-5d4b5295886d.mp4 + +- Rewrite Message action links ([#20123](https://github.com/RocketChat/Rocket.Chat/pull/20123)) + +- Rewrite: Message Attachments ([#20106](https://github.com/RocketChat/Rocket.Chat/pull/20106)) + + ![image](https://user-images.githubusercontent.com/5263975/104783709-69023d80-5765-11eb-968f-a2b93fdfb51e.png) + +- Security sync ([#20430](https://github.com/RocketChat/Rocket.Chat/pull/20430)) + +- Update "Industry" setting ([#20510](https://github.com/RocketChat/Rocket.Chat/pull/20510)) + +- Update Apps-Engine and permissions translations ([#20491](https://github.com/RocketChat/Rocket.Chat/pull/20491)) + + Update Apps-Engine version and apply changes in translations for the changed permissions. Please review the texts on the translation files to make sure they're clear. + +- Update Apps-Engine version ([#20482](https://github.com/RocketChat/Rocket.Chat/pull/20482)) + + Update Apps-Engine version with some fixes for the current RC cycle. + +- Update password policy English translation ([#20118](https://github.com/RocketChat/Rocket.Chat/pull/20118) by [@zdumitru](https://github.com/zdumitru)) + +
+ +### 👩‍💻👨‍💻 Contributors 😍 + +- [@Darshilp326](https://github.com/Darshilp326) +- [@Karting06](https://github.com/Karting06) +- [@RonLek](https://github.com/RonLek) +- [@aKn1ghtOut](https://github.com/aKn1ghtOut) +- [@aditya-mitra](https://github.com/aditya-mitra) +- [@bdelwood](https://github.com/bdelwood) +- [@bhavayAnand9](https://github.com/bhavayAnand9) +- [@dependabot[bot]](https://github.com/dependabot[bot]) +- [@i-kychukov](https://github.com/i-kychukov) +- [@ikyuchukov](https://github.com/ikyuchukov) +- [@moniang](https://github.com/moniang) +- [@sushant52](https://github.com/sushant52) +- [@tlskinneriv](https://github.com/tlskinneriv) +- [@wggdeveloper](https://github.com/wggdeveloper) +- [@yash-rajpal](https://github.com/yash-rajpal) +- [@zdumitru](https://github.com/zdumitru) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@MartinSchoeler](https://github.com/MartinSchoeler) +- [@d-gubert](https://github.com/d-gubert) +- [@dougfabris](https://github.com/dougfabris) +- [@gabriellsh](https://github.com/gabriellsh) +- [@ggazzo](https://github.com/ggazzo) +- [@lolimay](https://github.com/lolimay) +- [@lucassartor](https://github.com/lucassartor) +- [@murtaza98](https://github.com/murtaza98) +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) +- [@rafaelblink](https://github.com/rafaelblink) +- [@renatobecker](https://github.com/renatobecker) +- [@rodrigok](https://github.com/rodrigok) +- [@sampaiodiego](https://github.com/sampaiodiego) +- [@tassoevan](https://github.com/tassoevan) +- [@thassiov](https://github.com/thassiov) +- [@tiagoevanp](https://github.com/tiagoevanp) + +# 3.10.5 +`2021-01-27 · 1 🐛 · 1 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.21.0-alpha.4235` + +### 🐛 Bug fixes + + +- Security Hotfix + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@sampaiodiego](https://github.com/sampaiodiego) + +# 3.10.4 +`2021-01-14 · 1 🐛 · 1 🔍 · 2 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.21.0-alpha.4235` + +### 🐛 Bug fixes + + +- Room's list showing all rooms with same name ([#20176](https://github.com/RocketChat/Rocket.Chat/pull/20176)) + + Add a migration to fix the room's list for those who ran version 3.10.1 and got it scrambled when a new user was registered. + +
+🔍 Minor changes + + +- Chore: Change console.warning() to console.warn() ([#20200](https://github.com/RocketChat/Rocket.Chat/pull/20200)) + +
+ +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@lucassartor](https://github.com/lucassartor) +- [@sampaiodiego](https://github.com/sampaiodiego) + +# 3.10.3 +`2021-01-09 · 1 🐛 · 1 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.21.0-alpha.4235` + +### 🐛 Bug fixes + + +- User registration updating wrong subscriptions ([#20128](https://github.com/RocketChat/Rocket.Chat/pull/20128)) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@sampaiodiego](https://github.com/sampaiodiego) + +# 3.10.2 +`2021-01-08 · 1 🐛 · 1 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.21.0-alpha.4235` + +### 🐛 Bug fixes + + +- Tabbar is opened ([#20122](https://github.com/RocketChat/Rocket.Chat/pull/20122)) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@ggazzo](https://github.com/ggazzo) + +# 3.10.1 +`2021-01-08 · 11 🐛 · 7 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.21.0-alpha.4235` + +### 🐛 Bug fixes + + +- **ENTERPRISE:** Omnichannel custom fields not storing additional form values ([#19953](https://github.com/RocketChat/Rocket.Chat/pull/19953)) + +- Actions from User Info panel ([#20073](https://github.com/RocketChat/Rocket.Chat/pull/20073) by [@Darshilp326](https://github.com/Darshilp326)) + + Users can be removed from channels without any error message. + +- Agent information panel not rendering ([#19965](https://github.com/RocketChat/Rocket.Chat/pull/19965)) + +- Creation of Omnichannel rooms not working correctly through the Apps when the agent parameter is set ([#19997](https://github.com/RocketChat/Rocket.Chat/pull/19997)) + +- Messages being updated when not required after user changes his profile ([#20114](https://github.com/RocketChat/Rocket.Chat/pull/20114)) + +- OAuth users being asked to change password on second login ([#20003](https://github.com/RocketChat/Rocket.Chat/pull/20003)) + +- Omnichannel Agents unable to take new chats in the queue ([#20022](https://github.com/RocketChat/Rocket.Chat/pull/20022)) + +- Omnichannel Business Hours form is not being rendered ([#20007](https://github.com/RocketChat/Rocket.Chat/pull/20007)) + +- Omnichannel raw model importing meteor dependency ([#20093](https://github.com/RocketChat/Rocket.Chat/pull/20093)) + +- Omnichannel rooms breaking after return to queue or forward ([#20089](https://github.com/RocketChat/Rocket.Chat/pull/20089)) + +- User Audio notification preference not being applied ([#20061](https://github.com/RocketChat/Rocket.Chat/pull/20061)) + +### 👩‍💻👨‍💻 Contributors 😍 + +- [@Darshilp326](https://github.com/Darshilp326) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@gabriellsh](https://github.com/gabriellsh) +- [@murtaza98](https://github.com/murtaza98) +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) +- [@rafaelblink](https://github.com/rafaelblink) +- [@renatobecker](https://github.com/renatobecker) +- [@sampaiodiego](https://github.com/sampaiodiego) + +# 3.10.0 +`2020-12-29 · 6 🎉 · 10 🚀 · 29 🐛 · 39 🔍 · 20 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.21.0-alpha.4235` + +### 🎉 New features + + +- Custom scroll ([#19701](https://github.com/RocketChat/Rocket.Chat/pull/19701)) + +- Omnichannel Contact Center (Directory) ([#19931](https://github.com/RocketChat/Rocket.Chat/pull/19931)) + +- REST Endpoint `instances.get` ([#19926](https://github.com/RocketChat/Rocket.Chat/pull/19926)) + + Returns an array of instances on the cluster. + +- REST endpoints to add and retrieve Enterprise licenses ([#19925](https://github.com/RocketChat/Rocket.Chat/pull/19925)) + +- Update Checker Description ([#19892](https://github.com/RocketChat/Rocket.Chat/pull/19892)) + +- User preference for audio notifications ([#19924](https://github.com/RocketChat/Rocket.Chat/pull/19924)) + + ![image](https://user-images.githubusercontent.com/40830821/102808922-dfe32b00-439f-11eb-9268-6d0cf69dc64c.png) + +### 🚀 Improvements + + +- Removed useEndpointDataExperimental hook usage ([#19496](https://github.com/RocketChat/Rocket.Chat/pull/19496)) + +- Replace useClipboard ([#19764](https://github.com/RocketChat/Rocket.Chat/pull/19764)) + +- Replace usePrefersReducedMotion ([#19759](https://github.com/RocketChat/Rocket.Chat/pull/19759)) + +- Rewrite contextualbar OTR panel ([#19674](https://github.com/RocketChat/Rocket.Chat/pull/19674)) + +- Rewrite contextualbar RoomMembers - AddUsers as React Component ([#19803](https://github.com/RocketChat/Rocket.Chat/pull/19803)) + +- Rewrite contextualbar RoomMembers - InviteUsers ([#19694](https://github.com/RocketChat/Rocket.Chat/pull/19694)) + +- Rewrite contextualbar RoomMembers as React Component ([#19841](https://github.com/RocketChat/Rocket.Chat/pull/19841)) + +- Rewrite NotificationPreferences to React component ([#19672](https://github.com/RocketChat/Rocket.Chat/pull/19672)) + +- Rewrite Room Files as React Component ([#19580](https://github.com/RocketChat/Rocket.Chat/pull/19580)) + +- Show all screen when printing screen ([#19928](https://github.com/RocketChat/Rocket.Chat/pull/19928)) + +### 🐛 Bug fixes + + +- 'Not Allowed' in message auditing ([#19762](https://github.com/RocketChat/Rocket.Chat/pull/19762)) + +- **ENTERPRISE:** Omnichannel Department form is not correctly storing the list of departments allowed for forwarding ([#19793](https://github.com/RocketChat/Rocket.Chat/pull/19793)) + +- Add fallback message when show notification content is disabled ([#19516](https://github.com/RocketChat/Rocket.Chat/pull/19516) by [@youssef-md](https://github.com/youssef-md)) + +- Admin Users screen sorting showing deactivated users in wrong order ([#19898](https://github.com/RocketChat/Rocket.Chat/pull/19898)) + +- Custom Avatar ([#19805](https://github.com/RocketChat/Rocket.Chat/pull/19805)) + +- Download my data with file uploads ([#19862](https://github.com/RocketChat/Rocket.Chat/pull/19862)) + +- Emails not showing up in Admin/Users ([#19727](https://github.com/RocketChat/Rocket.Chat/pull/19727)) + +- File Tab Order ([#19729](https://github.com/RocketChat/Rocket.Chat/pull/19729)) + +- Forgot password endpoint return status ([#19842](https://github.com/RocketChat/Rocket.Chat/pull/19842)) + +- Group DMs title when user changes his/her name ([#19834](https://github.com/RocketChat/Rocket.Chat/pull/19834)) + +- Hightlights validation on Account Preferences page ([#19902](https://github.com/RocketChat/Rocket.Chat/pull/19902) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) + + This PR fixes two issues in the account settings "preferences" panel. + Once set, the "Highlighted Words" setting cannot be reset to an empty string. This was fixed by changing the string validation from checking the length to checking the type of variable. + Secondly, it tracks the changes to correctly identify if changes after the last "save changes" action have been made, using an "updates" state variable, instead of just comparing against the initialValue that does not change on clicking "save changes". + +- Image preview for image URLs on messages ([#19734](https://github.com/RocketChat/Rocket.Chat/pull/19734)) + +- Issue with oembed ([#19923](https://github.com/RocketChat/Rocket.Chat/pull/19923)) + +- Issue with oembed ([#19886](https://github.com/RocketChat/Rocket.Chat/pull/19886)) + +- Issue with special message rendering ([#19817](https://github.com/RocketChat/Rocket.Chat/pull/19817)) + +- Omnichannel Departments Canned Responses ([#19830](https://github.com/RocketChat/Rocket.Chat/pull/19830)) + +- Problem with attachment render ([#19854](https://github.com/RocketChat/Rocket.Chat/pull/19854)) + +- Room scrolling to top after returns to a opened room ([#19945](https://github.com/RocketChat/Rocket.Chat/pull/19945)) + +- RoomForeword ([#19875](https://github.com/RocketChat/Rocket.Chat/pull/19875)) + +- Sidebar presence will now correctly update for Omnichannel rooms ([#19746](https://github.com/RocketChat/Rocket.Chat/pull/19746)) + +- Sidebar UI disappearing ([#19725](https://github.com/RocketChat/Rocket.Chat/pull/19725)) + +- Some apps were not correctly enabled during startup in HA environments ([#19763](https://github.com/RocketChat/Rocket.Chat/pull/19763)) + +- Spotify oEmbed ([#19825](https://github.com/RocketChat/Rocket.Chat/pull/19825)) + +- Startup error when using MongoDB with a password containing special characters ([#19749](https://github.com/RocketChat/Rocket.Chat/pull/19749)) + +- Status on searchlist ([#19935](https://github.com/RocketChat/Rocket.Chat/pull/19935)) + +- UIKit Modal not scrolling ([#19690](https://github.com/RocketChat/Rocket.Chat/pull/19690)) + +- Update base image in Dockerfile.rhel ([#19036](https://github.com/RocketChat/Rocket.Chat/pull/19036) by [@andykrohg](https://github.com/andykrohg)) + +- User email showing [object Object] ([#19870](https://github.com/RocketChat/Rocket.Chat/pull/19870)) + +- User Info 'Local Time' translation keyword ([#19879](https://github.com/RocketChat/Rocket.Chat/pull/19879) by [@J4r3tt](https://github.com/J4r3tt)) + +
+🔍 Minor changes + + +- bump fuselage ([#19736](https://github.com/RocketChat/Rocket.Chat/pull/19736)) + +- Bump ini from 1.3.5 to 1.3.8 in /ee/server/services ([#19844](https://github.com/RocketChat/Rocket.Chat/pull/19844) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump systeminformation from 4.30.1 to 4.33.0 in /ee/server/services ([#19929](https://github.com/RocketChat/Rocket.Chat/pull/19929) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Chore: Fix Caddy download URL in Snaps ([#19912](https://github.com/RocketChat/Rocket.Chat/pull/19912)) + +- Chore: Add watch.settings to events whitelist ([#19850](https://github.com/RocketChat/Rocket.Chat/pull/19850)) + +- Chore: Change Youtube test to verify if has an iframe with max-width ([#19863](https://github.com/RocketChat/Rocket.Chat/pull/19863)) + +- Chore: Remove extra parentheses from return type ([#19598](https://github.com/RocketChat/Rocket.Chat/pull/19598) by [@ArnoSaine](https://github.com/ArnoSaine)) + +- Chore: Update Pull Request template ([#19768](https://github.com/RocketChat/Rocket.Chat/pull/19768)) + + Improve the template of Pull Requests in order to make it clear reducing duplicated information and removing the visible checklists that were generating noise and misunderstanding with the PR progress. + + - Moved the checklists to inside comments + + - Merge the changelog and proposed changes sections to have a single source of description that goes to the changelog + + - Remove the screenshot section, they can be added inside the description + + - Changed the proposed changes title to incentivizing the usage of images and videos + +- Frontend folder structure ([#19631](https://github.com/RocketChat/Rocket.Chat/pull/19631)) + +- Improve Docker container size by adding chown to ADD command ([#19796](https://github.com/RocketChat/Rocket.Chat/pull/19796)) + +- Improve: Report Weekly Active Users to statistics ([#19843](https://github.com/RocketChat/Rocket.Chat/pull/19843)) + + Add the fields `uniqueUsersOfLastWeek`, `uniqueDevicesOfLastWeek` and `uniqueOSOfLastWeek` to the statistics report among the daily and monthly already reported. + +- Language update from LingoHub 🤖 on 2020-12-21Z ([#19922](https://github.com/RocketChat/Rocket.Chat/pull/19922)) + +- Merge EE and Community translations and LingoHub manual sync ([#19723](https://github.com/RocketChat/Rocket.Chat/pull/19723)) + +- Merge master into develop & Set version to 3.10.0-develop ([#19720](https://github.com/RocketChat/Rocket.Chat/pull/19720)) + +- Message parsing and rendering - Phase 1 ([#19654](https://github.com/RocketChat/Rocket.Chat/pull/19654)) + +- Regression: "My Account" page doesn't load ([#19753](https://github.com/RocketChat/Rocket.Chat/pull/19753)) + +- Regression: Add currently running instance to instances.get endpoint ([#19955](https://github.com/RocketChat/Rocket.Chat/pull/19955)) + +- Regression: Add Members showing the wrong template ([#19748](https://github.com/RocketChat/Rocket.Chat/pull/19748)) + +- Regression: Add missing translations on the Omnichannel Contact Center(Directory) ([#19968](https://github.com/RocketChat/Rocket.Chat/pull/19968)) + +- Regression: Admin Sidebar Scroll ([#19944](https://github.com/RocketChat/Rocket.Chat/pull/19944)) + +- Regression: Check permissions properly when fetching rooms in Omnichannel Directory ([#19951](https://github.com/RocketChat/Rocket.Chat/pull/19951)) + +- Regression: contextualBar folder structure ([#19761](https://github.com/RocketChat/Rocket.Chat/pull/19761)) + +- Regression: Double Scrollbars on tables ([#19980](https://github.com/RocketChat/Rocket.Chat/pull/19980)) + + Before: + ![image](https://user-images.githubusercontent.com/40830821/103242719-0ec84680-4936-11eb-87a7-68b6eea8de7b.png) + + + After: + ![image](https://user-images.githubusercontent.com/40830821/103242680-ee988780-4935-11eb-99e2-a95de99f78f1.png) + +- Regression: Failed autolinker and markdown rendering ([#19831](https://github.com/RocketChat/Rocket.Chat/pull/19831)) + +- Regression: fix broken members list ([#19806](https://github.com/RocketChat/Rocket.Chat/pull/19806)) + +- Regression: Fix member list Actions ([#19876](https://github.com/RocketChat/Rocket.Chat/pull/19876)) + +- Regression: Fix oembed ([#19978](https://github.com/RocketChat/Rocket.Chat/pull/19978)) + +- Regression: Fix Room Files for DMs ([#19874](https://github.com/RocketChat/Rocket.Chat/pull/19874)) + +- Regression: Fix sorting indicators on Admin Users page ([#19950](https://github.com/RocketChat/Rocket.Chat/pull/19950)) + +- Regression: Header Styles fixes ([#19946](https://github.com/RocketChat/Rocket.Chat/pull/19946)) + +- Regression: Omnichannel Custom Fields Form no longer working after refactoring ([#19948](https://github.com/RocketChat/Rocket.Chat/pull/19948)) + + The Omnichannel `Custom Fields` form is not working anymore after some refactorings on client-side. + When the user clicks on `Custom Field` in the Omnichannel menu, a blank page appears. + +- Regression: polishing licenses endpoints ([#19981](https://github.com/RocketChat/Rocket.Chat/pull/19981)) + +- Regression: roomInfo folder structure ([#19787](https://github.com/RocketChat/Rocket.Chat/pull/19787)) + +- Regression: RoomMembers Permission ([#19867](https://github.com/RocketChat/Rocket.Chat/pull/19867)) + +- Regression: User Info Context bar breaking. ([#19807](https://github.com/RocketChat/Rocket.Chat/pull/19807)) + +- Regression: UserCard "See full profile" link broken ([#19941](https://github.com/RocketChat/Rocket.Chat/pull/19941)) + +- Regression: UserInfoWithData endpoint variable ([#19816](https://github.com/RocketChat/Rocket.Chat/pull/19816)) + +- Remove Heroku from readme ([#19901](https://github.com/RocketChat/Rocket.Chat/pull/19901)) + +- Rewrite: Room Header ([#19808](https://github.com/RocketChat/Rocket.Chat/pull/19808)) + +
+ +### 👩‍💻👨‍💻 Contributors 😍 + +- [@ArnoSaine](https://github.com/ArnoSaine) +- [@J4r3tt](https://github.com/J4r3tt) +- [@aKn1ghtOut](https://github.com/aKn1ghtOut) +- [@andykrohg](https://github.com/andykrohg) +- [@dependabot[bot]](https://github.com/dependabot[bot]) +- [@youssef-md](https://github.com/youssef-md) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@MartinSchoeler](https://github.com/MartinSchoeler) +- [@alansikora](https://github.com/alansikora) +- [@dougfabris](https://github.com/dougfabris) +- [@g-thome](https://github.com/g-thome) +- [@gabriellsh](https://github.com/gabriellsh) +- [@geekgonecrazy](https://github.com/geekgonecrazy) +- [@ggazzo](https://github.com/ggazzo) +- [@rafaelblink](https://github.com/rafaelblink) +- [@renatobecker](https://github.com/renatobecker) +- [@rodrigok](https://github.com/rodrigok) +- [@sampaiodiego](https://github.com/sampaiodiego) +- [@tassoevan](https://github.com/tassoevan) +- [@thassiov](https://github.com/thassiov) +- [@tiagoevanp](https://github.com/tiagoevanp) + +# 3.9.4 +`2020-12-31 · 3 🐛 · 1 🔍 · 6 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.21.0-alpha.4235` + +### 🐛 Bug fixes + + +- Omnichannel Departments Canned Responses ([#19830](https://github.com/RocketChat/Rocket.Chat/pull/19830)) + +- Room scrolling to top after returns to a opened room ([#19945](https://github.com/RocketChat/Rocket.Chat/pull/19945)) + +- Status on searchlist ([#19935](https://github.com/RocketChat/Rocket.Chat/pull/19935)) + +
+🔍 Minor changes + + +- Regression: Fix oembed ([#19978](https://github.com/RocketChat/Rocket.Chat/pull/19978)) + +
+ +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@dougfabris](https://github.com/dougfabris) +- [@gabriellsh](https://github.com/gabriellsh) +- [@ggazzo](https://github.com/ggazzo) +- [@renatobecker](https://github.com/renatobecker) +- [@sampaiodiego](https://github.com/sampaiodiego) +- [@tassoevan](https://github.com/tassoevan) + +# 3.9.3 +`2020-12-18 · 2 🐛 · 1 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.21.0-alpha.4235` + +### 🐛 Bug fixes + + +- Issue with special message rendering ([#19817](https://github.com/RocketChat/Rocket.Chat/pull/19817)) + +- Problem with attachment render ([#19854](https://github.com/RocketChat/Rocket.Chat/pull/19854)) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@MartinSchoeler](https://github.com/MartinSchoeler) + +# 3.9.2 +`2020-12-17 · 5 🐛 · 6 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.21.0-alpha.4235` + +### 🐛 Bug fixes + + +- 'Not Allowed' in message auditing ([#19762](https://github.com/RocketChat/Rocket.Chat/pull/19762)) + +- **ENTERPRISE:** Omnichannel Department form is not correctly storing the list of departments allowed for forwarding ([#19793](https://github.com/RocketChat/Rocket.Chat/pull/19793)) + +- Download my data with file uploads ([#19862](https://github.com/RocketChat/Rocket.Chat/pull/19862)) + +- Forgot password endpoint return status ([#19842](https://github.com/RocketChat/Rocket.Chat/pull/19842)) + +- Some apps were not correctly enabled during startup in HA environments ([#19763](https://github.com/RocketChat/Rocket.Chat/pull/19763)) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@MartinSchoeler](https://github.com/MartinSchoeler) +- [@g-thome](https://github.com/g-thome) +- [@rafaelblink](https://github.com/rafaelblink) +- [@renatobecker](https://github.com/renatobecker) +- [@sampaiodiego](https://github.com/sampaiodiego) +- [@thassiov](https://github.com/thassiov) + +# 3.9.1 +`2020-12-05 · 5 🐛 · 4 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.20.0` + +### 🐛 Bug fixes + + +- Exception on certain login cases including SAML + +- Image preview for image URLs on messages ([#19734](https://github.com/RocketChat/Rocket.Chat/pull/19734)) + +- Sidebar presence will now correctly update for Omnichannel rooms ([#19746](https://github.com/RocketChat/Rocket.Chat/pull/19746)) + +- Sidebar UI disappearing ([#19725](https://github.com/RocketChat/Rocket.Chat/pull/19725)) + +- Startup error when using MongoDB with a password containing special characters ([#19749](https://github.com/RocketChat/Rocket.Chat/pull/19749)) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@alansikora](https://github.com/alansikora) +- [@g-thome](https://github.com/g-thome) +- [@gabriellsh](https://github.com/gabriellsh) +- [@sampaiodiego](https://github.com/sampaiodiego) + +# 3.9.0 +`2020-11-28 · 2 🎉 · 16 🚀 · 27 🐛 · 31 🔍 · 21 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.20.0` + +### 🎉 New features + + +- 2 Factor Authentication when using OAuth and SAML ([#11726](https://github.com/RocketChat/Rocket.Chat/pull/11726) by [@Hudell](https://github.com/Hudell) & [@MarcosSpessatto](https://github.com/MarcosSpessatto)) + +- Added setting to disable password changes for users who log in using SSO ([#10391](https://github.com/RocketChat/Rocket.Chat/pull/10391) by [@Hudell](https://github.com/Hudell)) + +### 🚀 Improvements + + +- **ENTERPRISE:** UI/UX enhancements in Omnichannel Monitors page ([#19495](https://github.com/RocketChat/Rocket.Chat/pull/19495)) + +- **ENTERPRISE:** UI/UX enhancements in Omnichannel Priorities page ([#19512](https://github.com/RocketChat/Rocket.Chat/pull/19512)) + +- **ENTERPRISE:** UI/UX enhancements in Omnichannel Tags page ([#19510](https://github.com/RocketChat/Rocket.Chat/pull/19510)) + +- **ENTERPRISE:** UI/UX enhancements in Omnichannel Units page ([#19500](https://github.com/RocketChat/Rocket.Chat/pull/19500)) + +- Add support to `replace` operation when using Change Stream ([#19579](https://github.com/RocketChat/Rocket.Chat/pull/19579)) + +- Bundle Size Client ([#19533](https://github.com/RocketChat/Rocket.Chat/pull/19533)) + + temporarily removes some codeblock languages + Moved some libraries to dynamic imports + Removed some shared code not used on the client side + +- Forward Omnichannel room to agent in another department ([#19576](https://github.com/RocketChat/Rocket.Chat/pull/19576) by [@mrfigueiredo](https://github.com/mrfigueiredo)) + +- KeyboardShortcuts as React component ([#19518](https://github.com/RocketChat/Rocket.Chat/pull/19518)) + +- Remove Box dependence from Tag and Badge components ([#19467](https://github.com/RocketChat/Rocket.Chat/pull/19467)) + +- Remove Box props from Avatar component ([#19491](https://github.com/RocketChat/Rocket.Chat/pull/19491)) + +- Rewrite Auto-Translate as a React component ([#19633](https://github.com/RocketChat/Rocket.Chat/pull/19633)) + +- Rewrite Room Info ([#19511](https://github.com/RocketChat/Rocket.Chat/pull/19511)) + +- SlackBridge threads performance improvement ([#19338](https://github.com/RocketChat/Rocket.Chat/pull/19338) by [@antkaz](https://github.com/antkaz)) + +- UI/UX enhancements in department pages following the design system ([#19421](https://github.com/RocketChat/Rocket.Chat/pull/19421)) + +- UI/UX enhancements in Omnichannel Triggers page ([#19485](https://github.com/RocketChat/Rocket.Chat/pull/19485)) + +- UI/UX enhancements in Omnichannnel Current Chats page ([#19397](https://github.com/RocketChat/Rocket.Chat/pull/19397)) + +### 🐛 Bug fixes + + +- Allow username change if LDAP is enabled but their username is not linked to an LDAP field ([#19381](https://github.com/RocketChat/Rocket.Chat/pull/19381) by [@robertfromont](https://github.com/robertfromont)) + + LDAP users can change their username if the LDAP_Username_Field setting is blank. + +- Auto Translate ([#19599](https://github.com/RocketChat/Rocket.Chat/pull/19599)) + +- Channel actions not working when reduce motion is active ([#19638](https://github.com/RocketChat/Rocket.Chat/pull/19638)) + +- Column width was not following the design system in Omnichannel Departments page ([#19601](https://github.com/RocketChat/Rocket.Chat/pull/19601)) + +- Engagement dashboard on old Mongo versions ([#19616](https://github.com/RocketChat/Rocket.Chat/pull/19616)) + +- Engagement dashboard: graphs adjustment ([#19450](https://github.com/RocketChat/Rocket.Chat/pull/19450)) + +- IE11 - Update ui kit and fuselage bundle ([#19561](https://github.com/RocketChat/Rocket.Chat/pull/19561)) + +- Input without label and email ordering missing on Omnichannel Agents page ([#19414](https://github.com/RocketChat/Rocket.Chat/pull/19414)) + +- Issue with drag and drop ([#19593](https://github.com/RocketChat/Rocket.Chat/pull/19593)) + +- LDAP Unique Identifier Field can not use operational attributes ([#19571](https://github.com/RocketChat/Rocket.Chat/pull/19571) by [@truongtx8](https://github.com/truongtx8)) + +- Omnichannel Analytics page doesn't have field labels ([#19400](https://github.com/RocketChat/Rocket.Chat/pull/19400)) + +- Outgoing integrations without trigger words or with multiple commas ([#19488](https://github.com/RocketChat/Rocket.Chat/pull/19488)) + +- Prevent headerRoom's click to open room/direct info ([#19596](https://github.com/RocketChat/Rocket.Chat/pull/19596)) + +- Regex was not working properly on visitors.search endpoint ([#19577](https://github.com/RocketChat/Rocket.Chat/pull/19577)) + +- Restore Message View Mode Preference ([#19458](https://github.com/RocketChat/Rocket.Chat/pull/19458)) + + [FIX] Restore Message View Mode Preference + +- Role description not updating ([#19236](https://github.com/RocketChat/Rocket.Chat/pull/19236)) + +- Save button enabled by default in Omnichannel Business Hours Form ([#19493](https://github.com/RocketChat/Rocket.Chat/pull/19493)) + +- Settings may not update internal cache immediately ([#19628](https://github.com/RocketChat/Rocket.Chat/pull/19628)) + +- Setup Wizard User Creation Locking up ([#19509](https://github.com/RocketChat/Rocket.Chat/pull/19509)) + + [FIX] Setup Wizard User Creation Locking up + +- Size of embed Youtube on threads for small screens ([#19514](https://github.com/RocketChat/Rocket.Chat/pull/19514)) + +- The width of list columns was not following the design system in Omnichannel Agents page ([#19625](https://github.com/RocketChat/Rocket.Chat/pull/19625)) + +- The width of list columns was not following the design system in Omnichannel Managers page ([#19624](https://github.com/RocketChat/Rocket.Chat/pull/19624)) + +- TOTP Being ignored when changing our own avatar ([#19475](https://github.com/RocketChat/Rocket.Chat/pull/19475)) + + [FIX] TOTP Being ignored when changing our own avatar + +- Typo in custom oauth from environment variable ([#19570](https://github.com/RocketChat/Rocket.Chat/pull/19570)) + +- UI/UX issues on Omnichannel Managers page ([#19410](https://github.com/RocketChat/Rocket.Chat/pull/19410)) + +- Unread count for all messages when mentioning an user ([#16884](https://github.com/RocketChat/Rocket.Chat/pull/16884) by [@subham103](https://github.com/subham103)) + +- Wrong margin of description field in Omnichannel Webhooks page ([#19487](https://github.com/RocketChat/Rocket.Chat/pull/19487)) + +
+🔍 Minor changes + + +- [IMPROVES] Omnichannel - Custom Fields pages. ([#19473](https://github.com/RocketChat/Rocket.Chat/pull/19473)) + +- Bump bcrypt from 4.0.1 to 5.0.0 in /ee/server/services ([#19387](https://github.com/RocketChat/Rocket.Chat/pull/19387) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump systeminformation from 4.27.3 to 4.30.1 in /ee/server/services ([#19543](https://github.com/RocketChat/Rocket.Chat/pull/19543) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump xml-crypto from 1.5.3 to 2.0.0 ([#19383](https://github.com/RocketChat/Rocket.Chat/pull/19383) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- chore: Debounce sidebar list ([#19590](https://github.com/RocketChat/Rocket.Chat/pull/19590)) + +- Fix Docker preview image build ([#19627](https://github.com/RocketChat/Rocket.Chat/pull/19627)) + +- Fix permission duplicated error on startup causing CI to halt ([#19653](https://github.com/RocketChat/Rocket.Chat/pull/19653)) + +- Improve performance of migration 211 (adding mostImportantRole to sessions) ([#19700](https://github.com/RocketChat/Rocket.Chat/pull/19700)) + +- Improve REST endpoint to log user out from other clients ([#19642](https://github.com/RocketChat/Rocket.Chat/pull/19642)) + +- LingoHub based on develop ([#19592](https://github.com/RocketChat/Rocket.Chat/pull/19592)) + +- LingoHub based on develop ([#19131](https://github.com/RocketChat/Rocket.Chat/pull/19131)) + +- Manual LingoHub update ([#19620](https://github.com/RocketChat/Rocket.Chat/pull/19620)) + +- Merge master into develop & Set version to 3.9.0-develop ([#19534](https://github.com/RocketChat/Rocket.Chat/pull/19534)) + +- React Room Container ([#19634](https://github.com/RocketChat/Rocket.Chat/pull/19634)) + +- Regression: Collapsed messages container in safari ([#19668](https://github.com/RocketChat/Rocket.Chat/pull/19668)) + +- Regression: Fix Avatar x40 ([#19564](https://github.com/RocketChat/Rocket.Chat/pull/19564)) + +- Regression: Fix Custom OAuth 2FA ([#19691](https://github.com/RocketChat/Rocket.Chat/pull/19691)) + +- Regression: Fix LDAP 2FA not working when Login Fallback is off ([#19659](https://github.com/RocketChat/Rocket.Chat/pull/19659)) + +- Regression: Fix multiple react blazed template rendering at the same time ([#19679](https://github.com/RocketChat/Rocket.Chat/pull/19679)) + +- Regression: Fix wrong template on photoswipe ([#19575](https://github.com/RocketChat/Rocket.Chat/pull/19575)) + +- Regression: Issues with Safari ([#19671](https://github.com/RocketChat/Rocket.Chat/pull/19671)) + +- Regression: object-fit for image element and Box margin in AppAvatar component ([#19698](https://github.com/RocketChat/Rocket.Chat/pull/19698)) + +- REGRESSION: Photoswipe not working ([#19569](https://github.com/RocketChat/Rocket.Chat/pull/19569)) + +- Regression: Room Info Edit action ([#19581](https://github.com/RocketChat/Rocket.Chat/pull/19581)) + +- Regression: Room Info maxAgeDefault variable ([#19582](https://github.com/RocketChat/Rocket.Chat/pull/19582)) + +- Regression: URL preview problem ([#19685](https://github.com/RocketChat/Rocket.Chat/pull/19685)) + +- Regression: Verticalbar size ([#19670](https://github.com/RocketChat/Rocket.Chat/pull/19670)) + +- Release 3.8.2 ([#19705](https://github.com/RocketChat/Rocket.Chat/pull/19705)) + +- Report DAU and MAU by role ([#19657](https://github.com/RocketChat/Rocket.Chat/pull/19657)) + +- Update Apps-Engine version ([#19639](https://github.com/RocketChat/Rocket.Chat/pull/19639)) + +- Update Apps-Engine version ([#19702](https://github.com/RocketChat/Rocket.Chat/pull/19702)) + +
+ +### 👩‍💻👨‍💻 Contributors 😍 + +- [@Hudell](https://github.com/Hudell) +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) +- [@antkaz](https://github.com/antkaz) +- [@dependabot[bot]](https://github.com/dependabot[bot]) +- [@mrfigueiredo](https://github.com/mrfigueiredo) +- [@robertfromont](https://github.com/robertfromont) +- [@subham103](https://github.com/subham103) +- [@truongtx8](https://github.com/truongtx8) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@MartinSchoeler](https://github.com/MartinSchoeler) +- [@d-gubert](https://github.com/d-gubert) +- [@dougfabris](https://github.com/dougfabris) +- [@g-thome](https://github.com/g-thome) +- [@gabriellsh](https://github.com/gabriellsh) +- [@geekgonecrazy](https://github.com/geekgonecrazy) +- [@ggazzo](https://github.com/ggazzo) +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) +- [@rafaelblink](https://github.com/rafaelblink) +- [@renatobecker](https://github.com/renatobecker) +- [@rodrigok](https://github.com/rodrigok) +- [@sampaiodiego](https://github.com/sampaiodiego) +- [@tiagoevanp](https://github.com/tiagoevanp) + +# 3.8.5 +`2020-12-31 · 1 🔍 · 1 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.19.0` + +
+🔍 Minor changes + + +- Regression: Fix oembed ([#19978](https://github.com/RocketChat/Rocket.Chat/pull/19978)) + +
+ +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@sampaiodiego](https://github.com/sampaiodiego) + +# 3.8.4 +`2020-12-18 · 2 🐛 · 1 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.19.0` + +### 🐛 Bug fixes + + +- Issue with special message rendering ([#19817](https://github.com/RocketChat/Rocket.Chat/pull/19817)) + +- Problem with attachment render ([#19854](https://github.com/RocketChat/Rocket.Chat/pull/19854)) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@MartinSchoeler](https://github.com/MartinSchoeler) + +# 3.8.3 +`2020-12-05 · 1 🐛 · 1 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.19.0` + +### 🐛 Bug fixes + + +- Exception on certain login cases including SAML + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@sampaiodiego](https://github.com/sampaiodiego) + # 3.8.2 -`2020-11-27 · 2 🐛 · 1 👩‍💻👨‍💻` +`2020-11-27 · 2 🐛 · 1 🔍 · 2 👩‍💻👨‍💻` ### Engine versions - Node: `12.18.4` @@ -15,9 +1942,18 @@ - Server crash while reading settings for allowed and blocked email domain lists ([#19683](https://github.com/RocketChat/Rocket.Chat/pull/19683)) +
+🔍 Minor changes + + +- Release 3.8.2 ([#19705](https://github.com/RocketChat/Rocket.Chat/pull/19705)) + +
+ ### 👩‍💻👨‍💻 Core Team 🤓 - [@g-thome](https://github.com/g-thome) +- [@sampaiodiego](https://github.com/sampaiodiego) # 3.8.1 `2020-11-19 · 3 🐛 · 1 🔍 · 3 👩‍💻👨‍💻` @@ -345,6 +2281,44 @@ - [@thassiov](https://github.com/thassiov) - [@tiagoevanp](https://github.com/tiagoevanp) +# 3.7.4 +`2020-12-18 · 2 🐛 · 1 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.18.0` + +### 🐛 Bug fixes + + +- Issue with special message rendering ([#19817](https://github.com/RocketChat/Rocket.Chat/pull/19817)) + +- Problem with attachment render ([#19854](https://github.com/RocketChat/Rocket.Chat/pull/19854)) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@MartinSchoeler](https://github.com/MartinSchoeler) + +# 3.7.3 +`2020-12-05 · 1 🐛 · 1 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.18.0` + +### 🐛 Bug fixes + + +- Exception on certain login cases including SAML + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@sampaiodiego](https://github.com/sampaiodiego) + # 3.7.2 `2020-11-13 · 4 🐛 · 1 🔍 · 4 👩‍💻👨‍💻` @@ -788,8 +2762,10 @@ - **2FA:** Password enforcement setting and 2FA protection when saving settings or resetting E2E encryption ([#18640](https://github.com/RocketChat/Rocket.Chat/pull/18640)) - - Increase the 2FA remembering time from 5min to 30min - - Add new setting to enforce 2FA password fallback (enabled only for new installations) + - Increase the 2FA remembering time from 5min to 30min + + - Add new setting to enforce 2FA password fallback (enabled only for new installations) + - Require 2FA to save settings and reset E2E Encryption keys - **Omnichannel:** Allow set other agent status via method `livechat:changeLivechatStatus ` ([#18571](https://github.com/RocketChat/Rocket.Chat/pull/18571)) @@ -807,7 +2783,7 @@ - 2FA by Email setting showing for the user even when disabled by the admin ([#18473](https://github.com/RocketChat/Rocket.Chat/pull/18473)) - The option to disable/enable the **Two-factor authentication via Email** at `Account > Security > Two Factor Authentication + The option to disable/enable the **Two-factor authentication via Email** at `Account > Security > Two Factor Authentication ` was visible even when the setting **Enable Two Factor Authentication via Email** at `Admin > Accounts > Two Factor Authentication` was disabled leading to misbehavior since the functionality was disabled. - Agents enabledDepartment attribute not set on collection ([#18614](https://github.com/RocketChat/Rocket.Chat/pull/18614) by [@paulobernardoaf](https://github.com/paulobernardoaf)) @@ -1136,7 +3112,7 @@ - External MP3 encoder worker for audio recording ([#18277](https://github.com/RocketChat/Rocket.Chat/pull/18277)) -- Sign in with apple (iOS client only) ([#18258](https://github.com/RocketChat/Rocket.Chat/pull/18258)) +- Sign in with apple (iOS client only) ([#18258](https://github.com/RocketChat/Rocket.Chat/pull/18258) by [@djorkaeffalexandre](https://github.com/djorkaeffalexandre)) Add Sign in with Apple service for the iOS client-only, support for the Web and Android clients will land in future releases. @@ -1149,7 +3125,7 @@ ### 🚀 Improvements -- Change setting that blocks unauthenticated access to avatar to public ([#18316](https://github.com/RocketChat/Rocket.Chat/pull/18316)) +- Change setting that blocks unauthenticated access to avatar to public ([#18316](https://github.com/RocketChat/Rocket.Chat/pull/18316) by [@djorkaeffalexandre](https://github.com/djorkaeffalexandre)) - Improve performance and remove agents when the department is removed ([#17049](https://github.com/RocketChat/Rocket.Chat/pull/17049) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) @@ -1157,13 +3133,16 @@ - Mention autocomplete UI and performance improvements ([#18309](https://github.com/RocketChat/Rocket.Chat/pull/18309)) - * New setting to configure the number of suggestions `Admin > Layout > User Interface > Number of users' autocomplete suggestions` (default 5) - * The UI shows whenever the user is not a member of the room - * The UI shows when the suggestion came from the last messages for quick selection/reply - * The suggestions follow this order: - * The user with the exact username and member of the room - * The user with the exact username but not a member of the room (if allowed to list non-members) - * The users containing the text in username, name or nickname and member of the room + * New setting to configure the number of suggestions `Admin > Layout > User Interface > Number of users' autocomplete suggestions` (default 5) + + * The UI shows whenever the user is not a member of the room + + * The UI shows when the suggestion came from the last messages for quick selection/reply + + * The suggestions follow this order: + * The user with the exact username and member of the room + * The user with the exact username but not a member of the room (if allowed to list non-members) + * The users containing the text in username, name or nickname and member of the room * The users containing the text in username, name or nickname and not a member of the room (if allowed to list non-members) - Message action styles ([#18190](https://github.com/RocketChat/Rocket.Chat/pull/18190)) @@ -1267,11 +3246,11 @@ - Regression: nickname field in user profile. ([#18359](https://github.com/RocketChat/Rocket.Chat/pull/18359)) -- Regression: Notification with id-only isn't showed by iOS devices ([#18353](https://github.com/RocketChat/Rocket.Chat/pull/18353)) +- Regression: Notification with id-only isn't showed by iOS devices ([#18353](https://github.com/RocketChat/Rocket.Chat/pull/18353) by [@djorkaeffalexandre](https://github.com/djorkaeffalexandre)) - Regression: Preferences crashing when User has no preferences set. ([#18341](https://github.com/RocketChat/Rocket.Chat/pull/18341)) -- Regression: Provide a fallback text when push notification is idOnly ([#18373](https://github.com/RocketChat/Rocket.Chat/pull/18373)) +- Regression: Provide a fallback text when push notification is idOnly ([#18373](https://github.com/RocketChat/Rocket.Chat/pull/18373) by [@djorkaeffalexandre](https://github.com/djorkaeffalexandre)) - Regression: Remove calls to Console API in useForm hook ([#18244](https://github.com/RocketChat/Rocket.Chat/pull/18244)) @@ -1312,6 +3291,7 @@ - [@antkaz](https://github.com/antkaz) - [@cking-vonix](https://github.com/cking-vonix) - [@darigovresearch](https://github.com/darigovresearch) +- [@djorkaeffalexandre](https://github.com/djorkaeffalexandre) - [@lpilz](https://github.com/lpilz) - [@mariaeduardacunha](https://github.com/mariaeduardacunha) - [@omarchehab98](https://github.com/omarchehab98) @@ -1321,7 +3301,6 @@ - [@MartinSchoeler](https://github.com/MartinSchoeler) - [@d-gubert](https://github.com/d-gubert) -- [@djorkaeffalexandre](https://github.com/djorkaeffalexandre) - [@gabriellsh](https://github.com/gabriellsh) - [@geekgonecrazy](https://github.com/geekgonecrazy) - [@ggazzo](https://github.com/ggazzo) @@ -1440,7 +3419,7 @@ - **API:** Endpoint `settings.addCustomOAuth` to create Custom OAuth services ([#14912](https://github.com/RocketChat/Rocket.Chat/pull/14912) by [@g-rauhoeft](https://github.com/g-rauhoeft)) -- **API:** New endpoints to manage User Custom Status `custom-user-status.create`, custom-user-status.delete` and `custom-user-status.update` ([#16550](https://github.com/RocketChat/Rocket.Chat/pull/16550)) +- **API:** New endpoints to manage User Custom Status `custom-user-status.create`, custom-user-status.delete` and `custom-user-status.update` ([#16550](https://github.com/RocketChat/Rocket.Chat/pull/16550) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - **ENTERPRISE:** Download engagement data ([#17920](https://github.com/RocketChat/Rocket.Chat/pull/17920)) @@ -1458,7 +3437,7 @@ - Blocked Media Types setting ([#17617](https://github.com/RocketChat/Rocket.Chat/pull/17617)) -- Highlight matching words in message search results ([#16166](https://github.com/RocketChat/Rocket.Chat/pull/16166)) +- Highlight matching words in message search results ([#16166](https://github.com/RocketChat/Rocket.Chat/pull/16166) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Make ldap avatar source field customizable ([#12958](https://github.com/RocketChat/Rocket.Chat/pull/12958) by [@alexbartsch](https://github.com/alexbartsch)) @@ -1505,10 +3484,10 @@ - Split NOTIFICATIONS_SCHEDULE_DELAY into three separate variables ([#17669](https://github.com/RocketChat/Rocket.Chat/pull/17669) by [@jazztickets](https://github.com/jazztickets)) - Email notification delay can now be customized with the following environment variables: - NOTIFICATIONS_SCHEDULE_DELAY_ONLINE - NOTIFICATIONS_SCHEDULE_DELAY_AWAY - NOTIFICATIONS_SCHEDULE_DELAY_OFFLINE + Email notification delay can now be customized with the following environment variables: + NOTIFICATIONS_SCHEDULE_DELAY_ONLINE + NOTIFICATIONS_SCHEDULE_DELAY_AWAY + NOTIFICATIONS_SCHEDULE_DELAY_OFFLINE Setting the value to -1 disable notifications for that type. - Threads ([#17416](https://github.com/RocketChat/Rocket.Chat/pull/17416)) @@ -1562,7 +3541,7 @@ - Missing i18n key for setting: Verify Email for External Accounts ([#18002](https://github.com/RocketChat/Rocket.Chat/pull/18002)) -- Missing pinned icon indicator for messages pinned ([#16448](https://github.com/RocketChat/Rocket.Chat/pull/16448)) +- Missing pinned icon indicator for messages pinned ([#16448](https://github.com/RocketChat/Rocket.Chat/pull/16448) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Missing User when forwarding Omnichannel conversations via Apps-Engine ([#17918](https://github.com/RocketChat/Rocket.Chat/pull/17918)) @@ -1570,7 +3549,7 @@ - No rotate option, to prevent image quality loss ([#15196](https://github.com/RocketChat/Rocket.Chat/pull/15196) by [@stleitner](https://github.com/stleitner)) -- No Way to Display Password Policy on Password Reset Screen ([#16400](https://github.com/RocketChat/Rocket.Chat/pull/16400)) +- No Way to Display Password Policy on Password Reset Screen ([#16400](https://github.com/RocketChat/Rocket.Chat/pull/16400) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Not possible to translate the label of custom fields in user's Info ([#15595](https://github.com/RocketChat/Rocket.Chat/pull/15595) by [@antkaz](https://github.com/antkaz)) @@ -1596,7 +3575,7 @@ - Update AmazonS3 file upload with error handling and sync operation ([#10372](https://github.com/RocketChat/Rocket.Chat/pull/10372) by [@madhavmalhotra3089](https://github.com/madhavmalhotra3089)) -- User can resend email verification if email is invalid or is empty ([#16095](https://github.com/RocketChat/Rocket.Chat/pull/16095)) +- User can resend email verification if email is invalid or is empty ([#16095](https://github.com/RocketChat/Rocket.Chat/pull/16095) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - User is prompted to reset their password when logging with OAuth ([#18001](https://github.com/RocketChat/Rocket.Chat/pull/18001)) @@ -1732,6 +3711,7 @@ - [@Siedlerchr](https://github.com/Siedlerchr) - [@alexbartsch](https://github.com/alexbartsch) - [@antkaz](https://github.com/antkaz) +- [@ashwaniYDV](https://github.com/ashwaniYDV) - [@aviral243](https://github.com/aviral243) - [@bhardwajaditya](https://github.com/bhardwajaditya) - [@c0dzilla](https://github.com/c0dzilla) @@ -1763,7 +3743,6 @@ - [@MartinSchoeler](https://github.com/MartinSchoeler) - [@Sing-Li](https://github.com/Sing-Li) - [@alansikora](https://github.com/alansikora) -- [@ashwaniYDV](https://github.com/ashwaniYDV) - [@d-gubert](https://github.com/d-gubert) - [@engelgabriel](https://github.com/engelgabriel) - [@gabriellsh](https://github.com/gabriellsh) @@ -1908,11 +3887,11 @@ - **ENTERPRISE:** Omnichannel Last-Chatted Agent Preferred option ([#17666](https://github.com/RocketChat/Rocket.Chat/pull/17666)) - If activated, this feature will store the last agent that assisted each Omnichannel visitor when a conversation is taken. So, when a visitor returns(it works with any entry point, Livechat, Facebook, REST API, and so on) and starts a new chat, the routing system checks: - - 1 - The visitor object for any stored agent that the visitor has previously talked to; - 2 - If a previous agent is not found, the system will try to find a previous conversation of the same visitor. If a room is found, the system will get the previous agent from the room; - + If activated, this feature will store the last agent that assisted each Omnichannel visitor when a conversation is taken. So, when a visitor returns(it works with any entry point, Livechat, Facebook, REST API, and so on) and starts a new chat, the routing system checks: + + 1 - The visitor object for any stored agent that the visitor has previously talked to; + 2 - If a previous agent is not found, the system will try to find a previous conversation of the same visitor. If a room is found, the system will get the previous agent from the room; + After this process, if an agent has been found, the system will check the agent's availability to assist the new chat. If it's not available, then the routing system will get the next available agent in the queue. - **ENTERPRISE:** Support for custom Livechat registration form fields ([#17581](https://github.com/RocketChat/Rocket.Chat/pull/17581)) @@ -1941,7 +3920,7 @@ - Rewrite admin pages ([#17388](https://github.com/RocketChat/Rocket.Chat/pull/17388) by [@mariaeduardacunha](https://github.com/mariaeduardacunha)) -- Screen Lock settings - mobile client ([#17523](https://github.com/RocketChat/Rocket.Chat/pull/17523)) +- Screen Lock settings - mobile client ([#17523](https://github.com/RocketChat/Rocket.Chat/pull/17523) by [@djorkaeffalexandre](https://github.com/djorkaeffalexandre)) - Show user's status description by the usernames in messages list ([#14892](https://github.com/RocketChat/Rocket.Chat/pull/14892) by [@wreiske](https://github.com/wreiske)) @@ -2017,16 +3996,19 @@ - Notification sounds ([#17616](https://github.com/RocketChat/Rocket.Chat/pull/17616)) - * Global CDN config was ignored when loading the sound files - * Upload of custom sounds wasn't getting the file extension correctly - * Some translations were missing + * Global CDN config was ignored when loading the sound files + + * Upload of custom sounds wasn't getting the file extension correctly + + * Some translations were missing + * Edit and delete of custom sounds were not working correctly - Omnichannel departments are not saved when the offline channel name is not defined ([#17553](https://github.com/RocketChat/Rocket.Chat/pull/17553)) - Omnichannel room priorities system messages were create on every saved room info ([#17479](https://github.com/RocketChat/Rocket.Chat/pull/17479) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) -- Password reset/change accepting current password as new password ([#16331](https://github.com/RocketChat/Rocket.Chat/pull/16331)) +- Password reset/change accepting current password as new password ([#16331](https://github.com/RocketChat/Rocket.Chat/pull/16331) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Push settings enabled when push gateway is selected ([#17582](https://github.com/RocketChat/Rocket.Chat/pull/17582)) @@ -2161,7 +4143,9 @@ - [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@Nikhil713](https://github.com/Nikhil713) - [@TaimurAzhar](https://github.com/TaimurAzhar) +- [@ashwaniYDV](https://github.com/ashwaniYDV) - [@dependabot[bot]](https://github.com/dependabot[bot]) +- [@djorkaeffalexandre](https://github.com/djorkaeffalexandre) - [@dudizilla](https://github.com/dudizilla) - [@fbartels](https://github.com/fbartels) - [@huzaifahj](https://github.com/huzaifahj) @@ -2184,9 +4168,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 - [@MartinSchoeler](https://github.com/MartinSchoeler) -- [@ashwaniYDV](https://github.com/ashwaniYDV) - [@d-gubert](https://github.com/d-gubert) -- [@djorkaeffalexandre](https://github.com/djorkaeffalexandre) - [@engelgabriel](https://github.com/engelgabriel) - [@gabriellsh](https://github.com/gabriellsh) - [@geekgonecrazy](https://github.com/geekgonecrazy) @@ -2307,14 +4289,19 @@ - Better Push and Email Notification logic ([#17357](https://github.com/RocketChat/Rocket.Chat/pull/17357)) - We are still using the same logic to define which notifications every new message will generate, it takes some servers' settings, users's preferences and subscriptions' settings in consideration to determine who will receive each notification type (desktop, audio, email and mobile push), but now it doesn't check the user's status (online, away, offline) for email and mobile push notifications but send those notifications to a new queue with the following rules: - - - When the user is online the notification is scheduled to be sent in 120 seconds - - When the user is away the notification is scheduled to be sent in 120 seconds minus the amount of time he is away - - When the user is offline the notification is scheduled to be sent right away - - When the user reads a channel all the notifications for that user are removed (clear queue) - - When a notification is processed to be sent to a user and there are other scheduled notifications: - - All the scheduled notifications for that user are rescheduled to now + We are still using the same logic to define which notifications every new message will generate, it takes some servers' settings, users's preferences and subscriptions' settings in consideration to determine who will receive each notification type (desktop, audio, email and mobile push), but now it doesn't check the user's status (online, away, offline) for email and mobile push notifications but send those notifications to a new queue with the following rules: + + + - When the user is online the notification is scheduled to be sent in 120 seconds + + - When the user is away the notification is scheduled to be sent in 120 seconds minus the amount of time he is away + + - When the user is offline the notification is scheduled to be sent right away + + - When the user reads a channel all the notifications for that user are removed (clear queue) + + - When a notification is processed to be sent to a user and there are other scheduled notifications: + - All the scheduled notifications for that user are rescheduled to now - The current notification goes back to the queue to be processed ordered by creation date - Buttons to check/uncheck all users and channels on import ([#17207](https://github.com/RocketChat/Rocket.Chat/pull/17207)) @@ -2354,7 +4341,7 @@ - Redesign Administration > Import ([#17289](https://github.com/RocketChat/Rocket.Chat/pull/17289)) -- User gets UI feedback when message is pinned or unpinned ([#16056](https://github.com/RocketChat/Rocket.Chat/pull/16056)) +- User gets UI feedback when message is pinned or unpinned ([#16056](https://github.com/RocketChat/Rocket.Chat/pull/16056) by [@ashwaniYDV](https://github.com/ashwaniYDV)) ### 🐛 Bug fixes @@ -2365,7 +4352,7 @@ - 404 error when clicking an username ([#17275](https://github.com/RocketChat/Rocket.Chat/pull/17275)) -- Admin panel custom sounds, multiple sound playback fix and added single play/pause button ([#16215](https://github.com/RocketChat/Rocket.Chat/pull/16215)) +- Admin panel custom sounds, multiple sound playback fix and added single play/pause button ([#16215](https://github.com/RocketChat/Rocket.Chat/pull/16215) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Allow Screensharing in BBB Iframe ([#17290](https://github.com/RocketChat/Rocket.Chat/pull/17290) by [@wolbernd](https://github.com/wolbernd)) @@ -2383,17 +4370,17 @@ - Email not verified message ([#16236](https://github.com/RocketChat/Rocket.Chat/pull/16236)) -- Fixed email sort button in directory -> users ([#16606](https://github.com/RocketChat/Rocket.Chat/pull/16606)) +- Fixed email sort button in directory -> users ([#16606](https://github.com/RocketChat/Rocket.Chat/pull/16606) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Global event click-message-link not fired ([#16771](https://github.com/RocketChat/Rocket.Chat/pull/16771)) - Import slack's multiple direct messages as direct rooms instead of private groups ([#17206](https://github.com/RocketChat/Rocket.Chat/pull/17206)) -- In Create a New Channel, input should be focused on channel name instead of invite users ([#16405](https://github.com/RocketChat/Rocket.Chat/pull/16405)) +- In Create a New Channel, input should be focused on channel name instead of invite users ([#16405](https://github.com/RocketChat/Rocket.Chat/pull/16405) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - LDAP users lose session on refresh ([#17302](https://github.com/RocketChat/Rocket.Chat/pull/17302)) -- No maxlength(120) defined for custom user status ([#16534](https://github.com/RocketChat/Rocket.Chat/pull/16534)) +- No maxlength(120) defined for custom user status ([#16534](https://github.com/RocketChat/Rocket.Chat/pull/16534) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Omnichannel SMS / WhatsApp integration errors due to missing location data ([#17288](https://github.com/RocketChat/Rocket.Chat/pull/17288)) @@ -2401,7 +4388,7 @@ - Prevent user from getting stuck on login, if there is some bad fname ([#17331](https://github.com/RocketChat/Rocket.Chat/pull/17331)) -- Red color error outline is not removed after password update on profile details ([#16536](https://github.com/RocketChat/Rocket.Chat/pull/16536)) +- Red color error outline is not removed after password update on profile details ([#16536](https://github.com/RocketChat/Rocket.Chat/pull/16536) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Remove properties from users.info response ([#17238](https://github.com/RocketChat/Rocket.Chat/pull/17238) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) @@ -2480,6 +4467,7 @@ - [@Nikhil713](https://github.com/Nikhil713) - [@RavenSystem](https://github.com/RavenSystem) - [@aKn1ghtOut](https://github.com/aKn1ghtOut) +- [@ashwaniYDV](https://github.com/ashwaniYDV) - [@benkroeger](https://github.com/benkroeger) - [@c0dzilla](https://github.com/c0dzilla) - [@dependabot[bot]](https://github.com/dependabot[bot]) @@ -2499,7 +4487,6 @@ - [@MartinSchoeler](https://github.com/MartinSchoeler) - [@alansikora](https://github.com/alansikora) -- [@ashwaniYDV](https://github.com/ashwaniYDV) - [@d-gubert](https://github.com/d-gubert) - [@gabriellsh](https://github.com/gabriellsh) - [@ggazzo](https://github.com/ggazzo) @@ -2663,7 +4650,7 @@ - Open the Visitor Info panel automatically when the agent enters an Omnichannel room ([#16496](https://github.com/RocketChat/Rocket.Chat/pull/16496)) -- Route to get updated roles after a date ([#16610](https://github.com/RocketChat/Rocket.Chat/pull/16610) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Route to get updated roles after a date ([#16610](https://github.com/RocketChat/Rocket.Chat/pull/16610) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@ashwaniYDV](https://github.com/ashwaniYDV)) - SAML config to allow clock drift ([#16751](https://github.com/RocketChat/Rocket.Chat/pull/16751) by [@localguru](https://github.com/localguru)) @@ -2677,7 +4664,7 @@ - Translation via MS translate ([#16363](https://github.com/RocketChat/Rocket.Chat/pull/16363) by [@mrsimpson](https://github.com/mrsimpson)) - Adds Microsoft's translation service (https://translator.microsoft.com/) as a provider for translation of messages. + Adds Microsoft's translation service (https://translator.microsoft.com/) as a provider for translation of messages. In addition to implementing the interface (similar to google and DeepL), a small change has been done in order to display the translation provider on the UI. - Two Factor authentication via email ([#15949](https://github.com/RocketChat/Rocket.Chat/pull/15949)) @@ -2693,9 +4680,9 @@ - Add option to require authentication on user's shield endpoint ([#16845](https://github.com/RocketChat/Rocket.Chat/pull/16845) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) -- Added autofocus to Directory ([#16217](https://github.com/RocketChat/Rocket.Chat/pull/16217)) +- Added autofocus to Directory ([#16217](https://github.com/RocketChat/Rocket.Chat/pull/16217) by [@ashwaniYDV](https://github.com/ashwaniYDV)) -- Added timer in video message recorder ([#16221](https://github.com/RocketChat/Rocket.Chat/pull/16221)) +- Added timer in video message recorder ([#16221](https://github.com/RocketChat/Rocket.Chat/pull/16221) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Allow login of non LDAP users when LDAP is enabled ([#16949](https://github.com/RocketChat/Rocket.Chat/pull/16949)) @@ -2705,7 +4692,7 @@ - Contextual bar autofocus ([#16915](https://github.com/RocketChat/Rocket.Chat/pull/16915)) -- Displays `Nothing found` on admin sidebar when search returns nothing ([#16255](https://github.com/RocketChat/Rocket.Chat/pull/16255)) +- Displays `Nothing found` on admin sidebar when search returns nothing ([#16255](https://github.com/RocketChat/Rocket.Chat/pull/16255) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Fallback content-type as application/octet-stream for FileSystem uploads ([#16776](https://github.com/RocketChat/Rocket.Chat/pull/16776) by [@georgmu](https://github.com/georgmu)) @@ -2727,14 +4714,14 @@ - Tab Bar actions reorder ([#17072](https://github.com/RocketChat/Rocket.Chat/pull/17072)) -- Use `rocket.cat` as default bot If `InternalHubot_Username` is undefined ([#16371](https://github.com/RocketChat/Rocket.Chat/pull/16371)) +- Use `rocket.cat` as default bot If `InternalHubot_Username` is undefined ([#16371](https://github.com/RocketChat/Rocket.Chat/pull/16371) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - User gets feedback when a message has been starred or unstarred ([#13860](https://github.com/RocketChat/Rocket.Chat/pull/13860) by [@fliptrail](https://github.com/fliptrail)) ### 🐛 Bug fixes -- "Jump to message" is rendered twice when message is starred. ([#16170](https://github.com/RocketChat/Rocket.Chat/pull/16170)) +- "Jump to message" is rendered twice when message is starred. ([#16170](https://github.com/RocketChat/Rocket.Chat/pull/16170) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - `users.setStatus` API was ignoring the user from params when trying to set status of other users ([#16128](https://github.com/RocketChat/Rocket.Chat/pull/16128) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@rm-yakovenko](https://github.com/rm-yakovenko)) @@ -2784,9 +4771,9 @@ - Federation Event ROOM_ADD_USER not being dispatched ([#16878](https://github.com/RocketChat/Rocket.Chat/pull/16878) by [@1rV1N-git](https://github.com/1rV1N-git)) -- File uploads out of threads are not visible in regular message view ([#16416](https://github.com/RocketChat/Rocket.Chat/pull/16416)) +- File uploads out of threads are not visible in regular message view ([#16416](https://github.com/RocketChat/Rocket.Chat/pull/16416) by [@ashwaniYDV](https://github.com/ashwaniYDV)) -- Flextab information is not working when clicking on visitor or agent username in Omnichannel messages ([#16797](https://github.com/RocketChat/Rocket.Chat/pull/16797)) +- Flextab information is not working when clicking on visitor or agent username in Omnichannel messages ([#16797](https://github.com/RocketChat/Rocket.Chat/pull/16797) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - ie11 support ([#16682](https://github.com/RocketChat/Rocket.Chat/pull/16682)) @@ -2826,7 +4813,7 @@ - Prune message saying `files deleted` and `messages deleted` even when singular message or file in prune ([#16322](https://github.com/RocketChat/Rocket.Chat/pull/16322) by [@ritwizsinha](https://github.com/ritwizsinha)) -- Public channel cannot be accessed via URL when 'Allow Anonymous Read' is active ([#16914](https://github.com/RocketChat/Rocket.Chat/pull/16914)) +- Public channel cannot be accessed via URL when 'Allow Anonymous Read' is active ([#16914](https://github.com/RocketChat/Rocket.Chat/pull/16914) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Race conditions on/before login ([#16989](https://github.com/RocketChat/Rocket.Chat/pull/16989)) @@ -2838,7 +4825,7 @@ - Regression: New 'app' role with no permissions when updating to 3.0.0 ([#16637](https://github.com/RocketChat/Rocket.Chat/pull/16637)) -- Remove Reply in DM from Omnichannel rooms ([#16957](https://github.com/RocketChat/Rocket.Chat/pull/16957)) +- Remove Reply in DM from Omnichannel rooms ([#16957](https://github.com/RocketChat/Rocket.Chat/pull/16957) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Remove spaces from i18n placeholders to show Personal access token ([#16724](https://github.com/RocketChat/Rocket.Chat/pull/16724) by [@harakiwi1](https://github.com/harakiwi1)) @@ -2848,7 +4835,7 @@ - SAML login errors not showing on UI ([#17219](https://github.com/RocketChat/Rocket.Chat/pull/17219)) -- Show error message if password and confirm password not equal ([#16247](https://github.com/RocketChat/Rocket.Chat/pull/16247)) +- Show error message if password and confirm password not equal ([#16247](https://github.com/RocketChat/Rocket.Chat/pull/16247) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Slackbridge-import command doesn't work ([#16645](https://github.com/RocketChat/Rocket.Chat/pull/16645) by [@antkaz](https://github.com/antkaz)) @@ -2858,7 +4845,7 @@ - Text formatted to remain within button even on screen resize ([#14136](https://github.com/RocketChat/Rocket.Chat/pull/14136) by [@Rodriq](https://github.com/Rodriq)) -- There is no option to pin a thread message by admin ([#16457](https://github.com/RocketChat/Rocket.Chat/pull/16457)) +- There is no option to pin a thread message by admin ([#16457](https://github.com/RocketChat/Rocket.Chat/pull/16457) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - TypeError when trying to load avatar of an invalid room. ([#16699](https://github.com/RocketChat/Rocket.Chat/pull/16699)) @@ -2934,7 +4921,7 @@ - Fix: Console error on login ([#16704](https://github.com/RocketChat/Rocket.Chat/pull/16704)) -- Fix: Correctly aligned input element of custom user status component ([#16151](https://github.com/RocketChat/Rocket.Chat/pull/16151)) +- Fix: Correctly aligned input element of custom user status component ([#16151](https://github.com/RocketChat/Rocket.Chat/pull/16151) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Fix: Error message on startup of multiple instances related to the metrics’ server ([#17152](https://github.com/RocketChat/Rocket.Chat/pull/17152)) @@ -2950,13 +4937,13 @@ - Fix: Padding required in the Facebook Messenger option in Livechat ([#16202](https://github.com/RocketChat/Rocket.Chat/pull/16202) by [@ritwizsinha](https://github.com/ritwizsinha)) -- Fix: Removed some hardcoded texts ([#16304](https://github.com/RocketChat/Rocket.Chat/pull/16304)) +- Fix: Removed some hardcoded texts ([#16304](https://github.com/RocketChat/Rocket.Chat/pull/16304) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Fix: StreamCast was not working correctly ([#16983](https://github.com/RocketChat/Rocket.Chat/pull/16983)) - Fixed Line break incorrectly being called apostrophe in code ([#16918](https://github.com/RocketChat/Rocket.Chat/pull/16918) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) -- Fixed translate variable in UnarchiveRoom Modal ([#16310](https://github.com/RocketChat/Rocket.Chat/pull/16310)) +- Fixed translate variable in UnarchiveRoom Modal ([#16310](https://github.com/RocketChat/Rocket.Chat/pull/16310) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Improve room types usage ([#16753](https://github.com/RocketChat/Rocket.Chat/pull/16753)) @@ -2974,7 +4961,7 @@ - New Troubleshoot section for disabling features ([#17114](https://github.com/RocketChat/Rocket.Chat/pull/17114)) -- Redirected to home when a room has been deleted instead of getting broken link(blank page) of deleted room ([#16227](https://github.com/RocketChat/Rocket.Chat/pull/16227)) +- Redirected to home when a room has been deleted instead of getting broken link(blank page) of deleted room ([#16227](https://github.com/RocketChat/Rocket.Chat/pull/16227) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Reduce notifyUser propagation ([#17088](https://github.com/RocketChat/Rocket.Chat/pull/17088)) @@ -2986,7 +4973,7 @@ - Regression: Broken Search if users without DM subscriptions are listed ([#17074](https://github.com/RocketChat/Rocket.Chat/pull/17074)) -- Regression: Can't login with 2FA over REST API when 2FA via Email is enabled ([#17128](https://github.com/RocketChat/Rocket.Chat/pull/17128)) +- Regression: Can't login with 2FA over REST API when 2FA via Email is enabled ([#17128](https://github.com/RocketChat/Rocket.Chat/pull/17128) by [@djorkaeffalexandre](https://github.com/djorkaeffalexandre)) - Regression: Check Omnichannel routing system before emitting queue changes ([#17087](https://github.com/RocketChat/Rocket.Chat/pull/17087)) @@ -3066,8 +5053,10 @@ - [@aKn1ghtOut](https://github.com/aKn1ghtOut) - [@antkaz](https://github.com/antkaz) - [@aryamanpuri](https://github.com/aryamanpuri) +- [@ashwaniYDV](https://github.com/ashwaniYDV) - [@col-panic](https://github.com/col-panic) - [@dependabot[bot]](https://github.com/dependabot[bot]) +- [@djorkaeffalexandre](https://github.com/djorkaeffalexandre) - [@fliptrail](https://github.com/fliptrail) - [@georgmu](https://github.com/georgmu) - [@harakiwi1](https://github.com/harakiwi1) @@ -3088,9 +5077,7 @@ - [@PrajvalRaval](https://github.com/PrajvalRaval) - [@Sing-Li](https://github.com/Sing-Li) -- [@ashwaniYDV](https://github.com/ashwaniYDV) - [@d-gubert](https://github.com/d-gubert) -- [@djorkaeffalexandre](https://github.com/djorkaeffalexandre) - [@engelgabriel](https://github.com/engelgabriel) - [@gabriellsh](https://github.com/gabriellsh) - [@geekgonecrazy](https://github.com/geekgonecrazy) @@ -3579,7 +5566,7 @@ - Login change language button ([#16085](https://github.com/RocketChat/Rocket.Chat/pull/16085) by [@mariaeduardacunha](https://github.com/mariaeduardacunha)) -- Mail Msg Cancel button not closing the flexbar ([#16263](https://github.com/RocketChat/Rocket.Chat/pull/16263)) +- Mail Msg Cancel button not closing the flexbar ([#16263](https://github.com/RocketChat/Rocket.Chat/pull/16263) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Missing edited icon in newly created messages ([#16484](https://github.com/RocketChat/Rocket.Chat/pull/16484)) @@ -3597,7 +5584,7 @@ - SafePorts: Ports 80, 8080 & 443 linked to respective protocols (#16108) ([#16108](https://github.com/RocketChat/Rocket.Chat/pull/16108)) -- Save password without confirmation ([#16060](https://github.com/RocketChat/Rocket.Chat/pull/16060)) +- Save password without confirmation ([#16060](https://github.com/RocketChat/Rocket.Chat/pull/16060) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Send message with pending messages ([#16474](https://github.com/RocketChat/Rocket.Chat/pull/16474)) @@ -3605,7 +5592,7 @@ - Slack CSV User Importer ([#16253](https://github.com/RocketChat/Rocket.Chat/pull/16253)) -- The "click to load" text is hard-coded and not translated. ([#16142](https://github.com/RocketChat/Rocket.Chat/pull/16142)) +- The "click to load" text is hard-coded and not translated. ([#16142](https://github.com/RocketChat/Rocket.Chat/pull/16142) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Thread message icon overlapping text ([#16083](https://github.com/RocketChat/Rocket.Chat/pull/16083)) @@ -3726,6 +5713,7 @@ - [@Cool-fire](https://github.com/Cool-fire) - [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@antkaz](https://github.com/antkaz) +- [@ashwaniYDV](https://github.com/ashwaniYDV) - [@aviral243](https://github.com/aviral243) - [@mariaeduardacunha](https://github.com/mariaeduardacunha) - [@mrsimpson](https://github.com/mrsimpson) @@ -3736,7 +5724,6 @@ - [@LuluGO](https://github.com/LuluGO) - [@MartinSchoeler](https://github.com/MartinSchoeler) -- [@ashwaniYDV](https://github.com/ashwaniYDV) - [@d-gubert](https://github.com/d-gubert) - [@gabriellsh](https://github.com/gabriellsh) - [@geekgonecrazy](https://github.com/geekgonecrazy) @@ -3748,6 +5735,26 @@ - [@sampaiodiego](https://github.com/sampaiodiego) - [@tassoevan](https://github.com/tassoevan) +# 2.4.14 +`2020-12-18 · 2 🐛 · 1 👩‍💻👨‍💻` + +### Engine versions +- Node: `8.17.0` +- NPM: `6.13.4` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.11.2` + +### 🐛 Bug fixes + + +- Issue with special message rendering ([#19817](https://github.com/RocketChat/Rocket.Chat/pull/19817)) + +- Problem with attachment render ([#19854](https://github.com/RocketChat/Rocket.Chat/pull/19854)) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@MartinSchoeler](https://github.com/MartinSchoeler) + # 2.4.12 `2020-05-11 · 1 🐛 · 1 👩‍💻👨‍💻` @@ -4148,7 +6155,7 @@ - Guest's name field missing when forwarding livechat rooms ([#15991](https://github.com/RocketChat/Rocket.Chat/pull/15991)) -- Importer: Variable name appearing instead of it's value ([#16010](https://github.com/RocketChat/Rocket.Chat/pull/16010)) +- Importer: Variable name appearing instead of it's value ([#16010](https://github.com/RocketChat/Rocket.Chat/pull/16010) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Incorrect translation key on Livechat Appearance template ([#15975](https://github.com/RocketChat/Rocket.Chat/pull/15975) by [@ritwizsinha](https://github.com/ritwizsinha)) @@ -4226,6 +6233,7 @@ - [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@antkaz](https://github.com/antkaz) +- [@ashwaniYDV](https://github.com/ashwaniYDV) - [@breaking-let](https://github.com/breaking-let) - [@iannuzzelli](https://github.com/iannuzzelli) - [@localguru](https://github.com/localguru) @@ -4238,7 +6246,6 @@ ### 👩‍💻👨‍💻 Core Team 🤓 - [@MartinSchoeler](https://github.com/MartinSchoeler) -- [@ashwaniYDV](https://github.com/ashwaniYDV) - [@d-gubert](https://github.com/d-gubert) - [@gabriellsh](https://github.com/gabriellsh) - [@geekgonecrazy](https://github.com/geekgonecrazy) @@ -5267,6 +7274,25 @@ - [@sampaiodiego](https://github.com/sampaiodiego) - [@tassoevan](https://github.com/tassoevan) +# 1.3.5 +`2020-12-18 · 2 🐛 · 1 👩‍💻👨‍💻` + +### Engine versions +- Node: `8.11.4` +- NPM: `6.4.1` +- Apps-Engine: `1.5.1` + +### 🐛 Bug fixes + + +- Issue with special message rendering ([#19817](https://github.com/RocketChat/Rocket.Chat/pull/19817)) + +- Problem with attachment render ([#19854](https://github.com/RocketChat/Rocket.Chat/pull/19854)) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@MartinSchoeler](https://github.com/MartinSchoeler) + # 1.3.3 `2019-11-19 · 2 🐛 · 2 👩‍💻👨‍💻` @@ -6057,7 +8083,7 @@ - [Fix] broken logo url in app.json ([#14572](https://github.com/RocketChat/Rocket.Chat/pull/14572) by [@jaredmoody](https://github.com/jaredmoody)) -- [IMPROVEMENT] Add tooltip to to notify user the purpose of back button in discussion ([#13872](https://github.com/RocketChat/Rocket.Chat/pull/13872)) +- [IMPROVEMENT] Add tooltip to to notify user the purpose of back button in discussion ([#13872](https://github.com/RocketChat/Rocket.Chat/pull/13872) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - [IMPROVEMENT] Don't group messages with different alias ([#14257](https://github.com/RocketChat/Rocket.Chat/pull/14257) by [@jungeonkim](https://github.com/jungeonkim)) @@ -6134,6 +8160,7 @@ - [@Kailash0311](https://github.com/Kailash0311) - [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@arminfelder](https://github.com/arminfelder) +- [@ashwaniYDV](https://github.com/ashwaniYDV) - [@bhardwajaditya](https://github.com/bhardwajaditya) - [@gsunit](https://github.com/gsunit) - [@jaredmoody](https://github.com/jaredmoody) @@ -6151,7 +8178,6 @@ ### 👩‍💻👨‍💻 Core Team 🤓 - [@alansikora](https://github.com/alansikora) -- [@ashwaniYDV](https://github.com/ashwaniYDV) - [@d-gubert](https://github.com/d-gubert) - [@engelgabriel](https://github.com/engelgabriel) - [@geekgonecrazy](https://github.com/geekgonecrazy) @@ -6593,7 +8619,7 @@ - Improve cloud section ([#13820](https://github.com/RocketChat/Rocket.Chat/pull/13820)) -- In home screen Rocket.Chat+ is dispalyed as Rocket.Chat ([#13784](https://github.com/RocketChat/Rocket.Chat/pull/13784)) +- In home screen Rocket.Chat+ is dispalyed as Rocket.Chat ([#13784](https://github.com/RocketChat/Rocket.Chat/pull/13784) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Legal pages' style ([#13677](https://github.com/RocketChat/Rocket.Chat/pull/13677)) @@ -6781,7 +8807,7 @@ - Added federation ping, loopback and dashboard ([#14007](https://github.com/RocketChat/Rocket.Chat/pull/14007)) -- Adds French translation of Personal Access Token ([#13779](https://github.com/RocketChat/Rocket.Chat/pull/13779)) +- Adds French translation of Personal Access Token ([#13779](https://github.com/RocketChat/Rocket.Chat/pull/13779) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Allow set env var METEOR_OPLOG_TOO_FAR_BEHIND ([#14017](https://github.com/RocketChat/Rocket.Chat/pull/14017)) @@ -6975,7 +9001,7 @@ - Regression: wrong expression at messageBox.actions.remove() ([#14192](https://github.com/RocketChat/Rocket.Chat/pull/14192)) -- Remove bitcoin link in Readme.md since the link is broken ([#13935](https://github.com/RocketChat/Rocket.Chat/pull/13935)) +- Remove bitcoin link in Readme.md since the link is broken ([#13935](https://github.com/RocketChat/Rocket.Chat/pull/13935) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - Remove dependency of RC namespace in rc-livechat/imports, lib, server/api, server/hooks and server/lib ([#13379](https://github.com/RocketChat/Rocket.Chat/pull/13379) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) @@ -7073,6 +9099,7 @@ - [@Peym4n](https://github.com/Peym4n) - [@TkTech](https://github.com/TkTech) - [@algomaster99](https://github.com/algomaster99) +- [@ashwaniYDV](https://github.com/ashwaniYDV) - [@bhardwajaditya](https://github.com/bhardwajaditya) - [@bsharrow](https://github.com/bsharrow) - [@fliptrail](https://github.com/fliptrail) @@ -7115,7 +9142,6 @@ - [@LuluGO](https://github.com/LuluGO) - [@alansikora](https://github.com/alansikora) -- [@ashwaniYDV](https://github.com/ashwaniYDV) - [@d-gubert](https://github.com/d-gubert) - [@engelgabriel](https://github.com/engelgabriel) - [@geekgonecrazy](https://github.com/geekgonecrazy) @@ -14177,4 +16203,4 @@ - [@graywolf336](https://github.com/graywolf336) - [@marceloschmidt](https://github.com/marceloschmidt) - [@rodrigok](https://github.com/rodrigok) -- [@sampaiodiego](https://github.com/sampaiodiego) \ No newline at end of file +- [@sampaiodiego](https://github.com/sampaiodiego) diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md new file mode 100644 index 0000000000000..9ab0a11c366a4 --- /dev/null +++ b/KNOWN_ISSUES.md @@ -0,0 +1,6 @@ +## `registerFieldTemplate` is deprecated + hmm it's true :(, we don't encourage this type of customization anymore, it ends up opening some security holes, we prefer the use of UIKit. If you feel any difficulty let us know +## `attachment.actions` is deprecated + same reason above +## `attachment PDF preview` is no longer being rendered + it is temporarily disabled, nowadays is huge effort render the previews and requires the download of the entire file on the client. We are working to improve this :) \ No newline at end of file diff --git a/README.md b/README.md index 0cf726caac006..0a7ced500f871 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,6 @@ * [IndieHosters](#indiehosters) * [Ubuntu 16.04](#ubuntu-1604) * [Cloudron.io](#cloudronio) - * [Heroku](#heroku) * [Helm Kubernetes](#helm-kubernetes) * [Scalingo](#scalingo) * [Sloppy.io](#sloppyio) @@ -142,11 +141,6 @@ Install Rocket.Chat on [Cloudron](https://cloudron.io) Smartserver: [![Install](https://cloudron.io/img/button.svg)](https://cloudron.io/button.html?app=chat.rocket.cloudronapp) -## Heroku -Host your own Rocket.Chat server for **FREE** with [One-Click Deploy](https://heroku.com/deploy). - -[![Deploy](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy?template=https://github.com/RocketChat/Rocket.Chat/tree/master) - ## Helm Kubernetes Deploy on Kubernetes using the official [helm chart](https://github.com/helm/charts/tree/master/stable/rocketchat). diff --git a/app/2fa/client/TOTPCrowd.js b/app/2fa/client/TOTPCrowd.js new file mode 100644 index 0000000000000..44a08fe8b69f4 --- /dev/null +++ b/app/2fa/client/TOTPCrowd.js @@ -0,0 +1,35 @@ +import { Meteor } from 'meteor/meteor'; +import { Accounts } from 'meteor/accounts-base'; + +import { Utils2fa } from './lib/2fa'; +import '../../crowd/client/index'; + +Meteor.loginWithCrowdAndTOTP = function(username, password, code, callback) { + const loginRequest = { + crowd: true, + username, + crowdPassword: password, + }; + + Accounts.callLoginMethod({ + methodArguments: [{ + totp: { + login: loginRequest, + code, + }, + }], + userCallback(error) { + if (error) { + Utils2fa.reportError(error, callback); + } else { + callback && callback(); + } + }, + }); +}; + +const { loginWithCrowd } = Meteor; + +Meteor.loginWithCrowd = function(username, password, callback) { + Utils2fa.overrideLoginMethod(loginWithCrowd, [username, password], callback, Meteor.loginWithCrowdAndTOTP); +}; diff --git a/app/2fa/client/TOTPGoogle.js b/app/2fa/client/TOTPGoogle.js new file mode 100644 index 0000000000000..24c7c66b6908c --- /dev/null +++ b/app/2fa/client/TOTPGoogle.js @@ -0,0 +1,41 @@ +import { Meteor } from 'meteor/meteor'; +import { Accounts } from 'meteor/accounts-base'; +import { Google } from 'meteor/google-oauth'; +import _ from 'underscore'; + +import { Utils2fa } from './lib/2fa'; + +const loginWithGoogleAndTOTP = function(options, code, callback) { + // support a callback without options + if (!callback && typeof options === 'function') { + callback = options; + options = null; + } + + if (Meteor.isCordova && Google.signIn) { + // After 20 April 2017, Google OAuth login will no longer work from + // a WebView, so Cordova apps must use Google Sign-In instead. + // https://github.com/meteor/meteor/issues/8253 + Google.signIn(options, callback); + return; + } // Use Google's domain-specific login page if we want to restrict creation to + // a particular email domain. (Don't use it if restrictCreationByEmailDomain + // is a function.) Note that all this does is change Google's UI --- + // accounts-base/accounts_server.js still checks server-side that the server + // has the proper email address after the OAuth conversation. + + + if (typeof Accounts._options.restrictCreationByEmailDomain === 'string') { + options = _.extend({}, options || {}); + options.loginUrlParameters = _.extend({}, options.loginUrlParameters || {}); + options.loginUrlParameters.hd = Accounts._options.restrictCreationByEmailDomain; + } + + const credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback, code); + Google.requestCredential(options, credentialRequestCompleteCallback); +}; + +const { loginWithGoogle } = Meteor; +Meteor.loginWithGoogle = function(options, cb) { + Utils2fa.overrideLoginMethod(loginWithGoogle, [options], cb, loginWithGoogleAndTOTP); +}; diff --git a/app/2fa/client/TOTPLDAP.js b/app/2fa/client/TOTPLDAP.js new file mode 100644 index 0000000000000..0d74719f7cf87 --- /dev/null +++ b/app/2fa/client/TOTPLDAP.js @@ -0,0 +1,51 @@ +import { Meteor } from 'meteor/meteor'; +import { Accounts } from 'meteor/accounts-base'; + +import { Utils2fa } from './lib/2fa'; +import '../../ldap/client/loginHelper'; + +Meteor.loginWithLDAPAndTOTP = function(...args) { + // Pull username and password + const username = args.shift(); + const ldapPass = args.shift(); + + // Check if last argument is a function. if it is, pop it off and set callback to it + const callback = typeof args[args.length - 1] === 'function' ? args.pop() : null; + // The last argument before the callback is the totp code + const code = args.pop(); + + // if args still holds options item, grab it + const ldapOptions = args.length > 0 ? args.shift() : {}; + + // Set up loginRequest object + const loginRequest = { + ldap: true, + username, + ldapPass, + ldapOptions, + }; + + Accounts.callLoginMethod({ + methodArguments: [{ + totp: { + login: loginRequest, + code, + }, + }], + userCallback(error) { + if (error) { + Utils2fa.reportError(error, callback); + } else { + callback && callback(); + } + }, + }); +}; + +const { loginWithLDAP } = Meteor; + +Meteor.loginWithLDAP = function(...args) { + const callback = typeof args[args.length - 1] === 'function' ? args.pop() : null; + + Utils2fa.overrideLoginMethod(loginWithLDAP, args, callback, Meteor.loginWithLDAPAndTOTP, args[0]); +}; diff --git a/app/2fa/client/TOTPOAuth.js b/app/2fa/client/TOTPOAuth.js new file mode 100644 index 0000000000000..685c28eaa186f --- /dev/null +++ b/app/2fa/client/TOTPOAuth.js @@ -0,0 +1,138 @@ +import { Meteor } from 'meteor/meteor'; +import { Accounts } from 'meteor/accounts-base'; +import { Facebook } from 'meteor/facebook-oauth'; +import { Github } from 'meteor/github-oauth'; +import { Twitter } from 'meteor/twitter-oauth'; +import { MeteorDeveloperAccounts } from 'meteor/meteor-developer-oauth'; +import { Linkedin } from 'meteor/pauli:linkedin-oauth'; +import { OAuth } from 'meteor/oauth'; +import s from 'underscore.string'; + +import { Utils2fa } from './lib/2fa'; +import { process2faReturn } from './callWithTwoFactorRequired'; +import { CustomOAuth } from '../../custom-oauth'; + +let lastCredentialToken = null; +let lastCredentialSecret = null; + +Accounts.oauth.tryLoginAfterPopupClosed = function(credentialToken, callback, totpCode, credentialSecret = null) { + credentialSecret = credentialSecret || OAuth._retrieveCredentialSecret(credentialToken) || null; + const methodArgument = { + oauth: { + credentialToken, + credentialSecret, + }, + }; + + lastCredentialToken = credentialToken; + lastCredentialSecret = credentialSecret; + + if (totpCode && typeof totpCode === 'string') { + methodArgument.totp = { + code: totpCode, + }; + } + + Accounts.callLoginMethod({ + methodArguments: [methodArgument], + userCallback: callback && function(err) { + callback(Utils2fa.convertError(err)); + } }); +}; + +Accounts.oauth.credentialRequestCompleteHandler = function(callback, totpCode) { + return function(credentialTokenOrError) { + if (credentialTokenOrError && credentialTokenOrError instanceof Error) { + callback && callback(credentialTokenOrError); + } else { + Accounts.oauth.tryLoginAfterPopupClosed(credentialTokenOrError, callback, totpCode); + } + }; +}; + +const createOAuthTotpLoginMethod = (credentialProvider) => (options, code, callback) => { + // support a callback without options + if (!callback && typeof options === 'function') { + callback = options; + options = null; + } + + if (lastCredentialToken && lastCredentialSecret) { + Accounts.oauth.tryLoginAfterPopupClosed(lastCredentialToken, callback, code, lastCredentialSecret); + } else { + const provider = (credentialProvider && credentialProvider()) || this; + const credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback, code); + provider.requestCredential(options, credentialRequestCompleteCallback); + } + + lastCredentialToken = null; + lastCredentialSecret = null; +}; + +const loginWithOAuthTokenAndTOTP = createOAuthTotpLoginMethod(); + +const loginWithFacebookAndTOTP = createOAuthTotpLoginMethod(() => Facebook); +const { loginWithFacebook } = Meteor; +Meteor.loginWithFacebook = function(options, cb) { + Utils2fa.overrideLoginMethod(loginWithFacebook, [options], cb, loginWithFacebookAndTOTP); +}; + +const loginWithGithubAndTOTP = createOAuthTotpLoginMethod(() => Github); +const { loginWithGithub } = Meteor; +Meteor.loginWithGithub = function(options, cb) { + Utils2fa.overrideLoginMethod(loginWithGithub, [options], cb, loginWithGithubAndTOTP); +}; + +const loginWithMeteorDeveloperAccountAndTOTP = createOAuthTotpLoginMethod(() => MeteorDeveloperAccounts); +const { loginWithMeteorDeveloperAccount } = Meteor; +Meteor.loginWithMeteorDeveloperAccount = function(options, cb) { + Utils2fa.overrideLoginMethod(loginWithMeteorDeveloperAccount, [options], cb, loginWithMeteorDeveloperAccountAndTOTP); +}; + +const loginWithTwitterAndTOTP = createOAuthTotpLoginMethod(() => Twitter); +const { loginWithTwitter } = Meteor; +Meteor.loginWithTwitter = function(options, cb) { + Utils2fa.overrideLoginMethod(loginWithTwitter, [options], cb, loginWithTwitterAndTOTP); +}; + +const loginWithLinkedinAndTOTP = createOAuthTotpLoginMethod(() => Linkedin); +const { loginWithLinkedin } = Meteor; +Meteor.loginWithLinkedin = function(options, cb) { + Utils2fa.overrideLoginMethod(loginWithLinkedin, [options], cb, loginWithLinkedinAndTOTP); +}; + +Accounts.onPageLoadLogin((loginAttempt) => { + if (loginAttempt?.error?.error !== 'totp-required') { + return; + } + + const { methodArguments } = loginAttempt; + if (!methodArguments?.length) { + return; + } + + const oAuthArgs = methodArguments.find((arg) => arg.oauth); + const { credentialToken, credentialSecret } = oAuthArgs.oauth; + const cb = loginAttempt.userCallback; + + process2faReturn({ + error: loginAttempt.error, + originalCallback: cb, + onCode: (code) => { + Accounts.oauth.tryLoginAfterPopupClosed(credentialToken, cb, code, credentialSecret); + }, + }); +}); + +const oldConfigureLogin = CustomOAuth.prototype.configureLogin; +CustomOAuth.prototype.configureLogin = function(...args) { + const loginWithService = `loginWith${ s.capitalize(this.name) }`; + + oldConfigureLogin.apply(this, args); + + const oldMethod = Meteor[loginWithService]; + + Meteor[loginWithService] = function(options, cb) { + Utils2fa.overrideLoginMethod(oldMethod, [options], cb, loginWithOAuthTokenAndTOTP); + }; +}; diff --git a/app/2fa/client/TOTPPassword.js b/app/2fa/client/TOTPPassword.js index cf10bc405816c..430049560bac1 100644 --- a/app/2fa/client/TOTPPassword.js +++ b/app/2fa/client/TOTPPassword.js @@ -2,17 +2,10 @@ import { Meteor } from 'meteor/meteor'; import { Accounts } from 'meteor/accounts-base'; import toastr from 'toastr'; +import { Utils2fa } from './lib/2fa'; import { t } from '../../utils'; import { process2faReturn } from './callWithTwoFactorRequired'; -function reportError(error, callback) { - if (callback) { - callback(error); - } else { - throw error; - } -} - Meteor.loginWithPasswordAndTOTP = function(selector, password, code, callback) { if (typeof selector === 'string') { if (selector.indexOf('@') === -1) { @@ -34,7 +27,7 @@ Meteor.loginWithPasswordAndTOTP = function(selector, password, code, callback) { }], userCallback(error) { if (error) { - reportError(error, callback); + Utils2fa.reportError(error, callback); } else { callback && callback(); } diff --git a/app/2fa/client/TOTPSaml.js b/app/2fa/client/TOTPSaml.js new file mode 100644 index 0000000000000..94c2673b8c5f0 --- /dev/null +++ b/app/2fa/client/TOTPSaml.js @@ -0,0 +1,32 @@ +import { Meteor } from 'meteor/meteor'; +import { Accounts } from 'meteor/accounts-base'; + +import { Utils2fa } from './lib/2fa'; +import '../../meteor-accounts-saml/client/saml_client'; + +Meteor.loginWithSamlTokenAndTOTP = function(credentialToken, code, callback) { + Accounts.callLoginMethod({ + methodArguments: [{ + totp: { + login: { + saml: true, + credentialToken, + }, + code, + }, + }], + userCallback(error) { + if (error) { + Utils2fa.reportError(error, callback); + } else { + callback && callback(); + } + }, + }); +}; + +const { loginWithSamlToken } = Meteor; + +Meteor.loginWithSamlToken = function(options, callback) { + Utils2fa.overrideLoginMethod(loginWithSamlToken, [options], callback, Meteor.loginWithSamlTokenAndTOTP); +}; diff --git a/app/2fa/client/index.js b/app/2fa/client/index.js index 1ad86d365b7b0..24fd7cc729461 100644 --- a/app/2fa/client/index.js +++ b/app/2fa/client/index.js @@ -1,2 +1,7 @@ import './callWithTwoFactorRequired'; import './TOTPPassword'; +import './TOTPOAuth'; +import './TOTPGoogle'; +import './TOTPSaml'; +import './TOTPLDAP'; +import './TOTPCrowd'; diff --git a/app/2fa/client/lib/2fa.js b/app/2fa/client/lib/2fa.js new file mode 100644 index 0000000000000..bef17e6979ec4 --- /dev/null +++ b/app/2fa/client/lib/2fa.js @@ -0,0 +1,51 @@ +import { Meteor } from 'meteor/meteor'; +import toastr from 'toastr'; +import { Accounts } from 'meteor/accounts-base'; + +import { t } from '../../../utils/client'; +import { process2faReturn } from '../callWithTwoFactorRequired'; + +export class Utils2fa { + static reportError(error, callback) { + if (callback) { + callback(error); + } else { + throw error; + } + } + + static convertError(err) { + if (err && err instanceof Meteor.Error && err.error === Accounts.LoginCancelledError.numericError) { + return new Accounts.LoginCancelledError(err.reason); + } + + return err; + } + + static overrideLoginMethod(loginMethod, loginArgs, cb, loginMethodTOTP, emailOrUsername) { + loginMethod.apply(this, loginArgs.concat([(error) => { + if (!error || error.error !== 'totp-required') { + return cb(error); + } + + process2faReturn({ + error, + emailOrUsername, + originalCallback: cb, + onCode: (code) => { + loginMethodTOTP && loginMethodTOTP.apply(this, loginArgs.concat([code, (error) => { + if (error) { + console.log(error); + } + if (error && error.error === 'totp-invalid') { + toastr.error(t('Invalid_two_factor_code')); + cb(); + } else { + cb(error); + } + }])); + }, + }); + }])); + } +} diff --git a/app/2fa/server/code/ICodeCheck.ts b/app/2fa/server/code/ICodeCheck.ts index 3c8f6a896fc3a..3cdd9fb6f4e7e 100644 --- a/app/2fa/server/code/ICodeCheck.ts +++ b/app/2fa/server/code/ICodeCheck.ts @@ -9,9 +9,9 @@ export interface IProcessInvalidCodeResult { export interface ICodeCheck { readonly name: string; - isEnabled(user: IUser): boolean; + isEnabled(user: IUser, force?: boolean): boolean; - verify(user: IUser, code: string): boolean; + verify(user: IUser, code: string, force?: boolean): boolean; processInvalidCode(user: IUser): IProcessInvalidCodeResult; } diff --git a/app/2fa/server/code/PasswordCheckFallback.ts b/app/2fa/server/code/PasswordCheckFallback.ts index ad11f02711677..ed6a3898d9a88 100644 --- a/app/2fa/server/code/PasswordCheckFallback.ts +++ b/app/2fa/server/code/PasswordCheckFallback.ts @@ -7,7 +7,10 @@ import { IUser } from '../../../../definition/IUser'; export class PasswordCheckFallback implements ICodeCheck { public readonly name = 'password'; - public isEnabled(user: IUser): boolean { + public isEnabled(user: IUser, force: boolean): boolean { + if (force) { + return true; + } // TODO: Remove this setting for version 4.0 forcing the // password fallback for who has password set. if (settings.get('Accounts_TwoFactorAuthentication_Enforce_Password_Fallback')) { @@ -16,8 +19,8 @@ export class PasswordCheckFallback implements ICodeCheck { return false; } - public verify(user: IUser, code: string): boolean { - if (!this.isEnabled(user)) { + public verify(user: IUser, code: string, force: boolean): boolean { + if (!this.isEnabled(user, force)) { return false; } diff --git a/app/2fa/server/code/index.ts b/app/2fa/server/code/index.ts index 6bf56df20d66f..c708400c80d6c 100644 --- a/app/2fa/server/code/index.ts +++ b/app/2fa/server/code/index.ts @@ -15,6 +15,7 @@ import { IMethodConnection } from '../../../../definition/IMethodThisType'; export interface ITwoFactorOptions { disablePasswordFallback?: boolean; disableRememberMe?: boolean; + requireSecondFactor?: boolean; // whether any two factor should be required } export const totpCheck = new TOTPCheck(); @@ -83,6 +84,11 @@ export function isAuthorizedForToken(connection: IMethodConnection, user: IUser, return false; } + // if any two factor is required, early abort + if (options.requireSecondFactor) { + return false; + } + if (tokenObject.bypassTwoFactor === true) { return true; } @@ -131,7 +137,29 @@ interface ICheckCodeForUser { connection?: IMethodConnection; } -function _checkCodeForUser({ user, code, method, options = {}, connection }: ICheckCodeForUser): boolean { +const getSecondFactorMethod = (user: IUser, method: string | undefined, options: ITwoFactorOptions): ICodeCheck | undefined => { + // try first getting one of the available methods or the one that was already provided + const selectedMethod = getMethodByNameOrFirstActiveForUser(user, method); + if (selectedMethod) { + return selectedMethod; + } + + // if none found but a second factor is required, chose the password check + if (options.requireSecondFactor) { + return passwordCheckFallback; + } + + // check if password fallback is enabled + if (!options.disablePasswordFallback && passwordCheckFallback.isEnabled(user, !!options.requireSecondFactor)) { + return passwordCheckFallback; + } +}; + +export function checkCodeForUser({ user, code, method, options = {}, connection }: ICheckCodeForUser): boolean { + if (process.env.TEST_MODE && !options.requireSecondFactor) { + return true; + } + if (typeof user === 'string') { user = getUserForCheck(user); } @@ -145,13 +173,10 @@ function _checkCodeForUser({ user, code, method, options = {}, connection }: ICh return true; } - let selectedMethod = getMethodByNameOrFirstActiveForUser(user, method); - + // select a second factor method or return if none is found/available + const selectedMethod = getSecondFactorMethod(user, method, options); if (!selectedMethod) { - if (options.disablePasswordFallback || !passwordCheckFallback.isEnabled(user)) { - return true; - } - selectedMethod = passwordCheckFallback; + return true; } if (!code) { @@ -161,7 +186,7 @@ function _checkCodeForUser({ user, code, method, options = {}, connection }: ICh throw new Meteor.Error('totp-required', 'TOTP Required', { method: selectedMethod.name, ...data, availableMethods }); } - const valid = selectedMethod.verify(user, code); + const valid = selectedMethod.verify(user, code, options.requireSecondFactor); if (!valid) { throw new Meteor.Error('totp-invalid', 'TOTP Invalid', { method: selectedMethod.name }); @@ -173,5 +198,3 @@ function _checkCodeForUser({ user, code, method, options = {}, connection }: ICh return true; } - -export const checkCodeForUser = process.env.TEST_MODE ? (): boolean => true : _checkCodeForUser; diff --git a/app/2fa/server/loginHandler.js b/app/2fa/server/loginHandler.js index 7138a7b3d61f6..ae85326bb1966 100644 --- a/app/2fa/server/loginHandler.js +++ b/app/2fa/server/loginHandler.js @@ -1,4 +1,7 @@ +import { Meteor } from 'meteor/meteor'; import { Accounts } from 'meteor/accounts-base'; +import { OAuth } from 'meteor/oauth'; +import { check } from 'meteor/check'; import { callbacks } from '../../callbacks'; import { checkCodeForUser } from './code/index'; @@ -12,11 +15,70 @@ Accounts.registerLoginHandler('totp', function(options) { }); callbacks.add('onValidateLogin', (login) => { - if (login.type !== 'password') { - return; + if (login.type === 'resume' || login.type === 'proxy') { + return login; + } + + const [loginArgs] = login.methodArguments; + // CAS login doesn't yet support 2FA. + if (loginArgs.cas) { + return login; } - const { totp } = login.methodArguments[0]; + const { totp } = loginArgs; checkCodeForUser({ user: login.user, code: totp && totp.code, options: { disablePasswordFallback: true } }); + + return login; }, callbacks.priority.MEDIUM, '2fa'); + +const recreateError = (errorDoc) => { + let error; + + if (errorDoc.meteorError) { + error = new Meteor.Error(); + delete errorDoc.meteorError; + } else { + error = new Error(); + } + + Object.getOwnPropertyNames(errorDoc).forEach((key) => { + error[key] = errorDoc[key]; + }); + return error; +}; + +OAuth._retrievePendingCredential = function(key, ...args) { + const credentialSecret = args.length > 0 && args[0] !== undefined ? args[0] : null; + check(key, String); + + const pendingCredential = OAuth._pendingCredentials.findOne({ + key, + credentialSecret, + }); + + if (!pendingCredential) { + return; + } + + if (pendingCredential.credential.error) { + OAuth._pendingCredentials.remove({ + _id: pendingCredential._id, + }); + return recreateError(pendingCredential.credential.error); + } + + // Work-around to make the credentials reusable for 2FA + const future = new Date(); + future.setMinutes(future.getMinutes() + 2); + + OAuth._pendingCredentials.update({ + _id: pendingCredential._id, + }, { + $set: { + createdAt: future, + }, + }); + + return OAuth.openSecret(pendingCredential.credential); +}; diff --git a/app/action-links/client/index.js b/app/action-links/client/index.js index c09886562a2d9..89bf0fccfb2cc 100644 --- a/app/action-links/client/index.js +++ b/app/action-links/client/index.js @@ -1,6 +1,4 @@ import { actionLinks } from './lib/actionLinks'; -import './init'; -import './stylesheets/actionLinks.css'; export { actionLinks, diff --git a/app/action-links/client/init.js b/app/action-links/client/init.js deleted file mode 100644 index b5f218b1ca182..0000000000000 --- a/app/action-links/client/init.js +++ /dev/null @@ -1,33 +0,0 @@ -import { Blaze } from 'meteor/blaze'; -import { Template } from 'meteor/templating'; - -import { handleError } from '../../utils/client'; -import { fireGlobalEvent, Layout } from '../../ui-utils/client'; -import { messageArgs } from '../../ui-utils/client/lib/messageArgs'; -import { actionLinks } from './lib/actionLinks'; - - -Template.room.events({ - 'click [data-actionlink]'(event, instance) { - event.preventDefault(); - event.stopPropagation(); - - const data = Blaze.getData(event.currentTarget); - const { msg } = messageArgs(data); - if (Layout.isEmbedded()) { - return fireGlobalEvent('click-action-link', { - actionlink: $(event.currentTarget).data('actionlink'), - value: msg._id, - message: msg, - }); - } - - if (msg._id) { - actionLinks.run($(event.currentTarget).data('actionlink'), msg._id, instance, (err) => { - if (err) { - handleError(err); - } - }); - } - }, -}); diff --git a/app/action-links/client/stylesheets/actionLinks.css b/app/action-links/client/stylesheets/actionLinks.css deleted file mode 100644 index 1b5a9977c5f27..0000000000000 --- a/app/action-links/client/stylesheets/actionLinks.css +++ /dev/null @@ -1,32 +0,0 @@ -.message { - & .actionLinks { - margin-top: 4px; - margin-bottom: 4px; - padding: 0; - - text-align: center; - - & li { - position: relative; - - display: inline; - - padding-right: 2px; - - list-style: none; - - cursor: pointer; - - & .action-link { - margin: 0 2px; - padding: 5px; - - border-radius: 7px; - } - } - - & li:last-child::after { - content: none; - } - } -} diff --git a/app/api/server/api.d.ts b/app/api/server/api.d.ts new file mode 100644 index 0000000000000..9e968448bbc0c --- /dev/null +++ b/app/api/server/api.d.ts @@ -0,0 +1,6 @@ +import { APIClass } from '.'; + +export declare const API: { + v1: APIClass; + default: APIClass; +}; diff --git a/app/api/server/api.js b/app/api/server/api.js index d717396a49867..905049f62367a 100644 --- a/app/api/server/api.js +++ b/app/api/server/api.js @@ -432,6 +432,7 @@ export class APIClass extends Restivus { const loginCompatibility = (bodyParams, request) => { // Grab the username or email that the user is logging in with const { user, username, email, password, code: bodyCode } = bodyParams; + let usernameToLDAPLogin = ''; if (password == null) { return bodyParams; @@ -449,10 +450,13 @@ export class APIClass extends Restivus { if (typeof user === 'string') { auth.user = user.includes('@') ? { email: user } : { username: user }; + usernameToLDAPLogin = user; } else if (username) { auth.user = { username }; + usernameToLDAPLogin = username; } else if (email) { auth.user = { email }; + usernameToLDAPLogin = email; } if (auth.user == null) { @@ -466,11 +470,21 @@ export class APIClass extends Restivus { }; } + const objectToLDAPLogin = { + ldap: true, + username: usernameToLDAPLogin, + ldapPass: auth.password, + ldapOptions: {}, + }; + if (settings.get('LDAP_Enable') && !code) { + return objectToLDAPLogin; + } + if (code) { return { totp: { code, - login: auth, + login: settings.get('LDAP_Enable') ? objectToLDAPLogin : auth, }, }; } @@ -639,20 +653,51 @@ API = { }; const defaultOptionsEndpoint = function _defaultOptionsEndpoint() { - if (this.request.method === 'OPTIONS' && this.request.headers['access-control-request-method']) { - if (settings.get('API_Enable_CORS') === true) { - this.response.writeHead(200, { - 'Access-Control-Allow-Origin': settings.get('API_CORS_Origin'), - 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, HEAD, PATCH', - 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept, X-User-Id, X-Auth-Token, x-visitor-token, Authorization', - }); - } else { - this.response.writeHead(405); - this.response.write('CORS not enabled. Go to "Admin > General > REST Api" to enable it.'); - } - } else { - this.response.writeHead(404); + // check if a pre-flight request + if (!this.request.headers['access-control-request-method'] && !this.request.headers.origin) { + this.done(); + return; + } + + if (!settings.get('API_Enable_CORS')) { + this.response.writeHead(405); + this.response.write('CORS not enabled. Go to "Admin > General > REST Api" to enable it.'); + this.done(); + return; + } + + const CORSOriginSetting = String(settings.get('API_CORS_Origin')); + + const defaultHeaders = { + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, HEAD, PATCH', + 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept, X-User-Id, X-Auth-Token, x-visitor-token, Authorization', + }; + + if (CORSOriginSetting === '*') { + this.response.writeHead(200, { + 'Access-Control-Allow-Origin': '*', + ...defaultHeaders, + }); + this.done(); + return; } + + const origins = CORSOriginSetting + .trim() + .split(',') + .map((origin) => String(origin).trim().toLocaleLowerCase()); + + // if invalid origin reply without required CORS headers + if (!origins.includes(this.request.headers.origin)) { + this.done(); + return; + } + + this.response.writeHead(200, { + 'Access-Control-Allow-Origin': this.request.headers.origin, + Vary: 'Origin', + ...defaultHeaders, + }); this.done(); }; @@ -665,24 +710,6 @@ const createApi = function _createApi(_api, options = {}) { auth: getUserAuth(), }, options)); - delete _api._config.defaultHeaders['Access-Control-Allow-Origin']; - delete _api._config.defaultHeaders['Access-Control-Allow-Headers']; - delete _api._config.defaultHeaders.Vary; - - if (settings.get('API_Enable_CORS')) { - const origin = settings.get('API_CORS_Origin'); - - if (origin) { - _api._config.defaultHeaders['Access-Control-Allow-Origin'] = origin; - - if (origin !== '*') { - _api._config.defaultHeaders.Vary = 'Origin'; - } - } - - _api._config.defaultHeaders['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, X-User-Id, X-Auth-Token'; - } - return _api; }; diff --git a/app/api/server/index.js b/app/api/server/index.js index be4ade59e8cfd..82d23266efb0c 100644 --- a/app/api/server/index.js +++ b/app/api/server/index.js @@ -37,5 +37,8 @@ import './v1/webdav'; import './v1/oauthapps'; import './v1/custom-sounds'; import './v1/custom-user-status'; +import './v1/instances'; +import './v1/banners'; +import './v1/email-inbox'; export { API, APIClass, defaultRateLimiterOptions } from './api'; diff --git a/app/api/server/lib/emailInbox.js b/app/api/server/lib/emailInbox.js new file mode 100644 index 0000000000000..43d7a0e8f64ff --- /dev/null +++ b/app/api/server/lib/emailInbox.js @@ -0,0 +1,79 @@ +import { EmailInbox } from '../../../models/server/raw'; +import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; +import { Users } from '../../../models'; + +export async function findEmailInboxes({ userId, query = {}, pagination: { offset, count, sort } }) { + if (!await hasPermissionAsync(userId, 'manage-email-inbox')) { + throw new Error('error-not-allowed'); + } + const cursor = EmailInbox.find(query, { + sort: sort || { name: 1 }, + skip: offset, + limit: count, + }); + + const total = await cursor.count(); + + const emailInboxes = await cursor.toArray(); + + return { + emailInboxes, + count: emailInboxes.length, + offset, + total, + }; +} + +export async function findOneEmailInbox({ userId, _id }) { + if (!await hasPermissionAsync(userId, 'manage-email-inbox')) { + throw new Error('error-not-allowed'); + } + return EmailInbox.findOneById(_id); +} + +export async function insertOneOrUpdateEmailInbox(userId, emailInboxParams) { + const { _id, active, name, email, description, senderInfo, department, smtp, imap } = emailInboxParams; + + if (!_id) { + emailInboxParams._createdAt = new Date(); + emailInboxParams._updatedAt = new Date(); + emailInboxParams._createdBy = Users.findOne(userId, { fields: { username: 1 } }); + return EmailInbox.insertOne(emailInboxParams); + } + + const emailInbox = await findOneEmailInbox({ userId, id: _id }); + + if (!emailInbox) { + throw new Error('error-invalid-email-inbox'); + } + + const updateEmailInbox = { + $set: { + active, + name, + email, + description, + senderInfo, + smtp, + imap, + _updatedAt: new Date(), + }, + }; + + if (department === 'All') { + updateEmailInbox.$unset = { + department: 1, + }; + } else { + updateEmailInbox.$set.department = department; + } + + return EmailInbox.updateOne({ _id }, updateEmailInbox); +} + +export async function findOneEmailInboxByEmail({ userId, email }) { + if (!await hasPermissionAsync(userId, 'manage-email-inbox')) { + throw new Error('error-not-allowed'); + } + return EmailInbox.findOne({ email }); +} diff --git a/app/api/server/lib/users.js b/app/api/server/lib/users.js index f15e765ff6681..78f5adb17ad6b 100644 --- a/app/api/server/lib/users.js +++ b/app/api/server/lib/users.js @@ -1,7 +1,6 @@ -import s from 'underscore.string'; - import { Users } from '../../../models/server/raw'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; +import { escapeRegExp } from '../../../../lib/escapeRegExp'; export async function findUsersToAutocomplete({ uid, selector }) { if (!await hasPermissionAsync(uid, 'view-outside-room')) { @@ -23,7 +22,7 @@ export async function findUsersToAutocomplete({ uid, selector }) { limit: 10, }; - const users = await Users.findActiveByUsernameOrNameRegexWithExceptionsAndConditions(new RegExp(s.escapeRegExp(selector.term), 'i'), exceptions, conditions, options).toArray(); + const users = await Users.findActiveByUsernameOrNameRegexWithExceptionsAndConditions(new RegExp(escapeRegExp(selector.term), 'i'), exceptions, conditions, options).toArray(); return { items: users, diff --git a/app/api/server/v1/banners.ts b/app/api/server/v1/banners.ts new file mode 100644 index 0000000000000..07a8077d72463 --- /dev/null +++ b/app/api/server/v1/banners.ts @@ -0,0 +1,50 @@ +import { Promise } from 'meteor/promise'; +import { Meteor } from 'meteor/meteor'; +import { Match, check } from 'meteor/check'; + +import { API } from '../api'; +import { Banner } from '../../../../server/sdk'; +import { BannerPlatform } from '../../../../definition/IBanner'; + +API.v1.addRoute('banners.getNew', { authRequired: true }, { + get() { + check(this.queryParams, Match.ObjectIncluding({ + platform: String, + bid: Match.Maybe(String), + })); + + const { platform, bid: bannerId } = this.queryParams; + if (!platform) { + throw new Meteor.Error('error-missing-param', 'The required "platform" param is missing.'); + } + + if (!Object.values(BannerPlatform).includes(platform)) { + throw new Meteor.Error('error-unknown-platform', 'Platform is unknown.'); + } + + const banners = Promise.await(Banner.getNewBannersForUser(this.userId, platform, bannerId)); + + return API.v1.success({ banners }); + }, +}); + +API.v1.addRoute('banners.dismiss', { authRequired: true }, { + post() { + check(this.bodyParams, Match.ObjectIncluding({ + bannerId: String, + })); + + const { bannerId } = this.bodyParams; + + if (!bannerId || !bannerId.trim()) { + throw new Meteor.Error('error-missing-param', 'The required "bannerId" param is missing.'); + } + + try { + Promise.await(Banner.dismiss(this.userId, bannerId)); + return API.v1.success(); + } catch (e) { + return API.v1.failure(); + } + }, +}); diff --git a/app/api/server/v1/channels.js b/app/api/server/v1/channels.js index 76af348d9a6c3..baf85dac47813 100644 --- a/app/api/server/v1/channels.js +++ b/app/api/server/v1/channels.js @@ -188,7 +188,7 @@ function createChannelValidator(params) { function createChannel(userId, params) { const readOnly = typeof params.readOnly !== 'undefined' ? params.readOnly : false; - const id = Meteor.runAsUser(userId, () => Meteor.call('createChannel', params.name, params.members ? params.members : [], readOnly, params.customFields)); + const id = Meteor.runAsUser(userId, () => Meteor.call('createChannel', params.name, params.members ? params.members : [], readOnly, params.customFields, params.extraData)); return { channel: findChannelByIdOrName({ params: { roomId: id.rid }, userId: this.userId }), diff --git a/app/api/server/v1/chat.js b/app/api/server/v1/chat.js index 447c10bd5d57a..a4ed9765ebe4e 100644 --- a/app/api/server/v1/chat.js +++ b/app/api/server/v1/chat.js @@ -655,6 +655,9 @@ API.v1.addRoute('chat.getStarredMessages', { authRequired: true }, { sort, }, })); + + messages.messages = normalizeMessagesForUser(messages.messages, this.userId); + return API.v1.success(messages); }, }); diff --git a/app/api/server/v1/email-inbox.js b/app/api/server/v1/email-inbox.js new file mode 100644 index 0000000000000..e7452fc5ffe1e --- /dev/null +++ b/app/api/server/v1/email-inbox.js @@ -0,0 +1,131 @@ +import { check, Match } from 'meteor/check'; + +import { API } from '../api'; +import { findEmailInboxes, findOneEmailInbox, insertOneOrUpdateEmailInbox } from '../lib/emailInbox'; +import { hasPermission } from '../../../authorization/server/functions/hasPermission'; +import { EmailInbox } from '../../../models'; +import Users from '../../../models/server/models/Users'; +import { sendTestEmailToInbox } from '../../../../server/features/EmailInbox/EmailInbox_Outgoing'; + +API.v1.addRoute('email-inbox.list', { authRequired: true }, { + get() { + const { offset, count } = this.getPaginationItems(); + const { sort, query } = this.parseJsonQuery(); + const emailInboxes = Promise.await(findEmailInboxes({ userId: this.userId, query, pagination: { offset, count, sort } })); + + return API.v1.success(emailInboxes); + }, +}); + +API.v1.addRoute('email-inbox', { authRequired: true }, { + post() { + if (!hasPermission(this.userId, 'manage-email-inbox')) { + throw new Error('error-not-allowed'); + } + check(this.bodyParams, { + _id: Match.Maybe(String), + name: String, + email: String, + active: Boolean, + description: Match.Maybe(String), + senderInfo: Match.Maybe(String), + department: Match.Maybe(String), + smtp: Match.ObjectIncluding({ + password: String, + port: Number, + secure: Boolean, + server: String, + username: String, + }), + imap: Match.ObjectIncluding({ + password: String, + port: Number, + secure: Boolean, + server: String, + username: String, + }), + }); + + const emailInboxParams = this.bodyParams; + + const { _id } = emailInboxParams; + + Promise.await(insertOneOrUpdateEmailInbox(this.userId, emailInboxParams)); + + return API.v1.success({ _id }); + }, +}); + +API.v1.addRoute('email-inbox/:_id', { authRequired: true }, { + get() { + check(this.urlParams, { + _id: String, + }); + + const { _id } = this.urlParams; + if (!_id) { throw new Error('error-invalid-param'); } + const emailInboxes = Promise.await(findOneEmailInbox({ userId: this.userId, _id })); + + return API.v1.success(emailInboxes); + }, + delete() { + if (!hasPermission(this.userId, 'manage-email-inbox')) { + throw new Error('error-not-allowed'); + } + check(this.urlParams, { + _id: String, + }); + + const { _id } = this.urlParams; + if (!_id) { throw new Error('error-invalid-param'); } + + const emailInboxes = EmailInbox.findOneById(_id); + + if (!emailInboxes) { + return API.v1.notFound(); + } + EmailInbox.removeById(_id); + return API.v1.success({ _id }); + }, +}); + +API.v1.addRoute('email-inbox.search', { authRequired: true }, { + get() { + if (!hasPermission(this.userId, 'manage-email-inbox')) { + throw new Error('error-not-allowed'); + } + check(this.queryParams, { + email: String, + }); + + const { email } = this.queryParams; + const emailInbox = Promise.await(EmailInbox.findOne({ email })); + + return API.v1.success({ emailInbox }); + }, +}); + +API.v1.addRoute('email-inbox.send-test/:_id', { authRequired: true }, { + post() { + if (!hasPermission(this.userId, 'manage-email-inbox')) { + throw new Error('error-not-allowed'); + } + check(this.urlParams, { + _id: String, + }); + + const { _id } = this.urlParams; + if (!_id) { throw new Error('error-invalid-param'); } + const emailInbox = Promise.await(findOneEmailInbox({ userId: this.userId, _id })); + + if (!emailInbox) { + return API.v1.notFound(); + } + + const user = Users.findOneById(this.userId); + + Promise.await(sendTestEmailToInbox(emailInbox, user)); + + return API.v1.success({ _id }); + }, +}); diff --git a/app/api/server/v1/groups.js b/app/api/server/v1/groups.js index ba5002f8bbb42..c3f41db768f77 100644 --- a/app/api/server/v1/groups.js +++ b/app/api/server/v1/groups.js @@ -223,12 +223,16 @@ API.v1.addRoute('groups.create', { authRequired: true }, { if (this.bodyParams.customFields && !(typeof this.bodyParams.customFields === 'object')) { return API.v1.failure('Body param "customFields" must be an object if provided'); } + if (this.bodyParams.extraData && !(typeof this.bodyParams.extraData === 'object')) { + return API.v1.failure('Body param "extraData" must be an object if provided'); + } const readOnly = typeof this.bodyParams.readOnly !== 'undefined' ? this.bodyParams.readOnly : false; let id; + Meteor.runAsUser(this.userId, () => { - id = Meteor.call('createPrivateGroup', this.bodyParams.name, this.bodyParams.members ? this.bodyParams.members : [], readOnly, this.bodyParams.customFields); + id = Meteor.call('createPrivateGroup', this.bodyParams.name, this.bodyParams.members ? this.bodyParams.members : [], readOnly, this.bodyParams.customFields, this.bodyParams.extraData); }); return API.v1.success({ diff --git a/app/api/server/v1/import.js b/app/api/server/v1/import.js index 81f18e38eab73..614c174201d2d 100644 --- a/app/api/server/v1/import.js +++ b/app/api/server/v1/import.js @@ -95,6 +95,31 @@ API.v1.addRoute('downloadPendingFiles', { authRequired: true }, { }, }); +API.v1.addRoute('downloadPendingAvatars', { authRequired: true }, { + post() { + if (!this.userId) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'downloadPendingAvatars' }); + } + + if (!hasPermission(this.userId, 'run-import')) { + throw new Meteor.Error('not_authorized'); + } + + const importer = Importers.get('pending-avatars'); + if (!importer) { + throw new Meteor.Error('error-importer-not-defined', 'The Pending File Importer was not found.', { method: 'downloadPendingAvatars' }); + } + + importer.instance = new importer.importer(importer); // eslint-disable-line new-cap + const count = importer.instance.prepareFileCount(); + + return API.v1.success({ + success: true, + count, + }); + }, +}); + API.v1.addRoute('getCurrentImportOperation', { authRequired: true }, { get() { if (!this.userId) { diff --git a/app/api/server/v1/instances.ts b/app/api/server/v1/instances.ts new file mode 100644 index 0000000000000..e6586a7c12a73 --- /dev/null +++ b/app/api/server/v1/instances.ts @@ -0,0 +1,28 @@ +import { getInstanceConnection } from '../../../../server/stream/streamBroadcast'; +import { hasPermission } from '../../../authorization/server'; +import { API } from '../api'; +import InstanceStatus from '../../../models/server/models/InstanceStatus'; +import { IInstanceStatus } from '../../../../definition/IInstanceStatus'; + +API.v1.addRoute('instances.get', { authRequired: true }, { + get() { + if (!hasPermission(this.userId, 'view-statistics')) { + return API.v1.unauthorized(); + } + + const instances = InstanceStatus.find().fetch(); + + return API.v1.success({ + instances: instances.map((instance: IInstanceStatus) => { + const connection = getInstanceConnection(instance); + if (connection) { + delete connection.instanceRecord; + } + return { + ...instance, + connection, + }; + }), + }); + }, +}); diff --git a/app/api/server/v1/invites.js b/app/api/server/v1/invites.js index 9409458e3093a..fd17ec3661908 100644 --- a/app/api/server/v1/invites.js +++ b/app/api/server/v1/invites.js @@ -1,5 +1,3 @@ -import { Meteor } from 'meteor/meteor'; - import { API } from '../api'; import { findOrCreateInvite } from '../../../invites/server/functions/findOrCreateInvite'; import { removeInvite } from '../../../invites/server/functions/removeInvite'; @@ -46,10 +44,6 @@ API.v1.addRoute('validateInviteToken', { authRequired: false }, { post() { const { token } = this.bodyParams; - if (!token) { - throw new Meteor.Error('error-invalid-token', 'The invite token is invalid.', { method: 'validateInviteToken', field: 'token' }); - } - let valid = true; try { validateInviteToken(token); diff --git a/app/api/server/v1/misc.js b/app/api/server/v1/misc.js index 1af2c884a860f..5d06b01e495ec 100644 --- a/app/api/server/v1/misc.js +++ b/app/api/server/v1/misc.js @@ -5,7 +5,6 @@ import { check } from 'meteor/check'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { EJSON } from 'meteor/ejson'; import { DDPRateLimiter } from 'meteor/ddp-rate-limiter'; -import s from 'underscore.string'; import { hasRole, hasPermission } from '../../../authorization/server'; import { Info } from '../../../utils/server'; @@ -15,6 +14,7 @@ import { API } from '../api'; import { getDefaultUserFields } from '../../../utils/server/functions/getDefaultUserFields'; import { getURL } from '../../../utils/lib/getURL'; import { StdOut } from '../../../logger/server/streamer'; +import { escapeHTML } from '../../../../lib/escapeHTML'; // DEPRECATED @@ -49,7 +49,8 @@ API.v1.addRoute('info', { authRequired: false }, { API.v1.addRoute('me', { authRequired: true }, { get() { - return API.v1.success(this.getUserInfo(Users.findOneById(this.userId, { fields: getDefaultUserFields() }))); + const { 'services.password.bcrypt': password, ...fields } = getDefaultUserFields(); + return API.v1.success(this.getUserInfo(Users.findOneById(this.userId, { fields }))); }, }); @@ -128,9 +129,9 @@ API.v1.addRoute('shield.svg', { authRequired: false, rateLimiterOptions: { numRe const width = leftSize + rightSize; const height = 20; - channel = s.escapeHTML(channel); - text = s.escapeHTML(text); - name = s.escapeHTML(name); + channel = escapeHTML(channel); + text = escapeHTML(text); + name = escapeHTML(name); return { headers: { 'Content-Type': 'image/svg+xml;charset=utf-8' }, diff --git a/app/api/server/v1/rooms.js b/app/api/server/v1/rooms.js index 35707c919b82d..9cd0126c713b3 100644 --- a/app/api/server/v1/rooms.js +++ b/app/api/server/v1/rooms.js @@ -234,7 +234,7 @@ API.v1.addRoute('rooms.leave', { authRequired: true }, { API.v1.addRoute('rooms.createDiscussion', { authRequired: true }, { post() { - const { prid, pmid, reply, t_name, users } = this.bodyParams; + const { prid, pmid, reply, t_name, users, encrypted } = this.bodyParams; if (!prid) { return API.v1.failure('Body parameter "prid" is required.'); } @@ -245,12 +245,17 @@ API.v1.addRoute('rooms.createDiscussion', { authRequired: true }, { return API.v1.failure('Body parameter "users" must be an array.'); } + if (encrypted !== undefined && typeof encrypted !== 'boolean') { + return API.v1.failure('Body parameter "encrypted" must be a boolean when included.'); + } + const discussion = Meteor.runAsUser(this.userId, () => Meteor.call('createDiscussion', { prid, pmid, t_name, reply, users: users || [], + encrypted, })); return API.v1.success({ discussion }); diff --git a/app/api/server/v1/users.js b/app/api/server/v1/users.js index b182c36330504..a7d99d933c728 100644 --- a/app/api/server/v1/users.js +++ b/app/api/server/v1/users.js @@ -507,7 +507,15 @@ API.v1.addRoute('users.updateOwnBasicInfo', { authRequired: true }, { typedPassword: this.bodyParams.data.currentPassword, }; - Meteor.runAsUser(this.userId, () => Meteor.call('saveUserProfile', userData, this.bodyParams.customFields)); + // saveUserProfile now uses the default two factor authentication procedures, so we need to provide that + const twoFactorOptions = !userData.typedPassword + ? null + : { + twoFactorCode: userData.typedPassword, + twoFactorMethod: 'password', + }; + + Meteor.runAsUser(this.userId, () => Meteor.call('saveUserProfile', userData, this.bodyParams.customFields, twoFactorOptions)); return API.v1.success({ user: Users.findOneById(this.userId, { fields: API.v1.defaultFieldsToExclude }) }); }, @@ -614,11 +622,8 @@ API.v1.addRoute('users.forgotPassword', { authRequired: false }, { return API.v1.failure('The \'email\' param is required'); } - const emailSent = Meteor.call('sendForgotPasswordEmail', email); - if (emailSent) { - return API.v1.success(); - } - return API.v1.failure('User not found'); + Meteor.call('sendForgotPasswordEmail', email); + return API.v1.success(); }, }); @@ -779,6 +784,18 @@ API.v1.addRoute('users.requestDataDownload', { authRequired: true }, { }, }); +API.v1.addRoute('users.logoutOtherClients', { authRequired: true }, { + post() { + try { + const result = Meteor.call('logoutOtherClients'); + + return API.v1.success(result); + } catch (error) { + return API.v1.failure(error); + } + }, +}); + API.v1.addRoute('users.autocomplete', { authRequired: true }, { get() { const { selector } = this.queryParams; diff --git a/app/apps/assets/stylesheets/apps.css b/app/apps/assets/stylesheets/apps.css deleted file mode 100644 index e9776bd9a8380..0000000000000 --- a/app/apps/assets/stylesheets/apps.css +++ /dev/null @@ -1,432 +0,0 @@ -.rc-apps-section, -.rc-apps-marketplace { - display: flex; - - overflow: auto; - flex-direction: column; - - height: 100vh; - - padding: 1.25rem 2rem; - - font-size: 14px; - - &.page-settings .rc-apps-container { - a { - color: var(--rc-color-button-primary); - - font-weight: 500; - } - } - - h1 { - margin-bottom: 0; - - letter-spacing: 0; - text-transform: initial; - - color: var(--color-dark-medium); - - font-size: 22px; - font-weight: normal; - line-height: 28px; - } - - h2 { - margin-top: 10px; - margin-bottom: 0; - - letter-spacing: 0; - text-transform: initial; - - color: var(--color-dark); - - font-size: 16px; - font-weight: 500; - line-height: 24px; - } - - h3 { - margin-bottom: 0; - - text-align: left; - letter-spacing: 0; - text-transform: initial; - - color: var(--color-gray); - - font-size: 14px; - font-weight: 500; - line-height: 20px; - } - - .rc-apps-container { - margin-top: 0; - padding-bottom: 15px; - } - - .rc-apps-container__header { - padding-top: 10px; - - border-bottom: 1.5px solid var(--color-gray-lightest); - } - - /* - .js-install { - margin-top: 6px; - } */ - - .content { - /* display: block !important; */ - padding: 0 !important; - - > .rc-apps-container { - display: block; - overflow-y: scroll; - - padding: 0 !important; - } - - > .rc-apps-details { - display: block; - } - } - - .rc-apps-category { - margin-right: 8px; - padding: 8px; - - text-align: left; - letter-spacing: -0.17px; - text-transform: uppercase; - - color: #9da2a9; - border-radius: 2px; - background: var(--color-gray-lightest); - - font-size: 12px; - font-weight: 500; - } - - .app-enable-loading .loading-animation { - margin-left: 50px; - justify-content: left; - } - - .apps-error { - display: flex; - flex-direction: column; - - width: 100%; - height: calc(100% - 60px); - padding: 25px 40px; - - font-size: 45px; - align-items: center; - justify-content: center; - } - - .rc-table-avatar { - width: 40px; - height: 40px; - margin: 0 7px; - } - - .rc-table-info { - height: 40px; - margin: 0 7px; - } - - .rc-app-price { - position: relative; - top: -3px; - } - - .rc-table-td--medium { - width: 300px; - } - - .rc-table td { - padding: 0.5rem 0; - - padding-right: 10px; - } - - &__wrap-actions { - & > .loading { - display: none; - } - - &.loading { - & > .loading { - display: block; - - font-size: 11px; - font-weight: 600; - - & > .rc-icon--loading { - animation: spin 1s linear infinite; - } - } - - & > .apps-installer { - display: none; - } - } - } - - .arrow-up { - transform: rotate(180deg); - } - - &.page-settings .content .rocket-form .section { - padding: 0 2.5em; - - border-bottom: none; - - &:hover { - background-color: var(--rc-color-primary-lightest); - } - } - - .rc-table-content { - display: flex; - overflow-x: auto; - flex-direction: column; - flex: 1 1 100%; - - height: 100vh; - - margin-top: 20px; - - & .rc-form-filters { - margin: 8px 0; - } - - & .js-sort { - cursor: pointer; - - &.is-sorting .table-fake-th .rc-icon { - opacity: 1; - } - } - - & .table-fake-th { - &:hover .rc-icon { - opacity: 1; - } - - & .rc-icon { - transition: opacity 0.3s; - - opacity: 0; - - font-size: 1rem; - } - } - - & tbody .rc-table-tr .rc-apps-section__app-menu-trigger { - visibility: hidden; - } - - & tbody .rc-table-tr:hover .rc-apps-section__app-menu-trigger { - visibility: visible; - } - - & tbody .rc-table-tr:not(.table-no-click):not(.table-no-pointer):hover { - background-color: #f7f8fa; - } - - & .rc-table-info { - margin: 0; - justify-content: center; - - & .rc-table-title, - & .rc-table-subtitle { - font-size: 0.875rem; - line-height: 1.25rem; - } - - & .rc-apps-categories { - display: flex; - - height: 1.25rem; - margin: 0 -0.25rem; - align-items: center; - flex-wrap: wrap; - - & .rc-apps-category { - overflow: hidden; - flex: 0 0 auto; - - box-sizing: border-box; - margin: 0.125rem 0.25rem; - padding: 0.0625rem 0.25rem; - - text-transform: none; - text-overflow: ellipsis; - - color: var(--color-gray); - background-color: var(--color-gray-lightest); - - font-size: 0.625rem; - font-weight: 500; - line-height: 0.875rem; - } - } - } - } - - @media (width <= 700px) { - .rc-table-content { - & th:not(:first-child), - & td:not(:first-child) { - display: none; - } - } - } - - &__app-menu-trigger { - - position: relative; - - display: flex; - flex: 0 0 auto; - - margin-left: auto; - padding: 0; - - font-size: 0.875rem; - line-height: 1.25rem; - align-items: center; - appearance: none; - margin-inline-start: auto; - - &:active { - transform: translateY(2px); - - opacity: 0.9; - } - - &:active::before { - top: -2px; - } - - &::before { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - - content: ""; - cursor: pointer; - } - - & .rc-icon { - margin: 0; - } - } - - &__spinning-icon { - animation: spin 1s linear infinite; - } - - &__button--working { - opacity: 0.6; - } - - &__status { - width: 100%; - - color: var(--rc-color-primary-light); - - line-height: 40px; - - &--warning { - color: var(--rc-color-alert); - } - - &--failed { - color: var(--rc-color-error); - } - } - - &__status-column { - width: 150px; - } - - tr .rc-apps-section__table-button--hideable { - visibility: hidden; - } - - tr .rc-apps-section__table-button--working, - tr:hover .rc-apps-section__table-button--hideable { - visibility: visible; - } - - .rc-apps-section__table-button--working { - opacity: 0.6; - } -} - -.rc-game { - &__list { - .rc-table-title { - width: 135px; - } - } - - &__container { - height: calc(100% - 79px); - } - - &__close { - position: absolute; - top: -30px; - right: -25px; - - cursor: pointer; - - color: #e9ebee; - - font-size: 20px; - - &:hover { - color: white; - } - } - - &__main { - min-width: 400px; - height: 100%; - } -} - -.rc-game-modal { - &__container { - margin: -14px -14px -18px; - } - - &__main { - min-height: 730px; - } -} - -.rc-icon.game-center__invite-players-icon.game-center__invite-players-icon--plus { - font-size: 18px; - - &:hover { - color: #1d74f5; - } -} - -@keyframes play90 { - 0% { - right: -798px; - } - - 100% { - right: 2px; - } -} diff --git a/app/apps/client/gameCenter/gameCenter.js b/app/apps/client/gameCenter/gameCenter.js index c7eef85de9896..8ffee03cd9c00 100644 --- a/app/apps/client/gameCenter/gameCenter.js +++ b/app/apps/client/gameCenter/gameCenter.js @@ -3,6 +3,7 @@ import { ReactiveVar } from 'meteor/reactive-var'; import { modal } from '../../../ui-utils/client'; import { APIClient, t, handleError } from '../../../utils/client'; +import './gameCenter.html'; const getExternalComponents = async (instance) => { try { diff --git a/app/apps/client/gameCenter/tabBar.js b/app/apps/client/gameCenter/tabBar.js deleted file mode 100644 index fe5883119c97f..0000000000000 --- a/app/apps/client/gameCenter/tabBar.js +++ /dev/null @@ -1,31 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { Tracker } from 'meteor/tracker'; - -import { APIClient } from '../../../utils/client'; -import { TabBar } from '../../../ui-utils/client'; -import { settings } from '../../../settings/client'; - -import './gameCenter.html'; - -Meteor.startup(function() { - Tracker.autorun(async function() { - if (!settings.get('Apps_Game_Center_enabled')) { - return TabBar.removeButton('gameCenter'); - } - - const { externalComponents } = await APIClient.get('apps/externalComponents'); - - if (!externalComponents.length) { - return TabBar.removeButton('gameCenter'); - } - - TabBar.addButton({ - groups: ['channel', 'group', 'direct'], - id: 'gameCenter', - i18nTitle: 'Apps_Game_Center', - icon: 'game', - template: 'GameCenter', - order: -1, - }); - }); -}); diff --git a/app/apps/client/gameCenter/tabBar.ts b/app/apps/client/gameCenter/tabBar.ts new file mode 100644 index 0000000000000..e7b452c66eb39 --- /dev/null +++ b/app/apps/client/gameCenter/tabBar.ts @@ -0,0 +1,28 @@ +import { useMemo } from 'react'; + +import { useSetting } from '../../../../client/contexts/SettingsContext'; +import { addAction } from '../../../../client/views/room/lib/Toolbox'; +import { useEndpointData } from '../../../../client/hooks/useEndpointData'; +import { AsyncStatePhase } from '../../../../client/hooks/useAsyncState'; + +addAction('game-center', () => { + const enabled = useSetting('Apps_Game_Center_enabled'); + + const { value = { externalComponents: [] }, phase: state, error } = useEndpointData('/apps/externalComponents'); + + const hasExternalComponents = value && value.externalComponents.length > 0; + const hasError = !!error; + return useMemo(() => + (enabled + && state === AsyncStatePhase.LOADING + && !hasError + && hasExternalComponents + ? { + groups: ['channel', 'group', 'direct'], + id: 'game-center', + title: 'Apps_Game_Center', + icon: 'game', + template: 'GameCenter', + order: -1, + } : null), [enabled, hasError, hasExternalComponents, state]); +}); diff --git a/app/apps/client/orchestrator.js b/app/apps/client/orchestrator.js index 796737853c580..adb0e0837bd00 100644 --- a/app/apps/client/orchestrator.js +++ b/app/apps/client/orchestrator.js @@ -65,11 +65,12 @@ class AppClientOrchestrator { getAppsFromMarketplace = async () => { const appsOverviews = await APIClient.get('apps', { marketplace: 'true' }); - return appsOverviews.map(({ latest, price, pricingPlans, purchaseType }) => ({ + return appsOverviews.map(({ latest, price, pricingPlans, purchaseType, permissions }) => ({ ...latest, price, pricingPlans, purchaseType, + permissions, })); } @@ -125,20 +126,22 @@ class AppClientOrchestrator { return languages; } - installApp = async (appId, version) => { + installApp = async (appId, version, permissionsGranted) => { const { app } = await APIClient.post('apps/', { appId, marketplace: true, version, + permissionsGranted, }); return app; } - updateApp = async (appId, version) => { + updateApp = async (appId, version, permissionsGranted) => { const { app } = await APIClient.post(`apps/${ appId }`, { appId, marketplace: true, version, + permissionsGranted, }); return app; } diff --git a/app/apps/server/bridges/bridges.js b/app/apps/server/bridges/bridges.js index ed62b2c6479e2..d03160e02d0fc 100644 --- a/app/apps/server/bridges/bridges.js +++ b/app/apps/server/bridges/bridges.js @@ -2,6 +2,7 @@ import { AppBridges } from '@rocket.chat/apps-engine/server/bridges'; import { AppActivationBridge } from './activation'; import { AppDetailChangesBridge } from './details'; +import { AppCloudBridge } from './cloud'; import { AppCommandsBridge } from './commands'; import { AppApisBridge } from './api'; import { AppEnvironmentalVariableBridge } from './environmental'; @@ -39,6 +40,7 @@ export class RealAppBridges extends AppBridges { this._uploadBridge = new AppUploadBridge(orch); this._uiInteractionBridge = new UiInteractionBridge(orch); this._schedulerBridge = new AppSchedulerBridge(orch); + this._cloudWorkspaceBridge = new AppCloudBridge(orch); } getCommandBridge() { @@ -108,4 +110,8 @@ export class RealAppBridges extends AppBridges { getSchedulerBridge() { return this._schedulerBridge; } + + getCloudWorkspaceBridge() { + return this._cloudWorkspaceBridge; + } } diff --git a/app/apps/server/bridges/cloud.ts b/app/apps/server/bridges/cloud.ts new file mode 100644 index 0000000000000..f8894a0fbb0cb --- /dev/null +++ b/app/apps/server/bridges/cloud.ts @@ -0,0 +1,21 @@ +import { Meteor } from 'meteor/meteor'; +import { ICloudWorkspaceBridge } from '@rocket.chat/apps-engine/server/bridges'; +import { IWorkspaceToken } from '@rocket.chat/apps-engine/definition/cloud/IWorkspaceToken'; + +import { getWorkspaceAccessTokenWithScope } from '../../../cloud/server'; +import { AppServerOrchestrator } from '../orchestrator'; + +const boundGetWorkspaceAccessToken = Meteor.bindEnvironment(getWorkspaceAccessTokenWithScope); + +export class AppCloudBridge implements ICloudWorkspaceBridge { + // eslint-disable-next-line no-empty-function + constructor(private readonly orch: AppServerOrchestrator) {} + + public async getWorkspaceToken(scope: string, appId: string): Promise { + this.orch.debugLog(`App ${ appId } is getting the workspace's token`); + + const token = boundGetWorkspaceAccessToken(scope); + + return token; + } +} diff --git a/app/apps/server/bridges/listeners.js b/app/apps/server/bridges/listeners.js index c387ff7559806..9ae81d2c23fdd 100644 --- a/app/apps/server/bridges/listeners.js +++ b/app/apps/server/bridges/listeners.js @@ -41,10 +41,7 @@ export class AppListenerBridge { case AppInterface.IPostLivechatGuestSaved: case AppInterface.IPostLivechatRoomSaved: return 'livechatEvent'; - case AppInterface.IUIKitInteractionHandler: - case AppInterface.IUIKitLivechatInteractionHandler: - case AppInterface.IPostExternalComponentOpened: - case AppInterface.IPostExternalComponentClosed: + default: return 'defaultEvent'; } })(); diff --git a/app/apps/server/bridges/livechat.js b/app/apps/server/bridges/livechat.js index d4adfa35b8384..b48ba38d75191 100644 --- a/app/apps/server/bridges/livechat.js +++ b/app/apps/server/bridges/livechat.js @@ -60,7 +60,7 @@ export class AppLivechatBridge { let agentRoom; if (agent && agent.id) { const user = Users.getAgentInfo(agent.id); - agentRoom = Object.assign({}, { agentId: user._id }); + agentRoom = Object.assign({}, { agentId: user._id, username: user.username }); } const result = await getRoom({ diff --git a/app/apps/server/communication/rest.js b/app/apps/server/communication/rest.js index e211ceea27996..fdf18d8d87bf4 100644 --- a/app/apps/server/communication/rest.js +++ b/app/apps/server/communication/rest.js @@ -23,23 +23,20 @@ export class AppsRestApi { this.loadAPI(); } - _handleFile(request, fileField) { + _handleMultipartFormData(request) { const busboy = new Busboy({ headers: request.headers }); - return Meteor.wrapAsync((callback) => { + const formFields = {}; busboy.on('file', Meteor.bindEnvironment((fieldname, file) => { - if (fieldname !== fileField) { - return callback(new Meteor.Error('invalid-field', `Expected the field "${ fileField }" but got "${ fieldname }" instead.`)); - } - const fileData = []; file.on('data', Meteor.bindEnvironment((data) => { fileData.push(data); })); - file.on('end', Meteor.bindEnvironment(() => callback(undefined, Buffer.concat(fileData)))); + file.on('end', Meteor.bindEnvironment(() => { formFields[fieldname] = Buffer.concat(fileData); })); })); - + busboy.on('field', (fieldname, val) => { formFields[fieldname] = val; }); + busboy.on('finish', Meteor.bindEnvironment(() => callback(undefined, formFields))); request.pipe(busboy); })(); } @@ -58,7 +55,7 @@ export class AppsRestApi { addManagementRoutes() { const orchestrator = this._orch; const manager = this._manager; - const fileHandler = this._handleFile; + const multipartFormDataHandler = this._handleMultipartFormData; const handleError = (message, e) => { // when there is no `response` field in the error, it means the request @@ -171,6 +168,7 @@ export class AppsRestApi { post() { let buff; let marketplaceInfo; + let permissionsGranted; if (this.bodyParams.url) { if (settings.get('Apps_Framework_Development_Mode') !== true) { @@ -190,6 +188,10 @@ export class AppsRestApi { } buff = result.content; + + if (this.bodyParams.downloadOnly) { + return API.v1.success({ buff }); + } } else if (this.bodyParams.appId && this.bodyParams.marketplace && this.bodyParams.version) { const baseUrl = orchestrator.getMarketplaceUrl(); @@ -233,6 +235,7 @@ export class AppsRestApi { buff = downloadResult.content; marketplaceInfo = marketplaceResult.data[0]; + permissionsGranted = this.bodyParams.permissionsGranted; } catch (err) { return API.v1.failure(err.message); } @@ -241,14 +244,23 @@ export class AppsRestApi { return API.v1.failure({ error: 'Direct installation of an App is disabled.' }); } - buff = fileHandler(this.request, 'app'); + const formData = multipartFormDataHandler(this.request); + buff = formData?.app; + permissionsGranted = (() => { + try { + const permissions = JSON.parse(formData?.permissions || ''); + return permissions.length ? permissions : undefined; + } catch { + return undefined; + } + })(); } if (!buff) { return API.v1.failure({ error: 'Failed to get a file to install for the App. ' }); } - const aff = Promise.await(manager.add(buff, true, marketplaceInfo)); + const aff = Promise.await(manager.add(buff, { marketplaceInfo, permissionsGranted, enable: true })); const info = aff.getAppInfo(); if (aff.hasStorageError()) { @@ -428,18 +440,16 @@ export class AppsRestApi { const baseUrl = orchestrator.getMarketplaceUrl(); const headers = getDefaultHeaders(); - const token = getWorkspaceAccessToken(); - if (token) { - headers.Authorization = `Bearer ${ token }`; - } + const token = getWorkspaceAccessToken(true, 'marketplace:download', false); let result; try { - result = HTTP.get(`${ baseUrl }/v2/apps/${ this.bodyParams.appId }/download/${ this.bodyParams.version }`, { + result = HTTP.get(`${ baseUrl }/v2/apps/${ this.bodyParams.appId }/download/${ this.bodyParams.version }?token=${ token }`, { headers, npmRequestOptions: { encoding: null }, }); } catch (e) { + console.log(e, e.response.content.toString()); orchestrator.getRocketChatLogger().error('Error getting the App from the Marketplace:', e.response.data); return API.v1.internalError(); } @@ -459,14 +469,14 @@ export class AppsRestApi { return API.v1.failure({ error: 'Direct updating of an App is disabled.' }); } - buff = fileHandler(this.request, 'app'); + buff = multipartFormDataHandler(this.request)?.app; } if (!buff) { return API.v1.failure({ error: 'Failed to get a file to install for the App. ' }); } - const aff = Promise.await(manager.update(buff)); + const aff = Promise.await(manager.update(buff, this.bodyParams.permissionsGranted)); const info = aff.getAppInfo(); if (aff.hasStorageError()) { diff --git a/app/apps/server/communication/uikit.js b/app/apps/server/communication/uikit.js index 15cd35338c5bd..3010447e93051 100644 --- a/app/apps/server/communication/uikit.js +++ b/app/apps/server/communication/uikit.js @@ -8,6 +8,7 @@ import { AppInterface } from '@rocket.chat/apps-engine/definition/metadata'; import { Users } from '../../../models/server'; import { settings } from '../../../settings/server'; import { Apps } from '../orchestrator'; +import { UiKitCoreApp } from '../../../../server/sdk'; const apiServer = express(); @@ -60,126 +61,231 @@ router.use((req, res, next) => { apiServer.use('/api/apps/ui.interaction/', router); -export class AppUIKitInteractionApi { - constructor(orch) { - this.orch = orch; +const getPayloadForType = (type, req) => { + if (type === UIKitIncomingInteractionType.BLOCK) { + const { + type, + actionId, + triggerId, + mid, + rid, + payload, + container, + } = req.body; + + const { visitor, user } = req; + const room = rid; // orch.getConverters().get('rooms').convertById(rid); + const message = mid; + + return { + type, + container, + actionId, + message, + triggerId, + payload, + user, + visitor, + room, + }; + } + + if (type === UIKitIncomingInteractionType.VIEW_CLOSED) { + const { + type, + actionId, + payload: { + view, + isCleared, + }, + } = req.body; + + const { user } = req; + + return { + type, + actionId, + user, + payload: { + view, + isCleared, + }, + }; + } + + if (type === UIKitIncomingInteractionType.VIEW_SUBMIT) { + const { + type, + actionId, + triggerId, + payload, + } = req.body; + + const { user } = req; + + return { + type, + actionId, + triggerId, + payload, + user, + }; + } + + throw new Error('Type not supported'); +}; + +router.post('/:appId', async (req, res, next) => { + const { + appId, + } = req.params; + + const isCore = await UiKitCoreApp.isRegistered(appId); + if (!isCore) { + return next(); + } + + const { + type, + } = req.body; + + try { + const payload = { + ...getPayloadForType(type, req), + appId, + }; + + const result = await UiKitCoreApp[type](payload); + + res.send(result); + } catch (e) { + console.error('ops', e); + res.status(500).send({ error: e.message }); + } +}); + +const appsRoutes = (orch) => (req, res) => { + const { + appId, + } = req.params; + + const { + type, + } = req.body; - router.post('/:appId', (req, res) => { + switch (type) { + case UIKitIncomingInteractionType.BLOCK: { const { + type, + actionId, + triggerId, + mid, + rid, + payload, + container, + } = req.body; + + const { visitor } = req; + const room = orch.getConverters().get('rooms').convertById(rid); + const user = orch.getConverters().get('users').convertToApp(req.user); + const message = mid && orch.getConverters().get('messages').convertById(mid); + + const action = { + type, + container, + appId, + actionId, + message, + triggerId, + payload, + user, + visitor, + room, + }; + + try { + const eventInterface = !visitor ? AppInterface.IUIKitInteractionHandler : AppInterface.IUIKitLivechatInteractionHandler; + + const result = Promise.await(orch.triggerEvent(eventInterface, action)); + + res.send(result); + } catch (e) { + res.status(500).send(e.message); + } + break; + } + + case UIKitIncomingInteractionType.VIEW_CLOSED: { + const { + type, + actionId, + payload: { + view, + isCleared, + }, + } = req.body; + + const user = orch.getConverters().get('users').convertToApp(req.user); + + const action = { + type, appId, - } = req.params; + actionId, + user, + payload: { + view, + isCleared, + }, + }; + + try { + Promise.await(orch.triggerEvent('IUIKitInteractionHandler', action)); + + res.sendStatus(200); + } catch (e) { + console.error(e); + res.status(500).send(e.message); + } + break; + } + case UIKitIncomingInteractionType.VIEW_SUBMIT: { const { type, + actionId, + triggerId, + payload, } = req.body; - switch (type) { - case UIKitIncomingInteractionType.BLOCK: { - const { - type, - actionId, - triggerId, - mid, - rid, - payload, - container, - } = req.body; - - const { visitor } = req; - const room = this.orch.getConverters().get('rooms').convertById(rid); - const user = this.orch.getConverters().get('users').convertToApp(req.user); - const message = mid && this.orch.getConverters().get('messages').convertById(mid); - - const action = { - type, - container, - appId, - actionId, - message, - triggerId, - payload, - user, - visitor, - room, - }; - - try { - const eventInterface = !visitor ? AppInterface.IUIKitInteractionHandler : AppInterface.IUIKitLivechatInteractionHandler; - - const result = Promise.await(this.orch.triggerEvent(eventInterface, action)); - - res.send(result); - } catch (e) { - res.status(500).send(e.message); - } - break; - } - - case UIKitIncomingInteractionType.VIEW_CLOSED: { - const { - type, - actionId, - payload: { - view, - isCleared, - }, - } = req.body; - - const user = this.orch.getConverters().get('users').convertToApp(req.user); - - const action = { - type, - appId, - actionId, - user, - payload: { - view, - isCleared, - }, - }; - - try { - Promise.await(this.orch.triggerEvent('IUIKitInteractionHandler', action)); - - res.sendStatus(200); - } catch (e) { - console.log(e); - res.status(500).send(e.message); - } - break; - } - - case UIKitIncomingInteractionType.VIEW_SUBMIT: { - const { - type, - actionId, - triggerId, - payload, - } = req.body; - - const user = this.orch.getConverters().get('users').convertToApp(req.user); - - const action = { - type, - appId, - actionId, - triggerId, - payload, - user, - }; - - try { - const result = Promise.await(this.orch.triggerEvent('IUIKitInteractionHandler', action)); - - res.send(result); - } catch (e) { - res.status(500).send(e.message); - } - break; - } + const user = orch.getConverters().get('users').convertToApp(req.user); + + const action = { + type, + appId, + actionId, + triggerId, + payload, + user, + }; + + try { + const result = Promise.await(orch.triggerEvent('IUIKitInteractionHandler', action)); + + res.send(result); + } catch (e) { + res.status(500).send(e.message); } + break; + } + } + + // TODO: validate payloads per type +}; + +export class AppUIKitInteractionApi { + constructor(orch) { + this.orch = orch; - // TODO: validate payloads per type - }); + router.post('/:appId', appsRoutes(orch)); } } diff --git a/app/apps/server/orchestrator.js b/app/apps/server/orchestrator.js index 2034b02ad13e5..612fe2bdf8a6a 100644 --- a/app/apps/server/orchestrator.js +++ b/app/apps/server/orchestrator.js @@ -18,7 +18,7 @@ function isTesting() { return process.env.TEST_MODE === 'true'; } -class AppServerOrchestrator { +export class AppServerOrchestrator { constructor() { this._isInitialized = false; } diff --git a/app/assistify/ai/README.md b/app/assistify/ai/README.md new file mode 100755 index 0000000000000..958bff6430875 --- /dev/null +++ b/app/assistify/ai/README.md @@ -0,0 +1,3 @@ +This package contains all the Smarti-"AI" which aims at accelerating the current conversation. +All artifacts shall be coupled loosely. +In near future, as soon as the technical framework is available, all features of this package shall be converted to a Rocketlet. diff --git a/app/assistify/ai/client/hooks/openAiTab.js b/app/assistify/ai/client/hooks/openAiTab.js new file mode 100644 index 0000000000000..47d47b2a7c5ef --- /dev/null +++ b/app/assistify/ai/client/hooks/openAiTab.js @@ -0,0 +1,19 @@ +import { Meteor } from 'meteor/meteor'; + +import { callbacks } from '../../../../callbacks/client'; + +/** + * Makes the knowledge base panel open on opening a room in which it is active + * (a request, an expertise or a livechat) + */ +callbacks.add('enter-room', function(subscription) { + if (Meteor.isCordova) { + return; // looks awkward on mobile if panel is opened by default + } + + if (subscription && subscription.t === 'l') { // no subscription: if a user joins a room without being subscribed to it + if ($('.messages-container-wrapper .contextual-bar').length === 0) { // the tab bar is closed + Meteor.setTimeout(() => $('div:not(.active) .rc-header .rc-room-actions .rc-room-actions__action[data-id="assistify-ai"] > button').click(), 1000); + } + } +}); diff --git a/app/assistify/ai/client/index.js b/app/assistify/ai/client/index.js new file mode 100644 index 0000000000000..841a5a0ae86ab --- /dev/null +++ b/app/assistify/ai/client/index.js @@ -0,0 +1,9 @@ +import '../models/AssistifySmarti.js'; +import './messageRenderer.js'; +import './tabbar.js'; +import './models/MessagesExtension.js'; +import '../public/stylesheets/smarti.css'; +import './hooks/openAiTab.js'; +import './views/app/tabbar/smarti.html'; +import './views/app/tabbar/smarti.js'; +import './smartiLoader.js'; diff --git a/app/assistify/ai/client/messageRenderer.js b/app/assistify/ai/client/messageRenderer.js new file mode 100644 index 0000000000000..e4d5d291e5d83 --- /dev/null +++ b/app/assistify/ai/client/messageRenderer.js @@ -0,0 +1,43 @@ +import s from 'underscore.string'; + +import { settings } from '../../../settings/client'; +import { callbacks } from '../../../callbacks/client'; + +/** + * This function is used as a callback which is executed while rendering the message. + * @see /packages/rocketchat-ui-message/client/renderMessageBody.js + * @param {*} message the message object including all properties + * @augments message.html - the rendered message body + */ +const highlightrecognizedTokens = function(message) { + const { recognizedTokens } = message; + if (settings.get('Assistify_AI_Smarti_Inline_Highlighting_Enabled')) { + const confExcluded = settings.get('Assistify_AI_Smarti_Inline_Highlighting_Excluded_Types'); + const excludedTypes = confExcluded ? new Set(confExcluded.split(',').map((item) => item.trim())) : new Set(); + + let { html } = message; + if (recognizedTokens) { + recognizedTokens.forEach((term) => { + if (!excludedTypes.has(term.type)) { + /* depending on the previous renderers, the content of the message will be wrapped in a

+ we'll remove it for this processing since else, the negative lookahead of the regex, + which prevents replacement inside html-tags such as links, will prevent replacing of any content + */ + const regexWrappedParagraph = new RegExp('^\s*

|<\/p>\s*$', 'gm'); + const wrappedInParagraph = html.search(regexWrappedParagraph) >= 0; + if (wrappedInParagraph) { + html = html.replace(regexWrappedParagraph, ''); + } + const regexpFindTerm = `(^|\\b|[\\s.,،'\\\"\\+!?:-])(${ s.escapeRegExp(term.value) })($|\\b|[\\s.,،'\\\"\\+!?:-])(?![^<]*>|[^<>]*<\\/)`; + html = html.replace(new RegExp(regexpFindTerm, 'gmi'), '$1$2$3'); + if (wrappedInParagraph) { + html = `

${ html }

`; + } + } + }); + } + message.html = html; + } +}; + +callbacks.add('renderMessage', highlightrecognizedTokens, callbacks.priority.LOW, 'smartiHighlighting'); diff --git a/app/assistify/ai/client/models/MessagesExtension.js b/app/assistify/ai/client/models/MessagesExtension.js new file mode 100644 index 0000000000000..daf7e93f8b15a --- /dev/null +++ b/app/assistify/ai/client/models/MessagesExtension.js @@ -0,0 +1,7 @@ +import { ChatMessages } from '../../../../ui'; + +Object.assign(ChatMessages, { + setRecognizedTokensById(id, recognizedTokens) { + return this.update({ _id: id }, { $set: { recognizedTokens } }); + }, +}); diff --git a/app/assistify/ai/client/smartiLoader.js b/app/assistify/ai/client/smartiLoader.js new file mode 100644 index 0000000000000..1034605b4e002 --- /dev/null +++ b/app/assistify/ai/client/smartiLoader.js @@ -0,0 +1,27 @@ +import { Meteor } from 'meteor/meteor'; + +import { settings } from '../../../settings/client'; + +/** + * Load Smarti script asynchronously to the window. + * This ensures the invalidation as settings are changed and allow the script to live beyond template lifetime. + */ +settings.onload('Assistify_AI_Smarti_Base_URL', function() { + Meteor.call('getSmartiUiScript', function(error, script) { + if (error) { + console.error('could not load Smarti:', error.message); + } else { + // generate a script tag for smarti JS + const doc = document; + const smartiScriptTag = doc.createElement('script'); + smartiScriptTag.type = 'text/javascript'; + smartiScriptTag.async = true; + smartiScriptTag.defer = true; + smartiScriptTag.innerHTML = script; + // insert the smarti script tag as first script tag + const firstScriptTag = doc.getElementsByTagName('script')[0]; + firstScriptTag.parentNode.insertBefore(smartiScriptTag, firstScriptTag); + console.debug('loaded Smarti successfully'); + } + }); +}); diff --git a/app/assistify/ai/client/tabbar.js b/app/assistify/ai/client/tabbar.js new file mode 100755 index 0000000000000..bdedd7753ff03 --- /dev/null +++ b/app/assistify/ai/client/tabbar.js @@ -0,0 +1,25 @@ + +import { Tracker } from 'meteor/tracker'; + +import { settings } from '../../../settings/client'; +import { addAction, deleteAction } from '../../../../client/views/room/lib/Toolbox'; + +Tracker.autorun(() => { + const enabled = settings.get('Assistify_AI_Enabled'); + if (enabled) { + addAction('assistify-ai', { + groups: ['channel', 'group', 'live'], + id: 'assistify-ai', + i18nTitle: 'Knowledge_Base', + icon: 'book', + template: 'AssistifySmarti', + order: -1, + }); + } else { + try { + deleteAction('assistify-ai'); + } catch (err) { + // may not exist, not an issue + } + } +}); diff --git a/app/assistify/ai/client/views/app/tabbar/smarti.html b/app/assistify/ai/client/views/app/tabbar/smarti.html new file mode 100644 index 0000000000000..a3dfb777e9b10 --- /dev/null +++ b/app/assistify/ai/client/views/app/tabbar/smarti.html @@ -0,0 +1,55 @@ + + + diff --git a/app/assistify/ai/client/views/app/tabbar/smarti.js b/app/assistify/ai/client/views/app/tabbar/smarti.js new file mode 100644 index 0000000000000..b77945672017e --- /dev/null +++ b/app/assistify/ai/client/views/app/tabbar/smarti.js @@ -0,0 +1,141 @@ +import { Meteor } from 'meteor/meteor'; +import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; +import { Template } from 'meteor/templating'; +import { ReactiveVar } from 'meteor/reactive-var'; + +import { ChatRoom } from '../../../../../../models/client/models/ChatRoom'; +import { Notifications } from '../../../../../../notifications/client'; +import { settings } from '../../../../../../settings/client'; +import { ChatSubscription } from '../../../../../../models/client'; + +Template.AssistifySmarti.onCreated(function() { + this.room = new ReactiveVar(null); + this.smartiLoaded = new ReactiveVar(false); + this.maxTriesLoading = 10; + this.timeoutMs = 2000; + this.currentTryLoading = new ReactiveVar(0); + this.reactOnSmartiDirty = new ReactiveVar(true); + + const instance = this; + + this.autorun(() => { + if (instance.data.rid) { + const room = ChatRoom.findOne(instance.data.rid); + instance.room.set(room); + } + }); + + /* + Once this template is created (meaning: Once the tab is opened), + the user is interested in what Smarti is analyzing => + Hook into an event issued by the backend to allow requesting an analysis + */ + Notifications.onRoom(instance.data.rid, 'assistify-smarti-dirty', () => { + if (this.reactOnSmartiDirty.get()) { + if (!settings.get('Assistify_AI_Smarti_Inline_Highlighting_Enabled')) { // Inline highlighting will anyway trigger the analysis - we don't need it twice + Meteor.call('analyze', instance.data.rid); + } + } + }); +}); + +Template.AssistifySmarti.onDestroyed(function() { + this.reactOnSmartiDirty.set(false); + clearTimeout(this.loading); +}); + +/** + * Create Smarti (as soon as the script is loaded) + * @namespace SmartiWidget + */ +Template.AssistifySmarti.onRendered(function() { + const instance = this; + + /* in order to avoid duplicated scrollbars, have the outer one hidden */ + // const parentContainer = this.$(':parent').parent(); + // parentContainer.css('overflow-y', 'initial'); + // this.$('.smarti-widget').css('overflow-y', 'auto'); + + function createSmarti() { + if (window.SmartiWidget === undefined) { + console.log(`Couldn't load Smarti-Widget - try ${ instance.currentTryLoading.get() }`); + instance.currentTryLoading.set(instance.currentTryLoading.get() + 1); + if (instance.currentTryLoading.get() < instance.maxTriesLoading) { + instance.loading = setTimeout(createSmarti, instance.timeoutMs); + } + } else { + instance.smartiLoaded.set(true); + const ROCKET_CHAT_URL = settings.get('Site_Url').replace(/\/?$/, '/'); + // stripping only the protocol ("http") from the site-url either creates a secure or an insecure websocket connection + const WEBSOCKET_URL = `ws${ ROCKET_CHAT_URL.substring(4) }websocket/`; + const WIDGET_POSTING_TYPE = settings.get('Assistify_AI_Widget_Posting_Type') || 'postRichText'; + const SMARTI_CLIENT_NAME = settings.get('Assistify_AI_Smarti_Domain'); + + const smartiOptions = { + socketEndpoint: WEBSOCKET_URL, + clientName: SMARTI_CLIENT_NAME, + channel: instance.data.rid, + postings: { + type: WIDGET_POSTING_TYPE, + cssInputSelector: '.rc-message-box .js-input-message', + }, + lang: localStorage.getItem('userLanguage').split('-')[0], + }; + + // propagate i18n - support formatted strings while doing that + const i18nSetting = settings.get('Assistify_AI_Smarti_Widget_i18n'); + const i18n = i18nSetting.search('\n') > -1 ? JSON.parse(i18nSetting) : i18nSetting; + if (i18n) { + smartiOptions.i18n = i18n; + } + + console.debug('Initializing Smarti with options: ', JSON.stringify(smartiOptions, null, 2)); + instance.smarti = new window.SmartiWidget(instance.find('.smarti #widgetContainer'), smartiOptions); + } + } + + createSmarti(); +}); + +Template.AssistifySmarti.helpers({ + isLivechat() { + const instance = Template.instance(); + return ChatSubscription.findOne({ rid: instance.data.rid }).t === 'l'; + }, + /** + This helper is needed in order to create an object which matches the actions bar importing parameters + */ + liveChatActions() { + const instance = Template.instance(); + return { roomId: instance.data.rid }; + }, + loadingClass() { + const instance = Template.instance(); + if (instance.smartiLoaded.get()) { + return 'ready'; + } + return instance.currentTryLoading.get() < instance.maxTriesLoading ? 'loading' : 'not-available'; + }, + isLoading() { + const instance = Template.instance(); + return !instance.smartiLoaded.get() && instance.currentTryLoading.get() < instance.maxTriesLoading; + }, + loadingNotification() { + const instance = Template.instance(); + if (instance.currentTryLoading.get() < instance.maxTriesLoading && instance.currentTryLoading.get() > 3) { + return TAPi18n.__('Widget_loading'); + } + + if (instance.currentTryLoading.get() === instance.maxTriesLoading) { + return TAPi18n.__('Widget_could_not_load'); + } + }, +}); + +Template.AssistifySmarti.events({ + 'click .js-resync-room'(event, instance) { + if (instance.data.rid) { + Meteor.call('resyncRoom', instance.data.rid); + } + }, +}); diff --git a/app/assistify/ai/models/AssistifySmarti.js b/app/assistify/ai/models/AssistifySmarti.js new file mode 100644 index 0000000000000..171f620f0ff18 --- /dev/null +++ b/app/assistify/ai/models/AssistifySmarti.js @@ -0,0 +1,50 @@ +import { Meteor } from 'meteor/meteor'; + +const Base = Meteor.isClient + ? require('../../../models/client/models/_Base').Base + : require('../../../models/server/models/_Base').Base; + +class AssistifySmarti extends Base { + constructor() { + super('assistify_smarti'); + + if (Meteor.isClient) { + this._initModel('assistify_smarti'); + } + } + + // FIND + findByRoomId(roomId, sort = { ts: -1 }) { + const query = { rid: roomId }; + + return this.find(query, { sort }); + } + + findOneByRoomId(roomId, sort = { ts: -1 }) { + const query = { rid: roomId }; + + return this.findOne(query, { sort }); + } + + findOneById(_id, options) { + const query = { _id }; + return this.findOne(query, options); + } + + findOneByConversationId(convId, options) { + const query = { conversationId: convId }; + return this.findOne(query, options); + } + + // REMOVE + clear() { + const query = { knowledgeProvider: 'smarti' }; + return this.remove(query); + } + + removeByRoomId(roomId) { + const query = { rid: roomId }; + return this.remove(query); + } +} +export const assistifySmarti = new AssistifySmarti(); diff --git a/app/assistify/ai/public/assistify-beta.png b/app/assistify/ai/public/assistify-beta.png new file mode 100644 index 0000000000000..89be65759f21c Binary files /dev/null and b/app/assistify/ai/public/assistify-beta.png differ diff --git a/app/assistify/ai/public/stylesheets/smarti.css b/app/assistify/ai/public/stylesheets/smarti.css new file mode 100644 index 0000000000000..bea34b40c40b3 --- /dev/null +++ b/app/assistify/ai/public/stylesheets/smarti.css @@ -0,0 +1,147 @@ +.smarti-widget .loading-notification { + margin: 0 20px; + + text-align: justify; + + font-style: italic; + line-height: 1.4; +} + +.external-search-content .smarti { + overflow-y: auto; +} + +.smarti #widgetContainer { + position: absolute; + + top: 0; + right: 0; + + overflow: hidden; + + width: 100%; + height: 100%; + + font-family: "Helvetica Neue", Arial, sans-serif; + font-size: 16px; + + line-height: normal; +} + +.smarti #widgetContainer #widgetWrapper { + position: relative; + + display: flex; + flex-direction: column; + + height: 100%; +} + +.smarti #widgetContainer #widgetWrapper #widgetHeader { + position: relative; + + background-color: white; /* hide book icon */ +} + +.smarti #widgetContainer #widgetWrapper #widgetHeader .widgetHeaderWrapper { + padding: 15px; +} + +.smarti #widgetContainer #widgetWrapper #widgetHeader h4 { + + margin-top: -10px; + + letter-spacing: 0; + + font-size: 20px; +} + +.smarti #widgetContainer #widgetWrapper #widgetHeader h4 img { + position: relative; + top: 5px; + + height: 30px; + margin-right: 4px; +} + +.smarti #widgetContainer #widgetWrapper #widgetBody { + position: relative; + + overflow-y: auto; + flex: 1; + + width: 100%; + padding: 10px; + + background: #ffffff; +} + +.smarti #widgetContainer #widgetWrapper #widgetFooter { + position: relative; + + width: 100%; + + transition: 0.2s; + + text-align: center; + + opacity: 1; + color: #747474; + + border-top: 1px solid #cbcbcb; + + background: #f4f4f4; +} + +.smarti #widgetContainer #widgetWrapper #widgetFooter .help-request-actions { + margin: 0; +} + +.smarti #widgetContainer #widgetWrapper #widgetFooter button { + + position: relative; + + display: inline-block; + + width: calc(100% - 20px); + + margin: 10px; + + padding: 15px 30px; + + -o-transition: 0.2s; + -ms-transition: 0.2s; + -moz-transition: 0.2s; + -webkit-transition: 0.2s; + transition: 0.2s; + + text-align: center; + + text-decoration: none; + + text-transform: uppercase; + + color: #747474; + border-radius: 3px; + + background: #cbcbcb; + + font-size: 14px; +} + +.smarti #widgetContainer #widgetWrapper #widgetFooter button:hover { + color: #ffffff; + background: #d5195b; +} + +.recognized-term { + cursor: pointer; +} + +.recognized-term .text { + color: #008378; +} + +.recognized-term .text:hover { + border-bottom: 1px dashed; +} diff --git a/app/assistify/ai/server/SmartiProxy.js b/app/assistify/ai/server/SmartiProxy.js new file mode 100644 index 0000000000000..ad7041e4612c9 --- /dev/null +++ b/app/assistify/ai/server/SmartiProxy.js @@ -0,0 +1,118 @@ +import { HTTP } from 'meteor/http'; + +import { settings } from '../../../settings/server'; +import { SystemLogger } from '../../../logger/server'; + +/** The HTTP methods. */ +export const verbs = { + get: 'GET', + post: 'POST', + put: 'PUT', + delete: 'DELETE', +}; + +/** + * The proxy propagates the HTTP requests to Smarti. + * All HTTP outbound traffic (from Rocket.Chat to Smarti) should pass the this proxy. + */ +export class SmartiProxy { + static get smartiAuthToken() { + return settings.get('Assistify_AI_Smarti_Auth_Token'); + } + + static get smartiUrl() { + return settings.get('Assistify_AI_Smarti_Base_URL'); + } + + /** + * Propagates requests to Smarti. + * Make sure all requests to Smarti are using this function. + * + * @param {String} method - the HTTP method to use + * @param {String} path - the path to call + * @param {Object} [parameters=null] - the http query params (optional) + * @param {String} [body=null] - the payload to pass (optional) + * @param {Function} onError=null - custom error handler + * + * @returns {Object} + */ + static propagateToSmarti(method, path, parameters = null, body = null, onError = null) { + const url = `${ SmartiProxy.smartiUrl }${ path }`; + const header = { + 'X-Auth-Token': SmartiProxy.smartiAuthToken, + 'Content-Type': 'application/json; charset=utf-8', + }; + try { + SystemLogger.debug('Sending request to Smarti', method, 'to', url, 'body', JSON.stringify(body)); + + const response = HTTP.call(method, url, { + params: parameters, + data: body, + headers: header, + }); + + if (response.statusCode < 400) { + return response.data || response.content; // .data if it's a json-response + } + SystemLogger.debug('Got unexpected result from Smarti', method, 'to', url, 'response', JSON.stringify(response)); + } catch (error) { + if (error && onError) { + return onError(error); + } + + SystemLogger.error('Could not complete', method, 'to', url, error.response); + SystemLogger.debug(error); + return { error }; + } + } + + static get googleSearchUrl() { + return settings.get('Assistify_AI_Google_CS_URL'); + } + + static get googleSearchKey() { + return settings.get('Assistify_AI_Google_CS_KEY'); + } + + static get googleSearchId() { + return settings.get('Assistify_AI_Google_CS_ID'); + } + + static propagateToGoogle(method, parameters = null, body = null, onError = null) { + const url = `${ SmartiProxy.googleSearchUrl }`; + const header = { + 'Content-Type': 'application/json; charset=utf-8', + Host: 'www.googleapis.com', + Origin: 'googleapis.com', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET,PUT,POST,DELETE', + 'Access-Control-Allow-Headers': 'Authorization', + }; + try { + SystemLogger.debug('Sending request to google', method, 'to', url, 'params', parameters); + + parameters.key = SmartiProxy.googleSearchKey; + parameters.cx = SmartiProxy.googleSearchId; + + const response = HTTP.call(method, url, { + params: parameters, + data: body, + headers: header, + }); + + if (response.statusCode === 200) { + SystemLogger.debug('response: ', response.data ? response.data : response.content); + return response.data || response.content; // .data if it's a json-response + } + SystemLogger.debug('Got unexpected result from google', method, 'to', url, 'response', JSON.stringify(response)); + } catch (error) { + if (error && onError) { + return onError(error); + } + + SystemLogger.error('Could not complete', method, 'to', url, error.response); + SystemLogger.debug(error); + return { error }; + } + } +} diff --git a/app/assistify/ai/server/SmartiRouter.js b/app/assistify/ai/server/SmartiRouter.js new file mode 100644 index 0000000000000..d1bba0a7f82fa --- /dev/null +++ b/app/assistify/ai/server/SmartiRouter.js @@ -0,0 +1,45 @@ +import { check, Match } from 'meteor/check'; + +import { SmartiAdapter } from './lib/SmartiAdapter'; +import { SystemLogger } from '../../../logger/server'; +import { API } from '../../../api/server'; +import { settings } from '../../../settings/server'; + +/** + * The SmartiRouter handles all incoming HTTP requests from Smarti. + * This is the only place, where adding routes to the Rocket.Chat API, related to Smarti + * All HTTP inbound traffic (from Rocket.Chat to Smarti) should pass the this router. + */ + +/** + * Add an incoming webhook '/newConversationResult' to receive answers from Smarti. + * This allows asynchronous callback from Smarti, when analyzing the conversation has finished. + */ +API.v1.addRoute('smarti.result/:_token', { authRequired: false }, { + + post() { + SystemLogger.debug('Smarti - Incoming HTTP requests', JSON.stringify(this.bodyParams, null, 2)); + + check(this.bodyParams.data, Match.ObjectIncluding({ + conversation: String, + })); + + const rcWebhookToken = settings.get('Assistify_AI_RocketChat_Webhook_Token'); + + // verify token + if (this.urlParams._token && this.urlParams._token === rcWebhookToken) { + SmartiAdapter.analysisCompleted(null, this.bodyParams.data.conversation, this.bodyParams.data); + return API.v1.success(); + } + return API.v1.unauthorized({ msg: 'token not valid' }); + }, +}); +/** + * Add an incoming webhook '/google.search' to serve google custom search requets. + */ +API.v1.addRoute('google.search', { authRequired: true }, { + + get() { + return SmartiAdapter.getGoogleResult(this.queryParams); + }, +}); diff --git a/app/assistify/ai/server/config.js b/app/assistify/ai/server/config.js new file mode 100644 index 0000000000000..55e3a574c21a6 --- /dev/null +++ b/app/assistify/ai/server/config.js @@ -0,0 +1,194 @@ +import { Meteor } from 'meteor/meteor'; + +import { settings } from '../../../settings/server'; +import { Settings } from '../../../models/server'; + +Meteor.startup(() => { + const addAISettings = function() { + this.section('Knowledge_Base', function() { + this.add('Assistify_AI_Enabled', false, { + type: 'boolean', + public: true, + i18nLabel: 'Enabled', + }); + + this.add('Assistify_AI_Source', '0', { + type: 'select', + values: [ + { key: '0', i18nLabel: 'Assistify_AI_Source_Smarti' }, + { key: '1', i18nLabel: 'Assistify_AI_Source_APIAI' }, + ], + public: true, + i18nLabel: 'Assistify_AI_Source', + }); + + this.add('Assistify_AI_Reload', 'reloadSmarti', { + type: 'action', + actionText: 'Reload_Settings', + }); + + this.add('Assistify_AI_Smarti_Base_URL', '', { + type: 'string', + public: true, + i18nLabel: 'Assistify_AI_Smarti_Base_URL', + i18nDescription: 'Assistify_AI_Smarti_Base_URL_Description', + }); + + let domain = settings.get('Site_Url'); + if (domain) { + domain = domain + .replace('https://', '') + .replace('http://', ''); + while (domain.charAt(domain.length - 1) === '/') { + domain = domain.substr(0, domain.length - 1); + } + } + this.add('Assistify_AI_Smarti_Domain', domain, { + type: 'string', + public: true, + i18nLabel: 'Assistify_AI_Smarti_Domain', + i18nDescription: 'Assistify_AI_Smarti_Domain_Description', + }); + + this.add('Assistify_AI_Smarti_Auth_Token', '', { + type: 'string', + public: true, + i18nLabel: 'Assistify_AI_Smarti_Auth_Token', + i18nDescription: 'Assistify_AI_Smarti_Auth_Token_Description', + }); + + this.add('Assistify_AI_Widget_Posting_Type', '', { + type: 'select', + values: [ + { key: 'suggestText', i18nLabel: 'Assistify_AI_Widget_Posting_Type_SuggestText' }, + { key: 'postText', i18nLabel: 'Assistify_AI_Widget_Posting_Type_PostText' }, + { key: 'postRichText', i18nLabel: 'Assistify_AI_Widget_Posting_Type_PostRichText' }, + ], + public: true, + i18nLabel: 'Assistify_AI_Widget_Posting_Type', + i18nDescription: 'Assistify_AI_Widget_Posting_Type_Description', + }); + + this.add('Assistify_AI_RocketChat_Webhook_Token', '', { + type: 'string', + public: true, + i18nLabel: 'Assistify_AI_RocketChat_Webhook_Token', + i18nDescription: 'Assistify_AI_RocketChat_Webhook_Token_Description', + }); + + this.add('Assistify_AI_RocketChat_Callback_URL', '', { + type: 'string', + public: true, + i18nLabel: 'Assistify_AI_RocketChat_Callback_URL', + i18nDescription: 'Assistify_AI_RocketChat_Callback_URL_Description', + }); + + this.add('Assistify_AI_Smarti_Inline_Highlighting_Enabled', false, { + type: 'boolean', + public: true, + i18nLabel: 'Assistify_AI_Smarti_Inline_Highlighting_Enabled', + i18nDescription: 'Assistify_AI_Smarti_Inline_Highlighting_Enabled_Description', + }); + + this.add('Assistify_AI_Smarti_Inline_Highlighting_Excluded_Types', 'Attribute', { + type: 'string', + public: true, + i18nLabel: 'Assistify_AI_Smarti_Inline_Highlighting_Excluded_Types', + i18nDescription: 'Assistify_AI_Smarti_Inline_Highlighting_Excluded_Types_Description', + enableQuery: [{ _id: 'Assistify_AI_Smarti_Inline_Highlighting_Enabled', value: true }], + }); + + this.add('Assistify_AI_Resync', 'triggerResync', { + type: 'action', + i18nDescription: 'Assistify_AI_Resync_Description', + actionText: 'Assistify_AI_Resync_Text', + sorter: 99, + }); + + this.add('Assistify_AI_Resync_Full', 'triggerFullResync', { + type: 'action', + i18nLabel: 'Assistify_AI_Resync_Full', + i18nDescription: 'Assistify_AI_Resync_Full_Description', + actionText: 'Assistify_AI_Resync_Full_Text', + sorter: 100, + }); + + this.add('Assistify_AI_Resync_Batchsize', 10, { + type: 'int', + public: true, + i18nLabel: 'Assistify_AI_Resync_Batchsize', + }); + + this.add('Assistify_AI_Resync_Batch_Timeout', 1000, { + type: 'int', + public: true, + i18nLabel: 'Assistify_AI_Resync_Batch_Timeout', + }); + + this.add('Assistify_AI_Resync_Message_Limit', 1000, { + type: 'int', + public: true, + i18nLabel: 'Assistify_AI_Resync_Message_Limit', + }); + + this.add('Assistify_AI_Smarti_Widget_i18n', '', { + type: 'code', + public: true, + i18nLabel: 'Assistify_AI_Smarti_Widget_i18n', + i18nDescription: 'Assistify_AI_Smarti_Widget_i18n_Description', + sorter: 200, + }); + }); + this.section('Assistify_AI_Google_CS', function() { + this.add('Assistify_AI_Google_CS_URL', '', { + type: 'string', + public: true, + i18nLabel: 'Assistify_AI_Google_CS_URL', + i18nDescription: 'Assistify_AI_Google_CS_URL_Description', + }); + + this.add('Assistify_AI_Google_CS_KEY', '', { + type: 'string', + public: true, + i18nLabel: 'Assistify_AI_Google_CS_KEY', + i18nDescription: 'Assistify_AI_Google_CS_KEY_Description', + }); + + this.add('Assistify_AI_Google_CS_ID', '', { + type: 'string', + public: true, + i18nLabel: 'Assistify_AI_Google_CS_ID', + i18nDescription: 'Assistify_AI_Google_CS_ID_Description', + }); + }); + }; + + // add the setting group if needed or reuse the existing one + settings.get('Assistify') + ? settings.get('Assistify', addAISettings) + : settings.addGroup('Assistify', addAISettings); +}); + + +/* Propagate settings to Chatpal */ + +const setChatpalUrl = (smartiUrl) => { + Settings.update('CHATPAL_CONFIG', { + $set: + { + 'value.baseurl': smartiUrl, + 'value.backendtype': 'onsite', + }, + }); +}; + +settings.get('Assistify_AI_Smarti_Base_URL', (id, smartiUrl) => { + const client = Settings.findOneNotHiddenById('Assistify_AI_Smarti_Domain'); + if (client) { + setChatpalUrl(smartiUrl); + } +}); + +settings.get('Assistify_AI_Smarti_Auth_Token', (id, smartiAuthToken) => { + Settings.update('CHATPAL_CONFIG', { $set: { 'value.headerstring': `X-Auth-Token: ${ smartiAuthToken }` } }); +}); diff --git a/app/assistify/ai/server/hooks/closeLivechatKnowledgeAdapter.js b/app/assistify/ai/server/hooks/closeLivechatKnowledgeAdapter.js new file mode 100755 index 0000000000000..4ecd65d03f4bd --- /dev/null +++ b/app/assistify/ai/server/hooks/closeLivechatKnowledgeAdapter.js @@ -0,0 +1,22 @@ +/** + * Notifies the knowledgeProvider about the end of a livechat conversation + */ +import { SystemLogger } from '../../../../logger/server'; +import { callbacks } from '../../../../callbacks/server'; +import { getKnowledgeAdapter } from '../lib/KnowledgeAdapterProvider'; + +const _callbackOnClose = function(room) { + try { + const knowledgeAdapter = getKnowledgeAdapter(); + if (knowledgeAdapter && knowledgeAdapter.onClose) { + knowledgeAdapter.onClose(room); + } else { + SystemLogger.warn('No knowledge provider configured'); + } + } catch (e) { + SystemLogger.error('Error submitting closed conversation to knowledge provider ->', e); + } +}; + +callbacks.add('livechat.closeRoom', _callbackOnClose, callbacks.priority.LOW); +callbacks.add('assistify.closeRoom', _callbackOnClose, callbacks.priority.LOW); diff --git a/app/assistify/ai/server/hooks/sendMessageToKnowledgeAdapter.js b/app/assistify/ai/server/hooks/sendMessageToKnowledgeAdapter.js new file mode 100755 index 0000000000000..424a1f33bf2d1 --- /dev/null +++ b/app/assistify/ai/server/hooks/sendMessageToKnowledgeAdapter.js @@ -0,0 +1,80 @@ +import { Meteor } from 'meteor/meteor'; + +import { SystemLogger } from '../../../../logger/server'; +import { getKnowledgeAdapter } from '../lib/KnowledgeAdapterProvider'; +import { callbacks } from '../../../../callbacks/server'; +import { Rooms, Users } from '../../../../models/server'; +import { settings } from '../../../../settings/server'; + +// unregister callbacks for livechat and the hard-coded api.ai +callbacks.remove('afterSaveMessage', 'externalWebHook'); + +function isMessageRelevant(message, room) { + const user = Users.findOneById(message.u._id); + if (user && user.roles.bot) { + return; // do not trigger a new evaluation if the message was sent from a bot (particularly by assistify itself) + } + + const knowledgeEnabled = settings.get('Assistify_AI_Enabled'); + + if (!knowledgeEnabled) { + return false; + } + + if (!room) { + room = Rooms.findOneById(message.rid); + } + + return !!getKnowledgeAdapter(); +} + +callbacks.add('afterSaveMessage', function(message, room) { + if (isMessageRelevant(message, room)) { + const knowledgeAdapter = getKnowledgeAdapter(); + SystemLogger.debug(`Send message ${ message._id } to knowledgeAdapter (Meteor.defer()`); + Meteor.defer(() => { + const helpRequest = Rooms.findOne(room.helpRequestId); + const context = {}; + if (helpRequest) { // there might be rooms without help request objects if they have been created inside the chat-application + context.contextType = 'ApplicationHelp'; + context.environmentType = helpRequest.supportArea; + context.environment = helpRequest.environment; + } + try { + SystemLogger.debug(`Calling onMessage(${ message._id });`); + knowledgeAdapter.onMessage(message, context, room.expertise ? [room.expertise] : []); + } catch (e) { + SystemLogger.error('Error using knowledge provider ->', e); + } + }); + return message; + } +}, callbacks.priority.LOW, 'Assistify_AI_OnMessage'); + +callbacks.add('afterDeleteMessage', function(message) { + if (isMessageRelevant(message)) { + const knowledgeAdapter = getKnowledgeAdapter(); + SystemLogger.debug(`Propagating delete of message${ message._id } to knowledge-adapter`); + Meteor.defer(() => { + try { + SystemLogger.debug(`Calling afterDeleteMessage(${ message._id });`); + knowledgeAdapter.afterMessageDeleted(message); + } catch (e) { + SystemLogger.error('Error using knowledge provider ->', e); + } + }); + } +}, callbacks.priority.LOW, 'Assistify_AI_afterDeleteMessage'); + +callbacks.add('afterRoomErased', function(room) { + const knowledgeAdapter = getKnowledgeAdapter(); + SystemLogger.debug(`Propagating delete of room ${ room._id } to knowledge-adapter`); + Meteor.defer(() => { + try { + SystemLogger.debug(`Calling afterRoomErased(${ room._id });`); + knowledgeAdapter.afterRoomErased(room); + } catch (e) { + SystemLogger.error('Error using knowledge provider ->', e); + } + }); +}, callbacks.priority.LOW, 'Assistify_AI_afterRoomErased'); diff --git a/app/assistify/ai/server/index.js b/app/assistify/ai/server/index.js new file mode 100644 index 0000000000000..5f6a2850695c9 --- /dev/null +++ b/app/assistify/ai/server/index.js @@ -0,0 +1,14 @@ +// import '../public/icons.svg'; +import '../models/AssistifySmarti.js'; + +import './methods/SmartiReload.js'; +import './methods/SmartiWidgetBackend.js'; +import './SmartiProxy.js'; +import './models/MessagesExtension.js'; +import './config.js'; +import './SmartiRouter.js'; +import './hooks/sendMessageToKnowledgeAdapter.js'; +import './hooks/closeLivechatKnowledgeAdapter.js'; +import './lib/SmartiAdapter.js'; +import './lib/AiApiAdapter.js'; +import './lib/KnowledgeAdapterProvider.js'; diff --git a/app/assistify/ai/server/lib/AiApiAdapter.js b/app/assistify/ai/server/lib/AiApiAdapter.js new file mode 100644 index 0000000000000..efe1b72f04e66 --- /dev/null +++ b/app/assistify/ai/server/lib/AiApiAdapter.js @@ -0,0 +1,36 @@ +import _ from 'underscore'; +import { HTTP } from 'meteor/http'; + +import { LivechatExternalMessage } from '../../../../models/server/raw'; + +export class ApiAiAdapter { + constructor(adapterProps) { + this.properties = adapterProps; + this.headers = { + 'Content-Type': 'application/json; charset=utf-8', + Authorization: `Bearer ${ this.properties.token }`, + }; + } + + onMessage(message) { + const responseAPIAI = HTTP.post(this.properties.url, { + data: { + query: message.msg, + lang: this.properties.language, + }, + headers: this.headers, + }); + if (responseAPIAI.data && responseAPIAI.data.status.code === 200 && !_.isEmpty(responseAPIAI.data.result.fulfillment.speech)) { + LivechatExternalMessage.insert({ + rid: message.rid, + msg: responseAPIAI.data.result.fulfillment.speech, + orig: message._id, + ts: new Date(), + }); + } + } + + onClose() { + // do nothing, api.ai does not learn from us. + } +} diff --git a/app/assistify/ai/server/lib/KnowledgeAdapterProvider.js b/app/assistify/ai/server/lib/KnowledgeAdapterProvider.js new file mode 100644 index 0000000000000..7b0a035630da6 --- /dev/null +++ b/app/assistify/ai/server/lib/KnowledgeAdapterProvider.js @@ -0,0 +1,36 @@ + +import { SmartiAdapter } from './SmartiAdapter'; +import { ApiAiAdapter } from './AiApiAdapter'; +import { settings } from '../../../../settings/server'; + +export function getKnowledgeAdapter() { + let knowledgeSource = ''; + + const KNOWLEDGE_SRC_SMARTI = '0'; + const KNOWLEDGE_SRC_APIAI = '1'; + + settings.get('Assistify_AI_Source', function(key, value) { + knowledgeSource = value; + }); + + const adapterProps = { + url: '', + token: '', + language: '', + }; + + switch (knowledgeSource) { + case KNOWLEDGE_SRC_APIAI: + adapterProps.url = 'https://api.api.ai/api/query?v=20150910'; + + settings.get('Assistify_AI_Apiai_Key', function(key, value) { + adapterProps.token = value; + }); + settings.get('Assistify_AI_Apiai_Language', function(key, value) { + adapterProps.language = value; + }); + return new ApiAiAdapter(adapterProps); + case KNOWLEDGE_SRC_SMARTI: + return SmartiAdapter; + } +} diff --git a/app/assistify/ai/server/lib/SmartiAdapter.js b/app/assistify/ai/server/lib/SmartiAdapter.js new file mode 100644 index 0000000000000..f14db05cbd4a9 --- /dev/null +++ b/app/assistify/ai/server/lib/SmartiAdapter.js @@ -0,0 +1,644 @@ +import { Meteor } from 'meteor/meteor'; + +import { SmartiProxy, verbs } from '../SmartiProxy'; +import { assistifySmarti } from '../../models/AssistifySmarti'; +import { Notifications } from '../../../../notifications/server'; +import { settings } from '../../../../settings/server'; +import { Messages, Rooms } from '../../../../models/server'; +import { SystemLogger } from '../../../../logger/server'; + +let syncTimer = 0; + +function terminateCurrentSync() { + if (syncTimer) { + Meteor.clearInterval(syncTimer); + } + + syncTimer = 0; +} + +function notifyClientsSmartiDirty(roomId, conversationId) { + Notifications.notifyRoom(roomId, 'assistify-smarti-dirty', { + roomId, + conversationId, + }); +} +/** + * The SmartiAdpater can be understood as an interface for all interaction with Smarti triggered by Rocket.Chat server. + * The SmartiAdapter sould not expose any methods that can directly be called by the client. + * This adapter has no state, as all settings are fully buffered. Thus, the complete class is static. + */ +export class SmartiAdapter { + static isEnabled() { + return !!settings.get('Assistify_AI_Enabled'); + } + + /** + * Returns the webhook URL that reveives the analysis callback from Smarti. + */ + static get rocketWebhookUrl() { + let rocketUrl = settings.get('Assistify_AI_RocketChat_Callback_URL') || settings.get('Site_Url'); + rocketUrl = rocketUrl ? rocketUrl.replace(/\/?$/, '/') : rocketUrl; + return `${ rocketUrl }api/v1/smarti.result/${ settings.get('Assistify_AI_RocketChat_Webhook_Token') }`; + } + + /** + * Cretaes an empty conversation when a new "Smarti" enabled room is created. + * Currently called by createExpertise, createRequestFromRoom, createEquestFromRoomId. + * + * Todo: It would be nice to have this registered as a hook, + * but there's no good implementation of this in the core: + * See createRoom.js: callbacks.run('afterCreateChannel', owner, room); + * + * @param {String} rid + */ + static afterCreateChannel(rid) { + SystemLogger.debug('Room created: ', rid); + SmartiAdapter._createAndPostConversation(rid); + } + + /** + * Event implementation that posts the message to Smarti. + * + * @param {object} message: { + * _id: STRING, + * rid: STRING, + * u: {*}, + * msg: STRING, + * ts: NUMBER, + * origin: STRING + * } + */ + static onMessage(message) { + try { + let conversationId = SmartiAdapter.getConversationId(message.rid); + if (!conversationId) { + // create conversation + SystemLogger.debug(`Conversation not found for room ${ message.rid }, create a new conversation.`); + const room = Rooms.findOneById(message.rid); + + if (room.t === 'd') { + return; // we'll not sync direct messages to Smarti for the time being + } + + conversationId = SmartiAdapter._createAndPostConversation(message.rid).id; + } + + const requestBodyMessage = { + id: message._id, + time: message.ts, + origin: 'User', // user.type, + content: message.msg, + user: { + id: message.u._id, + }, + metadata: {}, + // ,"private" : false + }; + + if (message.origin === 'smartiWidget') { + requestBodyMessage.metadata.skipAnalysis = true; + } + + SystemLogger.debug(`Conversation ${ conversationId } found for channel ${ message.rid }, perform conversation update.`); + let request_result; + if (message.editedAt) { + SystemLogger.debug('Trying to update existing message...'); + // update existing message + request_result = SmartiProxy.propagateToSmarti(verbs.put, `conversation/${ conversationId }/message/${ requestBodyMessage.id }`, null, requestBodyMessage, (error) => { + // 404 is expected if message doesn't exist + if (!error.response || error.response.statusCode === 404) { + SystemLogger.debug('Message not found!'); + SystemLogger.debug('Adding new message to conversation...'); + request_result = SmartiProxy.propagateToSmarti(verbs.post, `conversation/${ conversationId }/message`, null, requestBodyMessage); + } + }); + } else { + SystemLogger.debug('Adding new message to conversation...'); + request_result = SmartiProxy.propagateToSmarti(verbs.post, `conversation/${ conversationId }/message`, null, requestBodyMessage); + } + + if (request_result) { + Meteor.defer(() => SmartiAdapter._markMessageAsSynced(message._id)); + notifyClientsSmartiDirty(message.rid, conversationId); + // autosync: If a room was not in sync, but the new message could be synced, try to sync the room again + + /* + Remove autosync after a message is sent due to negative side effects: + In the course of marking a messages synced, an update on a message is performed. + This makes the UI load the updated message. + Depending on the message count resynced, this can have a severe impact on client performance + Meteor.defer(() => SmartiAdapter._tryResync(message.rid, false)); + */ + } else { + // if the message could not be synced this time, re-synch the complete room next time + Meteor.defer(() => SmartiAdapter._markRoomAsUnsynced(message.rid)); + } + } catch (e) { + // Something unexpected happened - maybe Smarti is not available at all. + // we at least cannot consider the sync successful + Meteor.defer(() => SmartiAdapter._markRoomAsUnsynced(message.rid)); + } + + if (settings.get('Assistify_AI_Smarti_Inline_Highlighting_Enabled')) { + Meteor.defer(() => SmartiAdapter.triggerAnalysis(message.rid)); + } + } + + /** + * Event implementation for deletion of messages. + * + * @param {*} message - the message which has just been deleted + */ + static afterMessageDeleted(message) { + const conversationId = SmartiAdapter.getConversationId(message.rid); + if (conversationId) { + SystemLogger.debug(`Smarti - Deleting message ${ message.rid } from conversation ${ conversationId }.`); + SmartiProxy.propagateToSmarti(verbs.delete, `conversation/${ conversationId }/message/${ message._id }`); + notifyClientsSmartiDirty(message.rid, conversationId); + } else { + SystemLogger.error(`Smarti - deleting message from conversation faild after delete message [ id: ${ message._id } ] from room [ id: ${ message.rid } ]`); + } + } + + /** + * Event implementation that publishes the conversation in Smarti. + * + * @param room - the room to close + * + * @returns {*} + */ + static onClose(room) { + const conversationId = SmartiAdapter.getConversationId(room._id); + + if (conversationId) { + const res = SmartiProxy.propagateToSmarti(verbs.put, `/conversation/${ conversationId }/meta.status`, null, 'Complete'); + if (!res) { + Meteor.defer(() => SmartiAdapter._markRoomAsUnsynced(room._id)); + } + } else { + SystemLogger.error(`Smarti - setting conversation to complete faild when closing room [ ${ room._id } ]`); + } + } + + /** + * Propagates the deletion of a complete conversation to Smarti. + * + * @param room - the room just deleted + */ + static afterRoomErased(room) { + const conversationId = SmartiAdapter.getConversationId(room._id); + if (conversationId) { + SystemLogger.debug(`Smarti - Deleting conversation ${ conversationId } after room ${ room._id } erased.`); + SmartiProxy.propagateToSmarti(verbs.delete, `/conversation/${ conversationId }`); + SmartiAdapter._removeMapping(room.id); + } else { + SystemLogger.error(`Smarti - deleting conversation faild after erasing room [ ${ room._id } ]`); + } + } + + + /** + * Returns the cached conversationId from the analyzed conversations cache (model AssistifySmarti). + * If the conversation is not cached it will be retrieved from Smarti's legacy/rocket.chat service. + * Finally the mapping cache (roomID <-> smartiResult) is updated. + * + * @param {*} roomId - the room for which the Smarti conversationId shall be retrieved + */ + static getConversationId(roomId) { + const cachedMapping = assistifySmarti.findOneByRoomId(roomId); + let conversationId = cachedMapping ? cachedMapping.conversationId : ''; + + if (conversationId) { + return conversationId; + } + + // uncached conversation + SystemLogger.debug('Trying Smarti legacy service to retrieve conversation...'); + const conversation = SmartiProxy.propagateToSmarti(verbs.get, `legacy/rocket.chat?channel_id=${ roomId }`, null, null, (error) => { + // 404 is expected if no mapping exists in Smarti + if (error && error.response && error.response.statusCode === 404) { + SystemLogger.warn(`No Smarti conversationId found (Server Error 404) for room: ${ roomId }`); + } else { + // some other error occurred + SystemLogger.error(`Unexpected error while retrieving Smarti conversationId for room: ${ roomId }`, error.response); + } + }); + if (conversation && conversation.id) { + // uncached conversation found in Smarti, update mapping ... + conversationId = conversation.id; + SmartiAdapter._updateMapping(roomId, conversationId); + } else { + SystemLogger.debug(`Smarti - no conversation found for room: ${ roomId }`); + } + return conversationId; + } + + /** + * At any point in time when the anylsis for a room has been done (onClose, onMessage), + * analysisCompleted() updates the mapping and notifies the room, in order to reload the Smarti result representation + * + * @param roomId - the RC room Id + * @param conversationId - the Smarti conversation Id + * @param analysisResult - the analysis result from Smarti + */ + static analysisCompleted(roomId, conversationId, analysisResult) { + if (roomId === null) { + const conversationCacheEntry = assistifySmarti.findOneByConversationId(conversationId); + if (conversationCacheEntry && conversationCacheEntry.rid) { + roomId = conversationCacheEntry.rid; + } else { + SystemLogger.error(`Smarti - no room found for conversation [ ${ conversationId } ] unable to notify room.`); + } + } + SystemLogger.debug('Smarti - retieved analysis result =', JSON.stringify(analysisResult, null, 2)); + SystemLogger.debug(`Smarti - analysis complete -> update cache and notify room [ ${ roomId } ] for conversation [ ${ conversationId } ]`); + Notifications.notifyRoom(roomId, 'newConversationResult', analysisResult); + + // update the affected message with the tokens extracted. This will trigger a re-rendering of the message + SmartiAdapter.updateTokensInMessages(roomId, analysisResult); + } + + static updateTokensInMessages(roomId, analysisResult) { + const allTerms = analysisResult.tokens.reduce((terms, token) => terms.set(token.value,{value: token.value, type: token.type}), new Map()); //eslint-disable-line + + // we'll check whether the tokens found were contained in the last messages. We just pick a bunch (and not only the last one) + // in order to handle potential message-sent-overlap while anlyzing + const lastMessages = Messages.findByRoomId(roomId, { sort: { _updatedAt: -1 }, limit: 5 }); + lastMessages.forEach((message) => { + let termsChanged = false; + const alreadyRecognizedTokens = new Map(); + if (message.recognizedTokens) { + message.recognizedTokens.forEach((token) => alreadyRecognizedTokens.set(token.value.toLowerCase(), token)); + } + + allTerms.forEach((token, term) => { + if (message.msg.indexOf(term) > -1) { + if (!alreadyRecognizedTokens.has(term.toLowerCase())) { + termsChanged = true; + alreadyRecognizedTokens.set(token.value, token); + } + } + }); + if (termsChanged) { + Messages.setRecognizedTokensById(message._id, Array.from(alreadyRecognizedTokens.values())); + } + }); + } + + /** + * Updates the mapping and triggers an asynchronous analysis. + */ + static triggerAnalysis(roomId) { + const conversationId = SmartiAdapter.getConversationId(roomId); + if (conversationId) { + SmartiProxy.propagateToSmarti(verbs.get, `conversation/${ conversationId }/analysis`, { callback: SmartiAdapter.rocketWebhookUrl }); // asynch + } else { + SystemLogger.error(`No conversation found for roomId ${ roomId }`); + } + } + + /** + * Triggers the re-synchronization with Smarti. + * + * @param {boolean} ignoreSyncFlag - if 'true' the dirty flag for outdated rooms and messages will be ignored. If 'false' only rooms and messages are synchronized that are marked as outdated. + */ + static resync(ignoreSyncFlag) { + terminateCurrentSync(); + + SystemLogger.info('Smarti resync triggered'); + + if (!SmartiAdapter._smartiAvailable()) { + return false; + } + + // flush the cache + if (ignoreSyncFlag) { + SmartiAdapter._clearMapping(); + } + + let syncModeQuery = {}; + if (!ignoreSyncFlag || ignoreSyncFlag !== true) { + syncModeQuery = { $or: [{ outOfSync: true }, { outOfSync: { $exists: false } }] }; + } + + const roomTypesQuery = { + $or: [ + { t: 'c' }, + { t: 'p' }, + { t: 'l' }, + ], + }; + + const query = { $and: [roomTypesQuery, syncModeQuery] }; + const roomProperties = { + _id: 1, + name: 1, + closedAt: 1, + outOfSync: 1, + u: 1, + v: 1, + }; + + const options = { fields: roomProperties, sort: { _id: 1 } }; + + const batchSize = parseInt(settings.get('Assistify_AI_Resync_Batchsize')) || 10; + const batchTimeout = parseInt(settings.get('Assistify_AI_Resync_Batch_Timeout')) || 1000; + + // determine the count once in order to have a maximum limit of batches - some healthy paranoia + const totalCount = Rooms.model.find(query, options).count(); + const maxBatches = totalCount / batchSize + 1; + let batchCount = 0; + let resyncFailed = false; + + // Meteor cursors are not Mongo-cursors: No native paging is possible => do it manually + const roomsCursor = Rooms.model.find(query, options); + + // unfortunately, there were memory garbage collection issues when using the intended + // paging with skip and limit inside the syncBatch function (see previous revision) + // => Fetch all the rooms upfront and page in the fetched array of rooms. + const rooms = roomsCursor.fetch(); + + function getNextBatch() { + const begin = batchCount * batchSize; + const end = begin + batchSize; + batchCount++; + + return rooms.slice(begin, end); + } + + function syncBatch() { + const roomsBatch = getNextBatch(); + // trigger the syncronisation for the whole batch + roomsBatch.forEach((room) => { + SystemLogger.debug('Smarti syncing', room.name); + resyncFailed = resyncFailed || !SmartiAdapter._tryResync(room._id, ignoreSyncFlag); // we're in a loop. We should not process the next item if an earlier one failed + }); + + if (roomsBatch.length === 0) { + SystemLogger.success('Sync with Smarti completed'); + terminateCurrentSync(); + return; + } + + if ((batchCount > maxBatches) || resyncFailed) { + SystemLogger.error('Sync with Smarti was not successful - try a delta-sync'); + terminateCurrentSync(); + } + } + + syncTimer = Meteor.setInterval(syncBatch, batchTimeout); + + return { + message: 'sync-triggered-successfully', + }; + } + + /** + * Performs the synchronization for a single room/conversation. + * + * @param {String} rid - the id of the room to sync + * @param {boolean} ignoreSyncFlag @see resync(ignoreSyncFlag) + */ + static resyncRoom(rid, ignoreSyncFlag) { + Meteor.defer(() => SmartiAdapter._tryResync(rid, ignoreSyncFlag)); + } + + static getGoogleResult(params) { + SystemLogger.info('getGoogleResult with params: ', params); + return SmartiProxy.propagateToGoogle(verbs.get, params); + } + + /** + * Performs the synchronization for a single room/conversation. + * + * @param {String} rid - the id of the room to sync + * @param {boolean} ignoreSyncFlag @see resync(ignoreSyncFlag) + */ + static _tryResync(roomId, ignoreSyncFlag) { + try { + SystemLogger.debug('Sync messages for room: ', roomId); + + const limit = parseInt(settings.get('Assistify_AI_Resync_Message_Limit')) || 10; + const messageFindOptions = { sort: { ts: -1 }, limit }; + + // only resync rooms containing outdated messages, if a delta sync is requested + if (!ignoreSyncFlag || ignoreSyncFlag !== true) { + const unsync = Messages.find({ lastSync: { $exists: false }, rid: roomId, t: { $exists: false } }, messageFindOptions).count(); + if (unsync === 0) { + SystemLogger.debug('Room is already in sync'); + SmartiAdapter._markRoomAsSynced(roomId); // this method was asked to resync, but there's nothing to be done => update the sync-metadata + return true; + } + + SystemLogger.debug('Messages out of sync: ', unsync); + } + + // delete convervation from Smarti, if already exists + const conversationId = SmartiAdapter.getConversationId(roomId); + if (conversationId) { + SystemLogger.debug(`Conversation found ${ conversationId } - delete and create new conversation`); + SmartiProxy.propagateToSmarti(verbs.delete, `conversation/${ conversationId }`); + + // delete the cached mapping as well - else, we'll point to the wrong conversation lteron + assistifySmarti.removeByRoomId(roomId); + } + + // get the messages of the room and create a conversation from it + const messages = Messages.find({ rid: roomId, t: { $exists: false } }, messageFindOptions).fetch(); + const newSmartiConversation = SmartiAdapter._createAndPostConversation(roomId, messages); + + // get the analysis result (synchronously), update the cache and notify rooms + // const analysisResult = SmartiProxy.propagateToSmarti(verbs.get, `conversation/${ newSmartiConversation.id }/analysis`); + // SmartiAdapter.analysisCompleted(roomId, newSmartiConversation.id, analysisResult); + SystemLogger.debug(`Smarti analysis completed for conversation ${ newSmartiConversation.id }`); + + for (let i = 0; i < messages.length; i++) { + Meteor.defer(() => SmartiAdapter._markMessageAsSynced(messages[i]._id)); + } + SmartiAdapter._markRoomAsSynced(roomId); + + // finally it's done + return true; + } catch (e) { + return false; + } + } + + /** + * Builds a new conversation and posts it to Smarti. + * If messages are not passed, a conversation without messages will created for the given room in Smarti. + * + * @param {object} room - the room object + * @param {Array} messages - the messages of that room + * + * @throws {Meteor.Error} - if the conversation could not be created in Smarti + */ + static _createAndPostConversation(roomId, messages) { + const room = Rooms.findOneById(roomId); + + // create the conversation "header/metadata" + const supportArea = SmartiAdapter._getSupportArea(room); + let userId = ''; + + // we need a small decision tree for the owner of a room. There even are channels without owner (GENERAL)! + if (room.u) { // normal rooms + userId = room.u._id; + } else if (room.v) { // livechat rooms + userId = room.v._id; + } + const conversationBody = { + meta: { + support_area: [supportArea], + channel_id: [room._id], + }, + user: { + id: userId, + }, + messages: [], + context: { + contextType: 'rocket.chat', + }, + }; + if (room.closedAt) { + conversationBody.meta.status = 'Complete'; + } + + // add messages to conversation, if present + if (messages && messages.length > 0) { + conversationBody.messages = []; + for (let i = 0; i < messages.length; i++) { + const newMessage = { + id: messages[i]._id, + time: messages[i].ts, + origin: 'User', + content: messages[i].msg, + user: { + id: messages[i].u._id, + }, + }; + conversationBody.messages.push(newMessage); + } + } + + // post the conversation + const conversation = SmartiProxy.propagateToSmarti(verbs.post, 'conversation', null, conversationBody, (error) => { + SystemLogger.error(`Smarti - unexpected server error: ${ JSON.stringify(error, null, 2) } occured when creating a new conversation: ${ JSON.stringify(conversationBody, null, 2) }`); + }); + if (!conversation && !conversation.id) { + const e = new Meteor.Error('Could not create conversation for room:', room._id); + SystemLogger.error(e); + } + SystemLogger.debug(`Smarti - New conversation with Id ${ conversation.id } created`); + SmartiAdapter._updateMapping(room._id, conversation.id); + return conversation; + } + + /** + * Returns the support area for a given room. + * The "support_area" in Smarti is an optional property. + * A historic conversation belonging to the same support_are increases relevance. + * + * @param {object} room - the room to get the support area for + */ + static _getSupportArea(room) { + let supportArea = room.topic || room.parentRoomId; + if (!supportArea) { + if (room.t === 'l') { + supportArea = 'livechat'; + } else { + supportArea = room.name; + } + } + + SystemLogger.debug('Room:', room); + return supportArea; + } + + static _markMessageAsSynced(messageId) { + SystemLogger.debug('_markMessageAsSynced', messageId); + + const messageDB = Messages; + const message = messageDB.findOneById(messageId); + const lastUpdate = message ? message._updatedAt : 0; + if (lastUpdate) { + messageDB.model.update( + { _id: messageId }, + { + $set: { + lastSync: lastUpdate, + }, + }); + SystemLogger.debug('Message Id: ', messageId, ' has been synced'); + } else { + SystemLogger.debug('Message Id: ', messageId, ' can not be synced'); + } + } + + static _markRoomAsSynced(rid) { + SystemLogger.debug('_markRoomAsSynced', rid); + Rooms.model.update( + { _id: rid }, + { + $set: { + outOfSync: false, + }, + }); + SystemLogger.debug('Room Id: ', rid, ' is in sync now'); + } + + static _markRoomAsUnsynced(rid) { + SystemLogger.debug('_markRoomAsUnsynced', rid); + Rooms.model.update( + { _id: rid }, + { + $set: { + outOfSync: true, + }, + }); + SystemLogger.debug('Room Id: ', rid, ' is out of sync'); + } + + static _updateMapping(roomId, conversationId) { + if (!roomId && !conversationId) { + const e = new Meteor.Error('Smarti - Unable to update mapping roomId or conversationId undefined'); + SystemLogger.error(e); + } + + assistifySmarti.update( + { + _id: roomId, + }, { + rid: roomId, + knowledgeProvider: 'smarti', + conversationId, + }, { + upsert: true, + }, + ); + } + + static _removeMapping(roomId) { + assistifySmarti.remove({ _id: roomId }); + } + + static _clearMapping() { + assistifySmarti.clear(); + } + + static _smartiAvailable() { + // if Smarti is not available stop immediately + const resp = SmartiProxy.propagateToSmarti(verbs.get, 'system/health', null, null, (error) => { + if (error.statusCode !== 200) { + const e = new Meteor.Error('Smarti not reachable!'); + SystemLogger.error('Stop synchronizing with Smarti immediately:', e); + } + }); + if (JSON.parse(resp).status !== 'UP') { + const e = new Meteor.Error('Smarti not healthy!'); + SystemLogger.error('Stop synchronizing with Smarti immediately:', e); + return false; + } + return true; + } +} diff --git a/app/assistify/ai/server/methods/SmartiReload.js b/app/assistify/ai/server/methods/SmartiReload.js new file mode 100644 index 0000000000000..8c598fbfe4c6d --- /dev/null +++ b/app/assistify/ai/server/methods/SmartiReload.js @@ -0,0 +1,40 @@ +import { Meteor } from 'meteor/meteor'; + +import { hasRole } from '../../../../authorization'; +import { SmartiAdapter } from '../lib/SmartiAdapter'; +import { RateLimiter } from '../../../../lib/server/lib'; + +Meteor.methods({ + + resyncRoom(rid, ignoreSyncFlag = true) { + if (!hasRole(Meteor.userId(), 'admin')) { + throw new Meteor.Error(`SmartiResynch - triggerResync not permitted for user [ ${ Meteor.userId() } ]`); + } + SmartiAdapter.resyncRoom(rid, ignoreSyncFlag); + }, + + triggerResync() { + if (!hasRole(Meteor.userId(), 'admin')) { + throw new Meteor.Error(`SmartiResynch - triggerResync not permitted for user [ ${ Meteor.userId() } ]`); + } + return SmartiAdapter.resync(false); + }, + + triggerFullResync() { + if (!hasRole(Meteor.userId(), 'admin')) { + throw new Meteor.Error(`SmartiResynch - triggerResync not permitted for user [ ${ Meteor.userId() } ]`); + } + return SmartiAdapter.resync(true); + }, +}); + +/** + * Limit exposed methods to prevent DOS. + */ + +RateLimiter.limitMethod('triggerResync', 1, 2000, { + userId() { return true; }, +}); +RateLimiter.limitMethod('triggerFullResync', 1, 2000, { + userId() { return true; }, +}); diff --git a/app/assistify/ai/server/methods/SmartiWidgetBackend.js b/app/assistify/ai/server/methods/SmartiWidgetBackend.js new file mode 100644 index 0000000000000..6d150116ebd39 --- /dev/null +++ b/app/assistify/ai/server/methods/SmartiWidgetBackend.js @@ -0,0 +1,192 @@ +import { Meteor } from 'meteor/meteor'; + +import { SystemLogger } from '../../../../logger/server'; +import { SmartiProxy, verbs } from '../SmartiProxy'; +import { SmartiAdapter } from '../lib/SmartiAdapter'; +import { RateLimiter } from '../../../../lib/server/lib'; +import { hasPermission } from '../../../../authorization/server'; +import { Rooms, Subscriptions } from '../../../../models/server'; + + +/** @namespace RateLimiter.limitFunction */ + +/** + * The SmartiWidgetBackend handles all interactions triggered by the Smarti widget (not by Rocket.Chat hooks). + * These 'Meteor.methods' are made available to be accessed via DDP, to be used in the Smarti widget. + */ +Meteor.methods({ + + /** + * Returns the conversation Id for the given client and its channel. + * + * @param {String} channelId - the channel Id + * + * @returns {String} - the conversation Id + */ + getConversationId(channelId) { + return RateLimiter.limitFunction( + SmartiAdapter.getConversationId, 5, 1000, { + userId(userId) { + return !hasPermission(userId, 'send-many-messages'); + }, + }, + )(channelId); + }, + + /** + * Returns the analyzed conversation by id. + * + * @param {String} conversationId - the conversation to retrieve + * + * @returns {Object} - the analysed conversation + */ + getConversation(conversationId) { + return RateLimiter.limitFunction( + SmartiProxy.propagateToSmarti, 5, 1000, { + userId(userId) { + return !hasPermission(userId, 'send-many-messages'); + }, + }, + )(verbs.get, `conversation/${ conversationId }/analysis`, null, null, (error) => { + // 404 is expected if no mapping exists + if (error.response && error.response.statusCode === 404) { + return null; + } + }); + }, + + /** + * This method provides the client a handler to request the asynchronous analysis of a room's messages + * It can e. g. be issued from the widget once it's opened + * @param {*} roomId The ID of the Rocket.Chat room which shall be analyzed + */ + analyze(roomId) { + return RateLimiter.limitFunction( + SmartiAdapter.triggerAnalysis, 5, 1000, { + userId(userId) { + return !hasPermission(userId, 'send-many-messages'); + }, + }, + )(roomId); + }, + + /** + * Returns the query builder results for the given conversation (used by Smarti widget) + * + * @param {String} conversationId - the conversation id to get results for + * @param {Number} templateIndex - the index of the template to get the results for + * @param {String} creator - the creator providing the suggestions + * @param {Number} start - the offset of the suggestion results (pagination) + * @param {Number} rows - number of the suggestion results (pagination) + * + * @returns {Object} - the suggestions + */ + getQueryBuilderResult(conversationId, templateIndex, creator, start, rows) { + return RateLimiter.limitFunction( + SmartiProxy.propagateToSmarti, 5, 1000, { + userId(userId) { + return !hasPermission(userId, 'send-many-messages'); + }, + }, + )(verbs.get, `conversation/${ conversationId }/analysis/template/${ templateIndex }/result/${ creator }`, { start, rows }); + }, + + searchConversations(queryParams) { + function unique(value, index, array) { + return array.indexOf(value) === index; + } + + const solrFilterBooleanLimit = 256; // there is a limit for boolean expressinos in a filter query of default 1024 and an additional limiter by the HTTP-server. Experiments showed this limit as magic number. + const findOptions = { limit: solrFilterBooleanLimit, sort: { ts: -1 }, fields: { _id: 1 } }; + const subscribedRooms = Subscriptions.find({ 'u._id': Meteor.userId() }, { limit: solrFilterBooleanLimit, sort: { ts: -1 }, fields: { rid: 1 } }).fetch().map((subscription) => subscription.rid); + const publicChannels = hasPermission(Meteor.userId(), 'view-c-room') ? Rooms.find({ t: 'c' }, findOptions).fetch().map((room) => room._id) : []; + const livechats = hasPermission(Meteor.userId(), 'view-l-room') ? Rooms.find({ t: 'l' }, findOptions).fetch().map((room) => room._id) : []; + + const accessibleRooms = livechats.concat(subscribedRooms).concat(publicChannels); + + let fq = `${ accessibleRooms.filter(unique).slice(0, solrFilterBooleanLimit).join(' OR ') }`; + fq = fq ? { fq: `meta_channel_id:(${ fq })` } : { fq: 'meta_channel_id:""' }; // fallback: if the user's not authorized to view any room, filter for "nothing" + const params = Object.assign(queryParams, fq); + + const searchResult = RateLimiter.limitFunction( + SmartiProxy.propagateToSmarti, 5, 1000, { + userId(userId) { + return !hasPermission(userId, 'send-many-messages'); + }, + }, + )(verbs.get, 'conversation/search', params); + SystemLogger.debug('SearchResult: ', JSON.stringify(searchResult, null, 2)); + return searchResult; + }, +}); + + +// ////////////////////////////////////////// +// ////// LOAD THE SMARTI JavaScript //////// +// ////////////////////////////////////////// + +// TODO: Prevent writing JavaScript into a inline