Skip to content

Commit 7e3be06

Browse files
author
vikasrohit
authored
Merge pull request #530 from topcoder-platform/hotfix/user_level_reports
Hotfix/user level reports
2 parents a490317 + aae6e8f commit 7e3be06

File tree

7 files changed

+452
-26
lines changed

7 files changed

+452
-26
lines changed

src/routes/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,10 @@ router.route('/v5/projects/metadata/planConfig/:key/versions/:version(\\d+)')
310310
.patch(require('./planConfig/version/update'))
311311
.delete(require('./planConfig/version/delete'));
312312

313+
// user level reports
314+
router.route('/v5/projects/reports/embed')
315+
.get(require('./userReports/getEmbedReport'));
316+
313317
// work streams
314318
router.route('/v5/projects/:projectId(\\d+)/workstreams')
315319
.get(require('./workStreams/list'))

src/routes/projectReports/getEmbedReport.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ module.exports = [
2020
let REPORTS = null;
2121
let allowedUsers = null;
2222
try {
23-
allowedUsers = JSON.parse(_.get(config, 'lookerConfig.ALLOWED_USERS', '[]'));
23+
allowedUsers = config.get('lookerConfig.ALLOWED_USERS');
24+
allowedUsers = allowedUsers ? JSON.parse(allowedUsers) : [];
2425
req.log.trace(allowedUsers, 'allowedUsers');
2526
REPORTS = JSON.parse(config.get('lookerConfig.EMBED_REPORTS_MAPPING'));
2627
} catch (error) {
@@ -105,7 +106,7 @@ module.exports = [
105106
const embedUrl = REPORTS[reportName];
106107
req.log.trace(`Generating embed URL for ${reportName} report, using ${embedUrl} as embed URL.`);
107108
if (embedUrl) {
108-
result = await lookerSerivce.generateEmbedUrl(req.authUser, project, member, embedUrl);
109+
result = await lookerSerivce.generateEmbedUrlForProject(req.authUser, project, member, embedUrl);
109110
} else {
110111
return res.status(404).send('Report not found');
111112
}

src/routes/projectReports/getEmbedReport.spec.js

Lines changed: 76 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ describe('GET embed report', () => {
164164

165165
it('should return 404 when report name not mock and not in EMBED_REPORTS_MAPPING', (done) => {
166166
const cfg = sinon.stub(config, 'get');
167-
cfg.withArgs('lookerConfig.USE_MOCK').returns(false);
167+
cfg.withArgs('lookerConfig.USE_MOCK').returns('false');
168168
request(server)
169169
.get(`/v5/projects/${project1.id}/reports/embed?reportName=random`)
170170
.set({
@@ -176,10 +176,27 @@ describe('GET embed report', () => {
176176
});
177177
});
178178

179+
it('should return 403 when report name not mock and not in EMBED_REPORTS_MAPPING', (done) => {
180+
const cfg = sinon.stub(config, 'get');
181+
cfg.withArgs('lookerConfig.USE_MOCK').returns('false');
182+
// allows only admin user
183+
cfg.withArgs('lookerConfig.ALLOWED_USERS').returns(`[${testUtil.userIds.admin}]`);
184+
cfg.withArgs('lookerConfig.EMBED_REPORTS_MAPPING').returns('{"mock": "/embed/looks/2"}');
185+
request(server)
186+
.get(`/v5/projects/${project1.id}/reports/embed?reportName=random`)
187+
.set({
188+
Authorization: `Bearer ${testUtil.jwts.member}`,
189+
})
190+
.expect(403, (err) => {
191+
cfg.restore();
192+
done(err);
193+
});
194+
});
195+
179196
it('should return 500 when get admin user error', (done) => {
180197
const cfg = sinon.stub(config, 'get');
181-
const gem = sinon.stub(lookerSerivce, 'generateEmbedUrl', () => 'generatedUrl');
182-
cfg.withArgs('lookerConfig.USE_MOCK').returns(false);
198+
const gem = sinon.stub(lookerSerivce, 'generateEmbedUrlForProject', () => 'generatedUrl');
199+
cfg.withArgs('lookerConfig.USE_MOCK').returns('false');
183200
cfg.withArgs('lookerConfig.EMBED_REPORTS_MAPPING').returns('{"mock-concrete-customer": "/embed/looks/2"}');
184201
request(server)
185202
.get(`/v5/projects/${project1.id}/reports/embed?reportName=mock`)
@@ -195,8 +212,8 @@ describe('GET embed report', () => {
195212

196213
it('should return 404 when the project template or product template is not found', (done) => {
197214
const cfg = sinon.stub(config, 'get');
198-
const gem = sinon.stub(lookerSerivce, 'generateEmbedUrl', () => 'generatedUrl');
199-
cfg.withArgs('lookerConfig.USE_MOCK').returns(false);
215+
const gem = sinon.stub(lookerSerivce, 'generateEmbedUrlForProject', () => 'generatedUrl');
216+
cfg.withArgs('lookerConfig.USE_MOCK').returns('false');
200217
cfg.withArgs('lookerConfig.EMBED_REPORTS_MAPPING').returns('{"mock-concrete-customer": "/embed/looks/2"}');
201218
request(server)
202219
.get(`/v5/projects/${project0.id}/reports/embed?reportName=mock`)
@@ -210,10 +227,48 @@ describe('GET embed report', () => {
210227
});
211228
});
212229

230+
it('should return mock url', (done) => {
231+
const cfg = sinon.stub(config, 'get');
232+
const gem = sinon.stub(lookerSerivce, 'generateEmbedUrlForProject', () => 'generatedUrl');
233+
const getUser = sinon.stub(util, 'getTopcoderUser', () => ({
234+
firstName: 'fn',
235+
lastName: 'ln',
236+
userId: testUtil.userIds.member,
237+
}));
238+
cfg.withArgs('lookerConfig.USE_MOCK').returns('true');
239+
cfg.withArgs('lookerConfig.EMBED_REPORTS_MAPPING')
240+
.returns('{"mock": "/customer/embed/looks/2"}');
241+
request(server)
242+
.get(`/v5/projects/${project1.id}/reports/embed?reportName=mock`)
243+
.set({
244+
Authorization: `Bearer ${testUtil.jwts.member}`,
245+
})
246+
.expect('Content-Type', /json/)
247+
.expect(200)
248+
.end((err, res) => {
249+
getUser.restore();
250+
gem.restore();
251+
cfg.restore();
252+
if (err) {
253+
done(err);
254+
} else {
255+
const resJson = res.body;
256+
should.exist(resJson);
257+
resJson.should.equal('generatedUrl');
258+
const [user, project, member, embedUrl] = gem.lastCall.args;
259+
user.userId.should.equal(testUtil.userIds.member);
260+
project.should.deep.equal({ id: project1.id });
261+
member.userId.should.equal(testUtil.userIds.member);
262+
embedUrl.should.equal('/customer/embed/looks/2');
263+
done();
264+
}
265+
});
266+
});
267+
213268
it('should return customer url', (done) => {
214269
const cfg = sinon.stub(config, 'get');
215-
const gem = sinon.stub(lookerSerivce, 'generateEmbedUrl', () => 'generatedUrl');
216-
cfg.withArgs('lookerConfig.USE_MOCK').returns(false);
270+
const gem = sinon.stub(lookerSerivce, 'generateEmbedUrlForProject', () => 'generatedUrl');
271+
cfg.withArgs('lookerConfig.USE_MOCK').returns('false');
217272
cfg.withArgs('lookerConfig.EMBED_REPORTS_MAPPING')
218273
.returns('{"mock-concrete-customer": "/customer/embed/looks/2"}');
219274
request(server)
@@ -233,9 +288,9 @@ describe('GET embed report', () => {
233288
should.exist(resJson);
234289
resJson.should.equal('generatedUrl');
235290
const [user, project, member, embedUrl] = gem.lastCall.args;
236-
user.userId.should.equal(40051331);
291+
user.userId.should.equal(testUtil.userIds.member);
237292
project.should.deep.equal({ id: project1.id });
238-
member.userId.should.equal(40051331);
293+
member.userId.should.equal(testUtil.userIds.member);
239294
member.role.should.equal('customer');
240295
embedUrl.should.equal('/customer/embed/looks/2');
241296
done();
@@ -245,13 +300,13 @@ describe('GET embed report', () => {
245300

246301
it('should return admin url', (done) => {
247302
const cfg = sinon.stub(config, 'get');
248-
const gem = sinon.stub(lookerSerivce, 'generateEmbedUrl', () => 'generatedUrl');
303+
const gem = sinon.stub(lookerSerivce, 'generateEmbedUrlForProject', () => 'generatedUrl');
249304
const getAdmin = sinon.stub(util, 'getTopcoderUser', () => ({
250305
firstName: 'fn',
251306
lastName: 'ln',
252307
userId: 40051333,
253308
}));
254-
cfg.withArgs('lookerConfig.USE_MOCK').returns(false);
309+
cfg.withArgs('lookerConfig.USE_MOCK').returns('false');
255310
cfg.withArgs('lookerConfig.EMBED_REPORTS_MAPPING').returns('{"mock-concrete-topcoder": "/admin/embed/looks/2"}');
256311
request(server)
257312
.get(`/v5/projects/${project1.id}/reports/embed?reportName=mock`)
@@ -271,9 +326,9 @@ describe('GET embed report', () => {
271326
should.exist(resJson);
272327
resJson.should.equal('generatedUrl');
273328
const [user, project, member, embedUrl] = gem.lastCall.args;
274-
user.userId.should.equal(40051333);
329+
user.userId.should.equal(testUtil.userIds.admin);
275330
project.should.deep.equal({ id: project1.id });
276-
member.userId.should.equal(40051333);
331+
member.userId.should.equal(testUtil.userIds.admin);
277332
member.firstName.should.equal('fn');
278333
member.lastName.should.equal('ln');
279334
member.role.should.equal('');
@@ -285,8 +340,8 @@ describe('GET embed report', () => {
285340

286341
it('should return copilot url', (done) => {
287342
const cfg = sinon.stub(config, 'get');
288-
const gem = sinon.stub(lookerSerivce, 'generateEmbedUrl', () => 'generatedUrl');
289-
cfg.withArgs('lookerConfig.USE_MOCK').returns(false);
343+
const gem = sinon.stub(lookerSerivce, 'generateEmbedUrlForProject', () => 'generatedUrl');
344+
cfg.withArgs('lookerConfig.USE_MOCK').returns('false');
290345
cfg.withArgs('lookerConfig.EMBED_REPORTS_MAPPING').returns('{"mock-concrete-copilot": "/copilot/embed/looks/2"}');
291346
request(server)
292347
.get(`/v5/projects/${project1.id}/reports/embed?reportName=mock`)
@@ -305,9 +360,9 @@ describe('GET embed report', () => {
305360
should.exist(resJson);
306361
resJson.should.equal('generatedUrl');
307362
const [user, project, member, embedUrl] = gem.lastCall.args;
308-
user.userId.should.equal(40051332);
363+
user.userId.should.equal(testUtil.userIds.copilot);
309364
project.should.deep.equal({ id: project1.id });
310-
member.userId.should.equal(40051332);
365+
member.userId.should.equal(testUtil.userIds.copilot);
311366
member.role.should.equal('copilot');
312367
embedUrl.should.equal('/copilot/embed/looks/2');
313368
done();
@@ -317,13 +372,13 @@ describe('GET embed report', () => {
317372

318373
it('should return admin url for project with product template', (done) => {
319374
const cfg = sinon.stub(config, 'get');
320-
const gem = sinon.stub(lookerSerivce, 'generateEmbedUrl', () => 'generatedUrl');
375+
const gem = sinon.stub(lookerSerivce, 'generateEmbedUrlForProject', () => 'generatedUrl');
321376
const getAdmin = sinon.stub(util, 'getTopcoderUser', () => ({
322377
firstName: 'fn',
323378
lastName: 'ln',
324379
userId: 40051333,
325380
}));
326-
cfg.withArgs('lookerConfig.USE_MOCK').returns(false);
381+
cfg.withArgs('lookerConfig.USE_MOCK').returns('false');
327382
cfg.withArgs('lookerConfig.EMBED_REPORTS_MAPPING')
328383
.returns('{"mock-prodCut-topcoder": "/admin/embed/looks/3"}');
329384
request(server)
@@ -344,9 +399,9 @@ describe('GET embed report', () => {
344399
should.exist(resJson);
345400
resJson.should.equal('generatedUrl');
346401
const [user, project, member, embedUrl] = gem.lastCall.args;
347-
user.userId.should.equal(40051333);
402+
user.userId.should.equal(testUtil.userIds.admin);
348403
project.should.deep.equal({ id: project3.id });
349-
member.userId.should.equal(40051333);
404+
member.userId.should.equal(testUtil.userIds.admin);
350405
member.firstName.should.equal('fn');
351406
member.lastName.should.equal('ln');
352407
member.role.should.equal('');
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/* eslint-disable no-unused-vars */
2+
import config from 'config';
3+
import _ from 'lodash';
4+
import { middleware as tcMiddleware } from 'tc-core-library-js';
5+
import util from '../../util';
6+
import { USER_ROLE, ADMIN_ROLES, MANAGER_ROLES } from '../../constants';
7+
import lookerSerivce from '../../services/lookerService';
8+
9+
const permissions = tcMiddleware.permissions;
10+
11+
12+
module.exports = [
13+
async (req, res, next) => {
14+
const mockReport = config.get('lookerConfig.USE_MOCK') === 'true';
15+
let reportName = mockReport ? 'mock' : req.query.reportName;
16+
const authUser = req.authUser;
17+
let REPORTS = null;
18+
let allowedUsers = null;
19+
try {
20+
allowedUsers = config.get('lookerConfig.ALLOWED_USERS');
21+
allowedUsers = allowedUsers ? JSON.parse(allowedUsers) : [];
22+
req.log.trace(allowedUsers, 'allowedUsers');
23+
REPORTS = JSON.parse(config.get('lookerConfig.EMBED_REPORTS_MAPPING'));
24+
} catch (error) {
25+
req.log.error(error);
26+
req.log.debug('Invalid reports mapping. Should be a valid JSON.');
27+
}
28+
if (!mockReport && !REPORTS) {
29+
return res.status(404).send('Report not found');
30+
}
31+
32+
try {
33+
const isAdmin = util.hasRoles(req, ADMIN_ROLES);
34+
const userDisallowed = allowedUsers.length > 0 && !allowedUsers.includes(authUser.userId);
35+
if (userDisallowed) {
36+
req.log.error(`User whitelisting prevented accessing report ${reportName} to ${authUser.userId}`);
37+
return res.status(403).send('User is not allowed to access the report');
38+
}
39+
const token = await util.getM2MToken();
40+
const callerUser = await util.getTopcoderUser(authUser.userId, token, req.log);
41+
req.log.trace(callerUser, 'callerUser');
42+
const member = {
43+
firstName: callerUser.firstName,
44+
lastName: callerUser.lastName,
45+
userId: authUser.userId,
46+
role: '',
47+
};
48+
let roleKey = '';
49+
if (!mockReport) {
50+
if (util.hasRoles(req, [USER_ROLE.COPILOT])) {
51+
roleKey = 'copilot';
52+
} else if (isAdmin || util.hasRoles(req, MANAGER_ROLES)) {
53+
roleKey = 'topcoder';
54+
} else {
55+
roleKey = 'customer';
56+
}
57+
reportName = `${reportName}-${roleKey}`;
58+
}
59+
// pick the report based on its name
60+
let result = {};
61+
const embedUrl = REPORTS[reportName];
62+
req.log.trace(`Generating embed URL for ${reportName} report, using ${embedUrl} as embed URL.`);
63+
if (embedUrl) {
64+
result = await lookerSerivce.generateEmbedUrlForUser(req.authUser, member, embedUrl);
65+
} else {
66+
return res.status(404).send('Report not found');
67+
}
68+
69+
req.log.trace(result);
70+
return res.status(200).json(result);
71+
} catch (err) {
72+
req.log.error(err);
73+
return res.status(500).send(err.toString());
74+
}
75+
},
76+
];

0 commit comments

Comments
 (0)