{{# with messageContext}}
- {{#each msg in messages}}{{#nrr nrrargs 'message' msg=msg room=room subscription=subscription settings=settings u=u}}{{/nrr}}{{/each}}
+ {{#each msg in messages}}{{#nrr nrrargs 'message' msg=msg room=room subscription=subscription settings=settings u=u customClass="mentions" context="mentions"}}{{/nrr}}{{/each}}
{{/with}}
{{#if hasMore}}
diff --git a/app/mentions-flextab/client/views/mentionsFlexTab.js b/app/mentions-flextab/client/views/mentionsFlexTab.js
index 24268b57ac94..37ae334f1434 100644
--- a/app/mentions-flextab/client/views/mentionsFlexTab.js
+++ b/app/mentions-flextab/client/views/mentionsFlexTab.js
@@ -10,9 +10,6 @@ Template.mentionsFlexTab.helpers({
messages() {
return Template.instance().cursor;
},
- message() {
- return _.extend(this, { customClass: 'mentions', actionContext: 'mentions' });
- },
hasMore() {
return Template.instance().hasMore.get();
},
diff --git a/app/message-attachments/client/messageAttachment.js b/app/message-attachments/client/messageAttachment.js
index 148db49fde2e..7caf9b5bd1ac 100644
--- a/app/message-attachments/client/messageAttachment.js
+++ b/app/message-attachments/client/messageAttachment.js
@@ -72,8 +72,8 @@ Template.messageAttachment.helpers({
return this.type === 'file';
},
isPDF() {
- if (this.type === 'file' && this.title_link.endsWith('.pdf') && Template.parentData().file) {
- this.fileId = Template.parentData().file._id;
+ if (this.type === 'file' && this.title_link.endsWith('.pdf') && Template.parentData().msg.file) {
+ this.fileId = Template.parentData().msg.file._id;
return true;
}
return false;
diff --git a/app/message-star/client/actionButton.js b/app/message-star/client/actionButton.js
index c528cd55810f..229a2a8037b4 100644
--- a/app/message-star/client/actionButton.js
+++ b/app/message-star/client/actionButton.js
@@ -12,7 +12,7 @@ Meteor.startup(function() {
id: 'star-message',
icon: 'star',
label: 'Star',
- context: ['starred', 'message', 'message-mobile'],
+ context: ['starred', 'message', 'message-mobile', 'threads'],
action() {
const { msg: message } = messageArgs(this);
message.starred = Meteor.userId();
@@ -37,7 +37,7 @@ Meteor.startup(function() {
id: 'unstar-message',
icon: 'star',
label: 'Unstar_Message',
- context: ['starred', 'message', 'message-mobile'],
+ context: ['starred', 'message', 'message-mobile', 'threads'],
action() {
const { msg: message } = messageArgs(this);
message.starred = false;
@@ -62,7 +62,7 @@ Meteor.startup(function() {
id: 'jump-to-star-message',
icon: 'jump',
label: 'Jump_to_message',
- context: ['starred'],
+ context: ['starred', 'threads'],
action() {
const { msg: message } = messageArgs(this);
if (window.matchMedia('(max-width: 500px)').matches) {
@@ -85,7 +85,7 @@ Meteor.startup(function() {
icon: 'permalink',
label: 'Get_link',
classes: 'clipboard',
- context: ['starred'],
+ context: ['starred', 'threads'],
async action(event) {
const { msg: message } = messageArgs(this);
$(event.currentTarget).attr('data-clipboard-text', await MessageAction.getPermaLink(message._id));
diff --git a/app/meteor-accounts-saml/client/saml_client.js b/app/meteor-accounts-saml/client/saml_client.js
index 36c33e1d3e83..c4d1e44ebb37 100644
--- a/app/meteor-accounts-saml/client/saml_client.js
+++ b/app/meteor-accounts-saml/client/saml_client.js
@@ -1,5 +1,6 @@
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
+import { Random } from 'meteor/random';
import { ServiceConfiguration } from 'meteor/service-configuration';
if (!Accounts.saml) {
@@ -94,12 +95,14 @@ Accounts.saml.initiateLogin = function(options, callback, dimensions) {
Meteor.loginWithSaml = function(options, callback) {
options = options || {};
- options.credentialToken = Meteor.default_connection._lastSessionId;
+ const credentialToken = `id-${ Random.id() }`;
+ options.credentialToken = credentialToken;
Accounts.saml.initiateLogin(options, function(/* error, result*/) {
Accounts.callLoginMethod({
methodArguments: [{
saml: true,
+ credentialToken,
}],
userCallback: callback,
});
diff --git a/app/meteor-accounts-saml/server/saml_server.js b/app/meteor-accounts-saml/server/saml_server.js
index ae750b8a79f8..8cfb3b1feee3 100644
--- a/app/meteor-accounts-saml/server/saml_server.js
+++ b/app/meteor-accounts-saml/server/saml_server.js
@@ -92,11 +92,11 @@ Meteor.methods({
});
Accounts.registerLoginHandler(function(loginRequest) {
- if (!loginRequest.saml) {
+ if (!loginRequest.saml || !loginRequest.credentialToken) {
return undefined;
}
- const loginResult = Accounts.saml.retrieveCredential(this.connection.id);
+ const loginResult = Accounts.saml.retrieveCredential(loginRequest.credentialToken);
if (Accounts.saml.settings.debug) {
console.log(`RESULT :${ JSON.stringify(loginResult) }`);
}
diff --git a/app/models/server/models/Sessions.js b/app/models/server/models/Sessions.js
index cb7fb3151b19..8b3b30c50e64 100644
--- a/app/models/server/models/Sessions.js
+++ b/app/models/server/models/Sessions.js
@@ -1,5 +1,374 @@
import { Base } from './_Base';
+export const aggregates = {
+ dailySessionsOfYesterday(collection, { year, month, day }) {
+ return collection.aggregate([{
+ $match: {
+ userId: { $exists: true },
+ lastActivityAt: { $exists: true },
+ device: { $exists: true },
+ type: 'session',
+ $or: [{
+ year: { $lt: year },
+ }, {
+ year,
+ month: { $lt: month },
+ }, {
+ year,
+ month,
+ day: { $lte: day },
+ }],
+ },
+ }, {
+ $sort: {
+ _id: 1,
+ },
+ }, {
+ $project: {
+ userId: 1,
+ device: 1,
+ day: 1,
+ month: 1,
+ year: 1,
+ time: { $trunc: { $divide: [{ $subtract: ['$lastActivityAt', '$loginAt'] }, 1000] } },
+ },
+ }, {
+ $match: {
+ time: { $gt: 0 },
+ },
+ }, {
+ $group: {
+ _id: {
+ userId: '$userId',
+ device: '$device',
+ day: '$day',
+ month: '$month',
+ year: '$year',
+ },
+ time: { $sum: '$time' },
+ sessions: { $sum: 1 },
+ },
+ }, {
+ $group: {
+ _id: {
+ userId: '$_id.userId',
+ day: '$_id.day',
+ month: '$_id.month',
+ year: '$_id.year',
+ },
+ time: { $sum: '$time' },
+ sessions: { $sum: '$sessions' },
+ devices: {
+ $push: {
+ sessions: '$sessions',
+ time: '$time',
+ device: '$_id.device',
+ },
+ },
+ },
+ }, {
+ $project: {
+ _id: 0,
+ type: { $literal: 'user_daily' },
+ _computedAt: { $literal: new Date() },
+ day: '$_id.day',
+ month: '$_id.month',
+ year: '$_id.year',
+ userId: '$_id.userId',
+ time: 1,
+ sessions: 1,
+ devices: 1,
+ },
+ }], { allowDiskUse: true });
+ },
+
+ getUniqueUsersOfYesterday(collection, { year, month, day }) {
+ return collection.aggregate([{
+ $match: {
+ year,
+ month,
+ day,
+ type: 'user_daily',
+ },
+ }, {
+ $group: {
+ _id: {
+ day: '$day',
+ month: '$month',
+ year: '$year',
+ },
+ count: {
+ $sum: 1,
+ },
+ sessions: {
+ $sum: '$sessions',
+ },
+ time: {
+ $sum: '$time',
+ },
+ },
+ }, {
+ $project: {
+ _id: 0,
+ count: 1,
+ sessions: 1,
+ time: 1,
+ },
+ }]).toArray();
+ },
+
+ getUniqueUsersOfLastMonth(collection, { year, month, day }) {
+ return collection.aggregate([{
+ $match: {
+ type: 'user_daily',
+ ...aggregates.getMatchOfLastMonthToday({ year, month, day }),
+ },
+ }, {
+ $group: {
+ _id: {
+ userId: '$userId',
+ },
+ sessions: {
+ $sum: '$sessions',
+ },
+ time: {
+ $sum: '$time',
+ },
+ },
+ }, {
+ $group: {
+ _id: 1,
+ count: {
+ $sum: 1,
+ },
+ sessions: {
+ $sum: '$sessions',
+ },
+ time: {
+ $sum: '$time',
+ },
+ },
+ }, {
+ $project: {
+ _id: 0,
+ count: 1,
+ sessions: 1,
+ time: 1,
+ },
+ }], { allowDiskUse: true }).toArray();
+ },
+
+ getMatchOfLastMonthToday({ year, month, day }) {
+ const pastMonthLastDay = (new Date(year, month - 1, 0)).getDate();
+ const currMonthLastDay = (new Date(year, month, 0)).getDate();
+
+ const lastMonthToday = new Date(year, month - 1, day);
+ lastMonthToday.setMonth(lastMonthToday.getMonth() - 1, (currMonthLastDay === day ? pastMonthLastDay : Math.min(pastMonthLastDay, day)) + 1);
+ const lastMonthTodayObject = {
+ year: lastMonthToday.getFullYear(),
+ month: lastMonthToday.getMonth() + 1,
+ day: lastMonthToday.getDate(),
+ };
+
+ if (year === lastMonthTodayObject.year && month === lastMonthTodayObject.month) {
+ return {
+ year,
+ month,
+ day: { $gte: lastMonthTodayObject.day, $lte: day },
+ };
+ }
+
+ if (year === lastMonthTodayObject.year) {
+ return {
+ year,
+ $and: [{
+ $or: [{
+ month: { $gt: lastMonthTodayObject.month },
+ }, {
+ month: lastMonthTodayObject.month,
+ day: { $gte: lastMonthTodayObject.day },
+ }],
+ }, {
+ $or: [{
+ month: { $lt: month },
+ }, {
+ month,
+ day: { $lte: day },
+ }],
+ }],
+ };
+ }
+
+ return {
+ $and: [{
+ $or: [{
+ year: { $gt: lastMonthTodayObject.year },
+ }, {
+ year: lastMonthTodayObject.year,
+ month: { $gt: lastMonthTodayObject.month },
+ }, {
+ year: lastMonthTodayObject.year,
+ month: lastMonthTodayObject.month,
+ day: { $gte: lastMonthTodayObject.day },
+ }],
+ }, {
+ $or: [{
+ year: { $lt: year },
+ }, {
+ year,
+ month: { $lt: month },
+ }, {
+ year,
+ month,
+ day: { $lte: day },
+ }],
+ }],
+ };
+ },
+
+ getUniqueDevicesOfLastMonth(collection, { year, month, day }) {
+ return collection.aggregate([{
+ $match: {
+ type: 'user_daily',
+ ...aggregates.getMatchOfLastMonthToday({ year, month, day }),
+ },
+ }, {
+ $unwind: '$devices',
+ }, {
+ $group: {
+ _id: {
+ type : '$devices.device.type',
+ name : '$devices.device.name',
+ version : '$devices.device.version',
+ },
+ count: {
+ $sum: '$devices.sessions',
+ },
+ time: {
+ $sum: '$devices.time',
+ },
+ },
+ }, {
+ $project: {
+ _id: 0,
+ type: '$_id.type',
+ name: '$_id.name',
+ version: '$_id.version',
+ count: 1,
+ time: 1,
+ },
+ }], { allowDiskUse: true }).toArray();
+ },
+
+ getUniqueDevicesOfYesterday(collection, { year, month, day }) {
+ return collection.aggregate([{
+ $match: {
+ year,
+ month,
+ day,
+ type: 'user_daily',
+ },
+ }, {
+ $unwind: '$devices',
+ }, {
+ $group: {
+ _id: {
+ type : '$devices.device.type',
+ name : '$devices.device.name',
+ version : '$devices.device.version',
+ },
+ count: {
+ $sum: '$devices.sessions',
+ },
+ time: {
+ $sum: '$devices.time',
+ },
+ },
+ }, {
+ $project: {
+ _id: 0,
+ type: '$_id.type',
+ name: '$_id.name',
+ version: '$_id.version',
+ count: 1,
+ time: 1,
+ },
+ }]).toArray();
+ },
+
+ getUniqueOSOfLastMonth(collection, { year, month, day }) {
+ return collection.aggregate([{
+ $match: {
+ type: 'user_daily',
+ 'devices.device.os.name': {
+ $exists: true,
+ },
+ ...aggregates.getMatchOfLastMonthToday({ year, month, day }),
+ },
+ }, {
+ $unwind: '$devices',
+ }, {
+ $group: {
+ _id: {
+ name : '$devices.device.os.name',
+ version : '$devices.device.os.version',
+ },
+ count: {
+ $sum: '$devices.sessions',
+ },
+ time: {
+ $sum: '$devices.time',
+ },
+ },
+ }, {
+ $project: {
+ _id: 0,
+ name: '$_id.name',
+ version: '$_id.version',
+ count: 1,
+ time: 1,
+ },
+ }], { allowDiskUse: true }).toArray();
+ },
+
+ getUniqueOSOfYesterday(collection, { year, month, day }) {
+ return collection.aggregate([{
+ $match: {
+ year,
+ month,
+ day,
+ type: 'user_daily',
+ 'devices.device.os.name': {
+ $exists: true,
+ },
+ },
+ }, {
+ $unwind: '$devices',
+ }, {
+ $group: {
+ _id: {
+ name : '$devices.device.os.name',
+ version : '$devices.device.os.version',
+ },
+ count: {
+ $sum: '$devices.sessions',
+ },
+ time: {
+ $sum: '$devices.time',
+ },
+ },
+ }, {
+ $project: {
+ _id: 0,
+ name: '$_id.name',
+ version: '$_id.version',
+ count: 1,
+ time: 1,
+ },
+ }]).toArray();
+ },
+};
+
export class Sessions extends Base {
constructor(...args) {
super(...args);
@@ -8,6 +377,7 @@ export class Sessions extends Base {
this.tryEnsureIndex({ instanceId: 1, sessionId: 1, userId: 1 });
this.tryEnsureIndex({ instanceId: 1, sessionId: 1 });
this.tryEnsureIndex({ year: 1, month: 1, day: 1, type: 1 });
+ this.tryEnsureIndex({ type: 1 });
this.tryEnsureIndex({ _computedAt: 1 }, { expireAfterSeconds: 60 * 60 * 24 * 45 });
}
@@ -23,34 +393,7 @@ export class Sessions extends Base {
year,
month,
day,
- data: Promise.await(this.model.rawCollection().aggregate([{
- $match: {
- year,
- month,
- day,
- type: 'user_daily',
- },
- }, {
- $group: {
- _id: {
- day: '$day',
- month: '$month',
- year: '$year',
- },
- count: {
- $sum: '$count',
- },
- time: {
- $sum: '$time',
- },
- },
- }, {
- $project: {
- _id: 0,
- count: 1,
- time: 1,
- },
- }]).toArray()),
+ data: Promise.await(aggregates.getUniqueUsersOfYesterday(this.model.rawCollection(), { year, month, day })),
};
}
@@ -60,36 +403,13 @@ export class Sessions extends Base {
const year = date.getFullYear();
const month = date.getMonth() + 1;
+ const day = date.getDate();
return {
year,
month,
- data: Promise.await(this.model.rawCollection().aggregate([{
- $match: {
- year,
- month,
- type: 'user_daily',
- },
- }, {
- $group: {
- _id: {
- month: '$month',
- year: '$year',
- },
- count: {
- $sum: '$count',
- },
- time: {
- $sum: '$time',
- },
- },
- }, {
- $project: {
- _id: 0,
- count: 1,
- time: 1,
- },
- }]).toArray()),
+ day,
+ data: Promise.await(aggregates.getUniqueUsersOfLastMonth(this.model.rawCollection(), { year, month, day })),
};
}
@@ -105,35 +425,23 @@ export class Sessions extends Base {
year,
month,
day,
- data: Promise.await(this.model.rawCollection().aggregate([{
- $match: {
- year,
- month,
- day,
- type: 'user_daily',
- },
- }, {
- $unwind: '$devices',
- }, {
- $group: {
- _id: {
- type : '$devices.type',
- name : '$devices.name',
- version : '$devices.version',
- },
- count: {
- $sum: '$count',
- },
- },
- }, {
- $project: {
- _id: 0,
- type: '$_id.type',
- name: '$_id.name',
- version: '$_id.version',
- count: 1,
- },
- }]).toArray()),
+ data: Promise.await(aggregates.getUniqueDevicesOfYesterday(this.model.rawCollection(), { year, month, day })),
+ };
+ }
+
+ getUniqueDevicesOfLastMonth() {
+ const date = new Date();
+ date.setDate(date.getDate() - 1);
+
+ const year = date.getFullYear();
+ const month = date.getMonth() + 1;
+ const day = date.getDate();
+
+ return {
+ year,
+ month,
+ day,
+ data: Promise.await(aggregates.getUniqueDevicesOfLastMonth(this.model.rawCollection(), { year, month, day })),
};
}
@@ -149,36 +457,23 @@ export class Sessions extends Base {
year,
month,
day,
- data: Promise.await(this.model.rawCollection().aggregate([{
- $match: {
- year,
- month,
- day,
- type: 'user_daily',
- 'devices.os.name': {
- $exists: true,
- },
- },
- }, {
- $unwind: '$devices',
- }, {
- $group: {
- _id: {
- name : '$devices.os.name',
- version : '$devices.os.version',
- },
- count: {
- $sum: '$count',
- },
- },
- }, {
- $project: {
- _id: 0,
- name: '$_id.name',
- version: '$_id.version',
- count: 1,
- },
- }]).toArray()),
+ data: Promise.await(aggregates.getUniqueOSOfYesterday(this.model.rawCollection(), { year, month, day })),
+ };
+ }
+
+ getUniqueOSOfLastMonth() {
+ const date = new Date();
+ date.setDate(date.getDate() - 1);
+
+ const year = date.getFullYear();
+ const month = date.getMonth() + 1;
+ const day = date.getDate();
+
+ return {
+ year,
+ month,
+ day,
+ data: Promise.await(aggregates.getUniqueOSOfLastMonth(this.model.rawCollection(), { year, month, day })),
};
}
diff --git a/app/models/server/models/Sessions.mocks.js b/app/models/server/models/Sessions.mocks.js
new file mode 100644
index 000000000000..731c40948b78
--- /dev/null
+++ b/app/models/server/models/Sessions.mocks.js
@@ -0,0 +1,7 @@
+import mock from 'mock-require';
+
+mock('./_Base', {
+ Base: class Base {
+ tryEnsureIndex() {}
+ },
+});
diff --git a/app/models/server/models/Sessions.tests.js b/app/models/server/models/Sessions.tests.js
new file mode 100644
index 000000000000..a90f70753696
--- /dev/null
+++ b/app/models/server/models/Sessions.tests.js
@@ -0,0 +1,821 @@
+/* eslint-env mocha */
+
+import assert from 'assert';
+import './Sessions.mocks.js';
+const mongoUnit = require('mongo-unit');
+const { MongoClient } = require('mongodb');
+const { aggregates } = require('./Sessions');
+
+const sessions_dates = [];
+const baseDate = new Date(2018, 6, 1);
+
+for (let index = 0; index < 365; index++) {
+ sessions_dates.push({
+ _id: `${ baseDate.getFullYear() }-${ baseDate.getMonth() + 1 }-${ baseDate.getDate() }`,
+ year: baseDate.getFullYear(),
+ month: baseDate.getMonth() + 1,
+ day: baseDate.getDate(),
+ });
+ baseDate.setDate(baseDate.getDate() + 1);
+}
+
+const DATA = {
+ sessions: [{
+ _id : 'fNFyFcjszvoN6Grip2',
+ day : 30,
+ instanceId : 'HvbqxukP8E65LAGMY',
+ month : 4,
+ sessionId : 'kiA4xX33AyzPgpBNs2',
+ year : 2019,
+ _updatedAt : new Date('2019-04-30T16:33:24.311Z'),
+ createdAt : new Date('2019-04-30T00:11:34.047Z'),
+ device : {
+ type : 'browser',
+ name : 'Firefox',
+ longVersion : '66.0.3',
+ os : {
+ name : 'Linux',
+ version : '12',
+ },
+ version : '66.0.3',
+ },
+ host : 'localhost:3000',
+ ip : '127.0.0.1',
+ loginAt : new Date('2019-04-30T00:11:34.047Z'),
+ type : 'session',
+ userId : 'xPZXw9xqM3kKshsse',
+ lastActivityAt : new Date('2019-04-30T00:16:20.349Z'),
+ closedAt : new Date('2019-04-30T00:16:20.349Z'),
+ }, {
+ _id : 'fNFyFcjszvoN6Grip',
+ day : 2,
+ instanceId : 'HvbqxukP8E65LAGMY',
+ month : 5,
+ sessionId : 'kiA4xX33AyzPgpBNs',
+ year : 2019,
+ _updatedAt : new Date('2019-05-06T16:33:24.311Z'),
+ createdAt : new Date('2019-05-03T00:11:34.047Z'),
+ device : {
+ type : 'browser',
+ name : 'Firefox',
+ longVersion : '66.0.3',
+ os : {
+ name : 'Linux',
+ version : '12',
+ },
+ version : '66.0.3',
+ },
+ host : 'localhost:3000',
+ ip : '127.0.0.1',
+ loginAt : new Date('2019-05-03T00:11:34.047Z'),
+ type : 'session',
+ userId : 'xPZXw9xqM3kKshsse',
+ lastActivityAt : new Date('2019-05-03T00:16:20.349Z'),
+ closedAt : new Date('2019-05-03T00:16:20.349Z'),
+ }, {
+ _id : 'oZMkfR3gFB6kuKDK2',
+ day : 2,
+ instanceId : 'HvbqxukP8E65LAGMY',
+ month : 5,
+ sessionId : 'i8uJFekr9np4x88kS',
+ year : 2019,
+ _updatedAt : new Date('2019-05-06T16:33:24.311Z'),
+ createdAt : new Date('2019-05-03T00:16:21.847Z'),
+ device : {
+ type : 'browser',
+ name : 'Chrome',
+ longVersion : '73.0.3683.103',
+ os : {
+ name : 'Mac OS',
+ version : '10.14.1',
+ },
+ version : '73.0.3683',
+ },
+ host : 'localhost:3000',
+ ip : '127.0.0.1',
+ loginAt : new Date('2019-05-03T00:16:21.846Z'),
+ type : 'session',
+ userId : 'xPZXw9xqM3kKshsse',
+ lastActivityAt : new Date('2019-05-03T00:17:21.081Z'),
+ closedAt : new Date('2019-05-03T00:17:21.081Z'),
+ }, {
+ _id : 'ABXKoXKTZpPpzLjKd',
+ day : 2,
+ instanceId : 'HvbqxukP8E65LAGMY',
+ month : 5,
+ sessionId : 'T8MB28cpx2ZjfEDXr',
+ year : 2019,
+ _updatedAt : new Date('2019-05-06T16:33:24.311Z'),
+ createdAt : new Date('2019-05-03T00:17:22.375Z'),
+ device : {
+ type : 'browser',
+ name : 'Chrome',
+ longVersion : '73.0.3683.103',
+ os : {
+ name : 'Mac OS',
+ version : '10.14.1',
+ },
+ version : '73.0.3683',
+ },
+ host : 'localhost:3000',
+ ip : '127.0.0.1',
+ loginAt : new Date('2019-05-03T00:17:22.375Z'),
+ type : 'session',
+ userId : 'xPZXw9xqM3kKshsse',
+ lastActivityAt : new Date('2019-05-03T01:48:31.695Z'),
+ closedAt : new Date('2019-05-03T01:48:31.695Z'),
+ }, {
+ _id : 's4ucvvcfBjnTEtYEb',
+ day : 2,
+ instanceId : 'HvbqxukP8E65LAGMY',
+ month : 5,
+ sessionId : '8mHbJJypgeRG27TYF',
+ year : 2019,
+ _updatedAt : new Date('2019-05-06T16:33:24.311Z'),
+ createdAt : new Date('2019-05-03T01:48:43.521Z'),
+ device : {
+ type : 'browser',
+ name : 'Chrome',
+ longVersion : '73.0.3683.103',
+ os : {
+ name : 'Mac OS',
+ version : '10.14.1',
+ },
+ version : '73.0.3683',
+ },
+ host : 'localhost:3000',
+ ip : '127.0.0.1',
+ loginAt : new Date('2019-05-03T01:48:43.521Z'),
+ type : 'session',
+ userId : 'xPZXw9xqM3kKshsse',
+ closedAt : new Date('2019-05-03T01:48:43.761Z'),
+ lastActivityAt : new Date('2019-05-03T01:48:43.761Z'),
+ }, {
+ _id : 'MDs9SzQKmwaDmXL8s',
+ day : 2,
+ instanceId : 'HvbqxukP8E65LAGMY',
+ month : 5,
+ sessionId : 'GmoBDPKy9RW2eXdCG',
+ year : 2019,
+ _updatedAt : new Date('2019-05-06T16:33:24.311Z'),
+ createdAt : new Date('2019-05-03T01:48:45.064Z'),
+ device : {
+ type : 'browser',
+ name : 'Chrome',
+ longVersion : '73.0.3683.103',
+ os : {
+ name : 'Mac OS',
+ version : '10.14.1',
+ },
+ version : '73.0.3683',
+ },
+ host : 'localhost:3000',
+ ip : '127.0.0.1',
+ loginAt : new Date('2019-05-03T01:48:45.064Z'),
+ type : 'session',
+ userId : 'xPZXw9xqM3kKshsse',
+ }, {
+ _id : 'CJwfxASo62FHDgqog',
+ day : 2,
+ instanceId : 'Nmwo2ttFeWZSrowNh',
+ month : 5,
+ sessionId : 'LMrrL4sbpNMLWYomA',
+ year : 2019,
+ _updatedAt : new Date('2019-05-06T16:33:24.311Z'),
+ createdAt : new Date('2019-05-03T01:50:31.098Z'),
+ device : {
+ type : 'browser',
+ name : 'Chrome',
+ longVersion : '73.0.3683.103',
+ os : {
+ name : 'Mac OS',
+ version : '10.14.1',
+ },
+ version : '73.0.3683',
+ },
+ host : 'localhost:3000',
+ ip : '127.0.0.1',
+ loginAt : new Date('2019-05-03T01:50:31.092Z'),
+ type : 'session',
+ userId : 'xPZXw9xqM3kKshsse',
+ closedAt : new Date('2019-05-03T01:50:31.355Z'),
+ lastActivityAt : new Date('2019-05-03T01:50:31.355Z'),
+ }, {
+ _id : 'iGAcPobWfTQtN6s4K',
+ day : 1,
+ instanceId : 'Nmwo2ttFeWZSrowNh',
+ month : 5,
+ sessionId : 'AsbjZRLNQMqfbyYFS',
+ year : 2019,
+ _updatedAt : new Date('2019-05-06T16:33:24.311Z'),
+ createdAt : new Date('2019-05-03T01:50:32.765Z'),
+ device : {
+ type : 'browser',
+ name : 'Chrome',
+ longVersion : '73.0.3683.103',
+ os : {
+ name : 'Mac OS',
+ version : '10.14.1',
+ },
+ version : '73.0.3683',
+ },
+ host : 'localhost:3000',
+ ip : '127.0.0.1',
+ loginAt : new Date('2019-05-03T01:50:32.765Z'),
+ type : 'session',
+ userId : 'xPZXw9xqM3kKshsse2',
+ lastActivityAt : new Date('2019-05-03T02:59:59.999Z'),
+ }],
+ sessions_dates,
+}; // require('./fixtures/testData.json')
+
+describe.only('Sessions Aggregates', () => {
+ let db;
+
+ if (!process.env.MONGO_URL) {
+ before(function() {
+ this.timeout(120000);
+ return mongoUnit.start({ version: '3.2.22' })
+ .then((testMongoUrl) => process.env.MONGO_URL = testMongoUrl);
+ });
+
+ after(() => { mongoUnit.stop(); });
+ }
+
+ before(function() {
+ return MongoClient.connect(process.env.MONGO_URL)
+ .then((client) => db = client.db('test'));
+ });
+
+ before(() => db.dropDatabase().then(() => {
+ const sessions = db.collection('sessions');
+ const sessions_dates = db.collection('sessions_dates');
+ return Promise.all([
+ sessions.insertMany(DATA.sessions),
+ sessions_dates.insertMany(DATA.sessions_dates),
+ ]);
+ }));
+
+ after(() => { db.close(); });
+
+ it('should have sessions_dates data saved', () => {
+ const collection = db.collection('sessions_dates');
+ return collection.find().toArray()
+ .then((docs) => assert.equal(docs.length, DATA.sessions_dates.length));
+ });
+
+ it('should match sessions between 2018-12-11 and 2019-1-10', () => {
+ const collection = db.collection('sessions_dates');
+ const $match = aggregates.getMatchOfLastMonthToday({ year: 2019, month: 1, day: 10 });
+
+ assert.deepEqual($match, {
+ $and: [{
+ $or: [
+ { year: { $gt: 2018 } },
+ { year: 2018, month: { $gt: 12 } },
+ { year: 2018, month: 12, day: { $gte: 11 } },
+ ],
+ }, {
+ $or: [
+ { year: { $lt: 2019 } },
+ { year: 2019, month: { $lt: 1 } },
+ { year: 2019, month: 1, day: { $lte: 10 } },
+ ],
+ }],
+ });
+
+ return collection.aggregate([{
+ $match,
+ }]).toArray()
+ .then((docs) => {
+ assert.equal(docs.length, 31);
+ assert.deepEqual(docs, [
+ { _id: '2018-12-11', year: 2018, month: 12, day: 11 },
+ { _id: '2018-12-12', year: 2018, month: 12, day: 12 },
+ { _id: '2018-12-13', year: 2018, month: 12, day: 13 },
+ { _id: '2018-12-14', year: 2018, month: 12, day: 14 },
+ { _id: '2018-12-15', year: 2018, month: 12, day: 15 },
+ { _id: '2018-12-16', year: 2018, month: 12, day: 16 },
+ { _id: '2018-12-17', year: 2018, month: 12, day: 17 },
+ { _id: '2018-12-18', year: 2018, month: 12, day: 18 },
+ { _id: '2018-12-19', year: 2018, month: 12, day: 19 },
+ { _id: '2018-12-20', year: 2018, month: 12, day: 20 },
+ { _id: '2018-12-21', year: 2018, month: 12, day: 21 },
+ { _id: '2018-12-22', year: 2018, month: 12, day: 22 },
+ { _id: '2018-12-23', year: 2018, month: 12, day: 23 },
+ { _id: '2018-12-24', year: 2018, month: 12, day: 24 },
+ { _id: '2018-12-25', year: 2018, month: 12, day: 25 },
+ { _id: '2018-12-26', year: 2018, month: 12, day: 26 },
+ { _id: '2018-12-27', year: 2018, month: 12, day: 27 },
+ { _id: '2018-12-28', year: 2018, month: 12, day: 28 },
+ { _id: '2018-12-29', year: 2018, month: 12, day: 29 },
+ { _id: '2018-12-30', year: 2018, month: 12, day: 30 },
+ { _id: '2018-12-31', year: 2018, month: 12, day: 31 },
+ { _id: '2019-1-1', year: 2019, month: 1, day: 1 },
+ { _id: '2019-1-2', year: 2019, month: 1, day: 2 },
+ { _id: '2019-1-3', year: 2019, month: 1, day: 3 },
+ { _id: '2019-1-4', year: 2019, month: 1, day: 4 },
+ { _id: '2019-1-5', year: 2019, month: 1, day: 5 },
+ { _id: '2019-1-6', year: 2019, month: 1, day: 6 },
+ { _id: '2019-1-7', year: 2019, month: 1, day: 7 },
+ { _id: '2019-1-8', year: 2019, month: 1, day: 8 },
+ { _id: '2019-1-9', year: 2019, month: 1, day: 9 },
+ { _id: '2019-1-10', year: 2019, month: 1, day: 10 },
+ ]);
+ });
+ });
+
+ it('should match sessions between 2019-1-11 and 2019-2-10', () => {
+ const collection = db.collection('sessions_dates');
+ const $match = aggregates.getMatchOfLastMonthToday({ year: 2019, month: 2, day: 10 });
+
+ assert.deepEqual($match, {
+ year: 2019,
+ $and: [{
+ $or: [
+ { month: { $gt: 1 } },
+ { month: 1, day: { $gte: 11 } },
+ ],
+ }, {
+ $or: [
+ { month: { $lt: 2 } },
+ { month: 2, day: { $lte: 10 } },
+ ],
+ }],
+ });
+
+ return collection.aggregate([{
+ $match,
+ }]).toArray()
+ .then((docs) => {
+ assert.equal(docs.length, 31);
+ assert.deepEqual(docs, [
+ { _id: '2019-1-11', year: 2019, month: 1, day: 11 },
+ { _id: '2019-1-12', year: 2019, month: 1, day: 12 },
+ { _id: '2019-1-13', year: 2019, month: 1, day: 13 },
+ { _id: '2019-1-14', year: 2019, month: 1, day: 14 },
+ { _id: '2019-1-15', year: 2019, month: 1, day: 15 },
+ { _id: '2019-1-16', year: 2019, month: 1, day: 16 },
+ { _id: '2019-1-17', year: 2019, month: 1, day: 17 },
+ { _id: '2019-1-18', year: 2019, month: 1, day: 18 },
+ { _id: '2019-1-19', year: 2019, month: 1, day: 19 },
+ { _id: '2019-1-20', year: 2019, month: 1, day: 20 },
+ { _id: '2019-1-21', year: 2019, month: 1, day: 21 },
+ { _id: '2019-1-22', year: 2019, month: 1, day: 22 },
+ { _id: '2019-1-23', year: 2019, month: 1, day: 23 },
+ { _id: '2019-1-24', year: 2019, month: 1, day: 24 },
+ { _id: '2019-1-25', year: 2019, month: 1, day: 25 },
+ { _id: '2019-1-26', year: 2019, month: 1, day: 26 },
+ { _id: '2019-1-27', year: 2019, month: 1, day: 27 },
+ { _id: '2019-1-28', year: 2019, month: 1, day: 28 },
+ { _id: '2019-1-29', year: 2019, month: 1, day: 29 },
+ { _id: '2019-1-30', year: 2019, month: 1, day: 30 },
+ { _id: '2019-1-31', year: 2019, month: 1, day: 31 },
+ { _id: '2019-2-1', year: 2019, month: 2, day: 1 },
+ { _id: '2019-2-2', year: 2019, month: 2, day: 2 },
+ { _id: '2019-2-3', year: 2019, month: 2, day: 3 },
+ { _id: '2019-2-4', year: 2019, month: 2, day: 4 },
+ { _id: '2019-2-5', year: 2019, month: 2, day: 5 },
+ { _id: '2019-2-6', year: 2019, month: 2, day: 6 },
+ { _id: '2019-2-7', year: 2019, month: 2, day: 7 },
+ { _id: '2019-2-8', year: 2019, month: 2, day: 8 },
+ { _id: '2019-2-9', year: 2019, month: 2, day: 9 },
+ { _id: '2019-2-10', year: 2019, month: 2, day: 10 },
+ ]);
+ });
+ });
+
+ it('should match sessions between 2019-5-1 and 2019-5-31', () => {
+ const collection = db.collection('sessions_dates');
+ const $match = aggregates.getMatchOfLastMonthToday({ year: 2019, month: 5, day: 31 });
+
+ assert.deepEqual($match, {
+ year: 2019,
+ month: 5,
+ day: { $gte: 1, $lte: 31 },
+ });
+
+ return collection.aggregate([{
+ $match,
+ }]).toArray()
+ .then((docs) => {
+ assert.equal(docs.length, 31);
+ assert.deepEqual(docs, [
+ { _id: '2019-5-1', year: 2019, month: 5, day: 1 },
+ { _id: '2019-5-2', year: 2019, month: 5, day: 2 },
+ { _id: '2019-5-3', year: 2019, month: 5, day: 3 },
+ { _id: '2019-5-4', year: 2019, month: 5, day: 4 },
+ { _id: '2019-5-5', year: 2019, month: 5, day: 5 },
+ { _id: '2019-5-6', year: 2019, month: 5, day: 6 },
+ { _id: '2019-5-7', year: 2019, month: 5, day: 7 },
+ { _id: '2019-5-8', year: 2019, month: 5, day: 8 },
+ { _id: '2019-5-9', year: 2019, month: 5, day: 9 },
+ { _id: '2019-5-10', year: 2019, month: 5, day: 10 },
+ { _id: '2019-5-11', year: 2019, month: 5, day: 11 },
+ { _id: '2019-5-12', year: 2019, month: 5, day: 12 },
+ { _id: '2019-5-13', year: 2019, month: 5, day: 13 },
+ { _id: '2019-5-14', year: 2019, month: 5, day: 14 },
+ { _id: '2019-5-15', year: 2019, month: 5, day: 15 },
+ { _id: '2019-5-16', year: 2019, month: 5, day: 16 },
+ { _id: '2019-5-17', year: 2019, month: 5, day: 17 },
+ { _id: '2019-5-18', year: 2019, month: 5, day: 18 },
+ { _id: '2019-5-19', year: 2019, month: 5, day: 19 },
+ { _id: '2019-5-20', year: 2019, month: 5, day: 20 },
+ { _id: '2019-5-21', year: 2019, month: 5, day: 21 },
+ { _id: '2019-5-22', year: 2019, month: 5, day: 22 },
+ { _id: '2019-5-23', year: 2019, month: 5, day: 23 },
+ { _id: '2019-5-24', year: 2019, month: 5, day: 24 },
+ { _id: '2019-5-25', year: 2019, month: 5, day: 25 },
+ { _id: '2019-5-26', year: 2019, month: 5, day: 26 },
+ { _id: '2019-5-27', year: 2019, month: 5, day: 27 },
+ { _id: '2019-5-28', year: 2019, month: 5, day: 28 },
+ { _id: '2019-5-29', year: 2019, month: 5, day: 29 },
+ { _id: '2019-5-30', year: 2019, month: 5, day: 30 },
+ { _id: '2019-5-31', year: 2019, month: 5, day: 31 },
+ ]);
+ });
+ });
+
+ it('should match sessions between 2019-4-1 and 2019-4-30', () => {
+ const collection = db.collection('sessions_dates');
+ const $match = aggregates.getMatchOfLastMonthToday({ year: 2019, month: 4, day: 30 });
+
+ assert.deepEqual($match, {
+ year: 2019,
+ month: 4,
+ day: { $gte: 1, $lte: 30 },
+ });
+
+ return collection.aggregate([{
+ $match,
+ }]).toArray()
+ .then((docs) => {
+ assert.equal(docs.length, 30);
+ assert.deepEqual(docs, [
+ { _id: '2019-4-1', year: 2019, month: 4, day: 1 },
+ { _id: '2019-4-2', year: 2019, month: 4, day: 2 },
+ { _id: '2019-4-3', year: 2019, month: 4, day: 3 },
+ { _id: '2019-4-4', year: 2019, month: 4, day: 4 },
+ { _id: '2019-4-5', year: 2019, month: 4, day: 5 },
+ { _id: '2019-4-6', year: 2019, month: 4, day: 6 },
+ { _id: '2019-4-7', year: 2019, month: 4, day: 7 },
+ { _id: '2019-4-8', year: 2019, month: 4, day: 8 },
+ { _id: '2019-4-9', year: 2019, month: 4, day: 9 },
+ { _id: '2019-4-10', year: 2019, month: 4, day: 10 },
+ { _id: '2019-4-11', year: 2019, month: 4, day: 11 },
+ { _id: '2019-4-12', year: 2019, month: 4, day: 12 },
+ { _id: '2019-4-13', year: 2019, month: 4, day: 13 },
+ { _id: '2019-4-14', year: 2019, month: 4, day: 14 },
+ { _id: '2019-4-15', year: 2019, month: 4, day: 15 },
+ { _id: '2019-4-16', year: 2019, month: 4, day: 16 },
+ { _id: '2019-4-17', year: 2019, month: 4, day: 17 },
+ { _id: '2019-4-18', year: 2019, month: 4, day: 18 },
+ { _id: '2019-4-19', year: 2019, month: 4, day: 19 },
+ { _id: '2019-4-20', year: 2019, month: 4, day: 20 },
+ { _id: '2019-4-21', year: 2019, month: 4, day: 21 },
+ { _id: '2019-4-22', year: 2019, month: 4, day: 22 },
+ { _id: '2019-4-23', year: 2019, month: 4, day: 23 },
+ { _id: '2019-4-24', year: 2019, month: 4, day: 24 },
+ { _id: '2019-4-25', year: 2019, month: 4, day: 25 },
+ { _id: '2019-4-26', year: 2019, month: 4, day: 26 },
+ { _id: '2019-4-27', year: 2019, month: 4, day: 27 },
+ { _id: '2019-4-28', year: 2019, month: 4, day: 28 },
+ { _id: '2019-4-29', year: 2019, month: 4, day: 29 },
+ { _id: '2019-4-30', year: 2019, month: 4, day: 30 },
+ ]);
+ });
+ });
+
+ it('should match sessions between 2019-2-1 and 2019-2-28', () => {
+ const collection = db.collection('sessions_dates');
+ const $match = aggregates.getMatchOfLastMonthToday({ year: 2019, month: 2, day: 28 });
+
+ assert.deepEqual($match, {
+ year: 2019,
+ month: 2,
+ day: { $gte: 1, $lte: 28 },
+ });
+
+ return collection.aggregate([{
+ $match,
+ }]).toArray()
+ .then((docs) => {
+ assert.equal(docs.length, 28);
+ assert.deepEqual(docs, [
+ { _id: '2019-2-1', year: 2019, month: 2, day: 1 },
+ { _id: '2019-2-2', year: 2019, month: 2, day: 2 },
+ { _id: '2019-2-3', year: 2019, month: 2, day: 3 },
+ { _id: '2019-2-4', year: 2019, month: 2, day: 4 },
+ { _id: '2019-2-5', year: 2019, month: 2, day: 5 },
+ { _id: '2019-2-6', year: 2019, month: 2, day: 6 },
+ { _id: '2019-2-7', year: 2019, month: 2, day: 7 },
+ { _id: '2019-2-8', year: 2019, month: 2, day: 8 },
+ { _id: '2019-2-9', year: 2019, month: 2, day: 9 },
+ { _id: '2019-2-10', year: 2019, month: 2, day: 10 },
+ { _id: '2019-2-11', year: 2019, month: 2, day: 11 },
+ { _id: '2019-2-12', year: 2019, month: 2, day: 12 },
+ { _id: '2019-2-13', year: 2019, month: 2, day: 13 },
+ { _id: '2019-2-14', year: 2019, month: 2, day: 14 },
+ { _id: '2019-2-15', year: 2019, month: 2, day: 15 },
+ { _id: '2019-2-16', year: 2019, month: 2, day: 16 },
+ { _id: '2019-2-17', year: 2019, month: 2, day: 17 },
+ { _id: '2019-2-18', year: 2019, month: 2, day: 18 },
+ { _id: '2019-2-19', year: 2019, month: 2, day: 19 },
+ { _id: '2019-2-20', year: 2019, month: 2, day: 20 },
+ { _id: '2019-2-21', year: 2019, month: 2, day: 21 },
+ { _id: '2019-2-22', year: 2019, month: 2, day: 22 },
+ { _id: '2019-2-23', year: 2019, month: 2, day: 23 },
+ { _id: '2019-2-24', year: 2019, month: 2, day: 24 },
+ { _id: '2019-2-25', year: 2019, month: 2, day: 25 },
+ { _id: '2019-2-26', year: 2019, month: 2, day: 26 },
+ { _id: '2019-2-27', year: 2019, month: 2, day: 27 },
+ { _id: '2019-2-28', year: 2019, month: 2, day: 28 },
+ ]);
+ });
+ });
+
+ it('should match sessions between 2019-1-28 and 2019-2-27', () => {
+ const collection = db.collection('sessions_dates');
+ const $match = aggregates.getMatchOfLastMonthToday({ year: 2019, month: 2, day: 27 });
+
+ assert.deepEqual($match, {
+ year: 2019,
+ $and: [{
+ $or: [
+ { month: { $gt: 1 } },
+ { month: 1, day: { $gte: 28 } },
+ ],
+ }, {
+ $or: [
+ { month: { $lt: 2 } },
+ { month: 2, day: { $lte: 27 } },
+ ],
+ }],
+ });
+
+ return collection.aggregate([{
+ $match,
+ }]).toArray()
+ .then((docs) => {
+ assert.equal(docs.length, 31);
+ assert.deepEqual(docs, [
+ { _id: '2019-1-28', year: 2019, month: 1, day: 28 },
+ { _id: '2019-1-29', year: 2019, month: 1, day: 29 },
+ { _id: '2019-1-30', year: 2019, month: 1, day: 30 },
+ { _id: '2019-1-31', year: 2019, month: 1, day: 31 },
+ { _id: '2019-2-1', year: 2019, month: 2, day: 1 },
+ { _id: '2019-2-2', year: 2019, month: 2, day: 2 },
+ { _id: '2019-2-3', year: 2019, month: 2, day: 3 },
+ { _id: '2019-2-4', year: 2019, month: 2, day: 4 },
+ { _id: '2019-2-5', year: 2019, month: 2, day: 5 },
+ { _id: '2019-2-6', year: 2019, month: 2, day: 6 },
+ { _id: '2019-2-7', year: 2019, month: 2, day: 7 },
+ { _id: '2019-2-8', year: 2019, month: 2, day: 8 },
+ { _id: '2019-2-9', year: 2019, month: 2, day: 9 },
+ { _id: '2019-2-10', year: 2019, month: 2, day: 10 },
+ { _id: '2019-2-11', year: 2019, month: 2, day: 11 },
+ { _id: '2019-2-12', year: 2019, month: 2, day: 12 },
+ { _id: '2019-2-13', year: 2019, month: 2, day: 13 },
+ { _id: '2019-2-14', year: 2019, month: 2, day: 14 },
+ { _id: '2019-2-15', year: 2019, month: 2, day: 15 },
+ { _id: '2019-2-16', year: 2019, month: 2, day: 16 },
+ { _id: '2019-2-17', year: 2019, month: 2, day: 17 },
+ { _id: '2019-2-18', year: 2019, month: 2, day: 18 },
+ { _id: '2019-2-19', year: 2019, month: 2, day: 19 },
+ { _id: '2019-2-20', year: 2019, month: 2, day: 20 },
+ { _id: '2019-2-21', year: 2019, month: 2, day: 21 },
+ { _id: '2019-2-22', year: 2019, month: 2, day: 22 },
+ { _id: '2019-2-23', year: 2019, month: 2, day: 23 },
+ { _id: '2019-2-24', year: 2019, month: 2, day: 24 },
+ { _id: '2019-2-25', year: 2019, month: 2, day: 25 },
+ { _id: '2019-2-26', year: 2019, month: 2, day: 26 },
+ { _id: '2019-2-27', year: 2019, month: 2, day: 27 },
+ ]);
+ });
+ });
+
+ it('should have sessions data saved', () => {
+ const collection = db.collection('sessions');
+ return collection.find().toArray()
+ .then((docs) => assert.equal(docs.length, DATA.sessions.length));
+ });
+
+ it('should generate daily sessions', () => {
+ const collection = db.collection('sessions');
+ return aggregates.dailySessionsOfYesterday(collection, { year: 2019, month: 5, day: 2 }).toArray()
+ .then((docs) => {
+ docs.forEach((doc) => {
+ doc._id = `${ doc.userId }-${ doc.year }-${ doc.month }-${ doc.day }`;
+ });
+
+ assert.equal(docs.length, 3);
+ assert.deepEqual(docs, [{
+ _id: 'xPZXw9xqM3kKshsse-2019-5-2',
+ time: 5814,
+ sessions: 3,
+ devices: [{
+ sessions: 1,
+ time: 286,
+ device: {
+ type: 'browser',
+ name: 'Firefox',
+ longVersion: '66.0.3',
+ os: {
+ name: 'Linux',
+ version: '12',
+ },
+ version: '66.0.3',
+ },
+ }, {
+ sessions: 2,
+ time: 5528,
+ device: {
+ type: 'browser',
+ name: 'Chrome',
+ longVersion: '73.0.3683.103',
+ os: {
+ name: 'Mac OS',
+ version: '10.14.1',
+ },
+ version: '73.0.3683',
+ },
+ }],
+ type: 'user_daily',
+ _computedAt: docs[0]._computedAt,
+ day: 2,
+ month: 5,
+ year: 2019,
+ userId: 'xPZXw9xqM3kKshsse',
+ }, {
+ _id: 'xPZXw9xqM3kKshsse-2019-4-30',
+ day: 30,
+ devices: [{
+ device: {
+ longVersion: '66.0.3',
+ name: 'Firefox',
+ os: {
+ name: 'Linux',
+ version: '12',
+ },
+ type: 'browser',
+ version: '66.0.3',
+ },
+ sessions: 1,
+ time: 286,
+ }],
+ month: 4,
+ sessions: 1,
+ time: 286,
+ type: 'user_daily',
+ _computedAt: docs[1]._computedAt,
+ userId: 'xPZXw9xqM3kKshsse',
+ year: 2019,
+ }, {
+ _id: 'xPZXw9xqM3kKshsse2-2019-5-1',
+ time: 4167,
+ sessions: 1,
+ devices: [{
+ sessions: 1,
+ time: 4167,
+ device: {
+ type: 'browser',
+ name: 'Chrome',
+ longVersion: '73.0.3683.103',
+ os: {
+ name: 'Mac OS',
+ version: '10.14.1',
+ },
+ version: '73.0.3683',
+ },
+ }],
+ type: 'user_daily',
+ _computedAt: docs[2]._computedAt,
+ day: 1,
+ month: 5,
+ year: 2019,
+ userId: 'xPZXw9xqM3kKshsse2',
+ }]);
+
+ return collection.insertMany(docs);
+ });
+ });
+
+ it('should have 2 unique users for month 5 of 2019', () => {
+ const collection = db.collection('sessions');
+ return aggregates.getUniqueUsersOfLastMonth(collection, { year: 2019, month: 5, day: 31 })
+ .then((docs) => {
+ assert.equal(docs.length, 1);
+ assert.deepEqual(docs, [{
+ count: 2,
+ sessions: 4,
+ time: 9981,
+ }]);
+ });
+ });
+
+ it('should have 1 unique user for 1st of month 5 of 2019', () => {
+ const collection = db.collection('sessions');
+ return aggregates.getUniqueUsersOfYesterday(collection, { year: 2019, month: 5, day: 1 })
+ .then((docs) => {
+ assert.equal(docs.length, 1);
+ assert.deepEqual(docs, [{
+ count: 1,
+ sessions: 1,
+ time: 4167,
+ }]);
+ });
+ });
+
+ it('should have 1 unique user for 2nd of month 5 of 2019', () => {
+ const collection = db.collection('sessions');
+ return aggregates.getUniqueUsersOfYesterday(collection, { year: 2019, month: 5, day: 2 })
+ .then((docs) => {
+ assert.equal(docs.length, 1);
+ assert.deepEqual(docs, [{
+ count: 1,
+ sessions: 3,
+ time: 5814,
+ }]);
+ });
+ });
+
+ it('should have 2 unique devices for month 5 of 2019', () => {
+ const collection = db.collection('sessions');
+ return aggregates.getUniqueDevicesOfLastMonth(collection, { year: 2019, month: 5, day: 31 })
+ .then((docs) => {
+ assert.equal(docs.length, 2);
+ assert.deepEqual(docs, [{
+ count: 3,
+ time: 9695,
+ type: 'browser',
+ name: 'Chrome',
+ version: '73.0.3683',
+ }, {
+ count: 1,
+ time: 286,
+ type: 'browser',
+ name: 'Firefox',
+ version: '66.0.3',
+ }]);
+ });
+ });
+
+ it('should have 2 unique devices for 2nd of month 5 of 2019', () => {
+ const collection = db.collection('sessions');
+ return aggregates.getUniqueDevicesOfYesterday(collection, { year: 2019, month: 5, day: 2 })
+ .then((docs) => {
+ assert.equal(docs.length, 2);
+ assert.deepEqual(docs, [{
+ count: 2,
+ time: 5528,
+ type: 'browser',
+ name: 'Chrome',
+ version: '73.0.3683',
+ }, {
+ count: 1,
+ time: 286,
+ type: 'browser',
+ name: 'Firefox',
+ version: '66.0.3',
+ }]);
+ });
+ });
+
+ it('should have 2 unique OS for month 5 of 2019', () => {
+ const collection = db.collection('sessions');
+ return aggregates.getUniqueOSOfLastMonth(collection, { year: 2019, month: 5, day: 31 })
+ .then((docs) => {
+ assert.equal(docs.length, 2);
+ assert.deepEqual(docs, [{
+ count: 3,
+ time: 9695,
+ name: 'Mac OS',
+ version: '10.14.1',
+ }, {
+ count: 1,
+ time: 286,
+ name: 'Linux',
+ version: '12',
+ }]);
+ });
+ });
+
+ it('should have 2 unique OS for 2nd of month 5 of 2019', () => {
+ const collection = db.collection('sessions');
+ return aggregates.getUniqueOSOfYesterday(collection, { year: 2019, month: 5, day: 2 })
+ .then((docs) => {
+ assert.equal(docs.length, 2);
+ assert.deepEqual(docs, [{
+ count: 2,
+ time: 5528,
+ name: 'Mac OS',
+ version: '10.14.1',
+ }, {
+ count: 1,
+ time: 286,
+ name: 'Linux',
+ version: '12',
+ }]);
+ });
+ });
+});
diff --git a/app/models/server/models/Users.js b/app/models/server/models/Users.js
index 14c86ee35bbe..38046d55b312 100644
--- a/app/models/server/models/Users.js
+++ b/app/models/server/models/Users.js
@@ -364,9 +364,9 @@ export class Users extends Base {
return this.findOne({ importIds: _id }, options);
}
- findOneByUsername(username, options) {
+ findOneByUsernameIgnoringCase(username, options) {
if (typeof username === 'string') {
- username = new RegExp(`^${ username }$`, 'i');
+ username = new RegExp(`^${ s.escapeRegExp(username) }$`, 'i');
}
const query = { username };
@@ -374,6 +374,12 @@ export class Users extends Base {
return this.findOne(query, options);
}
+ findOneByUsername(username, options) {
+ const query = { username };
+
+ return this.findOne(query, options);
+ }
+
findOneByEmailAddress(emailAddress, options) {
const query = { 'emails.address': new RegExp(`^${ s.escapeRegExp(emailAddress) }$`, 'i') };
diff --git a/app/reactions/client/init.js b/app/reactions/client/init.js
index 7eb39bcd4c8a..a896f59b2c98 100644
--- a/app/reactions/client/init.js
+++ b/app/reactions/client/init.js
@@ -55,6 +55,7 @@ Meteor.startup(function() {
context: [
'message',
'message-mobile',
+ 'threads',
],
action(event) {
event.stopPropagation();
diff --git a/app/slackbridge/server/RocketAdapter.js b/app/slackbridge/server/RocketAdapter.js
index b51e2cf9f950..bb395160dd82 100644
--- a/app/slackbridge/server/RocketAdapter.js
+++ b/app/slackbridge/server/RocketAdapter.js
@@ -355,9 +355,9 @@ export default class RocketAdapter {
const email = (rocketUserData.profile && rocketUserData.profile.email) || '';
let existingRocketUser;
if (!isBot) {
- existingRocketUser = Users.findOneByEmailAddress(email) || Users.findOneByUsername(rocketUserData.name);
+ existingRocketUser = Users.findOneByEmailAddress(email) || Users.findOneByUsernameIgnoringCase(rocketUserData.name);
} else {
- existingRocketUser = Users.findOneByUsername(rocketUserData.name);
+ existingRocketUser = Users.findOneByUsernameIgnoringCase(rocketUserData.name);
}
if (existingRocketUser) {
diff --git a/app/slashcommands-kick/server/server.js b/app/slashcommands-kick/server/server.js
index ee73f3bfdee0..d6e554fddf6e 100644
--- a/app/slashcommands-kick/server/server.js
+++ b/app/slashcommands-kick/server/server.js
@@ -18,7 +18,7 @@ const Kick = function(command, params, { rid }) {
}
const userId = Meteor.userId();
const user = Meteor.users.findOne(userId);
- const kickedUser = Users.findOneByUsername(username);
+ const kickedUser = Users.findOneByUsernameIgnoringCase(username);
if (kickedUser == null) {
return Notifications.notifyUser(userId, 'message', {
diff --git a/app/slashcommands-msg/server/server.js b/app/slashcommands-msg/server/server.js
index eed916392d6f..937021a3710d 100644
--- a/app/slashcommands-msg/server/server.js
+++ b/app/slashcommands-msg/server/server.js
@@ -28,7 +28,7 @@ function Msg(command, params, item) {
const message = trimmedParams.slice(separator + 1);
const targetUsernameOrig = trimmedParams.slice(0, separator);
const targetUsername = targetUsernameOrig.replace('@', '');
- const targetUser = Users.findOneByUsername(targetUsername);
+ const targetUser = Users.findOneByUsernameIgnoringCase(targetUsername);
if (targetUser == null) {
Notifications.notifyUser(Meteor.userId(), 'message', {
_id: Random.id(),
diff --git a/app/slashcommands-mute/server/mute.js b/app/slashcommands-mute/server/mute.js
index f0ee7fce936f..9dfcd272c20b 100644
--- a/app/slashcommands-mute/server/mute.js
+++ b/app/slashcommands-mute/server/mute.js
@@ -20,7 +20,7 @@ slashCommands.add('mute', function Mute(command, params, item) {
}
const userId = Meteor.userId();
const user = Meteor.users.findOne(userId);
- const mutedUser = Users.findOneByUsername(username);
+ const mutedUser = Users.findOneByUsernameIgnoringCase(username);
if (mutedUser == null) {
Notifications.notifyUser(userId, 'message', {
_id: Random.id(),
diff --git a/app/slashcommands-mute/server/unmute.js b/app/slashcommands-mute/server/unmute.js
index 01d29f2595ac..2618890d3671 100644
--- a/app/slashcommands-mute/server/unmute.js
+++ b/app/slashcommands-mute/server/unmute.js
@@ -20,7 +20,7 @@ slashCommands.add('unmute', function Unmute(command, params, item) {
return;
}
const user = Meteor.users.findOne(Meteor.userId());
- const unmutedUser = Users.findOneByUsername(username);
+ const unmutedUser = Users.findOneByUsernameIgnoringCase(username);
if (unmutedUser == null) {
return Notifications.notifyUser(Meteor.userId(), 'message', {
_id: Random.id(),
diff --git a/app/statistics/server/functions/get.js b/app/statistics/server/functions/get.js
index 634ecb94d115..96c1f02eb1c6 100644
--- a/app/statistics/server/functions/get.js
+++ b/app/statistics/server/functions/get.js
@@ -2,7 +2,6 @@ import _ from 'underscore';
import os from 'os';
import { Meteor } from 'meteor/meteor';
-import { MongoInternals } from 'meteor/mongo';
import { InstanceStatus } from 'meteor/konecty:multiple-instances-status';
import {
@@ -16,7 +15,7 @@ import {
LivechatVisitors,
} from '../../../models/server';
import { settings } from '../../../settings/server';
-import { Info } from '../../../utils/server';
+import { Info, getMongoInfo } from '../../../utils/server';
import { Migrations } from '../../../migrations/server';
import { statistics } from '../statisticsNamespace';
@@ -136,35 +135,17 @@ statistics.get = function _getStatistics() {
statistics.migration = Migrations._getControl();
statistics.instanceCount = InstanceStatus.getCollection().find({ _updatedAt: { $gt: new Date(Date.now() - process.uptime() * 1000 - 2000) } }).count();
- const { mongo } = MongoInternals.defaultRemoteCollectionDriver();
-
- if (mongo._oplogHandle && mongo._oplogHandle.onOplogEntry) {
- statistics.oplogEnabled = true;
- }
-
- try {
- const { version, storageEngine } = Promise.await(mongo.db.command({ serverStatus: 1 }));
- statistics.mongoVersion = version;
- statistics.mongoStorageEngine = storageEngine.name;
- } catch (e) {
- console.error('=== Error getting MongoDB info ===');
- console.error(e && e.toString());
- console.error('----------------------------------');
- console.error('Without mongodb version we can\'t ensure you are running a compatible version.');
- console.error('If you are running your mongodb with auth enabled and an user different from admin');
- console.error('you may need to grant permissions for this user to check cluster data.');
- console.error('You can do it via mongo shell running the following command replacing');
- console.error('the string YOUR_USER by the correct user\'s name:');
- console.error('');
- console.error(' db.runCommand({ grantRolesToUser: "YOUR_USER" , roles: [{role: "clusterMonitor", db: "admin"}]})');
- console.error('');
- console.error('==================================');
- }
+ const { oplogEnabled, mongoVersion, mongoStorageEngine } = getMongoInfo();
+ statistics.oplogEnabled = oplogEnabled;
+ statistics.mongoVersion = mongoVersion;
+ statistics.mongoStorageEngine = mongoStorageEngine;
statistics.uniqueUsersOfYesterday = Sessions.getUniqueUsersOfYesterday();
statistics.uniqueUsersOfLastMonth = Sessions.getUniqueUsersOfLastMonth();
statistics.uniqueDevicesOfYesterday = Sessions.getUniqueDevicesOfYesterday();
+ statistics.uniqueDevicesOfLastMonth = Sessions.getUniqueDevicesOfLastMonth();
statistics.uniqueOSOfYesterday = Sessions.getUniqueOSOfYesterday();
+ statistics.uniqueOSOfLastMonth = Sessions.getUniqueOSOfLastMonth();
return statistics;
};
diff --git a/app/statistics/server/lib/SAUMonitor.js b/app/statistics/server/lib/SAUMonitor.js
index 5db2c6c518f9..338c4b923bda 100644
--- a/app/statistics/server/lib/SAUMonitor.js
+++ b/app/statistics/server/lib/SAUMonitor.js
@@ -1,10 +1,11 @@
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import UAParser from 'ua-parser-js';
-import { UAParserMobile } from './UAParserMobile';
-import { Sessions } from '../../../models';
+import { UAParserMobile, UAParserDesktop } from './UAParserCustom';
+import { Sessions } from '../../../models/server';
import { Logger } from '../../../logger';
import { SyncedCron } from 'meteor/littledata:synced-cron';
+import { aggregates } from '../../../models/server/models/Sessions';
const getDateObj = (dateTime = new Date()) => ({
day: dateTime.getDate(),
@@ -199,6 +200,8 @@ export class SAUMonitorClass {
if (UAParserMobile.isMobileApp(uaString)) {
result = UAParserMobile.uaObject(uaString);
+ } else if (UAParserDesktop.isDesktopApp(uaString)) {
+ result = UAParserDesktop.uaObject(uaString);
} else {
const ua = new UAParser(uaString);
result = ua.getResult();
@@ -224,7 +227,7 @@ export class SAUMonitorClass {
}
if (result.device && (result.device.type || result.device.model)) {
- info.type = 'mobile-app';
+ info.type = result.device.type;
if (result.app && result.app.name) {
info.name = result.app.name;
@@ -331,43 +334,7 @@ export class SAUMonitorClass {
day: { $lte: yesterday.day },
};
- Sessions.model.rawCollection().aggregate([{
- $match: {
- userId: { $exists: true },
- lastActivityAt: { $exists: true },
- device: { $exists: true },
- ...match,
- },
- }, {
- $group: {
- _id: {
- userId: '$userId',
- day: '$day',
- month: '$month',
- year: '$year',
- },
- times: { $push: { $trunc: { $divide: [{ $subtract: ['$lastActivityAt', '$loginAt'] }, 1000] } } },
- devices: { $addToSet: '$device' },
- },
- }, {
- $project: {
- _id: '$_id',
- times: { $filter: { input: '$times', as: 'item', cond: { $gt: ['$$item', 0] } } },
- devices: '$devices',
- },
- }, {
- $project: {
- type: 'user_daily',
- _computedAt: new Date(),
- day: '$_id.day',
- month: '$_id.month',
- year: '$_id.year',
- userId: '$_id.userId',
- time: { $sum: '$times' },
- count: { $size: '$times' },
- devices: '$devices',
- },
- }]).forEach(Meteor.bindEnvironment((record) => {
+ aggregates.dailySessionsOfYesterday(Sessions.model.rawCollection(), yesterday).forEach(Meteor.bindEnvironment((record) => {
record._id = `${ record.userId }-${ record.year }-${ record.month }-${ record.day }`;
Sessions.upsert({ _id: record._id }, record);
}));
diff --git a/app/statistics/server/lib/UAParserMobile.js b/app/statistics/server/lib/UAParserCustom.js
similarity index 76%
rename from app/statistics/server/lib/UAParserMobile.js
rename to app/statistics/server/lib/UAParserCustom.js
index 7f1a48ab38af..6557fe73ca9c 100644
--- a/app/statistics/server/lib/UAParserMobile.js
+++ b/app/statistics/server/lib/UAParserCustom.js
@@ -1,3 +1,5 @@
+import UAParser from 'ua-parser-js';
+
const mergeDeep = ((target, source) => {
if (!(typeof target === 'object' && typeof source === 'object')) {
return target;
@@ -20,9 +22,9 @@ const mergeDeep = ((target, source) => {
return target;
});
-const UAParserMobile = {
+export const UAParserMobile = {
appName: 'RC Mobile',
- device: 'mobile',
+ device: 'mobile-app',
uaSeparator: ';',
props: {
os: {
@@ -103,4 +105,37 @@ const UAParserMobile = {
},
};
-export { UAParserMobile };
+export const UAParserDesktop = {
+ device: 'desktop-app',
+
+ isDesktopApp(uaString) {
+ if (!uaString || typeof uaString !== 'string') {
+ return false;
+ }
+
+ return uaString.includes(' Electron/');
+ },
+
+ uaObject(uaString) {
+ if (!this.isDesktopApp(uaString)) {
+ return {};
+ }
+
+ const ua = new UAParser(uaString);
+ const uaParsed = ua.getResult();
+
+ const [, name, version] = uaString.match(/(Rocket\.Chat)\/(\d+(\.\d+)+)/) || [];
+
+ return {
+ device: {
+ type: this.device,
+ },
+ os: uaParsed.os,
+ app: {
+ name,
+ version,
+ },
+ };
+ },
+};
+
diff --git a/app/statistics/server/lib/UAParserCustom.tests.js b/app/statistics/server/lib/UAParserCustom.tests.js
new file mode 100644
index 000000000000..e32c9263b1df
--- /dev/null
+++ b/app/statistics/server/lib/UAParserCustom.tests.js
@@ -0,0 +1,77 @@
+/* eslint-env mocha */
+
+import { expect } from 'chai';
+import { UAParserMobile, UAParserDesktop } from './UAParserCustom';
+
+const UAMobile = 'RC Mobile; iOS 12.2; v3.4.0 (250)';
+const UADesktop = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Rocket.Chat/2.15.2 Chrome/69.0.3497.128 Electron/4.1.4 Safari/537.36';
+const UAChrome = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36';
+
+describe('UAParserCustom', () => {
+
+ describe('UAParserMobile', () => {
+
+ it('should identify mobile UA', () => {
+ expect(UAParserMobile.isMobileApp(UAMobile)).to.be.true;
+ });
+
+ it('should not identify desktop UA', () => {
+ expect(UAParserMobile.isMobileApp(UADesktop)).to.be.false;
+ });
+
+ it('should not identify chrome UA', () => {
+ expect(UAParserMobile.isMobileApp(UAChrome)).to.be.false;
+ });
+
+ it('should parse mobile UA', () => {
+ expect(UAParserMobile.uaObject(UAMobile)).to.be.deep.equal({
+ device: {
+ type: 'mobile-app',
+ },
+ app: {
+ name: 'RC Mobile',
+ version: '3.4.0',
+ bundle: '250',
+ },
+ os: {
+ name: 'iOS',
+ version: '12.2',
+ },
+ });
+ });
+
+ });
+
+ describe('UAParserDesktop', () => {
+
+ it('should not identify mobile UA', () => {
+ expect(UAParserDesktop.isDesktopApp(UAMobile)).to.be.false;
+ });
+
+ it('should identify desktop UA', () => {
+ expect(UAParserDesktop.isDesktopApp(UADesktop)).to.be.true;
+ });
+
+ it('should not identify chrome UA', () => {
+ expect(UAParserDesktop.isDesktopApp(UAChrome)).to.be.false;
+ });
+
+ it('should parse desktop UA', () => {
+ expect(UAParserDesktop.uaObject(UADesktop)).to.be.deep.equal({
+ device: {
+ type: 'desktop-app',
+ },
+ app: {
+ name: 'Rocket.Chat',
+ version: '2.15.2',
+ },
+ os: {
+ name: 'Mac OS',
+ version: '10.14.1',
+ },
+ });
+ });
+
+ });
+
+});
diff --git a/app/theme/client/imports/components/contextual-bar.css b/app/theme/client/imports/components/contextual-bar.css
index 43c1801d3f3c..81659afb9a24 100644
--- a/app/theme/client/imports/components/contextual-bar.css
+++ b/app/theme/client/imports/components/contextual-bar.css
@@ -140,6 +140,7 @@
@media (width <= 1100px) {
.contextual-bar {
position: absolute;
+ z-index: 10;
right: 0;
}
diff --git a/app/theme/client/imports/general/base_old.css b/app/theme/client/imports/general/base_old.css
index eee5b20c315d..a9211b4a7790 100644
--- a/app/theme/client/imports/general/base_old.css
+++ b/app/theme/client/imports/general/base_old.css
@@ -5366,7 +5366,6 @@ rc-old select,
.room-leader .chat-now {
position: absolute;
- top: 15px;
right: 25px;
width: 80px;
@@ -5395,14 +5394,18 @@ rc-old select,
right: 0;
left: 0;
+ display: flex;
+
visibility: visible;
+ flex-direction: column;
height: 57px;
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);
- border-bottom: 1px solid;
+ border-bottom: 1px solid var(--color-gray-lightest);
+ justify-content: center;
&.message:hover {
background-color: #ffffff;
diff --git a/app/threads/client/flextab/thread.html b/app/threads/client/flextab/thread.html
index 3424cfff3b9a..e4a438a9b5fc 100644
--- a/app/threads/client/flextab/thread.html
+++ b/app/threads/client/flextab/thread.html
@@ -21,9 +21,9 @@