UNPKG

3.51 kBJavaScriptView Raw
1const _ = require("lodash");
2const moment = require("moment");
3const querystring = require("querystring");
4const { safeToArray } = require("./util");
5
6// time dimension
7const timeDims = [
8 "year",
9 "quarter",
10 "month",
11 "week",
12 "isoWeek",
13 "day",
14 "hour",
15 "min",
16 "second",
17];
18
19/**
20 * is group is a time
21 * @param {string} field
22 */
23const isTimeGroup = field => {
24 const splitted = field.split(".");
25
26 if (splitted.length <= 1) return false;
27
28 return timeDims.includes(_.last(splitted));
29};
30
31/**
32 * parse group
33 * eg...
34 * input: ["birthAt.hour", "birthAt.year", "tag"]
35 * output:
36 * [
37 * { field: 'birthAt', timeDim: 'hour', key: (val, dim) => moment(val).startOf(dim).toISOString() },
38 { field: 'tag' }
39 ]
40 * @param {[string]} group
41 */
42const parseGroup = group => {
43 const parsed = _.uniq(group).map(g => {
44 const ret = { field: g };
45 if (isTimeGroup(g)) {
46 ret.field = _.slice(g.split("."), 0, -1).join(".");
47 ret.timeDim = _.last(g.split("."));
48 // to key
49 ret.key = (val, dim) => {
50 return moment(val)
51 .startOf(dim)
52 .toISOString();
53 };
54 }
55 return ret;
56 });
57
58 const chain = _.chain(parsed)
59 .groupBy(p => p.field)
60 .mapValues(o => {
61 if (o.length <= 1) return o[0];
62 else {
63 return _.maxBy(o, g => _.indexOf(timeDims, g.timeDim));
64 }
65 });
66
67 return chain.values().value();
68};
69
70function handleAggs(aggs = {}, req, res) {
71 const path = req.path;
72 const query = req.cusQuery || {};
73 const agg = aggs[path];
74 const data = res.locals.data;
75
76 if (!agg) return data;
77 if (!_.isArray(data)) return data;
78
79 const { _group, _limit = 10, _start = 0, _order, _sort } = query;
80
81 const group = safeToArray(_group);
82
83 if (!group) return data;
84
85 const parsed = parseGroup(group);
86
87 const grouped = _.groupBy(data, r => {
88 return querystring.stringify(
89 parsed.map(g => {
90 if (g.key) {
91 return g.key(_.get(r, g.field), g.timeDim);
92 }
93
94 return _.get(r, g.field);
95 })
96 );
97 });
98
99 const result = _.keys(grouped).map(k => {
100 const fileds = querystring.parse(k);
101 // records in group key
102 const records = grouped[k];
103
104 // group dims
105 const dimensions = {};
106
107 for (let i = 0; i < parsed.length; i++) {
108 dimensions[parsed[i].field] = fileds[i];
109 }
110
111 // cal all metrics
112 const metrics = _.mapValues(agg, (aggConfig = "sum", m) => {
113 let aggFun;
114 if (_.isString(aggConfig)) {
115 switch (aggConfig) {
116 case "sum":
117 aggFun = rs => _.sumBy(rs, r => r[m]);
118 break;
119 case "avg":
120 aggFun = rs => _.sumBy(rs, r => r[m]) / rs.length;
121 break;
122 default:
123 throw new Error("Not support aggregation config: " + aggConfig);
124 }
125 }
126 if (_.isFunction(aggConfig)) {
127 aggFun = aggConfig;
128 }
129
130 return aggFun(records);
131 });
132
133 return {
134 id: querystring.stringify(dimensions),
135 ...dimensions,
136 ...metrics,
137 };
138 });
139
140 // set total header
141 res.setHeader("X-Total-Count", result.length);
142
143 let chain = _.chain(result);
144
145 // handle sort
146 if (_sort) {
147 const _sortSet = _sort.split(",");
148 const _orderSet = (_order || "").split(",").map(s => s.toLowerCase());
149 chain = chain.orderBy(_sortSet, _orderSet);
150 }
151
152 chain = chain.slice(_start, _start + _limit);
153
154 // add id
155
156 return chain.value();
157}
158
159module.exports.handleAggs = handleAggs;
160module.exports.parseGroup = parseGroup;