@dhis2/analytics 26.4.1 → 26.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/__fixtures__/fixtures.js +1 -0
- package/build/cjs/__fixtures__/json/api/analytics/outlierDetection.json +213 -0
- package/build/cjs/api/analytics/AnalyticsAggregate.js +27 -1
- package/build/cjs/api/analytics/AnalyticsBase.js +8 -7
- package/build/cjs/api/analytics/AnalyticsRequestBase.js +9 -5
- package/build/cjs/api/analytics/AnalyticsResponse.js +42 -39
- package/build/cjs/api/analytics/__tests__/Analytics.spec.js +5 -0
- package/build/cjs/api/analytics/__tests__/AnalyticsAggregate.spec.js +29 -0
- package/build/cjs/api/analytics/__tests__/AnalyticsBase.spec.js +36 -2
- package/build/cjs/components/DataDimension/DataDimension.js +31 -7
- package/build/cjs/components/DataDimension/DataTypeSelector.js +29 -8
- package/build/cjs/components/DataDimension/GroupSelector.js +7 -7
- package/build/cjs/components/DataDimension/ItemSelector.js +79 -61
- package/build/cjs/components/PeriodDimension/PeriodDimension.js +5 -2
- package/build/cjs/components/PeriodDimension/PeriodTransfer.js +64 -31
- package/build/cjs/components/PeriodDimension/__tests__/__snapshots__/PeriodDimension.spec.js.snap +1 -1
- package/build/cjs/components/PeriodDimension/__tests__/__snapshots__/PeriodSelector.spec.js.snap +1 -12
- package/build/cjs/components/VisTypeIcon.js +6 -1
- package/build/cjs/index.js +43 -1
- package/build/cjs/locales/en/translations.json +5 -1
- package/build/cjs/modules/__tests__/getAdaptedUiLayoutByType.spec.js +15 -0
- package/build/cjs/modules/axis.js +4 -0
- package/build/cjs/modules/getAdaptedUiLayoutByType.js +9 -0
- package/build/cjs/modules/layoutTypes.js +4 -2
- package/build/cjs/modules/layoutUiRules/__tests__/rules.spec.js +13 -1
- package/build/cjs/modules/layoutUiRules/index.js +12 -0
- package/build/cjs/modules/layoutUiRules/rules.js +22 -2
- package/build/cjs/modules/layoutUiRules/rulesHelper.js +4 -2
- package/build/cjs/modules/layoutUiRules/rulesUtils.js +7 -2
- package/build/cjs/modules/visTypeToLayoutType.js +2 -1
- package/build/cjs/modules/visTypes.js +9 -3
- package/build/es/__fixtures__/fixtures.js +1 -0
- package/build/es/__fixtures__/json/api/analytics/outlierDetection.json +213 -0
- package/build/es/api/analytics/AnalyticsAggregate.js +27 -1
- package/build/es/api/analytics/AnalyticsBase.js +7 -7
- package/build/es/api/analytics/AnalyticsRequestBase.js +9 -5
- package/build/es/api/analytics/AnalyticsResponse.js +42 -39
- package/build/es/api/analytics/__tests__/Analytics.spec.js +5 -0
- package/build/es/api/analytics/__tests__/AnalyticsAggregate.spec.js +29 -0
- package/build/es/api/analytics/__tests__/AnalyticsBase.spec.js +34 -1
- package/build/es/components/DataDimension/DataDimension.js +27 -6
- package/build/es/components/DataDimension/DataTypeSelector.js +30 -9
- package/build/es/components/DataDimension/GroupSelector.js +7 -7
- package/build/es/components/DataDimension/ItemSelector.js +80 -62
- package/build/es/components/PeriodDimension/PeriodDimension.js +5 -2
- package/build/es/components/PeriodDimension/PeriodTransfer.js +65 -32
- package/build/es/components/PeriodDimension/__tests__/__snapshots__/PeriodDimension.spec.js.snap +1 -1
- package/build/es/components/PeriodDimension/__tests__/__snapshots__/PeriodSelector.spec.js.snap +1 -12
- package/build/es/components/VisTypeIcon.js +8 -3
- package/build/es/index.js +4 -4
- package/build/es/locales/en/translations.json +5 -1
- package/build/es/modules/__tests__/getAdaptedUiLayoutByType.spec.js +16 -1
- package/build/es/modules/axis.js +5 -1
- package/build/es/modules/getAdaptedUiLayoutByType.js +10 -1
- package/build/es/modules/layoutTypes.js +2 -1
- package/build/es/modules/layoutUiRules/__tests__/rules.spec.js +14 -2
- package/build/es/modules/layoutUiRules/index.js +2 -2
- package/build/es/modules/layoutUiRules/rules.js +22 -3
- package/build/es/modules/layoutUiRules/rulesHelper.js +3 -2
- package/build/es/modules/layoutUiRules/rulesUtils.js +6 -2
- package/build/es/modules/visTypeToLayoutType.js +4 -3
- package/build/es/modules/visTypes.js +7 -3
- package/package.json +6 -3
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
{
|
|
2
|
+
"headers": [
|
|
3
|
+
{
|
|
4
|
+
"name": "dxname",
|
|
5
|
+
"column": "Data name",
|
|
6
|
+
"valueType": "TEXT",
|
|
7
|
+
"type": "java.lang.String",
|
|
8
|
+
"hidden": false,
|
|
9
|
+
"meta": false
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"name": "ouname",
|
|
13
|
+
"column": "Organisation unit name",
|
|
14
|
+
"valueType": "TEXT",
|
|
15
|
+
"type": "java.lang.String",
|
|
16
|
+
"hidden": false,
|
|
17
|
+
"meta": false
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "value",
|
|
21
|
+
"column": "Value",
|
|
22
|
+
"valueType": "NUMBER",
|
|
23
|
+
"type": "java.lang.Double",
|
|
24
|
+
"hidden": false,
|
|
25
|
+
"meta": false
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "absdev",
|
|
29
|
+
"column": "Absolute deviation",
|
|
30
|
+
"valueType": "NUMBER",
|
|
31
|
+
"type": "java.lang.Double",
|
|
32
|
+
"hidden": false,
|
|
33
|
+
"meta": false
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"name": "modifiedzscore",
|
|
37
|
+
"column": "Modified zScore",
|
|
38
|
+
"valueType": "NUMBER",
|
|
39
|
+
"type": "java.lang.Double",
|
|
40
|
+
"hidden": false,
|
|
41
|
+
"meta": false
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "median",
|
|
45
|
+
"column": "Median",
|
|
46
|
+
"valueType": "NUMBER",
|
|
47
|
+
"type": "java.lang.Double",
|
|
48
|
+
"hidden": false,
|
|
49
|
+
"meta": false
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"name": "lowerbound",
|
|
53
|
+
"column": "Lower boundary",
|
|
54
|
+
"valueType": "NUMBER",
|
|
55
|
+
"type": "java.lang.Double",
|
|
56
|
+
"hidden": false,
|
|
57
|
+
"meta": false
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "upperbound",
|
|
61
|
+
"column": "Upper boundary",
|
|
62
|
+
"valueType": "NUMBER",
|
|
63
|
+
"type": "java.lang.Double",
|
|
64
|
+
"hidden": false,
|
|
65
|
+
"meta": false
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"metaData": {
|
|
69
|
+
"maxResults": 100,
|
|
70
|
+
"count": 13,
|
|
71
|
+
"orderBy": "mean_abs_dev",
|
|
72
|
+
"threshold": 3.0,
|
|
73
|
+
"algorithm": "MOD_Z_SCORE"
|
|
74
|
+
},
|
|
75
|
+
"rowContext": {
|
|
76
|
+
|
|
77
|
+
},
|
|
78
|
+
"rows": [
|
|
79
|
+
[
|
|
80
|
+
"ANC 2nd visit",
|
|
81
|
+
"UMC (Urban Centre) Hospital",
|
|
82
|
+
"1669.0",
|
|
83
|
+
"920.0",
|
|
84
|
+
"3.902767295597484",
|
|
85
|
+
"749.0",
|
|
86
|
+
"-290.93013894085743",
|
|
87
|
+
"1788.9301389408574"
|
|
88
|
+
],
|
|
89
|
+
[
|
|
90
|
+
"ANC 1st visit",
|
|
91
|
+
"Charlotte CHP",
|
|
92
|
+
"49.0",
|
|
93
|
+
"39.0",
|
|
94
|
+
"7.515857142857143",
|
|
95
|
+
"10.0",
|
|
96
|
+
"-23.948490393535913",
|
|
97
|
+
"43.94849039353591"
|
|
98
|
+
],
|
|
99
|
+
[
|
|
100
|
+
"ANC 2nd visit",
|
|
101
|
+
"Charlotte CHP",
|
|
102
|
+
"40.0",
|
|
103
|
+
"33.0",
|
|
104
|
+
"22.258499999999998",
|
|
105
|
+
"7.0",
|
|
106
|
+
"-30.86489139031037",
|
|
107
|
+
"44.86489139031037"
|
|
108
|
+
],
|
|
109
|
+
[
|
|
110
|
+
"ANC 2nd visit",
|
|
111
|
+
"Wilberforce CHC",
|
|
112
|
+
"56.0",
|
|
113
|
+
"24.5",
|
|
114
|
+
"3.004590909090909",
|
|
115
|
+
"31.5",
|
|
116
|
+
"-1.3890179239210454",
|
|
117
|
+
"64.38901792392105"
|
|
118
|
+
],
|
|
119
|
+
[
|
|
120
|
+
"ANC 1st visit",
|
|
121
|
+
"Deep Eye water MCHP",
|
|
122
|
+
"40.0",
|
|
123
|
+
"16.0",
|
|
124
|
+
"3.5973333333333333",
|
|
125
|
+
"24.0",
|
|
126
|
+
"4.850913859925324",
|
|
127
|
+
"43.14908614007467"
|
|
128
|
+
],
|
|
129
|
+
[
|
|
130
|
+
"ANC 2nd visit",
|
|
131
|
+
"Lion for Lion Clinic",
|
|
132
|
+
"30.0",
|
|
133
|
+
"16.0",
|
|
134
|
+
"3.5973333333333333",
|
|
135
|
+
"14.0",
|
|
136
|
+
"-5.620896815302167",
|
|
137
|
+
"33.62089681530217"
|
|
138
|
+
],
|
|
139
|
+
[
|
|
140
|
+
"ANC 2nd visit",
|
|
141
|
+
"Deep Eye water MCHP",
|
|
142
|
+
"33.0",
|
|
143
|
+
"14.5",
|
|
144
|
+
"3.9121",
|
|
145
|
+
"18.5",
|
|
146
|
+
"-3.4245866551686603",
|
|
147
|
+
"40.42458665516866"
|
|
148
|
+
],
|
|
149
|
+
[
|
|
150
|
+
"ANC 2nd visit",
|
|
151
|
+
"Blessed Mokaka East Clinic",
|
|
152
|
+
"2.0",
|
|
153
|
+
"13.0",
|
|
154
|
+
"4.38425",
|
|
155
|
+
"15.0",
|
|
156
|
+
"-3.417043736713012",
|
|
157
|
+
"33.41704373671301"
|
|
158
|
+
],
|
|
159
|
+
[
|
|
160
|
+
"ANC 2nd visit",
|
|
161
|
+
"Malambay CHP",
|
|
162
|
+
"15.0",
|
|
163
|
+
"12.0",
|
|
164
|
+
"5.396",
|
|
165
|
+
"3.0",
|
|
166
|
+
"-13.770509831248425",
|
|
167
|
+
"19.770509831248425"
|
|
168
|
+
],
|
|
169
|
+
[
|
|
170
|
+
"ANC 2nd visit",
|
|
171
|
+
"Wellbody MCHP",
|
|
172
|
+
"20.0",
|
|
173
|
+
"10.0",
|
|
174
|
+
"3.3725",
|
|
175
|
+
"10.0",
|
|
176
|
+
"-10.999999999999996",
|
|
177
|
+
"30.999999999999996"
|
|
178
|
+
],
|
|
179
|
+
[
|
|
180
|
+
"ANC 1st visit",
|
|
181
|
+
"Blessed Mokaka East Clinic",
|
|
182
|
+
"26.0",
|
|
183
|
+
"10.0",
|
|
184
|
+
"4.496666666666667",
|
|
185
|
+
"16.0",
|
|
186
|
+
"1.6734512181055958",
|
|
187
|
+
"30.326548781894402"
|
|
188
|
+
],
|
|
189
|
+
[
|
|
190
|
+
"ANC 1st visit",
|
|
191
|
+
"Murray Town CHC",
|
|
192
|
+
"18.0",
|
|
193
|
+
"9.0",
|
|
194
|
+
"6.0705",
|
|
195
|
+
"9.0",
|
|
196
|
+
"-2.43571205496543",
|
|
197
|
+
"20.43571205496543"
|
|
198
|
+
],
|
|
199
|
+
[
|
|
200
|
+
"ANC 2nd visit",
|
|
201
|
+
"Thompson Bay MCHP",
|
|
202
|
+
"11.0",
|
|
203
|
+
"6.0",
|
|
204
|
+
"4.047",
|
|
205
|
+
"5.0",
|
|
206
|
+
"-2.038266127580332",
|
|
207
|
+
"12.038266127580332"
|
|
208
|
+
]
|
|
209
|
+
],
|
|
210
|
+
"headerWidth": 8,
|
|
211
|
+
"width": 8,
|
|
212
|
+
"height": 13
|
|
213
|
+
}
|
|
@@ -68,11 +68,37 @@ class AnalyticsAggregate extends AnalyticsBase {
|
|
|
68
68
|
* .withStartDate('2017-10-01')
|
|
69
69
|
* .withEndDate('2017-10-31');
|
|
70
70
|
*
|
|
71
|
-
* analytics.aggregate.getDebugSql(req)
|
|
71
|
+
* analytics.aggregate.getDebugSql(req)
|
|
72
72
|
* .then(console.log);
|
|
73
73
|
*/
|
|
74
74
|
getDebugSql(req) {
|
|
75
75
|
return this.fetch(req.withPath('debug/sql'));
|
|
76
76
|
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @param {!AnalyticsRequest} req Request object
|
|
80
|
+
*
|
|
81
|
+
* @returns {Promise} Promise that resolves with the SQL statement used to query the database.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* const req = new analytics.request()
|
|
85
|
+
* .withParameters({
|
|
86
|
+
* dx: 'fbfJHSPpUQD,cYeuwXTCPkU',
|
|
87
|
+
* pe: 'THIS_YEAR',
|
|
88
|
+
* ou: 'USER_ORGUNIT,USER_ORGUNIT_CHILDREN',
|
|
89
|
+
* headers: 'dxname,pename,ouname,value,absdev,modifiedzscore,median,lowerbound,upperbound',
|
|
90
|
+
* algorithm: 'MODIFIED_Z_SCORE',
|
|
91
|
+
* maxResults: 100,
|
|
92
|
+
* threshold: 3,
|
|
93
|
+
orderBy: 'value',
|
|
94
|
+
sortOrder: 'desc',
|
|
95
|
+
* });
|
|
96
|
+
*
|
|
97
|
+
* analytics.aggregate.getOutliersData(req)
|
|
98
|
+
* .then(console.log);
|
|
99
|
+
*/
|
|
100
|
+
getOutliersData(req) {
|
|
101
|
+
return this.fetch(req.withPath('outlierDetection'));
|
|
102
|
+
}
|
|
77
103
|
}
|
|
78
104
|
export default AnalyticsAggregate;
|
|
@@ -16,8 +16,8 @@ const analyticsQuery = {
|
|
|
16
16
|
parameters
|
|
17
17
|
} = _ref2;
|
|
18
18
|
return {
|
|
19
|
-
dimension: dimensions,
|
|
20
|
-
filter: filters,
|
|
19
|
+
dimension: dimensions.length ? dimensions : undefined,
|
|
20
|
+
filter: filters.length ? filters : undefined,
|
|
21
21
|
...parameters
|
|
22
22
|
};
|
|
23
23
|
}
|
|
@@ -38,8 +38,8 @@ const analyticsDataQuery = {
|
|
|
38
38
|
parameters
|
|
39
39
|
} = _ref4;
|
|
40
40
|
return {
|
|
41
|
-
dimension: dimensions,
|
|
42
|
-
filter: filters,
|
|
41
|
+
dimension: dimensions.length ? dimensions : undefined,
|
|
42
|
+
filter: filters.length ? filters : undefined,
|
|
43
43
|
...parameters,
|
|
44
44
|
skipMeta: true,
|
|
45
45
|
skipData: false
|
|
@@ -62,8 +62,8 @@ const analyticsMetaDataQuery = {
|
|
|
62
62
|
parameters
|
|
63
63
|
} = _ref6;
|
|
64
64
|
return {
|
|
65
|
-
dimension: dimensions,
|
|
66
|
-
filter: filters,
|
|
65
|
+
dimension: dimensions.length ? dimensions : undefined,
|
|
66
|
+
filter: filters.length ? filters : undefined,
|
|
67
67
|
...parameters,
|
|
68
68
|
skipMeta: false,
|
|
69
69
|
skipData: true,
|
|
@@ -71,7 +71,7 @@ const analyticsMetaDataQuery = {
|
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
73
|
};
|
|
74
|
-
const generateDimensionStrings = function () {
|
|
74
|
+
export const generateDimensionStrings = function () {
|
|
75
75
|
let dimensions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
76
76
|
let options = arguments.length > 1 ? arguments[1] : undefined;
|
|
77
77
|
if (options && options.sorted) {
|
|
@@ -47,7 +47,7 @@ class AnalyticsRequestBase {
|
|
|
47
47
|
buildUrl(options) {
|
|
48
48
|
// at least 1 dimension is required
|
|
49
49
|
let dimensions = this.dimensions;
|
|
50
|
-
if (options && options.sorted) {
|
|
50
|
+
if (dimensions.length && options !== null && options !== void 0 && options.sorted) {
|
|
51
51
|
dimensions = sortBy(dimensions, 'dimension');
|
|
52
52
|
}
|
|
53
53
|
const encodedDimensions = dimensions.map(_ref => {
|
|
@@ -57,7 +57,7 @@ class AnalyticsRequestBase {
|
|
|
57
57
|
} = _ref;
|
|
58
58
|
if (Array.isArray(items) && items.length) {
|
|
59
59
|
const encodedItems = items.map(customEncodeURIComponent);
|
|
60
|
-
if (options && options.sorted) {
|
|
60
|
+
if (options !== null && options !== void 0 && options.sorted) {
|
|
61
61
|
encodedItems.sort();
|
|
62
62
|
}
|
|
63
63
|
return `${dimension}:${encodedItems.join(';')}`;
|
|
@@ -65,7 +65,11 @@ class AnalyticsRequestBase {
|
|
|
65
65
|
return dimension;
|
|
66
66
|
});
|
|
67
67
|
const endPoint = [this.endPoint, this.path, this.program].filter(e => !!e).join('/');
|
|
68
|
-
|
|
68
|
+
let url = `${endPoint}.${this.format}`;
|
|
69
|
+
if (encodedDimensions.length) {
|
|
70
|
+
url += `?dimension=${encodedDimensions.join('&dimension=')}`;
|
|
71
|
+
}
|
|
72
|
+
return url;
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
/**
|
|
@@ -83,7 +87,7 @@ class AnalyticsRequestBase {
|
|
|
83
87
|
*/
|
|
84
88
|
buildQuery(options) {
|
|
85
89
|
let filters = this.filters;
|
|
86
|
-
if (options && options.sorted) {
|
|
90
|
+
if (filters.length && options !== null && options !== void 0 && options.sorted) {
|
|
87
91
|
filters = sortBy(filters, 'dimension');
|
|
88
92
|
}
|
|
89
93
|
const encodedFilters = filters.map(_ref2 => {
|
|
@@ -93,7 +97,7 @@ class AnalyticsRequestBase {
|
|
|
93
97
|
} = _ref2;
|
|
94
98
|
if (Array.isArray(items) && items.length) {
|
|
95
99
|
const encodedItems = items.map(customEncodeURIComponent);
|
|
96
|
-
if (options && options.sorted) {
|
|
100
|
+
if (options !== null && options !== void 0 && options.sorted) {
|
|
97
101
|
encodedItems.sort();
|
|
98
102
|
}
|
|
99
103
|
return `${dimension}:${encodedItems.join(';')}`;
|
|
@@ -51,7 +51,8 @@ class AnalyticsResponse {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
extractHeaders() {
|
|
54
|
-
|
|
54
|
+
// some endpoints (ie. outlierDetection) don't return dimensions in metaData
|
|
55
|
+
const dimensions = this.response.metaData.dimensions || {};
|
|
55
56
|
const headers = this.response.headers || [];
|
|
56
57
|
return headers.map((header, index) => new AnalyticsResponseHeader(header, {
|
|
57
58
|
isPrefix: isPrefixHeader(header, dimensions[header.name]),
|
|
@@ -87,48 +88,50 @@ class AnalyticsResponse {
|
|
|
87
88
|
items
|
|
88
89
|
} = metaData;
|
|
89
90
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
// some endpoints (ie. outlierDetection) don't return dimensions or items
|
|
92
|
+
if (dimensions && items) {
|
|
93
|
+
this.headers.filter(header => !DEFAULT_COLLECT_IGNORE_HEADERS.includes(header.name)).forEach(header => {
|
|
94
|
+
let ids;
|
|
93
95
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// create items
|
|
106
|
-
dimensions[header.name].forEach((prefixedId, index) => {
|
|
107
|
-
const id = ids[index];
|
|
108
|
-
const valueType = header.valueType;
|
|
109
|
-
const name = getNameByIdsByValueType(id, valueType);
|
|
110
|
-
items[prefixedId] = {
|
|
111
|
-
name
|
|
112
|
-
};
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
});
|
|
96
|
+
// collect row values
|
|
97
|
+
if (header.isCollect) {
|
|
98
|
+
ids = this.getSortedUniqueRowIdStringsByHeader(header);
|
|
99
|
+
dimensions[header.name] = ids;
|
|
100
|
+
} else {
|
|
101
|
+
ids = dimensions[header.name];
|
|
102
|
+
}
|
|
103
|
+
if (header.isPrefix) {
|
|
104
|
+
// create prefixed dimensions array
|
|
105
|
+
dimensions[header.name] = ids.map(id => getPrefixedId(id, header.name));
|
|
116
106
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
ouName = row[ouNameHeaderIndex];
|
|
127
|
-
items[ouId] = {
|
|
128
|
-
name: ouName
|
|
129
|
-
};
|
|
107
|
+
// create items
|
|
108
|
+
dimensions[header.name].forEach((prefixedId, index) => {
|
|
109
|
+
const id = ids[index];
|
|
110
|
+
const valueType = header.valueType;
|
|
111
|
+
const name = getNameByIdsByValueType(id, valueType);
|
|
112
|
+
items[prefixedId] = {
|
|
113
|
+
name
|
|
114
|
+
};
|
|
115
|
+
});
|
|
130
116
|
}
|
|
131
117
|
});
|
|
118
|
+
|
|
119
|
+
// for events, add items from 'ouname'
|
|
120
|
+
if (this.hasHeader(OUNAME) && this.hasHeader(OU)) {
|
|
121
|
+
const ouNameHeaderIndex = this.getHeader(OUNAME).getIndex();
|
|
122
|
+
const ouHeaderIndex = this.getHeader(OU).getIndex();
|
|
123
|
+
let ouId;
|
|
124
|
+
let ouName;
|
|
125
|
+
this.rows.forEach(row => {
|
|
126
|
+
ouId = row[ouHeaderIndex];
|
|
127
|
+
if (items[ouId] === undefined) {
|
|
128
|
+
ouName = row[ouNameHeaderIndex];
|
|
129
|
+
items[ouId] = {
|
|
130
|
+
name: ouName
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
132
135
|
}
|
|
133
136
|
return metaData;
|
|
134
137
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Analytics from '../Analytics.js';
|
|
2
2
|
import AnalyticsAggregate from '../AnalyticsAggregate.js';
|
|
3
|
+
import AnalyticsEnrollments from '../AnalyticsEnrollments.js';
|
|
3
4
|
import AnalyticsEvents from '../AnalyticsEvents.js';
|
|
4
5
|
import AnalyticsRequest from '../AnalyticsRequest.js';
|
|
5
6
|
describe('Analytics', () => {
|
|
@@ -7,6 +8,7 @@ describe('Analytics', () => {
|
|
|
7
8
|
beforeEach(() => {
|
|
8
9
|
analytics = new Analytics({
|
|
9
10
|
aggregate: new AnalyticsAggregate(),
|
|
11
|
+
enrollments: new AnalyticsEnrollments(),
|
|
10
12
|
events: new AnalyticsEvents(),
|
|
11
13
|
request: AnalyticsRequest
|
|
12
14
|
});
|
|
@@ -20,6 +22,9 @@ describe('Analytics', () => {
|
|
|
20
22
|
it('should contain an instance of AnalyticsAggregate', () => {
|
|
21
23
|
expect(analytics.aggregate).toBeInstanceOf(AnalyticsAggregate);
|
|
22
24
|
});
|
|
25
|
+
it('should contain an instance of AnalyticsEnrollments', () => {
|
|
26
|
+
expect(analytics.enrollments).toBeInstanceOf(AnalyticsEnrollments);
|
|
27
|
+
});
|
|
23
28
|
it('should contain an instance of AnalyticsEvents', () => {
|
|
24
29
|
expect(analytics.events).toBeInstanceOf(AnalyticsEvents);
|
|
25
30
|
});
|
|
@@ -84,4 +84,33 @@ describe('Analytics.aggregate', () => {
|
|
|
84
84
|
expect(data.height).toEqual(0);
|
|
85
85
|
}));
|
|
86
86
|
});
|
|
87
|
+
describe('.getOutliersData', () => {
|
|
88
|
+
beforeEach(() => {
|
|
89
|
+
aggregate = new AnalyticsAggregate(new DataEngineMock());
|
|
90
|
+
request = new AnalyticsRequest();
|
|
91
|
+
request.withParameters({
|
|
92
|
+
dx: 'fbfJHSPpUQD,cYeuwXTCPkU',
|
|
93
|
+
pe: 'THIS_YEAR',
|
|
94
|
+
ou: 'at6UHUQatSo',
|
|
95
|
+
headers: 'dxname,pename,ouname,value,absdev,modifiedzscore,median,lowerbound,upperbound',
|
|
96
|
+
algorithm: 'MOD_Z_SCORE',
|
|
97
|
+
maxResults: 100,
|
|
98
|
+
threshold: 3
|
|
99
|
+
});
|
|
100
|
+
fixture = fixtures.get('/api/analytics/outlierDetection');
|
|
101
|
+
dataEngineMock.query.mockReturnValue(Promise.resolve({
|
|
102
|
+
data: fixture
|
|
103
|
+
}));
|
|
104
|
+
});
|
|
105
|
+
it('should be a function', () => {
|
|
106
|
+
expect(aggregate.getOutliersData).toBeInstanceOf(Function);
|
|
107
|
+
});
|
|
108
|
+
it('should resolve a promise with data', () => aggregate.getOutliersData(request).then(data => {
|
|
109
|
+
expect(data.metaData.items).toEqual(fixture.metaData.items);
|
|
110
|
+
expect(data.metaData.dimensions).toEqual(fixture.metaData.dimensions);
|
|
111
|
+
expect(data.headers).toEqual(fixture.headers);
|
|
112
|
+
expect(data.width).toEqual(8);
|
|
113
|
+
expect(data.height).toEqual(13);
|
|
114
|
+
}));
|
|
115
|
+
});
|
|
87
116
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import AnalyticsBase from '../AnalyticsBase.js';
|
|
1
|
+
import AnalyticsBase, { generateDimensionStrings } from '../AnalyticsBase.js';
|
|
2
2
|
let base;
|
|
3
3
|
describe('constructor', () => {
|
|
4
4
|
beforeEach(() => {
|
|
@@ -12,4 +12,37 @@ describe('constructor', () => {
|
|
|
12
12
|
base = new AnalyticsBase(dataEngineMock);
|
|
13
13
|
expect(base.dataEngine).toBe(dataEngineMock);
|
|
14
14
|
});
|
|
15
|
+
});
|
|
16
|
+
describe('generateDimensionString', () => {
|
|
17
|
+
const tests = [{
|
|
18
|
+
input: [{
|
|
19
|
+
dimension: 'dim2',
|
|
20
|
+
items: ['item2', 'item1']
|
|
21
|
+
}, {
|
|
22
|
+
dimension: 'dim1',
|
|
23
|
+
items: ['item1']
|
|
24
|
+
}],
|
|
25
|
+
output: ['dim2:item2;item1', 'dim1:item1'],
|
|
26
|
+
outputSorted: ['dim1:item1', 'dim2:item1;item2']
|
|
27
|
+
}];
|
|
28
|
+
it('should return dimension strings correctly formatted', () => {
|
|
29
|
+
tests.forEach(_ref => {
|
|
30
|
+
let {
|
|
31
|
+
input,
|
|
32
|
+
output
|
|
33
|
+
} = _ref;
|
|
34
|
+
expect(generateDimensionStrings(input)).toEqual(output);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
it('should return dimension strings correctly formatted and sorted', () => {
|
|
38
|
+
tests.forEach(_ref2 => {
|
|
39
|
+
let {
|
|
40
|
+
input,
|
|
41
|
+
outputSorted
|
|
42
|
+
} = _ref2;
|
|
43
|
+
expect(generateDimensionStrings(input, {
|
|
44
|
+
sorted: true
|
|
45
|
+
})).toEqual(outputSorted);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
15
48
|
});
|
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
import { useConfig } from '@dhis2/app-runtime';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import React from 'react';
|
|
3
|
+
import React, { createContext, useContext, useCallback, useEffect, useState } from 'react';
|
|
4
|
+
import { dataTypeMap, DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM } from '../../modules/dataTypes.js';
|
|
4
5
|
import { DIMENSION_ID_DATA } from '../../modules/predefinedDimensions.js';
|
|
5
6
|
import ItemSelector from './ItemSelector.js';
|
|
7
|
+
const DataDimensionCtx = /*#__PURE__*/createContext({});
|
|
6
8
|
const DataDimension = _ref => {
|
|
7
9
|
let {
|
|
8
10
|
onSelect,
|
|
9
11
|
selectedDimensions,
|
|
10
12
|
displayNameProp,
|
|
13
|
+
enabledDataTypes,
|
|
11
14
|
infoBoxMessage,
|
|
12
|
-
onCalculationSave
|
|
15
|
+
onCalculationSave,
|
|
16
|
+
visType
|
|
13
17
|
} = _ref;
|
|
14
18
|
const {
|
|
15
19
|
serverVersion
|
|
16
20
|
} = useConfig();
|
|
17
|
-
const
|
|
21
|
+
const filterDataTypesByVersion = useCallback(dataTypes => dataTypes.filter(_ref2 => {
|
|
22
|
+
let {
|
|
23
|
+
id
|
|
24
|
+
} = _ref2;
|
|
25
|
+
return (
|
|
26
|
+
// Calculations only available from 2.40
|
|
27
|
+
id !== DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM || serverVersion.minor >= 40
|
|
28
|
+
);
|
|
29
|
+
}), [serverVersion.minor]);
|
|
30
|
+
const [dataTypes, setDataTypes] = useState(filterDataTypesByVersion(enabledDataTypes || Object.values(dataTypeMap)));
|
|
18
31
|
const onSelectItems = selectedItem => onSelect({
|
|
19
32
|
dimensionId: DIMENSION_ID_DATA,
|
|
20
33
|
items: selectedItem.map(item => ({
|
|
@@ -24,7 +37,12 @@ const DataDimension = _ref => {
|
|
|
24
37
|
expression: item.expression
|
|
25
38
|
}))
|
|
26
39
|
});
|
|
27
|
-
|
|
40
|
+
useEffect(() => enabledDataTypes && setDataTypes(filterDataTypesByVersion(enabledDataTypes)), [enabledDataTypes, filterDataTypesByVersion]);
|
|
41
|
+
return /*#__PURE__*/React.createElement(DataDimensionCtx.Provider, {
|
|
42
|
+
value: {
|
|
43
|
+
visType
|
|
44
|
+
}
|
|
45
|
+
}, /*#__PURE__*/React.createElement(ItemSelector, {
|
|
28
46
|
selectedItems: selectedDimensions.map(item => ({
|
|
29
47
|
value: item.id,
|
|
30
48
|
label: item.name,
|
|
@@ -37,9 +55,9 @@ const DataDimension = _ref => {
|
|
|
37
55
|
displayNameProp: displayNameProp,
|
|
38
56
|
infoBoxMessage: infoBoxMessage,
|
|
39
57
|
dataTest: 'data-dimension',
|
|
40
|
-
|
|
58
|
+
dataTypes: dataTypes,
|
|
41
59
|
onEDISave: onCalculationSave
|
|
42
|
-
});
|
|
60
|
+
}));
|
|
43
61
|
};
|
|
44
62
|
DataDimension.propTypes = {
|
|
45
63
|
displayNameProp: PropTypes.string.isRequired,
|
|
@@ -51,11 +69,14 @@ DataDimension.propTypes = {
|
|
|
51
69
|
type: PropTypes.string
|
|
52
70
|
})).isRequired,
|
|
53
71
|
onSelect: PropTypes.func.isRequired,
|
|
72
|
+
enabledDataTypes: PropTypes.array,
|
|
54
73
|
infoBoxMessage: PropTypes.string,
|
|
74
|
+
visType: PropTypes.string,
|
|
55
75
|
onCalculationSave: PropTypes.func
|
|
56
76
|
};
|
|
57
77
|
DataDimension.defaultProps = {
|
|
58
78
|
selectedDimensions: [],
|
|
59
79
|
onSelect: Function.prototype
|
|
60
80
|
};
|
|
81
|
+
export const useDataDimensionContext = () => useContext(DataDimensionCtx);
|
|
61
82
|
export default DataDimension;
|
|
@@ -3,22 +3,43 @@ import { SingleSelectField, SingleSelectOption } from '@dhis2/ui';
|
|
|
3
3
|
import PropTypes from 'prop-types';
|
|
4
4
|
import React from 'react';
|
|
5
5
|
import i18n from '../../locales/index.js';
|
|
6
|
-
import { DIMENSION_TYPE_ALL, dataTypeMap
|
|
6
|
+
import { DIMENSION_TYPE_ALL, dataTypeMap } from '../../modules/dataTypes.js';
|
|
7
|
+
import { getDisplayNameByVisType } from '../../modules/visTypes.js';
|
|
8
|
+
import { useDataDimensionContext } from './DataDimension.js';
|
|
7
9
|
import styles from './styles/DataTypeSelector.style.js';
|
|
8
10
|
const DataTypeSelector = _ref => {
|
|
9
|
-
var _dataTypes$currentDat;
|
|
10
11
|
let {
|
|
11
12
|
currentDataType,
|
|
13
|
+
dataTypes,
|
|
12
14
|
onChange,
|
|
13
|
-
dataTest
|
|
14
|
-
includeCalculations
|
|
15
|
+
dataTest
|
|
15
16
|
} = _ref;
|
|
17
|
+
const {
|
|
18
|
+
visType
|
|
19
|
+
} = useDataDimensionContext();
|
|
20
|
+
const label = i18n.t('Data Type');
|
|
16
21
|
return /*#__PURE__*/React.createElement("div", {
|
|
17
22
|
className: `jsx-${styles.__hash}` + " " + "container"
|
|
18
|
-
}, /*#__PURE__*/React.createElement(SingleSelectField, {
|
|
19
|
-
label:
|
|
23
|
+
}, dataTypes.length === 1 ? /*#__PURE__*/React.createElement(SingleSelectField, {
|
|
24
|
+
label: label,
|
|
20
25
|
dataTest: dataTest,
|
|
21
|
-
selected:
|
|
26
|
+
selected: dataTypes[0].id,
|
|
27
|
+
onChange: ref => onChange(ref.selected),
|
|
28
|
+
dense: true,
|
|
29
|
+
disabled: true,
|
|
30
|
+
helpText: visType ? i18n.t('Only {{dataType}} can be used in {{visType}}', {
|
|
31
|
+
dataType: dataTypeMap[dataTypes[0].id].getName(),
|
|
32
|
+
visType: getDisplayNameByVisType(visType)
|
|
33
|
+
}) : ''
|
|
34
|
+
}, dataTypes.map(type => /*#__PURE__*/React.createElement(SingleSelectOption, {
|
|
35
|
+
value: type.id,
|
|
36
|
+
key: type.id,
|
|
37
|
+
label: type.getName(),
|
|
38
|
+
dataTest: `${dataTest}-option-${type.id}`
|
|
39
|
+
}))) : /*#__PURE__*/React.createElement(SingleSelectField, {
|
|
40
|
+
label: label,
|
|
41
|
+
dataTest: dataTest,
|
|
42
|
+
selected: currentDataType || DIMENSION_TYPE_ALL,
|
|
22
43
|
onChange: ref => onChange(ref.selected),
|
|
23
44
|
dense: true
|
|
24
45
|
}, /*#__PURE__*/React.createElement(SingleSelectOption, {
|
|
@@ -26,7 +47,7 @@ const DataTypeSelector = _ref => {
|
|
|
26
47
|
key: DIMENSION_TYPE_ALL,
|
|
27
48
|
label: i18n.t('All types'),
|
|
28
49
|
dataTest: `${dataTest}-option-all`
|
|
29
|
-
}),
|
|
50
|
+
}), dataTypes.map(type => /*#__PURE__*/React.createElement(SingleSelectOption, {
|
|
30
51
|
value: type.id,
|
|
31
52
|
key: type.id,
|
|
32
53
|
label: type.getName(),
|
|
@@ -39,6 +60,6 @@ DataTypeSelector.propTypes = {
|
|
|
39
60
|
currentDataType: PropTypes.string.isRequired,
|
|
40
61
|
onChange: PropTypes.func.isRequired,
|
|
41
62
|
dataTest: PropTypes.string,
|
|
42
|
-
|
|
63
|
+
dataTypes: PropTypes.array
|
|
43
64
|
};
|
|
44
65
|
export default DataTypeSelector;
|