From 3756a74647eaa04e60297312fa6f171ea8452dd5 Mon Sep 17 00:00:00 2001 From: Gabriel Henriques Date: Fri, 4 Sep 2020 15:40:03 -0300 Subject: [PATCH 1/4] Refactored audit --- ee/app/auditing/client/routes.js | 6 +- .../client/templates/audit/audit.html | 138 +------ .../auditing/client/templates/audit/audit.js | 354 +----------------- ee/client/audit/AuditPage.js | 194 ++++++++++ ee/client/audit/AuditPage.stories.js | 11 + ee/client/audit/DateRangePicker.js | 122 ++++++ ee/client/audit/Result.js | 48 +++ ee/client/audit/RoomAutoComplete.js | 25 ++ ee/client/audit/UserAutoCompleteMultiple.js | 31 ++ ee/client/audit/VisitorAutoComplete.js | 22 ++ packages/rocketchat-i18n/i18n/en.i18n.json | 1 + 11 files changed, 483 insertions(+), 469 deletions(-) create mode 100644 ee/client/audit/AuditPage.js create mode 100644 ee/client/audit/AuditPage.stories.js create mode 100644 ee/client/audit/DateRangePicker.js create mode 100644 ee/client/audit/Result.js create mode 100644 ee/client/audit/RoomAutoComplete.js create mode 100644 ee/client/audit/UserAutoCompleteMultiple.js create mode 100644 ee/client/audit/VisitorAutoComplete.js diff --git a/ee/app/auditing/client/routes.js b/ee/app/auditing/client/routes.js index 334de697eb2b..0b40f5d75df7 100644 --- a/ee/app/auditing/client/routes.js +++ b/ee/app/auditing/client/routes.js @@ -1,10 +1,14 @@ import { FlowRouter } from 'meteor/kadira:flow-router'; import { BlazeLayout } from 'meteor/kadira:blaze-layout'; +import { createTemplateForComponent } from '../../../../client/reactAdapters'; + +createTemplateForComponent('auditPage', () => import('../../../client/audit/AuditPage')); + FlowRouter.route('/audit', { name: 'audit-home', action() { - BlazeLayout.render('main', { center: 'audit' }); + BlazeLayout.render('main', { center: 'auditPage' }); }, }); diff --git a/ee/app/auditing/client/templates/audit/audit.html b/ee/app/auditing/client/templates/audit/audit.html index b5247322ce60..4e147a15858b 100644 --- a/ee/app/auditing/client/templates/audit/audit.html +++ b/ee/app/auditing/client/templates/audit/audit.html @@ -1,127 +1,17 @@ - - - - diff --git a/ee/app/auditing/client/templates/audit/audit.js b/ee/app/auditing/client/templates/audit/audit.js index 8235c03cd28d..8ff72329cc87 100644 --- a/ee/app/auditing/client/templates/audit/audit.js +++ b/ee/app/auditing/client/templates/audit/audit.js @@ -1,12 +1,10 @@ -import { Blaze } from 'meteor/blaze'; import { Template } from 'meteor/templating'; import { ReactiveVar } from 'meteor/reactive-var'; import { FlowRouter } from 'meteor/kadira:flow-router'; -import { AutoComplete } from '../../../../../../app/meteor-autocomplete/client'; import { hasAllPermission } from '../../../../../../app/authorization/client'; import { messageContext } from '../../../../../../app/ui-utils/client/lib/messageContext'; -import { call, convertDate, scrollTo } from '../../utils.js'; +import { call } from '../../utils.js'; import './audit.html'; @@ -27,107 +25,8 @@ const loadMessages = async function({ rid, users, startDate, endDate = new Date( } }; -Template.audit.events({ - 'submit form'(e) { - e.preventDefault(); - }, - 'change input[type=date]'(e) { - e.currentTarget.parentElement.parentElement.parentElement.classList.remove('rc-input--error'); - }, - 'change [name=type]'(e, t) { - t.type.set(e.currentTarget.value); - }, - async 'click .js-submit'(e, t) { - const form = e.currentTarget.parentElement; - - const type = t.type.get(); - const result = { type }; - - e.currentTarget.blur(); - - if (type === 'd') { - if (!t.users) { - return form.querySelector('#autocomplete-users').classList.add('rc-input--error'); - } - form.querySelector('#autocomplete-users').classList.remove('rc-input--error'); - result.users = t.users.map((user) => user.username); - } else if (type === 'l') { - if (!t.visitor && !t.agent) { - form.querySelector('#autocomplete-agent').classList.add('rc-input--error'); - form.querySelector('#autocomplete-visitor').classList.add('rc-input--error'); - return; - } - - form.querySelector('#autocomplete-agent').classList.remove('rc-input--error'); - form.querySelector('#autocomplete-visitor').classList.remove('rc-input--error'); - - result.visitor = t.visitor?._id; - result.agent = t.agent?._id; - } else { - if (!t.room || !t.room._id) { - return form.querySelector('#autocomplete-room').classList.add('rc-input--error'); - } - form.querySelector('#autocomplete-room').classList.remove('rc-input--error'); - result.rid = t.room._id; - } - - if (!form.startDate.value) { - return form.startDate.parentElement.parentElement.parentElement.classList.add('rc-input--error'); - } - form.startDate.parentElement.parentElement.parentElement.classList.remove('rc-input--error'); - result.startDate = form.startDate.type === 'date' ? convertDate(form.startDate.value) : form.startDate.value; - - - result.msg = form.msg.value; - - if (!form.endDate.value) { - return form.endDate.parentElement.parentElement.parentElement.classList.add('rc-input--error'); - } - form.endDate.parentElement.parentElement.parentElement.classList.remove('rc-input--error'); - result.endDate = form.endDate.type === 'date' ? convertDate(form.endDate.value) : form.endDate.value; - result.endDate = new Date(result.endDate.getTime() + 86400000); - - await t.loadMessages(result); - - setTimeout(() => { - const offset = $(document.querySelector('.rc-audit-container ul')).offset(); - if (!offset) { - return; - } - scrollTo(document.querySelector('.rc-audit-container'), offset.top - 150, 300); - }, 150); - }, -}); Template.audit.helpers({ - onChange() { - const that = Template.instance(); - return function(value, key) { - that[key] = value; - }; - }, - prepareRoom: () => function(room) { - room.username = room.name; - return room; - }, - modifierUser: () => function(text, filter) { - const f = filter.get(); - return `@${ f.length === 0 ? text : text.replace(new RegExp(f), function(part) { - return `${ part }`; - }) }`; - }, - nTypeOthers() { - return ['d', 'l'].includes(Template.instance().type.get()); - }, - nTypeDM() { - return Template.instance().type.get() !== 'd'; - }, - nTypeOmni() { - return Template.instance().type.get() !== 'l'; - }, - type() { - return Template.instance().type.get(); - }, isLoading() { return Template.instance().loading.get(); }, @@ -137,14 +36,10 @@ Template.audit.helpers({ hasResults() { return Template.instance().hasResults.get(); }, - agentConditions() { - return { role: 'livechat-agent' }; - }, }); Template.audit.onCreated(async function() { this.messagesContext = new ReactiveVar({}); - this.type = new ReactiveVar(); this.loading = new ReactiveVar(false); this.hasResults = new ReactiveVar(false); @@ -159,243 +54,14 @@ Template.audit.onCreated(async function() { }); this.loadMessages = loadMessages.bind(this); -}); - -const acEvents = (key/* , variable, name*/) => ({ - 'click .rc-popup-list__item'(e, t) { - t[key].onItemClick(this, e); - }, - 'keydown input'(e, t) { - if ([8, 46].includes(e.keyCode) && e.target.value === '') { - const users = t.selected; - const usersArr = users.get(); - usersArr.pop(); - return users.set(usersArr); - } - t[key].onKeyDown(e); - }, - 'keyup input'(e, t) { - t[key].onKeyUp(e); - }, - 'focus input'(e, t) { - t[key].onFocus(e); - }, - 'blur input'(e, t) { - t[key].onBlur(e); - }, -}); - -Template.auditAutocompleteDirectMessage.events({ - ...acEvents('ac', 'selected'), - 'input input'(e, t) { - t.filter.set(e.target.value); - }, - 'click .rc-tags__tag-icon'(e, t) { - t.selected.set(); - }, - 'click .rc-tags__tag'({ target }, t) { - const { onClickTag } = t; - return onClickTag & onClickTag(Blaze.getData(target)); - }, -}); - -Template.auditAutocomplete.events({ - ...acEvents('ac', 'selected'), - 'input input'(e, t) { - t.filter.set(e.target.value); - }, - 'click .rc-tags__tag-icon'(e, t) { - t.selected.set(); - }, - 'click .rc-tags__tag'({ target }, t) { - const { onClickTag } = t; - return onClickTag & onClickTag(Blaze.getData(target)); - }, -}); - -Template.auditAutocompleteDirectMessage.helpers({ - selected() { - const instance = Template.instance(); - const selected = instance.selected.get(); - return selected && (instance.data.prepare ? instance.data.prepare(selected) : selected); - }, - config() { - const { filter } = Template.instance(); - return { - template_item: 'popupList_item_channel', - // noMatchTemplate: Template.roomSearchEmpty, - filter: filter.get(), - noMatchTemplate: 'userSearchEmpty', - modifier: (text) => (Template.parentData(8).modifier || function(text, filter) { - const f = filter.get(); - return `#${ f.length === 0 ? text : text.replace(new RegExp(f), function(part) { - return `${ part }`; - }) }`; - })(text, filter), - }; - }, - autocomplete(key) { - const instance = Template.instance(); - const param = instance.ac[key]; - return typeof param === 'function' ? param.apply(instance.ac) : param; - }, - items() { - return Template.instance().ac.filteredList(); - }, -}); - -Template.auditAutocomplete.helpers({ - selected() { - const instance = Template.instance(); - const selected = instance.selected.get(); - return selected && (instance.data.prepare ? instance.data.prepare(selected) : selected); - }, - config() { - const { filter } = Template.instance(); - return { - template_item: 'popupList_item_channel', - // noMatchTemplate: Template.roomSearchEmpty, - filter: filter.get(), - noMatchTemplate: 'userSearchEmpty', - modifier: (text) => (Template.parentData(8).modifier || function(text, filter) { - const f = filter.get(); - return `#${ f.length === 0 ? text : text.replace(new RegExp(f), function(part) { - return `${ part }`; - }) }`; - })(text, filter), - }; - }, - autocomplete(key) { - const instance = Template.instance(); - const param = instance.ac[key]; - return typeof param === 'function' ? param.apply(instance.ac) : param; - }, - items() { - return Template.instance().ac.filteredList(); - }, -}); - -Template.auditAutocomplete.onRendered(async function() { - const { selected } = this; - - this.ac.element = this.firstNode.querySelector('input'); - this.ac.$element = $(this.ac.element); - - this.ac.$element.on('autocompleteselect', function(e, { item }) { - selected.set(item); - }); -}); - -Template.auditAutocomplete.helpers({ - selected() { - const instance = Template.instance(); - const selected = instance.selected.get(); - return selected && (instance.data.prepare ? instance.data.prepare(selected) : selected); - }, - config() { - const { filter } = Template.instance(); - const { templateItem } = Template.instance().data; - return { - template_item: templateItem || 'popupList_item_channel', - // noMatchTemplate: Template.roomSearchEmpty, - filter: filter.get(), - noMatchTemplate: 'userSearchEmpty', - modifier: (text) => (Template.parentData(8).modifier || function(text, filter) { - const f = filter.get(); - return `#${ f.length === 0 ? text : text.replace(new RegExp(f), function(part) { - return `${ part }`; - }) }`; - })(text, filter), - }; - }, - autocomplete(key) { - const instance = Template.instance(); - const param = instance.ac[key]; - return typeof param === 'function' ? param.apply(instance.ac) : param; - }, - items() { - return Template.instance().ac.filteredList(); - }, -}); -const autocompleteConfig = ({ - collection, - endpoint, - field, - term = 'term', -}) => ({ - selector: { - item: '.rc-popup-list__item', - container: '.rc-popup-list__list', - }, - - limit: 10, - inputDelay: 300, - rules: [{ - // @TODO maybe change this 'collection' and/or template - collection, - endpoint, - field, - matchAll: true, - selector(match) { - return { - [term]: match, - }; - }, - sort: field, - }], -}); - -Template.auditAutocomplete.onCreated(function() { - this.filter = new ReactiveVar(''); - this.selected = new ReactiveVar(''); - - this.onClickTag = () => { - this.selected.set(''); - }; - - this.autorun(() => { - const value = this.selected.get(); - this.data.onChange(value, this.data.key); - }); - this.ac = new AutoComplete(autocompleteConfig({ - field: this.data.field, - collection: this.data.collection || 'CachedChannelList', - endpoint: this.data.endpoint || 'rooms.autocomplete.channelAndPrivate', - term: this.data.term || 'term', - })); - this.ac.tmplInst = this; -}); - - -Template.auditAutocompleteDirectMessage.onCreated(function() { - this.filter = new ReactiveVar(''); - this.selected = new ReactiveVar([]); - - this.onClickTag = ({ username }) => { - this.selected.set(this.selected.get().filter((user) => user.username !== username)); - }; - - this.autorun(() => { - const value = this.selected.get(); - this.data.onChange(value, this.data.key); - }); - this.ac = new AutoComplete(autocompleteConfig({ - field: this.data.field, - collection: this.data.collection || 'CachedChannelList', - endpoint: this.data.endpoint || 'rooms.autocomplete.channelAndPrivate', - term: this.data.term || 'term', - })); - this.ac.tmplInst = this; -}); - -Template.auditAutocompleteDirectMessage.onRendered(async function() { - const { selected } = this; - - this.ac.element = this.firstNode.querySelector('input'); - this.ac.$element = $(this.ac.element); - - this.ac.$element.on('autocompleteselect', function(e, { item }) { - selected.set([...selected.get(), item]); - }); + const { + visitor, + agent, + users, + rid, + } = this.data; + if (rid || users.length || agent || visitor) { + await this.loadMessages(this.data); + } }); diff --git a/ee/client/audit/AuditPage.js b/ee/client/audit/AuditPage.js new file mode 100644 index 000000000000..87ebba6a8e13 --- /dev/null +++ b/ee/client/audit/AuditPage.js @@ -0,0 +1,194 @@ +import React, { useMemo, useRef, useState } from 'react'; +import { Box, Field, TextInput, Select, ButtonGroup, Button, Margins } from '@rocket.chat/fuselage'; +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; + +import Page from '../../../client/components/basic/Page'; +import DateRangePicker from './DateRangePicker'; +import RoomAutoComplete from './RoomAutoComplete'; +import UserAutoCompleteMultiple from './UserAutoCompleteMultiple'; +import VisitorAutoComplete from './VisitorAutoComplete'; +import Result from './Result'; +import { UserAutoComplete } from '../../../client/components/basic/AutoComplete'; +import { useTranslation } from '../../../client/contexts/TranslationContext'; +import { useForm } from '../../../client/hooks/useForm'; + +const initialValues = { + msg: '', + type: '', + dateRange: { + start: '', + end: '', + }, + visitor: '', + agent: '', + rid: '', + users: [], +}; + +const AuditPage = () => { + const t = useTranslation(); + + const { values, handlers } = useForm(initialValues); + const setData = useRef(() => {}); + + const [errors, setErrors] = useState({}); + + const { + msg, + type, + dateRange: { + start: startDate, + end: endDate, + }, + visitor, + agent, + users, + rid, + } = values; + + const { + handleMsg, + handleType, + handleVisitor, + handleAgent, + handleUsers, + handleRid, + handleDateRange, + } = handlers; + + const typeOptions = useMemo(() => [ + ['', t('Others')], + ['d', t('Direct_Messages')], + ['l', t('Omnichannel')], + ], [t]); + + const onChangeUsers = useMutableCallback((value, action) => { + if (!action) { + if (users.includes(value)) { + return; + } + return handleUsers([...users, value]); + } + handleUsers(users.filter((current) => current !== value)); + }); + + const apply = useMutableCallback(() => { + if (!rid && type === '') { + return setErrors({ + rid: t('The_field_is_required', t('room_name')), + }); + } + + if (users.length < 2 && type === 'd') { + return setErrors({ + users: t('Select_at_least_two_users'), + }); + } + + if (type === 'l') { + const errors = {}; + + if (visitor === '') { + errors.visitor = t('The_field_is_required', t('Visitor')); + } + + if (agent === '') { + errors.visitor = t('The_field_is_required', t('Agent')); + } + + if (errors.visitor || errors.agent) { + return setErrors(errors); + } + } + + setErrors({}); + setData.current({ + msg, + type, + startDate: new Date(startDate), + endDate: new Date(endDate), + visitor, + agent, + users, + rid, + }); + }); + + return + + + + + + + {t('Message')} + + + + + + {t('Type')} + + - - {t('Date')} - + @@ -173,7 +170,7 @@ const AuditPage = () => { {t('Agent')} - + {errors.agent && {errors.agent} diff --git a/ee/client/audit/DateRangePicker.js b/ee/client/audit/DateRangePicker.js index 392144bd0a53..16b64373102e 100644 --- a/ee/client/audit/DateRangePicker.js +++ b/ee/client/audit/DateRangePicker.js @@ -112,9 +112,9 @@ const DateRangePicker = ({ onChange = () => {}, ...props }) => { return - - - + + + ; }; diff --git a/ee/client/audit/UserAutoCompleteMultiple.js b/ee/client/audit/UserAutoCompleteMultiple.js index 132e0871f66b..a0cc526cdad4 100644 --- a/ee/client/audit/UserAutoCompleteMultiple.js +++ b/ee/client/audit/UserAutoCompleteMultiple.js @@ -22,7 +22,7 @@ const UserAutoCompleteMultiple = React.memo((props) => { {...props} filter={filter} setFilter={setFilter} - renderSelected={({ value: selected }) => selected?.map((value) => {value})} + renderSelected={({ value: selected }) => selected?.map((value) => {value})} renderItem={({ value, ...props }) =>