Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/meteor/.mocharc.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ module.exports = {
'tests/unit/server/**/*.spec.ts',
'app/api/server/lib/**/*.spec.ts',
'app/file-upload/server/**/*.spec.ts',
'app/statistics/server/**/*.spec.ts',
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { expect } from 'chai';
import { describe, it, beforeEach, afterEach } from 'mocha';
import proxyquire from 'proxyquire';
import sinon from 'sinon';

const sandbox = sinon.createSandbox();

const mocks = {
Statistics: {
findLast: sandbox.stub(),
updateOne: sandbox.stub(),
},
statistics: {
save: sandbox.stub(),
},
serverFetch: sandbox.stub(),
getWorkspaceAccessToken: sandbox.stub().resolves('workspace-token'),
Meteor: {
absoluteUrl: sandbox.stub().returns('http://localhost:3000/'),
},
logger: {
error: sandbox.stub(),
},
};

const { sendUsageReport } = proxyquire.noCallThru().load('./sendUsageReport', {
'@rocket.chat/models': { Statistics: mocks.Statistics },
'@rocket.chat/server-fetch': { serverFetch: mocks.serverFetch },
'..': { statistics: mocks.statistics },
'../../../cloud/server': { getWorkspaceAccessToken: mocks.getWorkspaceAccessToken },
'meteor/meteor': { Meteor: mocks.Meteor },
});

describe('sendUsageReport', () => {
beforeEach(() => {
sandbox.resetHistory();
});

afterEach(() => {
delete process.env.RC_DISABLE_STATISTICS_REPORTING;
});

it('should save statistics locally and not send to collector when RC_DISABLE_STATISTICS_REPORTING is true', async () => {
process.env.RC_DISABLE_STATISTICS_REPORTING = 'true';

const result = await sendUsageReport(mocks.logger);

expect(mocks.statistics.save.called).to.be.true;
expect(mocks.serverFetch.called).to.be.false;
expect(result).to.be.undefined;
});

it('should save statistics locally and send to collector when RC_DISABLE_STATISTICS_REPORTING is false', async () => {
process.env.RC_DISABLE_STATISTICS_REPORTING = 'false';

const result = await sendUsageReport(mocks.logger);

expect(mocks.statistics.save.called).to.be.true;
expect(mocks.serverFetch.calledOnce).to.be.true;
expect(mocks.serverFetch.calledWith('https://collector.rocket.chat/', sinon.match({ method: 'POST' }))).to.be.true;
expect(result).to.be.undefined;
});
});
16 changes: 13 additions & 3 deletions apps/meteor/app/statistics/server/functions/sendUsageReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { tracerSpan } from '@rocket.chat/tracing';
import { Meteor } from 'meteor/meteor';

import { statistics } from '..';
import { shouldReportStatistics } from '../../../../server/cron/usageReport';
import { getWorkspaceAccessToken } from '../../../cloud/server';

async function sendStats(logger: Logger, cronStatistics: IStats): Promise<string | undefined> {
Expand Down Expand Up @@ -34,6 +35,10 @@ async function sendStats(logger: Logger, cronStatistics: IStats): Promise<string
}

export async function sendUsageReport(logger: Logger): Promise<string | undefined> {
// Even when disabled, we still generate statistics locally to avoid breaking
// internal processes, such as restriction checks for air-gapped workspaces.
const shouldSendToCollector = shouldReportStatistics();

return tracerSpan('generateStatistics', {}, async () => {
const last = await Statistics.findLast();
if (last) {
Expand All @@ -48,13 +53,18 @@ export async function sendUsageReport(logger: Logger): Promise<string | undefine
}

// if it doesn't it means the request failed, so we try sending again with the same data
return sendStats(logger, last);
if (shouldSendToCollector) {
return sendStats(logger, last);
}

return;
}
}

// if our latest stats has more than 24h, it is time to generate a new one and send it
const cronStatistics = await statistics.save();

return sendStats(logger, cronStatistics);
if (shouldSendToCollector) {
return sendStats(logger, cronStatistics);
}
});
}
11 changes: 10 additions & 1 deletion apps/meteor/server/cron/usageReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,21 @@ export const sendUsageReportAndComputeRestriction = async (statsToken?: string)
void AirGappedRestriction.computeRestriction(token);
};

export const shouldReportStatistics = () => process.env.RC_DISABLE_STATISTICS_REPORTING?.toLowerCase() !== 'true';

export async function usageReportCron(logger: Logger): Promise<void> {
const name = 'Generate and save statistics';
// The actual send suppression happens inside `sendUsageReport`, but since this
// is the entry point, we log a warning here when reporting is disabled.
if (!shouldReportStatistics()) {
logger.warn(
'Statistics reporting disabled via environment variable (RC_DISABLE_STATISTICS_REPORTING). This may impact product improvements.',
);
}

const statsToken = await sendUsageReport(logger);
await sendUsageReportAndComputeRestriction(statsToken);

const name = 'Generate and save statistics';
const now = new Date();

return cronJobs.add(name, `12 ${now.getHours()} * * *`, async () => {
Expand Down
Loading