Merge remote-tracking branch 'origin/develop' into ska-t-develop

pull/7479/head
Rodrigo Nascimento 8 years ago
commit 8b97bf06a7
  1. 28
      packages/rocketchat-analytics/client/loadScript.js
  2. 2
      packages/rocketchat-analytics/client/trackEvents.js
  3. 26
      packages/rocketchat-analytics/server/settings.js
  4. 14
      packages/rocketchat-authorization/client/usersNameChanged.js
  5. 1
      packages/rocketchat-authorization/package.js
  6. 8
      packages/rocketchat-i18n/i18n/de.i18n.json
  7. 15
      packages/rocketchat-lib/server/methods/getRoomRoles.js
  8. 80
      packages/rocketchat-theme/client/imports/base.css
  9. 18
      packages/rocketchat-theme/client/imports/chip.css
  10. 1
      packages/rocketchat-theme/client/main.css
  11. 16
      packages/rocketchat-theme/server/colors.less
  12. 25
      packages/rocketchat-ui-flextab/client/tabs/userEdit.html
  13. 59
      packages/rocketchat-ui-flextab/client/tabs/userEdit.js
  14. 33
      packages/rocketchat-ui/client/views/app/room.html
  15. 50
      packages/rocketchat-ui/client/views/app/room.js
  16. 8
      tests/end-to-end/ui/11-admin.js
  17. 1
      tests/end-to-end/ui/13-permissions.js
  18. 13
      tests/pageobjects/flex-tab.page.js

@ -2,6 +2,10 @@ Template.body.onRendered(() => {
Tracker.autorun((c) => {
const piwikUrl = RocketChat.settings.get('PiwikAnalytics_enabled') && RocketChat.settings.get('PiwikAnalytics_url');
const piwikSiteId = piwikUrl && RocketChat.settings.get('PiwikAnalytics_siteId');
const piwikPrependDomain = piwikUrl && RocketChat.settings.get('PiwikAnalytics_prependDomain');
const piwikCookieDomain = piwikUrl && RocketChat.settings.get('PiwikAnalytics_cookieDomain');
const piwikDomains = piwikUrl && RocketChat.settings.get('PiwikAnalytics_domains');
const piwikAdditionalTracker = piwikUrl && RocketChat.settings.get('PiwikAdditionalTrackers');
const googleId = RocketChat.settings.get('GoogleAnalytics_enabled') && RocketChat.settings.get('GoogleAnalytics_ID');
if (piwikSiteId || googleId) {
c.stop();
@ -14,7 +18,31 @@ Template.body.onRendered(() => {
window._paq.push(['trackPageView']);
window._paq.push(['enableLinkTracking']);
if (piwikPrependDomain) {
window._paq.push(['setDocumentTitle', `${ window.location.hostname }/${ document.title }`]);
}
const upperLevelDomain = `*.${ window.location.hostname.split('.').slice(1).join('.') }`;
if (piwikCookieDomain) {
window._paq.push(['setCookieDomain', upperLevelDomain]);
}
if (piwikDomains) {
// array
const domainsArray = piwikDomains.split(/\n/);
const domains = [];
for (let i = 0; i < domainsArray.length; i++) {
// only push domain if it contains a non whitespace character.
if (/\S/.test(domainsArray[i])) {
domains.push(`*.${ domainsArray[i].trim() }`);
}
}
window._paq.push(['setDomains', domains]);
}
(() => {
const addTrackers = JSON.parse(piwikAdditionalTracker);
for (let i = 0; i < addTrackers.length; i++) {
const tracker = addTrackers[i];
window._paq.push(['addTracker', `${ tracker['trackerURL'] }piwik.php`, tracker['siteId']]);
}
window._paq.push(['setTrackerUrl', `${ piwikUrl }piwik.php`]);
window._paq.push(['setSiteId', Number.parseInt(piwikSiteId)]);
const d = document;

@ -8,7 +8,7 @@ function trackEvent(category, action, label) {
}
if (!window._paq || window.ga) {
//Trigger the trackPageView manually as the page views don't seem to be tracked
//Trigger the trackPageView manually as the page views are only loaded when the loadScript.js code is executed
FlowRouter.triggers.enter([(route) => {
if (window._paq) {
const http = location.protocol;

@ -18,6 +18,32 @@ RocketChat.settings.addGroup('Analytics', function addSettings() {
i18nLabel: 'Client_ID',
enableQuery
});
this.add('PiwikAdditionalTrackers', '', {
type: 'string',
multiline: true,
public: true,
i18nLabel: 'PiwikAdditionalTrackers',
enableQuery
});
this.add('PiwikAnalytics_prependDomain', false, {
type: 'boolean',
public: true,
i18nLabel: 'PiwikAnalytics_prependDomain',
enableQuery
});
this.add('PiwikAnalytics_cookieDomain', false, {
type: 'boolean',
public: true,
i18nLabel: 'PiwikAnalytics_cookieDomain',
enableQuery
});
this.add('PiwikAnalytics_domains', '', {
type: 'string',
multiline: true,
public: true,
i18nLabel: 'PiwikAnalytics_domains',
enableQuery
});
});
this.section('Analytics_Google', function() {

@ -0,0 +1,14 @@
/* globals RoomRoles */
Meteor.startup(function() {
RocketChat.Notifications.onLogged('Users:NameChanged', function({_id, name}) {
RoomRoles.update({
'u._id': _id
}, {
$set: {
'u.name': name
}
}, {
multi: true
});
});
});

@ -34,6 +34,7 @@ Package.onUse(function(api) {
api.addFiles('client/requiresPermission.html', ['client']);
api.addFiles('client/route.js', ['client']);
api.addFiles('client/usersNameChanged.js', ['client']);
// views
api.addFiles('client/views/permissions.html', ['client']);

@ -991,6 +991,14 @@
"Pinned_a_message": "hat eine Nachricht angeheftet:",
"Pinned_Messages": "Fixierte Nachrichten",
"PiwikAnalytics_siteId_Description": "Die Website-ID zur Identifizierung dieser Website. Beispiel: 17",
"PiwikAdditionalTrackers" : "Zusätzliche Piwik Websites",
"PiwikAdditionalTrackers_Description" : "Geben Sie hier weitere Piwik Website URLs und SiteIDs in folgendem Format an, wenn Sie dieselben Daten in verschiedene Piwik Instanzen tracken möchten: [ { \"trackerURL\" : \"https://my.piwik.domain2/\", \"siteId\" : 42 }, { \"trackerURL\" : \"https://my.piwik.domain3/\", \"siteId\" : 15 } ]",
"PiwikAnalytics_prependDomain" : "Domain voranstellen",
"PiwikAnalytics_prependDomain_Description" : "Domain der Seite beim Tracken dem Seitentitel voranstellen",
"PiwikAnalytics_cookieDomain" : "Alle Subdomains",
"PiwikAnalytics_cookieDomain_Description" : "Besucher aufzeichnen auf allen Subdomains",
"PiwikAnalytics_domains" : "Verberge ausgehende Links",
"PiwikAnalytics_domains_Description" : "Verberge im Bericht über 'ausgehende Verweise' alle Klicks auf bekannte Alias-URLs. Bitte tragen Sie pro Zeile einen Domainnamen ein, verwenden Sie dabei keine Trennzeichen.",
"PiwikAnalytics_url_Description": "Die Piwik URL benötigt ein trailing slash. Beispiel: //piwik.rocket.chat/",
"Placeholder_for_email_or_username_login_field": "Platzhalter für das Feld der E-Mail-Adresse und des Benutzernamen",
"Placeholder_for_password_login_field": "Platzhalter für das Feld des Anmeldepassworts",

@ -1,6 +1,5 @@
Meteor.methods({
getRoomRoles(rid) {
check(rid, String);
if (!Meteor.userId() && RocketChat.settings.get('Accounts_AllowAnonymousRead') === false) {
@ -20,7 +19,19 @@ Meteor.methods({
}
};
const UI_Use_Real_Name = RocketChat.settings.get('UI_Use_Real_Name') === true;
const roles = RocketChat.models.Roles.find({ scope: 'Subscriptions', description: { $exists: 1, $ne: '' } }).fetch();
return RocketChat.models.Subscriptions.findByRoomIdAndRoles(rid, _.pluck(roles, '_id'), options).fetch();
const subscriptions = RocketChat.models.Subscriptions.findByRoomIdAndRoles(rid, _.pluck(roles, '_id'), options).fetch();
if (!UI_Use_Real_Name) {
return subscriptions;
} else {
return subscriptions.map(subscription => {
const user = RocketChat.models.Users.findOneById(subscription.u._id);
subscription.u.name = user && user.name;
return subscription;
});
}
}
});

@ -2606,6 +2606,12 @@ label.required::after {
&.selectable .message {
cursor: pointer;
}
&.has-leader {
& .wrapper {
padding-top: 57px;
}
}
}
.ticks-bar {
@ -3564,6 +3570,10 @@ body:not(.is-cordova) {
& #password {
width: 70%;
}
& #roleSelect {
width: 70%;
}
}
& nav {
@ -4970,52 +4980,38 @@ a + br.only-after-a {
color: #555555;
}
.room-leader-container {
height: 54px;
}
.room-leader {
position: fixed;
top: 60px;
left: 0;
z-index: 1;
background: #ffffff;
width: calc(100% - 40px);
color: #555555;
border-bottom: solid 1px #eaeaea;
height: 54px;
}
.room-leader .thumb {
top: 6px;
}
.room-leader .right {
position: absolute;
top: 10px;
left: 70px;
}
height: 57px;
left: 0;
right: 0;
z-index: 2;
border-bottom: 1px solid;
padding-bottom: 8px;
transition: transform 0.15s cubic-bezier(0.5, 0, 0.1, 1), visibility 0.15s cubic-bezier(0.5, 0, 0.1, 1);
visibility: visible;
.leader-status .status-text {
text-transform: capitalize;
padding-left: 15px;
font-size: 14px;
}
&.animated-hidden {
transform: translateY(-100%);
visibility: hidden;
}
.leader-status .color-ball {
width: 10px;
height: 10px;
position: absolute;
border-radius: 5px;
margin-top: 5px;
background: grey;
}
& .leader-name {
font-size: 18px;
}
.leader-status .color-ball.online {
background: green;
}
& .leader-status {
& .status-text {
padding-left: 15px;
font-size: 14px;
}
.leader-info .leader-name {
font-size: 18px;
& .color-ball {
width: 10px;
height: 10px;
position: absolute;
border-radius: 5px;
margin-top: 5px;
}
}
}

@ -0,0 +1,18 @@
.chip-container {
list-style-type: none;
margin: 15px;
}
.chip-container li {
display: inline-block;
background-color: #dddddd;
border-radius: 10px;
padding: 2px 8px 2px 2px;
margin: 1px 0;
cursor: pointer;
}
.chip-container li .icon-plus-circled {
opacity: 0.5;
font-size: 0.8rem;
}

@ -4,5 +4,6 @@
@import 'imports/keyframes.css';
@import 'imports/forms.css';
@import 'imports/base.css';
@import 'imports/chip.css';
@import 'imports/rtl.css';
@import 'imports/slider.css';

@ -767,6 +767,10 @@ i.status-online {
color: @status-online;
}
.status-bg-online {
background-color: @status-online;
}
.account-box .status-online .thumb::after,
.account-box .status.online::after,
.popup-user-status-online,
@ -785,6 +789,10 @@ i.status-away {
color: @status-away;
}
.status-bg-away {
background-color: @status-away;
}
.account-box .status-away .thumb::after,
.account-box .status.away::after,
.popup-user-status-away,
@ -798,6 +806,10 @@ i.status-busy {
color: @status-busy;
}
.status-bg-busy {
background-color: @status-busy;
}
.account-box .status-busy .thumb::after,
.account-box .status.busy::after,
.popup-user-status-busy,
@ -811,6 +823,10 @@ i.status-offline {
color: @status-offline;
}
.status-bg-offline {
background-color: @status-offline;
}
.popup-user-status-offline,
.status-offline::after,
.user-image.status-offline .avatar::after {

@ -40,15 +40,28 @@
</label>
</div>
{{/if}}
{{#unless user}}
<div class="input-line">
<label for="role">{{_ "Role"}}</label>
<select id="role">
{{#each role}}
<option value="{{_id}}" selected="{{selectUserRole}}">{{name}}</option>
<div class="input-line" data="{{this}}">
<label for="role">{{_ "Add_Role"}}</label>
{{#let roles=role}}
<select id="roleSelect" {{disabled role}}>
<option value="placeholder" disabled selected>{{_ "Select_role"}}</option>
{{#each roles}}
<option value="{{_id}}">{{name}}</option>
{{/each}}
</select>
{{/let}}
<button id="addRole" class="button" {{disabled role}}>{{_ 'Add_Role'}}</button>
<fieldset>
<ul class="chip-container current-user-roles">
{{#each userRoles}}
<li class="remove-role" title="{{this}}"><i class="icon-cancel-circled"></i>{{this}}</li>
{{/each}}
</ul>
</fieldset>
</div>
{{#unless user}}
<div class="input-line">
<label for="joinDefaultChannels">
<input type="checkbox" id="joinDefaultChannels" value="1" checked="true">

@ -1,6 +1,10 @@
import toastr from 'toastr';
Template.userEdit.helpers({
disabled(cursor) {
return cursor.count() === 0 ? 'disabled' : '';
},
canEditOrAdd() {
return (Template.instance().user && RocketChat.authz.hasAtLeastOnePermission('edit-other-user-info')) || (!Template.instance().user && RocketChat.authz.hasAtLeastOnePermission('create-user'));
},
@ -14,13 +18,12 @@ Template.userEdit.helpers({
},
role() {
return RocketChat.models.Roles.find({}, { sort: { description: 1, _id: 1 } });
const roles = Template.instance().roles.get();
return RocketChat.models.Roles.find({_id: {$nin:roles}, scope: 'Users'}, { sort: { description: 1, _id: 1 } });
},
selectUserRole() {
if (this._id === 'user') {
return 'selected';
}
userRoles() {
return Template.instance().roles.get();
},
name() {
@ -32,26 +35,50 @@ Template.userEdit.events({
'click .cancel'(e, t) {
e.stopPropagation();
e.preventDefault();
return t.cancel(t.find('form'));
t.roles.set([]);
t.cancel(t.find('form'));
},
'click .remove-role'(e, t) {
e.stopPropagation();
e.preventDefault();
let roles = t.roles.get();
roles = roles.filter(el => el !== this.valueOf());
t.roles.set(roles);
$(`[title=${ this }]`).remove();
},
'click #randomPassword'(e) {
e.stopPropagation();
e.preventDefault();
return $('#password').val(Random.id());
$('#password').val(Random.id());
},
'submit form'(e, t) {
'click #addRole'(e, instance) {
e.stopPropagation();
e.preventDefault();
if ($('#roleSelect').find(':selected').is(':disabled')) {
return;
}
const userRoles = [...instance.roles.get()];
userRoles.push($('#roleSelect').val());
instance.roles.set(userRoles);
$('#roleSelect').val('placeholder');
},
return t.save(e.currentTarget);
'submit form'(e, t) {
e.stopPropagation();
e.preventDefault();
t.save(e.currentTarget);
}
});
Template.userEdit.onCreated(function() {
let userData;
this.user = this.data != null ? this.data.user : undefined;
this.roles = this.user ? new ReactiveVar(this.user.roles) : new ReactiveVar([]);
const { tabBar } = Template.currentData();
@ -75,7 +102,15 @@ Template.userEdit.onCreated(function() {
userData.requirePasswordChange = this.$('#changePassword:checked').length > 0;
userData.joinDefaultChannels = this.$('#joinDefaultChannels:checked').length > 0;
userData.sendWelcomeEmail = this.$('#sendWelcomeEmail:checked').length > 0;
if (this.$('#role').val()) { userData.roles = [this.$('#role').val()]; }
const roleSelect = this.$('.remove-role').toArray();
if (roleSelect.length > 0) {
const notSorted = roleSelect.map(role => {
return role.title;
});
//Remove duplicate strings from the array
userData.roles = notSorted.filter((el, index) => notSorted.indexOf(el) === index);
}
return userData;
};
@ -93,6 +128,10 @@ Template.userEdit.onCreated(function() {
errors.push('Email');
}
if (!userData.roles) {
errors.push('Roles');
}
for (const error of Array.from(errors)) {
toastr.error(TAPi18n.__('error-the-field-is-required', { field: TAPi18n.__(error) }));
}

@ -93,7 +93,7 @@
</div>
{{/each}}
</div>
<div class="messages-box {{#if selectable}}selectable{{/if}} {{viewMode}}">
<div class="messages-box {{#if selectable}}selectable{{/if}} {{viewMode}} {{hasLeader}}">
<div class="ticks-bar"></div>
<button class="new-message background-primary-action-color color-content-background-color not">
<i class="icon-down-big"></i>
@ -109,26 +109,21 @@
</div>
</div>
{{/unless}}
{{#with roomLeader}}
<div class="room-leader message color-primary-font-color content-background-color border-component-color {{hideLeaderHeader}}">
<button class="thumb user-card-message">
{{> avatar username=username }}
</button>
<div class="leader-name">{{name}}</div>
<div class="leader-status userStatus">
<span class="color-ball status-bg-{{status}}"></span>
<span class="status-text leader-status-text">{{_ statusDisplay}}</span>
</div>
<a class="chat-now" href="{{chatNowLink}}">{{_ "Chat_Now"}}</a>
</div>
{{/with}}
<div class="wrapper {{#if hasMoreNext}}has-more-next{{/if}} {{hideUsername}} {{hideAvatar}}">
<ul aria-live="polite">
{{#if roomLeader}}
<li class="message room-leader-container">
<div class="room-leader">
<button class="thumb user-card-message">
<div class="avatar"><div class="avatar-image" style="background-image:url(/avatar/{{roomLeader.username}}?_dc=undefined);"></div></div></button>
<div class="right">
<div class="leader-info">
<div class="leader-name">{{roomLeader.name}}</div>
<div class="leader-status userStatus">
<span class="color-ball leader-status {{roomLeader.status}}"></span>
<span class="status-text leader-status-text">{{roomLeader.status}}</span>
</div>
</div>
</div>
<a class="chat-now" href="/direct/{{roomLeader.username}}">Chat Now</a>
</div>
</li>
{{/if}}
{{#if canPreview}}
{{#if hasMore}}
<li class="load-more">

@ -114,15 +114,20 @@ Template.room.helpers({
},
roomLeader() {
const roles = RoomRoles.find({rid: this._id, roles: 'leader'}).fetch();
if (roles.length > 0) {
const u = roles[0].u;
if (u._id === Meteor.user()._id) { return null; }
const currUser = RocketChat.models.Users.find({ _id: u._id}).fetch();
u['status'] = currUser.length > 0 ? 'online' : 'offline';
return u;
const roles = RoomRoles.findOne({ rid: this._id, roles: 'leader', 'u._id': { $ne: Meteor.userId() } });
if (roles) {
const leader = RocketChat.models.Users.findOne({ _id: roles.u._id }, { fields: { status: 1 }}) || {};
return {
...roles.u,
name: RocketChat.settings.get('UI_Use_Real_Name') ? (roles.u.name || roles.u.username) : roles.u.username,
status: leader.status || 'offline',
statusDisplay: (status => status.charAt(0).toUpperCase() + status.slice(1))(leader.status || 'offline')
};
}
return null;
},
chatNowLink() {
return RocketChat.roomTypes.getRouteLink('d', { name: this.username });
},
roomName() {
@ -272,6 +277,14 @@ Template.room.helpers({
};
});
return { buttons };
},
hideLeaderHeader() {
return Template.instance().hideLeaderHeader.get() ? 'animated-hidden' : '';
},
hasLeader() {
if (RoomRoles.findOne({ rid: this._id, roles: 'leader', 'u._id': { $ne: Meteor.userId() } }, { fields: { _id: 1 } })) {
return 'has-leader';
}
}
});
@ -298,16 +311,6 @@ Template.room.events({
}
},
'scroll .messages-box .wrapper'() {
const $wrapper = $('.messages-box .wrapper');
if ($wrapper.scrollTop() < lastScrollTop) {
$('.room-leader').removeClass('hidden');
} else if ($wrapper.scrollTop() > $('.room-leader-container').height()) {
$('.room-leader').addClass('hidden');
}
lastScrollTop = $wrapper.scrollTop();
},
'touchstart .message'(e, t) {
const touches = e.originalEvent.touches;
if (touches && touches.length) {
@ -493,7 +496,14 @@ Template.room.events({
}
},
'scroll .wrapper': _.throttle(function(e) {
'scroll .wrapper': _.throttle(function(e, t) {
if (e.target.scrollTop < lastScrollTop) {
t.hideLeaderHeader.set(false);
} else if (e.target.scrollTop > $('.room-leader').height()) {
t.hideLeaderHeader.set(true);
}
lastScrollTop = e.target.scrollTop;
if (RoomHistoryManager.isLoading(this._id) === false && RoomHistoryManager.hasMore(this._id) === true || RoomHistoryManager.hasMoreNext(this._id) === true) {
if (RoomHistoryManager.hasMore(this._id) === true && e.target.scrollTop === 0) {
return RoomHistoryManager.getMore(this._id);
@ -683,6 +693,8 @@ Template.room.onCreated(function() {
this.tabBar = new RocketChatTabBar();
this.tabBar.showGroup(FlowRouter.current().route.name);
this.hideLeaderHeader = new ReactiveVar(false);
this.resetSelection = enabled => {
this.selectable.set(enabled);
$('.messages-box .message.selected').removeClass('selected');

@ -316,7 +316,13 @@ describe('[Administration]', () => {
});
it('it should show the role dropdown', () => {
flexTab.usersAddUserRole.isVisible().should.be.true;
flexTab.usersAddUserRoleList.waitForVisible(5000);
flexTab.usersAddUserRoleList.isVisible().should.be.true;
});
it('ít should show the add role button', () => {
flexTab.usersAddUserRoleButton.waitForVisible(5000);
flexTab.usersAddUserRoleButton.isVisible().should.be.true;
});
it('it should show the join default channel checkbox', () => {

@ -97,6 +97,7 @@ describe('[Permissions]', () => {
flexTab.usersAddUserVerifiedCheckbox.click();
flexTab.usersAddUserPassword.setValue(password);
flexTab.usersAddUserChangePasswordCheckbox.click();
flexTab.addRole('user');
flexTab.usersButtonSave.click();
});

@ -85,8 +85,9 @@ class FlexTab extends Page {
get usersAddUserName() { return browser.element('#name'); }
get usersAddUserUsername() { return browser.element('#username'); }
get usersAddUserEmail() { return browser.element('#email'); }
get usersAddUserRoleList() { return browser.element('#roleSelect'); }
get usersAddUserPassword() { return browser.element('#password'); }
get usersAddUserRole() { return browser.element('#role'); }
get usersAddUserRoleButton() { return browser.element('#addRole'); }
get usersAddUserVerifiedCheckbox() { return browser.element('#verified'); }
get usersAddUserChangePasswordCheckbox() { return browser.element('#changePassword'); }
get usersAddUserDefaultChannelCheckbox() { return browser.element('#joinDefaultChannels'); }
@ -122,6 +123,16 @@ class FlexTab extends Page {
this.removeUserBtn.click();
}
addRole(role) {
this.usersAddUserRoleList.waitForVisible(5000);
this.usersAddUserRoleList.click();
browser.waitForVisible(`option[value=${ role }]`, 5000);
browser.click(`option[value=${ role }]`);
this.usersAddUserRoleButton.waitForVisible(5000);
this.usersAddUserRoleButton.click();
browser.waitForVisible(`.remove-role=${ role }`);
}
operateFlexTab(desiredTab, desiredState) {
//desiredState true=open false=closed
switch (desiredTab) {

Loading…
Cancel
Save