diff --git a/.meteor/packages b/.meteor/packages index d26a8f807b61..07715a27d4d0 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -190,3 +190,4 @@ assistify:defaults chatpal:search rocketchat:version-check meteorhacks:aggregate +overture8:wordcloud2 diff --git a/.meteor/versions b/.meteor/versions index b7b4b5a9e191..8c88851004ea 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -117,6 +117,7 @@ observe-sequence@1.0.16 ordered-dict@1.1.0 ostrio:cookies@2.2.4 pauli:accounts-linkedin@2.1.5 +overture8:wordcloud2@1.0.0 pauli:linkedin-oauth@1.2.0 percolate:synced-cron@1.3.2 promise@0.10.2 diff --git a/packages/assistify-ai/client/views/app/tabbar/smarti.js b/packages/assistify-ai/client/views/app/tabbar/smarti.js index f17d44e72e0d..441a3f339cdf 100644 --- a/packages/assistify-ai/client/views/app/tabbar/smarti.js +++ b/packages/assistify-ai/client/views/app/tabbar/smarti.js @@ -62,6 +62,14 @@ Template.AssistifySmarti.onRendered(function() { }, lang: localStorage.getItem('userLanguage').split('-')[0] }; + + //propagate i18n - support formatted strings while doing that + const i18nSetting = RocketChat.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); } @@ -74,14 +82,14 @@ Template.AssistifySmarti.onRendered(function() { Template.AssistifySmarti.helpers({ isLivechat() { const instance = Template.instance(); - return ChatSubscription.findOne({rid: instance.data.rid}).t === 'l'; + 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}; + return { roomId: instance.data.rid }; }, helpRequestByRoom() { const instance = Template.instance(); diff --git a/packages/assistify-ai/config.js b/packages/assistify-ai/config.js index d45f8739a28b..0f8029708a2c 100644 --- a/packages/assistify-ai/config.js +++ b/packages/assistify-ai/config.js @@ -69,6 +69,13 @@ Meteor.startup(() => { public: true, i18nLabel: 'Assistify_AI_RocketChat_Webhook_Token' }); + + this.add('Assistify_AI_Smarti_Widget_i18n', '', { + type: 'code', + public: true, + i18nLabel: 'Assistify_AI_Smarti_Widget_i18n', + i18nDescription: 'Assistify_AI_Smarti_Widget_i18n_Description' + }); }); }; diff --git a/packages/assistify-help-request/assets/stylesheets/helpRequestContext.less b/packages/assistify-help-request/assets/stylesheets/helpRequestContext.less index f093f432d7f3..21bf1813f12d 100755 --- a/packages/assistify-help-request/assets/stylesheets/helpRequestContext.less +++ b/packages/assistify-help-request/assets/stylesheets/helpRequestContext.less @@ -79,4 +79,16 @@ color: var(--input-placeholder-color); } } + + .rc-input { + &__icon { + &--right { + right: 1rem; + left: auto; + } + } + } + .rc-input__icon-svg--book-alt { + cursor: pointer; + } } diff --git a/packages/assistify-help-request/client/public/icons.svg b/packages/assistify-help-request/client/public/icons.svg index c62eda1caec2..c5e8bf5423fc 100644 --- a/packages/assistify-help-request/client/public/icons.svg +++ b/packages/assistify-help-request/client/public/icons.svg @@ -1,6 +1,17 @@ - - - - + + + + + + + + + + + + + + diff --git a/packages/assistify-help-request/client/views/creationDialog/AssistifyCreateRequest.html b/packages/assistify-help-request/client/views/creationDialog/AssistifyCreateRequest.html index 585bca1889a0..fbe10054fa53 100644 --- a/packages/assistify-help-request/client/views/creationDialog/AssistifyCreateRequest.html +++ b/packages/assistify-help-request/client/views/creationDialog/AssistifyCreateRequest.html @@ -1,76 +1,84 @@ - - diff --git a/packages/assistify-help-request/client/views/creationDialog/AssistifyCreateRequest.js b/packages/assistify-help-request/client/views/creationDialog/AssistifyCreateRequest.js index 89a1f3f7c567..4889e743ddfa 100755 --- a/packages/assistify-help-request/client/views/creationDialog/AssistifyCreateRequest.js +++ b/packages/assistify-help-request/client/views/creationDialog/AssistifyCreateRequest.js @@ -9,6 +9,14 @@ const acEvents = { t.ac.onItemClick(this, e); t.debounceValidateExpertise(this.item.name); }, + 'click .rc-input__icon-svg--book-alt'(e, t) { + e.preventDefault(); + t.topicSearchEnable.set(true); + }, + 'click #more-topics'(e, t) { + e.preventDefault(); + t.topicSearchEnable.set(true); + }, 'keydown [name="expertise"]'(e, t) { t.ac.onKeyDown(e); }, @@ -16,11 +24,15 @@ const acEvents = { t.ac.onKeyUp(e); }, 'focus [name="expertise"]'(e, t) { + if (t.expertise.get() === '' && t.showDropDown.get() === '') { + t.showDropDown.set('isShowing'); + } t.ac.onFocus(e); }, 'blur [name="expertise"]'(e, t) { t.ac.onBlur(e); t.debounceValidateExpertise(e.target.value); + t.debounceDropDown(); } }; @@ -29,17 +41,27 @@ Template.AssistifyCreateRequest.helpers({ autocomplete(key) { const instance = Template.instance(); const param = instance.ac[key]; + if (!Template.instance().expertise.get() && Template.instance().showDropDown.get() === 'isShowing') { + return true; // show the expertise auto complete drop down + } return typeof param === 'function' ? param.apply(instance.ac) : param; }, items() { - return Template.instance().ac.filteredList(); + const instance = Template.instance(); + if (instance.expertise.get() === '') { + if (instance.expertisesList.get() && instance.expertisesList.get().length <= 10) { + return instance.expertisesList.get(); + } + // instance.showDropDown.set(''); + } + return instance.ac.filteredList(); }, config() { const filter = Template.instance().expertise; return { filter: filter.get(), template_item: 'AssistifyCreateRequestAutocomplete', - noMatchTemplate: 'userSearchEmpty', + noMatchTemplate: 'AssistifyTopicSearchEmpty', modifier(text) { const f = filter.get(); return `#${ f.length === 0 ? text : text.replace(new RegExp(filter.get()), function(part) { @@ -50,7 +72,6 @@ Template.AssistifyCreateRequest.helpers({ }, createIsDisabled() { const instance = Template.instance(); - if (instance.validExpertise.get() && !instance.titleError.get()) { return ''; } else { @@ -68,10 +89,68 @@ Template.AssistifyCreateRequest.helpers({ titleError() { const instance = Template.instance(); return instance.titleError.get(); + }, + topicSearchEnable() { + const instance = Template.instance(); + return instance.topicSearchEnable.get(); + }, + getWordcloudProperties() { + const instance = Template.instance(); + const expertises = instance.expertisesList.get(); + + function getRandomArbitrary(min, max) { + return Math.random() * (max - min) + min; + } + + function getWordList() { + const list = []; + expertises.forEach(function(expertise) { + list.push([expertise.name, getRandomArbitrary(4, 10)]); + }); + return list; + } + + function setExpertise() { + return function(selectedExpertise) { + const expertise = expertises.find(expertise => expertise.name === selectedExpertise[0]); + if (expertise) { + instance.debounceWordCloudSelect(expertise); + } + instance.topicSearchEnable.set(''); //Search completed. + }; + } + + function onWordHover() { + return function(item) { + // To Do + return item; + + }; + } + + function setFlatness() { + return 0.5; + } + + return { + clearCanvas: true, + weightFactor: 8, + fontWeight: 'normal', + gridSize: 55, + shape: 'square', + rotateRatio: 0, + rotationSteps: 0, + drawOutOfBound: true, + shuffle: true, + ellipticity: setFlatness(), + list: getWordList(), + click: setExpertise(), + hover: onWordHover() + //setCanvas: getCanvas + }; } }); - Template.AssistifyCreateRequest.events({ ...acEvents, 'input #expertise-search'(e, t) { @@ -91,7 +170,6 @@ Template.AssistifyCreateRequest.events({ } else { t.debounceValidateRequestName(input.value); } - }, 'input #first_question'(e, t) { const input = e.target; @@ -150,10 +228,8 @@ Template.AssistifyCreateRequest.onRendered(function() { instance.expertise.set(item.name); $('input[name="expertise"]').val(item.name); instance.debounceValidateExpertise(item.name); - return instance.find('.js-save-request').focus(); }); - if (instance.requestTitle.get()) { titleElement.value = instance.requestTitle.get(); } @@ -164,7 +240,9 @@ Template.AssistifyCreateRequest.onRendered(function() { // strategy for setting the focus (yac!) if (!expertiseElement.value) { - expertiseElement.focus(); + Meteor.setTimeout(()=>{ + expertiseElement.focus(); + }, 1500); } else if (!questionElement.value) { questionElement.focus(); } else if (!titleElement.value) { @@ -172,18 +250,41 @@ Template.AssistifyCreateRequest.onRendered(function() { } else { questionElement.focus(); } + this.autorun(() => { + instance.debounceWordCloudSelect = _.debounce((expertise) => { + /* + * Update the expertise html reference to autocomplete + */ + instance.ac.element = this.find('#expertise-search'); + instance.ac.$element = $(instance.ac.element); + $('input[name="expertise"]').val(expertise.name); // copy the selected value to screen field + instance.ac.$element.on('autocompleteselect', function(e, {item}) { + instance.expertise.set(item.name); + $('input[name="expertise"]').val(item.name); + instance.debounceValidateExpertise(item.name); + return instance.find('.js-save-request').focus(); + }); + instance.expertise.set(expertise.name); + instance.debounceValidateExpertise(expertise.name); // invoke validation*/ + }, 200); + }); }); Template.AssistifyCreateRequest.onCreated(function() { const instance = this; - instance.expertise = new ReactiveVar(''); //the value of the text field instance.validExpertise = new ReactiveVar(false); instance.expertiseError = new ReactiveVar(null); instance.titleError = new ReactiveVar(null); instance.requestTitle = new ReactiveVar(''); instance.openingQuestion = new ReactiveVar(''); + instance.topicSearchEnable = new ReactiveVar(''); + instance.showDropDown = new ReactiveVar(''); + instance.expertisesList = new ReactiveVar(''); + instance.debounceDropDown = _.debounce(() => { + instance.showDropDown.set(''); + }, 200); instance.debounceValidateExpertise = _.debounce((expertise) => { if (!expertise) { @@ -234,7 +335,6 @@ Template.AssistifyCreateRequest.onCreated(function() { } } }, 500); - this.ac = new AutoComplete({ selector: { item: '.rc-popup-list__item', @@ -260,7 +360,7 @@ Template.AssistifyCreateRequest.onCreated(function() { }); this.ac.tmplInst = this; - //prefill form based on query parameters if passed + //pre-fill form based on query parameters if passed if (FlowRouter.current().queryParams) { const expertise = FlowRouter.getQueryParam('topic') || FlowRouter.getQueryParam('expertise'); if (expertise) { @@ -278,4 +378,9 @@ Template.AssistifyCreateRequest.onCreated(function() { instance.openingQuestion.set(question); } } + Meteor.call('expertiseList', {sort: 'name'}, function(err, result) { + if (result) { + instance.expertisesList.set(result.channels); + } + }); }); diff --git a/packages/assistify-help-request/client/views/creationDialog/AssistifyCreateRequestAutocomplete.html b/packages/assistify-help-request/client/views/creationDialog/AssistifyCreateRequestAutocomplete.html new file mode 100644 index 000000000000..05ee63bb0bad --- /dev/null +++ b/packages/assistify-help-request/client/views/creationDialog/AssistifyCreateRequestAutocomplete.html @@ -0,0 +1,5 @@ + diff --git a/packages/assistify-help-request/client/views/creationDialog/AssistifyTopicSearchEmpty.html b/packages/assistify-help-request/client/views/creationDialog/AssistifyTopicSearchEmpty.html new file mode 100644 index 000000000000..0771ade73cff --- /dev/null +++ b/packages/assistify-help-request/client/views/creationDialog/AssistifyTopicSearchEmpty.html @@ -0,0 +1,9 @@ + diff --git a/packages/assistify-help-request/client/views/creationDialog/AssistifyTopicSearchEmpty.js b/packages/assistify-help-request/client/views/creationDialog/AssistifyTopicSearchEmpty.js new file mode 100644 index 000000000000..03b54c3cdaac --- /dev/null +++ b/packages/assistify-help-request/client/views/creationDialog/AssistifyTopicSearchEmpty.js @@ -0,0 +1,15 @@ +Template.AssistifyTopicSearchEmpty.helpers({ + showMoreTopics() { + const instance = Template.instance(); + return instance.expertisesCount.get() > 10 ? true : false; + } +}); +Template.AssistifyTopicSearchEmpty.onCreated(function() { + const instance = this; + instance.expertisesCount = new ReactiveVar(''); + Meteor.call('expertiseList', {sort: 'name'}, function(err, result) { + if (result) { + instance.expertisesCount.set(result.channels.length); + } + }); +}); diff --git a/packages/assistify-help-request/client/views/creationDialog/AssistifyWordCloud.html b/packages/assistify-help-request/client/views/creationDialog/AssistifyWordCloud.html new file mode 100644 index 000000000000..b00d8d49130b --- /dev/null +++ b/packages/assistify-help-request/client/views/creationDialog/AssistifyWordCloud.html @@ -0,0 +1,5 @@ + diff --git a/packages/assistify-help-request/client/views/creationDialog/AssistifyWordCloud.js b/packages/assistify-help-request/client/views/creationDialog/AssistifyWordCloud.js new file mode 100644 index 000000000000..4afb23638394 --- /dev/null +++ b/packages/assistify-help-request/client/views/creationDialog/AssistifyWordCloud.js @@ -0,0 +1,33 @@ +/* eslint-disable no-unused-vars,new-cap */ +import { ReactiveVar } from 'meteor/reactive-var'; + +import { WordCloud } from 'meteor/overture8:wordcloud2'; + +function drawWords() { + const instance = Template.instance(); + const properties = instance.data.properties; + //properties.setCanvas(instance.canvasToDraw.get()); + window.WordCloud(instance.canvasToDraw.get(), properties); +} + +Template.AssistifyWordCloud.events({ +// To Do +}); + +Template.AssistifyWordCloud.helpers({ +// To Do +}); + +Template.AssistifyWordCloud.onRendered(function() { + const canvasToDraw = this.find('[id="wc-canvas"]'); + if (canvasToDraw) { + canvasToDraw.width = 1200; + canvasToDraw.height = 800; + this.canvasToDraw.set(canvasToDraw); + drawWords(); + } +}); + +Template.AssistifyWordCloud.onCreated(function() { + this.canvasToDraw = new ReactiveVar(''); +}); diff --git a/packages/assistify-help-request/lib/messageTypes/threadMessage.js b/packages/assistify-help-request/lib/messageTypes/threadMessage.js index bc7eec98fc96..aa2111f0910c 100644 --- a/packages/assistify-help-request/lib/messageTypes/threadMessage.js +++ b/packages/assistify-help-request/lib/messageTypes/threadMessage.js @@ -39,11 +39,11 @@ RocketChat.MessageTypes.registerType({ message: 'Thread_welcome_message', data(message) { /* Thread Welcome Message - * @Returns + * @Returns * Thread Initiator * Parent Room Name * Original Message - * */ + * */ const room = RocketChat.models.Rooms.findOne({_id: message.channels[0]._id}); let attachEvents = true; for (const e of Template.room.__eventMaps) { diff --git a/packages/assistify-help-request/package.js b/packages/assistify-help-request/package.js index 2cff535f8ccb..55874a80006d 100644 --- a/packages/assistify-help-request/package.js +++ b/packages/assistify-help-request/package.js @@ -18,6 +18,7 @@ Package.onUse(function(api) { api.use(['nimble:restivus', 'rocketchat:api'], 'server'); api.use('templating', 'client'); api.use('meteorhacks:inject-initial'); //for provisioning of svg-icons + //api.use('overture8:wordcloud2'); api.addFiles('help-request.js', 'server'); api.addFiles('server/types.js', 'server'); @@ -53,6 +54,7 @@ Package.onUse(function(api) { api.addFiles('server/methods/createRequestFromRoomId.js', 'server'); api.addFiles('server/methods/requestsList.js', 'server'); api.addFiles('server/methods/isValidExpertise.js', 'server'); + api.addFiles('server/methods/expertiseList.js', 'server'); // Hooks api.addFiles('server/hooks/sendMessageToKnowledgeAdapter.js', 'server'); @@ -74,6 +76,11 @@ Package.onUse(function(api) { api.addFiles('client/views/creationDialog/AssistifyCreateExpertise.html', 'client'); api.addFiles('client/views/creationDialog/AssistifyCreateExpertise.js', 'client'); api.addFiles('client/views/creationDialog/AssistifyCreateInputError.html', 'client'); + api.addFiles('client/views/creationDialog/AssistifyWordCloud.html', 'client'); + api.addFiles('client/views/creationDialog/AssistifyWordCloud.js', 'client'); + api.addFiles('client/views/creationDialog/AssistifyCreateRequestAutocomplete.html', 'client'); + api.addFiles('client/views/creationDialog/AssistifyTopicSearchEmpty.html', 'client'); + api.addFiles('client/views/creationDialog/AssistifyTopicSearchEmpty.js', 'client'); api.addFiles('client/views/sideNav/requests.html', 'client'); api.addFiles('client/views/sideNav/requests.js', 'client'); api.addFiles('client/views/sideNav/expertise.html', 'client'); @@ -81,6 +88,7 @@ Package.onUse(function(api) { api.addFiles('client/views/sideNav/listRequestsFlex.html', 'client'); api.addFiles('client/views/sideNav/listRequestsFlex.js', 'client'); api.addFiles('client/views/messageActions/AssistifyMessageAction.js', 'client'); + //Libraries api.addFiles('client/lib/collections.js', 'client'); diff --git a/packages/assistify-help-request/server/methods/createRequestFromExpertise.js b/packages/assistify-help-request/server/methods/createRequestFromExpertise.js index 3cf47caa618a..65c73022c6d9 100644 --- a/packages/assistify-help-request/server/methods/createRequestFromExpertise.js +++ b/packages/assistify-help-request/server/methods/createRequestFromExpertise.js @@ -29,7 +29,7 @@ export class CreateRequestFromExpertise extends CreateRequestBase { this._members = CreateRequestFromExpertise.getExperts(this._expertise); } const roomCreateResult = RocketChat.createRoom('r', this.name, Meteor.user() && Meteor.user().username, this._members, false, {expertise: this._expertise}); - if (this._requestTitle) { + if (this._expertise) { RocketChat.saveRoomTopic(roomCreateResult.rid, this._expertise, Meteor.user()); } this._createNotifications(roomCreateResult.rid, this._members.concat([Meteor.user().username])); diff --git a/packages/assistify-help-request/server/methods/createRequestFromRoomId.js b/packages/assistify-help-request/server/methods/createRequestFromRoomId.js index f9bc4017e9a1..d707ab0a7472 100644 --- a/packages/assistify-help-request/server/methods/createRequestFromRoomId.js +++ b/packages/assistify-help-request/server/methods/createRequestFromRoomId.js @@ -84,6 +84,9 @@ export class CreateRequestFromRoomId extends CreateRequestBase { if (parentRoom.t === 'e') { parentRoom.usernames.concat([Meteor.user().username]); } + if (parentRoom.name) { + RocketChat.saveRoomTopic(roomCreateResult.rid, parentRoom.name, Meteor.user()); + } // Invoke create notifications this._createNotifications(roomCreateResult.rid, parentRoom.usernames); // Instance of newly created room. diff --git a/packages/assistify-help-request/server/methods/expertiseList.js b/packages/assistify-help-request/server/methods/expertiseList.js new file mode 100644 index 000000000000..03edda67c091 --- /dev/null +++ b/packages/assistify-help-request/server/methods/expertiseList.js @@ -0,0 +1,46 @@ +/* globals _ */ +Meteor.methods({ + expertiseList({sort, limit}) { + this.unblock(); + check(sort, Match.Optional(String)); + check(limit, Match.Optional(Number)); + + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { + method: 'requestsList' + }); + } + + const options = { + fields: { + name: 1, + t: 1 + }, + sort: { + msgs: -1 + } + }; + + if (_.isNumber(limit)) { + options.limit = limit; + } + + if (sort.trim) { + switch (sort) { + case 'name': + options.sort = { + name: 1 + }; + break; + case 'msgs': + options.sort = { + msgs: -1 + }; + } + } + + return { + channels: RocketChat.models.Rooms.find({t: 'e'}, options).fetch() + }; + } +}); diff --git a/packages/assistify-help-request/server/models/Rooms.js b/packages/assistify-help-request/server/models/Rooms.js index 9d0a8385af96..60a0324b042e 100755 --- a/packages/assistify-help-request/server/models/Rooms.js +++ b/packages/assistify-help-request/server/models/Rooms.js @@ -37,5 +37,9 @@ _.extend(RocketChat.models.Rooms, { {$or}; return this._db.find(query, options); //do not use cache + }, + findByExpertise(expertise, options) { + const query = {expertise}; + return this.find(query, options); } }); diff --git a/packages/assistify-help-request/server/publications/Expertise.js b/packages/assistify-help-request/server/publications/Expertise.js index c84e6d0807f1..03f47c739167 100644 --- a/packages/assistify-help-request/server/publications/Expertise.js +++ b/packages/assistify-help-request/server/publications/Expertise.js @@ -17,7 +17,6 @@ Meteor.publish('autocompleteExpertise', function(selector) { }, limit: 10 }; - const cursorHandle = RocketChat.models.Rooms.findByNameContainingTypesAndTags(selector.term, [{type: 'e'}], options) .observeChanges({ added(_id, record) { diff --git a/packages/assistify-help-request/server/publications/Rooms.js b/packages/assistify-help-request/server/publications/Rooms.js index 1c0657220e44..33804f8a8cca 100755 --- a/packages/assistify-help-request/server/publications/Rooms.js +++ b/packages/assistify-help-request/server/publications/Rooms.js @@ -15,3 +15,18 @@ Meteor.publish('assistify:room', function({rid: roomId}) { return RocketChat.models.Rooms.findOneById(roomId, {fields: {helpRequestId: 1}}); }); + + +Meteor.publish('assistify:expertise', function() { + if (!this.userId) { + return this.error(new Meteor.Error('error-not-authorized', 'Not authorized')); + } + return RocketChat.models.Rooms.find({ + fields: { + _id: 1, + roomId: 1, + helpRequestId: 1, + expertise: 1 + } + }); +}); diff --git a/packages/rocketchat-i18n/i18n/assistifyAI.de.i18n.yml b/packages/rocketchat-i18n/i18n/assistifyAI.de.i18n.yml index b45397cece67..2fedf1c6d926 100755 --- a/packages/rocketchat-i18n/i18n/assistifyAI.de.i18n.yml +++ b/packages/rocketchat-i18n/i18n/assistifyAI.de.i18n.yml @@ -10,6 +10,22 @@ Assistify_AI_Smarti_Domain: Smarti Client-Name Assistify_AI_Smarti_Domain_Description: Trage hier den Smarti Client-Name ein, der zu dieser Rocket.Chat Instanz gehört. Assistify_AI_Smarti_Auth_Token: Smarti Client-Token Assistify_AI_Smarti_Auth_Token_Description: Trage hier den Smarti Client-Token ein, damit sich Rocket.Chat gegenüber Smarti authentifizieren kann. +Assistify_AI_Smarti_Widget_i18n: Texte des Smarti-Widgets +Assistify_AI_Smarti_Widget_i18n_Description: | + Ein JSON-Objekt, mit dem die Übersetzungen definiert werden. + + Mögliche Werte: + - widget.conversation.answer.title + - widget.conversation.answer.title_msg + - widget.latch.answer.title + + Beispiel: + { + "widget.conversation.answer.title": { + "de":"Ich habe eine passende Konversation gefunden:", + "en":"I found a similar conversation:" + } + } Assistify_AI_Widget_Posting_Type: Posting-Verhalten des Widgets Assistify_AI_Widget_Posting_Type_Description: Wähle wie die Vorschläge von Smarti in den Chat übernommen werden sollen. Assistify_AI_Widget_Posting_Type_SuggestText: Plain Text vorschlagen diff --git a/packages/rocketchat-i18n/i18n/assistifyAI.en.i18n.yml b/packages/rocketchat-i18n/i18n/assistifyAI.en.i18n.yml index 866df87f4eb0..5933d0a38d9a 100755 --- a/packages/rocketchat-i18n/i18n/assistifyAI.en.i18n.yml +++ b/packages/rocketchat-i18n/i18n/assistifyAI.en.i18n.yml @@ -10,6 +10,22 @@ Assistify_AI_Smarti_Domain: Smarti Client-Name Assistify_AI_Smarti_Domain_Description: Enter your Smarti Client-Name, that belongs to this Rocket.Chat instance. Assistify_AI_Smarti_Auth_Token: Smarti Client-Token Assistify_AI_Smarti_Auth_Token_Description: Enter your Smarti Client-Token, to authorize Rocket.Chat to access Smarti. +Assistify_AI_Smarti_Widget_i18n: Texts of the Smarti-Widget +Assistify_AI_Smarti_Widget_i18n_Description: | + A JSON-object which defines the translation strings. + + Possible values: + - widget.conversation.answer.title + - widget.conversation.answer.title_msg + - widget.latch.answer.title + + Example: + { + "widget.conversation.answer.title": { + "de":"Ich habe eine passende Konversation gefunden:", + "en":"I found a similar conversation:" + } + } Assistify_AI_Widget_Posting_Type: Widget posting behaviour Assistify_AI_Widget_Posting_Type_Description: Select how to send Smarti suggestions. Assistify_AI_Widget_Posting_Type_PostRichText: post rich text diff --git a/packages/rocketchat-i18n/i18n/assistifyHelpRequest.de.i18n.yml b/packages/rocketchat-i18n/i18n/assistifyHelpRequest.de.i18n.yml index 6ecf80f95cc7..d31ac4f76bb8 100755 --- a/packages/rocketchat-i18n/i18n/assistifyHelpRequest.de.i18n.yml +++ b/packages/rocketchat-i18n/i18n/assistifyHelpRequest.de.i18n.yml @@ -1,4 +1,6 @@ +About-help-channel: Um die Übersicht zu behalten wird deine Frage in einer neuen Anfrage gestellt. Experten werden automatisch hinzugefügt. Hier kannst du ungestört weiterreden. application: Anwendung +ask_help: Frage stellen Assistify_room_count: Letzte Raumnummer Choose_experts: Experten auswählen Close_HelpRequest: Chat beenden @@ -6,9 +8,9 @@ Close_request_comment: Platz für (berühmte) letzten Worte Close_request_warning: Ist die Frage beantwortet? Wenn Du jetzt bestätigst, werden wir aus dem Geschriebenen lernen. create-e: Thema anlegen create-r: Anfrage anlegen +Deactivate_close_comment: Kommentieren beim Schließen von Anfragen verhindern delete-e: Thema löschen delete-r: Anfrage löschen -Deactivate_close_comment: Kommentieren beim Schließen von Anfragen verhindern Expertise_description: Ein Thema beschreibt ein Wissensgebiet. Experten beantworten hierzu Anfragen Expertise_does_not_exist: Das Thema gibt es nicht Expertise_explanation: Ein Thema beschreibt ein Wissensgebiet. Zu einem Thema können Anfragen erstellt werden, die von den Experten, die dem Thema als Mitglieder zugeordnet sind, beantwortet werden können. @@ -23,37 +25,36 @@ Hide_expertise_warning: Bist Du Dir sicher, dass Du das Thema "%s" ausblenden m Hide_request_warning: Bist Du Dir sicher, dass Du die Anfrage "%s" ausblenden möchtest? Du kannst sie danach immer noch einsehen, wenn Du möchtest. Leave_expertise_warning: Bist Du Dir sicher, dass Du das Thema "%s" verlassen möchtest? Du bist danach kein Experte zu dem Thema mehr. Bei einer neuen Anfrage wirst Du nicht mehr gefragt werden! Leave_request_warning: Bist Du Dir sicher, dass Du die Anfrage "%s" verlassen möchtest? Du kannst sie danach nicht mehr einsehen und auch nicht in der Suche finden! +Link-requests: Hier geht es weiter zum Raum __roomName__ More_requests: Weitere Anfragen New_expertise: Neues Thema -New_request_for_expertise: Wähle das Thema Deiner Frage aus -New_request: Neue Anfrage New_request_created: Die neue Anfrage wurde erstellt New_request_first_question: Was möchtest Du fragen? +New_request_for_expertise: Wähle das Thema Deiner Frage aus New_request_title: Ein Titel hilft, die Anfrage wieder zu finden +New_request: Neue Anfrage No_expertise_yet: Keinem Thema zugeordnet No_requests_yet: Keine Anfragen bisher release: Version Request_already_exists: Eine Anfrage mit diesem Namen existiert bereits. -Request_closed: Anfrage geschlossen__comment__ Request_closed_explanation: Die Anfrage wurde geschlossen. Ihr könnt weiter schreiben, aber daraus wird nicht gelernt. Sollte es weitere Fragen geben, bitte eine neue Anfrage eröffnen. +Request_closed: Anfrage geschlossen__comment__ Request_description: Eine Anfrage richtet sich automatisch an die Experten eines Themas. Eine KI hilft dabei. -Request_explanation: Eine Anfrage richtet sich an Experten eines Themas: Gemeinsam mit dem Fragesteller wird versucht, eine Frage aus dem Wissensgebiet zu beantworten. Eine KI beschleunigt den Problemlösungsprozess. Aus abgeschlossenen Anfragen lernt das System und hilft, Fragen zu dem Thema in Zukunft schneller zu beantworten. +Request_explanation: "Eine Anfrage richtet sich an Experten eines Themas: Gemeinsam mit dem Fragesteller wird versucht, eine Frage aus dem Wissensgebiet zu beantworten. Eine KI beschleunigt den Problemlösungsprozess. Aus abgeschlossenen Anfragen lernt das System und hilft, Fragen zu dem Thema in Zukunft schneller zu beantworten." Request_no_special_char: Der Titel der Anfrage darf keine Sonderzeichen enthalten Request: Anfrage Requests: Anfragen Search_Requests: Anfragen suchen system: System SystemContext: System-Kontext -transaction: Transaktion -view-e-room: Alle Themen einsehen -view-r-room: Alle Anfragen einsehen -Your_question: Frage -ask_help: Frage stellen -About-help-channel: Um die Übersicht zu behalten wird deine Frage in einer neuen Anfrage gestellt. Experten werden automatisch hinzugefügt. Hier kannst du ungestört weiterreden. -Link-requests: Hier geht es weiter zum Raum __roomName__ Thread_start: Thread starten Thread_started_message_self: __initiator__ hat einen neuen Thread gestartet. Hilf dabei, die Sache geklärt zu bekommen! Thread_started_message: __initiator__ hat einen neuen Thread zur Nachricht von __author__ gestartet. Hilf dabei, die Sachezu klären! +Thread_view: Thread anzeigen Thread_welcome_message: __initiator__ hat aus __roomName__ heraus diesen Thread begonnen. Sobald sich die Sache erledigt hat, kannst Du den Kanal schließen. Threading_about: Du erstellst einen neuen Kanal, der mit dieser Nachricht beginnt. Alle Mitglieder des alten Raums werden dort rein eingeladen, um die Sache zu klären. -Thread_view: Thread anzeigen +Topic_Search: Weitere Themen +transaction: Transaktion +view-e-room: Alle Themen einsehen +view-r-room: Alle Anfragen einsehen +Your_question: Frage diff --git a/packages/rocketchat-i18n/i18n/assistifyHelpRequest.en.i18n.yml b/packages/rocketchat-i18n/i18n/assistifyHelpRequest.en.i18n.yml index 6a5083b43fad..68cf96a8a9a8 100755 --- a/packages/rocketchat-i18n/i18n/assistifyHelpRequest.en.i18n.yml +++ b/packages/rocketchat-i18n/i18n/assistifyHelpRequest.en.i18n.yml @@ -4,7 +4,11 @@ Choose_experts: Choose experts Close_HelpRequest: Close chat Close_request_comment: You can add (famous) last words Close_request_warning: Please do close the request once you are finished answering the questions asked. This way, we can learn from what you have written. +create-e: Create topic +create-r: Create request Deactivate_close_comment: Don't allow closing comments +delete-e: Delete topic +delete-r: Delete request Expertise_description: A topic is an area of knowledge with known experts. Expertise_does_not_exist: Expertise does not exist Expertise_explanation: A topic is an area of knowledge. Experts join a topic in order to help others solve their question in that area. @@ -21,17 +25,17 @@ Leave_expertise_warning: Are you sure you want to leave the topic "%s"? You'll n Leave_request_warning: Are you sure you want to leave the request "%s"? You'll not be able to see and join the conversation lateron, neither you'll find it searching for it! More_requests: Other requests New_expertise: New topic -New_request_for_expertise: The topic of the new request -New_request: New request New_request_created: Request created successfully New_request_first_question: What do you want to ask +New_request_for_expertise: The topic of the new request New_request_title: A title helps to find the request later on +New_request: New request No_expertise_yet: No topics yet No_requests_yet: No requests yet release: Version Request_already_exists: A request with this name already exists. -Request_closed: Request closed__comment__ Request_closed_explanation: You can keep on chatting, but we'll not learn from that. In case of additional questions, please create new requests. +Request_closed: Request closed__comment__ Request_description: A request is a conversation starting with a question on a particular topic. When finishing such a conversation, our system is going to learn from it in order to accelerate further request-solutions on that topic. Request_explanation: A request is automatically being adressed towards topic experts. AI helps solving it. Request_no_special_char: Request title cannot contain special characters @@ -40,17 +44,14 @@ Requests: Requests Search_Requests: Search requests system: System SystemContext: System context -transaction: Transaction -create-r: Create request -create-e: Create topic -delete-r: Delete request -delete-e: Delete topic -view-r-room: View all requests -view-e-room: View all topics -Your_question: Question Thread_start: Start Thread -Threading_about: You're about to start a new channel with the conversation you selected. All members of this channel will join you there to get things solved. -Thread_welcome_message: __initiator__ started this thread out of __roomName__ . As soon as things have been sorted out, you can indicate that by closing it. Thread_started_message_self: __initiator__ started a new thread. Find the initial message below - and show your support! Thread_started_message: __initiator__ started a new thread for __author__'s question. Find the initial message below - and show your support! Thread_view: View thread +Thread_welcome_message: __initiator__ started this thread out of __roomName__ . As soon as things have been sorted out, you can indicate that by closing it. +Threading_about: You're about to start a new channel with the conversation you selected. All members of this channel will join you there to get things solved. +Topic_Search: More topics +transaction: Transaction +view-e-room: View all topics +view-r-room: View all requests +Your_question: Question diff --git a/packages/rocketchat-i18n/i18n/de.i18n.json b/packages/rocketchat-i18n/i18n/de.i18n.json index 9b5eb7d98678..d084e966f366 100755 --- a/packages/rocketchat-i18n/i18n/de.i18n.json +++ b/packages/rocketchat-i18n/i18n/de.i18n.json @@ -1509,7 +1509,7 @@ "Please_go_to_the_Administration_page_then_Livechat_Facebook": "Gehe im Administrationsbereich auf Livechat > Facebook", "Please_select_an_user": "Bitte einen Benutzer auswählen", "Please_select_enabled_yes_or_no": "Bitte wähle aus, ob die Option aktiviert ist", - "Please_wait_activation": "Bitte warten, das kann einige Zeit in Anspruch nehmen", + "Please_wait_activation": "Bitte hab etwas Geduld, das kann einen Moment dauern", "Please_wait_while_OTR_is_being_established": "Bitte warte einen Moment, während OTR gestartet wird", "Please_wait_while_your_account_is_being_deleted": "Bitte warte einen Moment, während Dein Konto gelöscht wird", "Please_wait_while_your_profile_is_being_saved": "Bitte warten einen Moment, während Dein Profil gespeichert wird", @@ -1570,7 +1570,7 @@ "Read_only_group": "Schreibgeschützte Gruppe", "Read_only": "Schreibgeschützt", "RealName_Change_Disabled": "Der Rocket.Chat Administrator hat das ändern von Namen deaktiviert", - "Reason_To_Join": "Grund zu Join", + "Reason_To_Join": "Info für den Admin, warum Du beitreten möchtest", "Receive_alerts": "Empfange Alarme", "Record": "Aufnehmen", "Redirect_URI": "Weiterleitungs-URL", diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index b0fad7d506b9..5c6ccb9db509 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -697,6 +697,14 @@ "error-no-tokens-for-this-user": "There are no tokens for this user", "error-not-allowed": "Not allowed", "error-not-authorized": "Not authorized", + "error-password-policy-not-met": "Password does not meet the server's policy", + "error-password-policy-not-met-minLength": "Password does not meet the server's policy of minimum length (password too short)", + "error-password-policy-not-met-maxLength": "Password does not meet the server's policy of maximum length (password too long)", + "error-password-policy-not-met-repeatingCharacters": "Password not not meet the server's policy of forbidden repeating characters (you have too many of the same characters next to each other)", + "error-password-policy-not-met-oneLowercase": "Password does not meet the server's policy of at least one lowercase character", + "error-password-policy-not-met-oneUppercase": "Password does not meet the server's policy of at least one uppercase character", + "error-password-policy-not-met-oneNumber": "Password does not meet the server's policy of at least one numerical character", + "error-password-policy-not-met-oneSpecial": "Password does not meet the server's policy of at least one special character", "error-push-disabled": "Push is disabled", "error-remove-last-owner": "This is the last owner. Please set a new owner before removing this one.", "error-role-in-use": "Cannot delete role because it's in use", @@ -1491,6 +1499,25 @@ "Password": "Password", "Password_Change_Disabled": "Your Rocket.Chat administrator has disabled the changing of passwords", "Password_changed_successfully": "Password changed successfully", + "Password_Policy": "Password Policy", + "Accounts_Password_Policy_Enabled": "Enable Password Policy", + "Accounts_Password_Policy_Enabled_Description": "When enabled, user passwords must adhere to the policies set forth. Note: this only applies to new passwords, not existing passwords.", + "Accounts_Password_Policy_MinLength": "Minimum Length", + "Accounts_Password_Policy_MinLength_Description": "Ensures that passwords must have at least this amount of characters. Use `-1` to disable.", + "Accounts_Password_Policy_MaxLength": "Maximum Length", + "Accounts_Password_Policy_MaxLength_Description": "Ensures that passwords do not have more than this amount of characters. Use `-1` to disable.", + "Accounts_Password_Policy_ForbidRepeatingCharacters": "Forbid Repeating Characters", + "Accounts_Password_Policy_ForbidRepeatingCharacters_Description": "Ensures passwords do not contain the same character repeating next to each other.", + "Accounts_Password_Policy_ForbidRepeatingCharactersCount": "Max Repeating Characters", + "Accounts_Password_Policy_ForbidRepeatingCharactersCount_Description": "The amount of times a character can be repeating before it is not allowed.", + "Accounts_Password_Policy_AtLeastOneLowercase": "At Least One Lowercase", + "Accounts_Password_Policy_AtLeastOneLowercase_Description": "Enforce that a password contain at least one lowercase character.", + "Accounts_Password_Policy_AtLeastOneUppercase": "At Least One Uppercase", + "Accounts_Password_Policy_AtLeastOneUppercase_Description": "Enforce that a password contain at least one lowercase character.", + "Accounts_Password_Policy_AtLeastOneNumber": "At Least One Number", + "Accounts_Password_Policy_AtLeastOneNumber_Description": "Enforce that a password contain at least one numerical character.", + "Accounts_Password_Policy_AtLeastOneSpecialCharacter": "At Least One Symbol", + "Accounts_Password_Policy_AtLeastOneSpecialCharacter_Description": "Enforce that a password contain at least one special character.", "Past_Chats": "Past Chats", "Payload": "Payload", "People": "People", diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js index 78b17aa148d8..bdcc1ab478b2 100644 --- a/packages/rocketchat-lib/package.js +++ b/packages/rocketchat-lib/package.js @@ -115,6 +115,7 @@ Package.onUse(function(api) { api.addFiles('server/lib/sendEmailOnMessage.js', 'server'); api.addFiles('server/lib/sendNotificationsOnMessage.js', 'server'); api.addFiles('server/lib/validateEmailDomain.js', 'server'); + api.addFiles('server/lib/validatePasswordPolicy.js', 'server'); // SERVER MODELS api.addFiles('server/models/_Base.js', 'server'); diff --git a/packages/rocketchat-lib/rocketchat.info b/packages/rocketchat-lib/rocketchat.info index a8b171338ba9..58c306858581 100644 --- a/packages/rocketchat-lib/rocketchat.info +++ b/packages/rocketchat-lib/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "0.63.2-0.7.0" + "version": "0.63.2-0.7.1" } diff --git a/packages/rocketchat-lib/server/functions/saveUser.js b/packages/rocketchat-lib/server/functions/saveUser.js index c4d1b4089b77..b3d7ff730e07 100644 --- a/packages/rocketchat-lib/server/functions/saveUser.js +++ b/packages/rocketchat-lib/server/functions/saveUser.js @@ -187,7 +187,7 @@ RocketChat.saveUser = function(userId, userData) { RocketChat.setEmail(userData._id, userData.email, shouldSendVerificationEmailToUser); } - if (userData.password && userData.password.trim() && RocketChat.authz.hasPermission(userId, 'edit-other-user-password')) { + if (userData.password && userData.password.trim() && RocketChat.authz.hasPermission(userId, 'edit-other-user-password') && RocketChat.validatePasswordPolicy(userData.password)) { Accounts.setPassword(userData._id, userData.password.trim()); } diff --git a/packages/rocketchat-lib/server/lib/validatePasswordPolicy.js b/packages/rocketchat-lib/server/lib/validatePasswordPolicy.js new file mode 100644 index 000000000000..ffda738d16ae --- /dev/null +++ b/packages/rocketchat-lib/server/lib/validatePasswordPolicy.js @@ -0,0 +1,102 @@ +const options = { + enabled: false, + minLength: -1, + maxLength: -1, + forbidRepeatingCharacters: false, + forbidRepeatingCharactersCount: 3, //the regex is this number minus one + mustContainAtLeastOneLowercase: false, // /[A-Z]{3,}/ could do this instead of at least one + mustContainAtLeastOneUppercase: false, + mustContainAtLeastOneNumber: false, + mustContainAtLeastOneSpecialCharacter: false +}; + +const regex = { + forbiddingRepeatingCharacters: new RegExp(`(.)\\1{${ options.forbidRepeatingCharactersCount - 1 },}`), + mustContainAtLeastOneLowercase: new RegExp('[a-z]'), + mustContainAtLeastOneUppercase: new RegExp('[A-Z]'), + mustContainAtLeastOneNumber: new RegExp('[0-9]'), + mustContainAtLeastOneSpecialCharacter: new RegExp('[^A-Za-z0-9]') +}; + +// I know this isn't needed, but it's this way +// to allow this section to be collapsed in an IDE +function _registerSettingsListeners() { + RocketChat.settings.get('Accounts_Password_Policy_Enabled', function(key, value) { + options.enabled = value; + }); + + RocketChat.settings.get('Accounts_Password_Policy_MinLength', function(key, value) { + options.minLength = value; + }); + + RocketChat.settings.get('Accounts_Password_Policy_MaxLength', function(key, value) { + options.maxLength = value; + }); + + RocketChat.settings.get('Accounts_Password_Policy_ForbidRepeatingCharacters', function(key, value) { + options.forbidRepeatingCharacters = value; + }); + + RocketChat.settings.get('Accounts_Password_Policy_ForbidRepeatingCharactersCount', function(key, value) { + options.forbidRepeatingCharactersCount = value; + regex.forbiddingRepeatingCharacters = new RegExp(`(.)\\1{${ options.forbidRepeatingCharactersCount - 1 },}`); + }); + + RocketChat.settings.get('Accounts_Password_Policy_AtLeastOneLowercase', function(key, value) { + options.mustContainAtLeastOneLowercase = value; + }); + + RocketChat.settings.get('Accounts_Password_Policy_AtLeastOneUppercase', function(key, value) { + options.mustContainAtLeastOneUppercase = value; + }); + + RocketChat.settings.get('Accounts_Password_Policy_AtLeastOneNumber', function(key, value) { + options.mustContainAtLeastOneNumber = value; + }); + + RocketChat.settings.get('Accounts_Password_Policy_AtLeastOneSpecialCharacter', function(key, value) { + options.mustContainAtLeastOneSpecialCharacter = value; + }); +} + +RocketChat.validatePasswordPolicy = function _validatePasswordPolicy(password) { + if (!options.enabled) { + return true; + } + + if (!password || typeof password !== 'string' || !password.length) { + throw new Meteor.Error('error-password-policy-not-met', 'The password provided does not meet the server\'s password policy.'); + } + + if (options.minLength >= 1 && password.length < options.minLength) { + throw new Meteor.Error('error-password-policy-not-met-minLength', 'The password does not meet the minimum length password policy.'); + } + + if (options.maxLength >= 1 && password.length > options.maxLength) { + throw new Meteor.Error('error-password-policy-not-met-maxLength', 'The password does not meet the maximum length password policy.'); + } + + if (options.forbidRepeatingCharacters && regex.forbiddingRepeatingCharacters.test(password)) { + throw new Meteor.Error('error-password-policy-not-met-repeatingCharacters', 'The password contains repeating characters which is against the password policy.'); + } + + if (options.mustContainAtLeastOneLowercase && !regex.mustContainAtLeastOneLowercase.test(password)) { + throw new Meteor.Error('error-password-policy-not-met-oneLowercase', 'The password does not contain at least one lowercase character which is against the password policy.'); + } + + if (options.mustContainAtLeastOneUppercase && !regex.mustContainAtLeastOneUppercase.test(password)) { + throw new Meteor.Error('error-password-policy-not-met-oneUppercase', 'The password does not contain at least one uppercase character which is against the password policy.'); + } + + if (options.mustContainAtLeastOneNumber && !regex.mustContainAtLeastOneNumber.test(password)) { + throw new Meteor.Error('error-password-policy-not-met-oneNumber', 'The password does not contain at least one numerical character which is against the password policy.'); + } + + if (options.mustContainAtLeastOneSpecialCharacter && !regex.mustContainAtLeastOneSpecialCharacter.test(password)) { + throw new Meteor.Error('error-password-policy-not-met-oneSpecial', 'The password does not contain at least one special character which is against the password policy.'); + } + + return true; +}; + +_registerSettingsListeners(); diff --git a/packages/rocketchat-lib/server/startup/settings.js b/packages/rocketchat-lib/server/startup/settings.js index 2d7cf26a0b64..03bdb4eca1b9 100644 --- a/packages/rocketchat-lib/server/startup/settings.js +++ b/packages/rocketchat-lib/server/startup/settings.js @@ -453,6 +453,76 @@ RocketChat.settings.addGroup('Accounts', function() { type: 'boolean' }); }); + + this.section('Password_Policy', function() { + this.add('Accounts_Password_Policy_Enabled', false, { + type: 'boolean' + }); + + this.add('Accounts_Password_Policy_MinLength', 7, { + type: 'int', + enableQuery: { + _id: 'Accounts_Password_Policy_Enabled', + value: true + } + }); + + this.add('Accounts_Password_Policy_MaxLength', -1, { + type: 'int', + enableQuery: { + _id: 'Accounts_Password_Policy_Enabled', + value: true + } + }); + + this.add('Accounts_Password_Policy_ForbidRepeatingCharacters', true, { + type: 'boolean', + enableQuery: { + _id: 'Accounts_Password_Policy_Enabled', + value: true + } + }); + + this.add('Accounts_Password_Policy_ForbidRepeatingCharactersCount', 3, { + type: 'int', + enableQuery: { + _id: 'Accounts_Password_Policy_Enabled', + value: true + } + }); + + this.add('Accounts_Password_Policy_AtLeastOneLowercase', true, { + type: 'boolean', + enableQuery: { + _id: 'Accounts_Password_Policy_Enabled', + value: true + } + }); + + this.add('Accounts_Password_Policy_AtLeastOneUppercase', true, { + type: 'boolean', + enableQuery: { + _id: 'Accounts_Password_Policy_Enabled', + value: true + } + }); + + this.add('Accounts_Password_Policy_AtLeastOneNumber', true, { + type: 'boolean', + enableQuery: { + _id: 'Accounts_Password_Policy_Enabled', + value: true + } + }); + + this.add('Accounts_Password_Policy_AtLeastOneSpecialCharacter', true, { + type: 'boolean', + enableQuery: { + _id: 'Accounts_Password_Policy_Enabled', + value: true + } + }); + }); }); RocketChat.settings.addGroup('OAuth', function() { diff --git a/packages/rocketchat-theme/server/colors.less b/packages/rocketchat-theme/server/colors.less index 10af71555e89..282803cc32ee 100755 --- a/packages/rocketchat-theme/server/colors.less +++ b/packages/rocketchat-theme/server/colors.less @@ -954,6 +954,10 @@ label.required::after { background-color: @tertiary-background-color; } +.announcement { + background-color: @primary-background-color; +} + /** * Tabs */ diff --git a/server/methods/registerUser.js b/server/methods/registerUser.js index 392a2f794a5f..a043a6a48614 100644 --- a/server/methods/registerUser.js +++ b/server/methods/registerUser.js @@ -31,6 +31,8 @@ Meteor.methods({ throw new Meteor.Error ('error-user-registration-secret', 'User registration is only allowed via Secret URL', { method: 'registerUser' }); } + RocketChat.validatePasswordPolicy(formData.pass); + RocketChat.validateEmailDomain(formData.email); const userData = { diff --git a/server/methods/setUserPassword.js b/server/methods/setUserPassword.js index 327e523770aa..e8fe8c0ac8c6 100644 --- a/server/methods/setUserPassword.js +++ b/server/methods/setUserPassword.js @@ -18,6 +18,8 @@ Meteor.methods({ }); } + RocketChat.validatePasswordPolicy(password); + Accounts.setPassword(userId, password, { logout: false }); diff --git a/tests/end-to-end/assistify/01-wordcloud.js b/tests/end-to-end/assistify/01-wordcloud.js new file mode 100644 index 000000000000..1e2a7b734cb7 --- /dev/null +++ b/tests/end-to-end/assistify/01-wordcloud.js @@ -0,0 +1,47 @@ +/* eslint-env mocha */ + +import sideNav from '../../pageobjects/side-nav.page'; +import assistify from '../../pageobjects/assistify.page'; +import { adminUsername, adminEmail, adminPassword } from '../../data/user.js'; +import { checkIfUserIsAdmin } from '../../data/checks'; + +const topicName = 'topic-'; +const topicExpert = 'rocketchat.internal.admin.test'; +const numTopics = 11; + +describe('[Word-cloud Test]', () => { + + before(() => { + checkIfUserIsAdmin(adminUsername, adminEmail, adminPassword); + }); + + it(`create ${ numTopics } topics for word-cloud`, function() { + this.timeout(100000); + for (let i=1; i <= numTopics; i++) { + try { + sideNav.openChannel(topicName+i); + } catch (e) { + assistify.createTopic(topicName+i, topicExpert); + console.log('New topic created: ', topicName+i); + } + } + //done(); + }); + + it('open word-cloud', function(done) { + assistify.openWordCloud('d'); + assistify.wordCloudCanvas.waitForVisible(5000); + done(); + }); + + it('cleanup all topics', function(done) { + this.timeout(100000); + console.log('Topics cleaning started.'); + for (let i=1; i <= numTopics; i++) { + console.log('Topic Deleted', topicName+i); + assistify.deleteRoom(topicName+i); + } + done(); + }); + +}); diff --git a/tests/end-to-end/assistify/02-create-request.js b/tests/end-to-end/assistify/02-create-request.js index 2ad831f72263..ccea98eed3b3 100644 --- a/tests/end-to-end/assistify/02-create-request.js +++ b/tests/end-to-end/assistify/02-create-request.js @@ -41,19 +41,20 @@ describe('[Help Request]', function() { describe('[Clean Up]', function() { it('close new Topic', () => { console.log('Clean for the Topic and Expertise Started...', topicName); - assistify.closeTopic(helpRequest); + assistify.deleteRoom(helpRequest); }); }); }); describe('[Threading]', function() { const helpRequest = 'execute-test-cases'; + let helpRequestThreaded = null; const inChatHelp = 'what-is-test-case'; before(()=> { try { sideNav.searchChannel(inChatHelp); - assistify.closeTopic(inChatHelp); + assistify.deleteRoom(inChatHelp); console.log('Cleanup request from last run'); } catch (e) { console.log('In-Chat-Help preparation done'); @@ -102,6 +103,8 @@ describe('[Threading]', function() { it('It should create a new request from chat Room', function() { globalObject.confirmPopup(); + mainContent.channelTitle.waitForVisible(3000); + helpRequestThreaded = mainContent.channelTitle.getText(); sideNav.discovery.waitForVisible(3000); }); @@ -122,9 +125,10 @@ describe('[Threading]', function() { describe('[Clean Up]', function() { it('close the topics and request', () => { console.log('Clean for the Topic and Expertise Started...', topicName); - // assistify.closeTopic(inChatHelp); - assistify.closeTopic(helpRequest); - assistify.closeTopic(topicName); + assistify.deleteRoom(helpRequest); + assistify.deleteRoom(helpRequestThreaded); + browser.pause(1000); + assistify.deleteRoom(topicName); }); }); }); diff --git a/tests/end-to-end/ui_smarti/01-integration.js b/tests/end-to-end/ui_smarti/01-integration.js index ff26db299030..bc32ea5cf61e 100644 --- a/tests/end-to-end/ui_smarti/01-integration.js +++ b/tests/end-to-end/ui_smarti/01-integration.js @@ -104,7 +104,6 @@ describe('[Smarti Integration]', () => { const currentConversation = res.body.content.filter((conversation) => { return conversation.meta.channel_id[0] === roomId; })[0]; - currentConversation.should.not.be.empty; conversationId = currentConversation.id; }) @@ -161,13 +160,21 @@ describe('[Smarti Integration]', () => { .end(done); }); - it('close new Request', () => { - console.log('RequestName for cleanup', topicName); - assistify.closeTopic(topicName); + it('close new Request', (done) => { + sideNav.openChannel(requestName); + assistify.closeRequest(); + smarti.get(`/conversation/${ conversationId }`) + .set('Accept', 'application/json') + .set('X-Auth-Token', token) + .expect(200) + .expect((res) => { + res.body.meta.status.should.equal('Complete'); + }) + .end(done); }); it('delete created request', (done) => { - sideNav.openChannel(requestName); + sideNav.searchChannel(requestName); //closing the request hides it, so we need to re-search it assistify.deleteRoom(); smarti.get(`/conversation/${ conversationId }`) .set('Accept', 'application/json') @@ -189,7 +196,6 @@ describe('[Smarti Integration]', () => { }); it('close request', () => { assistify.closeRequest(); - }); }); describe('Second request', () => { diff --git a/tests/end-to-end/ui_smarti/02-widget.js b/tests/end-to-end/ui_smarti/02-widget.js index 188d1bacd421..1b78ddbb97d5 100644 --- a/tests/end-to-end/ui_smarti/02-widget.js +++ b/tests/end-to-end/ui_smarti/02-widget.js @@ -70,7 +70,7 @@ describe('[Smarti Widget]', () => { describe('Cleanup', () => { it('close new Topic', () => { console.log('TopicName for cleanup', topicName); - assistify.closeTopic(topicName); + assistify.deleteRoom(topicName); }); }); }); diff --git a/tests/pageobjects/assistify.page.js b/tests/pageobjects/assistify.page.js index 92632d38f5ad..f41cffc318f8 100644 --- a/tests/pageobjects/assistify.page.js +++ b/tests/pageobjects/assistify.page.js @@ -10,11 +10,13 @@ const Keys = { 'ENTER': '\uE007', 'ESCAPE': 'u\ue00c' }; + class Assistify extends Page { get knowledgebaseIcon() { return browser.element('.tab-button-icon--lightbulb'); } + // in order to communicate with Smarti we need the roomId. // funny enough, it's available in its DOM. A bit dirty, but very efficient get roomId() { @@ -98,11 +100,19 @@ class Assistify extends Page { return browser.element('nav.rc-tabs .rc-tabs__tab-link.AssistifyCreateRequest'); } - // Knowledgebase - get closeTopicBtn() { - return browser.element('.rc-button.rc-button--outline.rc-button--cancel.js-delete'); + get wordCloudLink() { + return browser.element('[id="more-topics"]'); + } + + get wordCloudButton() { + return browser.element('.rc-input__icon-svg--book-alt'); + } + + get wordCloudCanvas() { + return browser.element('[id="wc-canvas"]'); } + // Knowledgebase get editInfoBtn() { return browser.element('.rc-button.rc-button--icon.rc-button--outline.js-edit'); } @@ -119,11 +129,14 @@ class Assistify extends Page { return browser.element('#newTagInput'); } - get numberOfRequests() { return browser.element('#rocket-chat > aside > div.rooms-list > h3:nth-child(9) > span.badge'); } + get numberOfRequests() { + return browser.element('#rocket-chat > aside > div.rooms-list > h3:nth-child(9) > span.badge'); + } escape() { browser.keys(Keys.ESCAPE); } + createTopic(topicName, expert) { this.escape(); this.newChannelBtn.waitForVisible(3000); @@ -141,7 +154,7 @@ class Assistify extends Page { this.topicExperts.setValue(expert); browser.pause(500); browser.keys(Keys.TAB); - browser.pause(500); + // browser.pause(500); browser.waitUntil(function() { return browser.isEnabled('.create-channel__content [data-button="create"]'); @@ -152,6 +165,22 @@ class Assistify extends Page { browser.pause(500); } + openWordCloud(key) { + this.newChannelBtn.waitForVisible(10000); + this.newChannelBtn.click(); + this.tabs.waitForVisible(5000); + if (this.tabs) { + this.createRequestTab.waitForVisible(5000); + this.createRequestTab.click(); + } + + this.topicName.waitForVisible(5000); + this.topicName.setValue(key); + + this.wordCloudButton.waitForVisible(5000); + this.wordCloudButton.click(); + } + createHelpRequest(topicName, message, requestTitle) { this.escape(); this.newChannelBtn.waitForVisible(1000); @@ -201,7 +230,10 @@ class Assistify extends Page { global.confirmPopup(); } - deleteRoom() { + deleteRoom(roomName) { + if (roomName) { + sideNav.openChannel(roomName); + } flexTab.operateFlexTab('info', true); flexTab.editBtn.click(); flexTab.deleteBtn.click(); @@ -209,16 +241,15 @@ class Assistify extends Page { global.confirmPopup(); } - closeTopic(topicName) { - sideNav.openChannel(topicName); - flexTab.channelTab.waitForVisible(5000); - flexTab.channelTab.click(); - this.editInfoBtn.waitForVisible(5000); - this.editInfoBtn.click(); - this.closeTopicBtn.waitForVisible(5000); - this.closeTopicBtn.click(); - global.confirmPopup(); - } + /* closeTopic(topicName) { + flexTab.channelTab.waitForVisible(5000); + flexTab.channelTab.click(); + this.editInfoBtn.waitForVisible(5000); + this.editInfoBtn.click(); + this.closeTopicBtn.waitForVisible(5000); + this.closeTopicBtn.click(); + global.confirmPopup(); + }*/ clickKnowledgebase() { this.knowledgebaseIcon.waitForVisible(5000);