Skip to content

Commit b560364

Browse files
author
Vikas Agarwal
committed
Initial commit for reporting support
1 parent 711f99d commit b560364

File tree

8 files changed

+247
-2
lines changed

8 files changed

+247
-2
lines changed

config/custom-environment-variables.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,15 @@
5050
"accountsAppUrl": "ACCOUNTS_APP_URL",
5151
"inviteEmailSubject": "INVITE_EMAIL_SUBJECT",
5252
"inviteEmailSectionTitle": "INVITE_EMAIL_SECTION_TITLE",
53-
"SSO_REFCODES": "SSO_REFCODES"
53+
"SSO_REFCODES": "SSO_REFCODES",
54+
"lookerConfig": {
55+
"BASE_URL": "LOOKER_API_BASE_URL",
56+
"CLIENT_ID": "LOOKER_API_CLIENT_ID",
57+
"CLIENT_SECRET": "LOOKER_API_CLIENT_SECRET",
58+
"TOKEN": "TOKEN",
59+
"USE_MOCK": "LOOKER_API_ENABLE_MOCK",
60+
"QUERIES": {
61+
"REG_STATS": "LOOKER_API_REG_STATS_QUERY_ID"
62+
}
63+
}
5464
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
"babel-plugin-add-module-exports": "^0.2.1",
7474
"babel-plugin-transform-runtime": "^6.23.0",
7575
"babel-preset-es2015": "^6.9.0",
76-
"bunyan": "^1.8.1",
76+
"bunyan": "^1.8.12",
7777
"chai": "^3.5.0",
7878
"chai-as-promised": "^7.1.1",
7979
"eslint": "^3.16.1",

src/routes/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,9 @@ router.route('/v4/projects/:projectId(\\d+)/workstreams/:workStreamId(\\d+)/work
309309
.patch(require('./workItems/update'))
310310
.delete(require('./workItems/delete'));
311311

312+
router.route('/v4/projects/:projectId/reports')
313+
.get(require('./projectReports/getReport'));
314+
312315
// register error handler
313316
router.use((err, req, res, next) => { // eslint-disable-line no-unused-vars
314317
// DO NOT REMOVE next arg.. even though eslint

src/routes/projectReports/LookAuth.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/* eslint-disable func-names */
2+
/* eslint-disable require-jsdoc */
3+
/* eslint-disable valid-jsdoc */
4+
5+
// Look Auth
6+
7+
8+
import config from 'config';
9+
10+
const axios = require('axios');
11+
12+
const NEXT_5_MINS = 5 * 60 * 1000;
13+
14+
15+
function LookAuth(logger) {
16+
// load credentials from config
17+
this.BASE_URL = config.lookerConfig.BASE_URL;
18+
this.CLIENT_ID = config.lookerConfig.CLIENT_ID;
19+
this.CLIENT_SECRET = config.lookerConfig.CLIENT_SECRET;
20+
const token = config.lookerConfig.TOKEN;
21+
22+
this.logger = logger;
23+
24+
// Token is stringified and saved as string. It has 4 properties, access_token, expires_in and type, timestamp
25+
if (token) {
26+
this.lastToken = JSON.stringify(token);
27+
}
28+
}
29+
30+
LookAuth.prototype.getToken = async function () {
31+
const res = await new Promise((resolve) => {
32+
if (!this.isExpired()) {
33+
resolve(this.lastToken.access_token);
34+
} else {
35+
resolve('');
36+
}
37+
});
38+
if (res === '') {
39+
return this.login();
40+
}
41+
return res;
42+
};
43+
44+
/** *********************Login to Looker ************** */
45+
LookAuth.prototype.login = async function () {
46+
const loginUrl = `${this.BASE_URL}/login?client_id=${this.CLIENT_ID}&client_secret=${this.CLIENT_SECRET}`;
47+
const res = await axios.post(loginUrl, {}, { headers: { 'Content-Type': 'application/json' } });
48+
this.lastToken = res.data;
49+
this.lastToken.timestamp = new Date().getTime();
50+
return this.lastToken.access_token;
51+
};
52+
53+
54+
/** ***************Check if the Token has expired ********** */
55+
LookAuth.prototype.isExpired = function () {
56+
// If no token is present, assume the token has expired
57+
if (!this.lastToken) {
58+
return true;
59+
}
60+
61+
const tokenTimestamp = this.lastToken.timestamp;
62+
const expiresIn = this.lastToken.expires_in;
63+
const currentTimestamp = new Date().getTime();
64+
65+
// If the token will good for next 5 minutes
66+
if ((tokenTimestamp + expiresIn + NEXT_5_MINS) > currentTimestamp) {
67+
return false;
68+
}
69+
// Token is good, and can be used to make the next call.
70+
return true;
71+
};
72+
73+
module.exports = LookAuth;

src/routes/projectReports/LookRun.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/* eslint-disable valid-jsdoc */
2+
/* eslint-disable require-jsdoc */
3+
/* eslint-disable func-names */
4+
5+
import config from 'config';
6+
import LookAuth from './LookAuth';
7+
8+
const axios = require('axios');
9+
10+
function LookApi(logger) {
11+
this.BASE_URL = config.lookerConfig.BASE_URL;
12+
this.formatting = 'json';
13+
this.limit = 5000;
14+
this.logger = logger;
15+
this.lookAuth = new LookAuth(logger);
16+
}
17+
18+
LookApi.prototype.runLook = function (lookId) {
19+
const endpoint = `${this.BASE_URL}/looks/${lookId}/run/${this.formatting}?limit=${this.limit}`;
20+
return this.callApi(endpoint);
21+
};
22+
23+
LookApi.prototype.findUserByEmail = function (email) {
24+
const filter = { 'user.email': email };
25+
return this.runQueryWithFilter(1234, filter);
26+
};
27+
28+
LookApi.prototype.findByHandle = function (handle) {
29+
const filter = { 'user.handle': handle };
30+
return this.runQueryWithFilter(12345, filter);
31+
};
32+
33+
LookApi.prototype.findProjectRegSubmissions = function (directProjectId) {
34+
const queryId = config.lookerConfig.QUERIES.REG_STATS;
35+
const fields = ['challenge.track', 'challenge.num_registrations', 'challenge.num_submissions'];
36+
const view = 'challenge';
37+
const filters = { 'challenge.tc_direct_project_id': directProjectId };
38+
return this.runQueryWithFilter(queryId, view, fields, filters);
39+
};
40+
41+
LookApi.prototype.runQueryWithFilter = function (queryId, view, fields, filters) {
42+
const endpoint = `${this.BASE_URL}/queries/run/${this.formatting}`;
43+
44+
const body = {
45+
id: queryId,
46+
model: 'topcoder_model_main',
47+
view,
48+
filters,
49+
fields,
50+
// sorts: ['user.email desc 0'],
51+
limit: 10,
52+
query_timezon: 'America/Los_Angeles',
53+
54+
};
55+
return this.callApi(endpoint, body);
56+
};
57+
58+
LookApi.prototype.runQuery = function (queryId) {
59+
const endpoint = `${this.BASE_URL}/queries/${queryId}/run/${this.formatting}?limit=${this.limit}`;
60+
return this.callApi(endpoint);
61+
};
62+
63+
LookApi.prototype.callApi = function (endpoint, body) {
64+
return this.lookAuth.getToken().then((token) => {
65+
let newReq = null;
66+
if (body) {
67+
newReq = axios.post(endpoint, body, {
68+
headers: { 'Content-Type': 'application/json', 'Authorization': `token ${token}` }
69+
});
70+
} else {
71+
newReq = axios.get(endpoint);
72+
}
73+
return newReq;
74+
}).then((res) => {
75+
this.logger.info(res.data);
76+
return res.data;
77+
});
78+
};
79+
80+
module.exports = LookApi;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/* eslint-disable no-unused-vars */
2+
import config from 'config';
3+
import _ from 'lodash';
4+
5+
import { middleware as tcMiddleware } from 'tc-core-library-js';
6+
import models from '../../models';
7+
import LookApi from './LookRun';
8+
import mock from './mock';
9+
import util from '../../util';
10+
11+
const permissions = tcMiddleware.permissions;
12+
13+
14+
module.exports = [
15+
permissions('project.view'),
16+
async (req, res, next) => {
17+
const projectId = Number(req.params.projectId);
18+
const reportName = req.query.reportName;
19+
20+
21+
if (config.lookerConfig.USE_MOCK) {
22+
req.log.info('using mock');
23+
// using mock
24+
return mock(projectId, reportName, req, res);
25+
// res.status(200).json(util.wrapResponse(req.id, project));
26+
}
27+
const lookApi = new LookApi(req.log);
28+
const project = await models.Project.find({
29+
where: { id: projectId },
30+
attributes: ['directProjectId'],
31+
raw: true,
32+
});
33+
const directProjectId = _.get(project, 'directProjectId');
34+
if (!directProjectId) {
35+
return res.status(400).send('Direct Project not linked');
36+
}
37+
if (reportName === 'summary') {
38+
try {
39+
const result = await lookApi.findProjectRegSubmissions(directProjectId);
40+
req.log.debug(result);
41+
return res.status(200).json(util.wrapResponse(req.id, result));
42+
} catch (err) {
43+
req.log.error(err);
44+
return res.status(500).send(err.toString());
45+
}
46+
}
47+
return res.status(404).send('Report not found');
48+
},
49+
];

src/routes/projectReports/mock.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import util from '../../util';
2+
3+
const summaryJson = require('./mockFiles/summary.json');
4+
5+
module.exports = (projectId, reportName, req, res) => {
6+
if (Number(projectId) === 123456) {
7+
res.status(500).json('Invalid project id');
8+
} else if (reportName === 'summary') {
9+
res.status(200).json(util.wrapResponse(req.id, summaryJson));
10+
} else {
11+
res.status(400).json('Invalid report name');
12+
}
13+
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[ { "challenge.track": "Develop",
2+
3+
"challenge.num_registrations": 399,
4+
5+
"challenge.num_submissions": 72 },
6+
7+
{ "challenge.track": "Design",
8+
9+
"challenge.num_registrations": 54,
10+
11+
"challenge.num_submissions": 28 },
12+
13+
{ "challenge.track": null,
14+
15+
"challenge.num_registrations": 453,
16+
17+
"challenge.num_submissions": 100 } ]

0 commit comments

Comments
 (0)