@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.
Files changed (63) hide show
  1. package/build/cjs/__fixtures__/fixtures.js +1 -0
  2. package/build/cjs/__fixtures__/json/api/analytics/outlierDetection.json +213 -0
  3. package/build/cjs/api/analytics/AnalyticsAggregate.js +27 -1
  4. package/build/cjs/api/analytics/AnalyticsBase.js +8 -7
  5. package/build/cjs/api/analytics/AnalyticsRequestBase.js +9 -5
  6. package/build/cjs/api/analytics/AnalyticsResponse.js +42 -39
  7. package/build/cjs/api/analytics/__tests__/Analytics.spec.js +5 -0
  8. package/build/cjs/api/analytics/__tests__/AnalyticsAggregate.spec.js +29 -0
  9. package/build/cjs/api/analytics/__tests__/AnalyticsBase.spec.js +36 -2
  10. package/build/cjs/components/DataDimension/DataDimension.js +31 -7
  11. package/build/cjs/components/DataDimension/DataTypeSelector.js +29 -8
  12. package/build/cjs/components/DataDimension/GroupSelector.js +7 -7
  13. package/build/cjs/components/DataDimension/ItemSelector.js +79 -61
  14. package/build/cjs/components/PeriodDimension/PeriodDimension.js +5 -2
  15. package/build/cjs/components/PeriodDimension/PeriodTransfer.js +64 -31
  16. package/build/cjs/components/PeriodDimension/__tests__/__snapshots__/PeriodDimension.spec.js.snap +1 -1
  17. package/build/cjs/components/PeriodDimension/__tests__/__snapshots__/PeriodSelector.spec.js.snap +1 -12
  18. package/build/cjs/components/VisTypeIcon.js +6 -1
  19. package/build/cjs/index.js +43 -1
  20. package/build/cjs/locales/en/translations.json +5 -1
  21. package/build/cjs/modules/__tests__/getAdaptedUiLayoutByType.spec.js +15 -0
  22. package/build/cjs/modules/axis.js +4 -0
  23. package/build/cjs/modules/getAdaptedUiLayoutByType.js +9 -0
  24. package/build/cjs/modules/layoutTypes.js +4 -2
  25. package/build/cjs/modules/layoutUiRules/__tests__/rules.spec.js +13 -1
  26. package/build/cjs/modules/layoutUiRules/index.js +12 -0
  27. package/build/cjs/modules/layoutUiRules/rules.js +22 -2
  28. package/build/cjs/modules/layoutUiRules/rulesHelper.js +4 -2
  29. package/build/cjs/modules/layoutUiRules/rulesUtils.js +7 -2
  30. package/build/cjs/modules/visTypeToLayoutType.js +2 -1
  31. package/build/cjs/modules/visTypes.js +9 -3
  32. package/build/es/__fixtures__/fixtures.js +1 -0
  33. package/build/es/__fixtures__/json/api/analytics/outlierDetection.json +213 -0
  34. package/build/es/api/analytics/AnalyticsAggregate.js +27 -1
  35. package/build/es/api/analytics/AnalyticsBase.js +7 -7
  36. package/build/es/api/analytics/AnalyticsRequestBase.js +9 -5
  37. package/build/es/api/analytics/AnalyticsResponse.js +42 -39
  38. package/build/es/api/analytics/__tests__/Analytics.spec.js +5 -0
  39. package/build/es/api/analytics/__tests__/AnalyticsAggregate.spec.js +29 -0
  40. package/build/es/api/analytics/__tests__/AnalyticsBase.spec.js +34 -1
  41. package/build/es/components/DataDimension/DataDimension.js +27 -6
  42. package/build/es/components/DataDimension/DataTypeSelector.js +30 -9
  43. package/build/es/components/DataDimension/GroupSelector.js +7 -7
  44. package/build/es/components/DataDimension/ItemSelector.js +80 -62
  45. package/build/es/components/PeriodDimension/PeriodDimension.js +5 -2
  46. package/build/es/components/PeriodDimension/PeriodTransfer.js +65 -32
  47. package/build/es/components/PeriodDimension/__tests__/__snapshots__/PeriodDimension.spec.js.snap +1 -1
  48. package/build/es/components/PeriodDimension/__tests__/__snapshots__/PeriodSelector.spec.js.snap +1 -12
  49. package/build/es/components/VisTypeIcon.js +8 -3
  50. package/build/es/index.js +4 -4
  51. package/build/es/locales/en/translations.json +5 -1
  52. package/build/es/modules/__tests__/getAdaptedUiLayoutByType.spec.js +16 -1
  53. package/build/es/modules/axis.js +5 -1
  54. package/build/es/modules/getAdaptedUiLayoutByType.js +10 -1
  55. package/build/es/modules/layoutTypes.js +2 -1
  56. package/build/es/modules/layoutUiRules/__tests__/rules.spec.js +14 -2
  57. package/build/es/modules/layoutUiRules/index.js +2 -2
  58. package/build/es/modules/layoutUiRules/rules.js +22 -3
  59. package/build/es/modules/layoutUiRules/rulesHelper.js +3 -2
  60. package/build/es/modules/layoutUiRules/rulesUtils.js +6 -2
  61. package/build/es/modules/visTypeToLayoutType.js +4 -3
  62. package/build/es/modules/visTypes.js +7 -3
  63. 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
- return `${endPoint}.${this.format}?dimension=${encodedDimensions.join('&dimension=')}`;
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
- const dimensions = this.response.metaData.dimensions;
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
- // populate metaData dimensions and items
91
- this.headers.filter(header => !DEFAULT_COLLECT_IGNORE_HEADERS.includes(header.name)).forEach(header => {
92
- let ids;
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
- // collect row values
95
- if (header.isCollect) {
96
- ids = this.getSortedUniqueRowIdStringsByHeader(header);
97
- dimensions[header.name] = ids;
98
- } else {
99
- ids = dimensions[header.name];
100
- }
101
- if (header.isPrefix) {
102
- // create prefixed dimensions array
103
- dimensions[header.name] = ids.map(id => getPrefixedId(id, header.name));
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
- // for events, add items from 'ouname'
118
- if (this.hasHeader(OUNAME) && this.hasHeader(OU)) {
119
- const ouNameHeaderIndex = this.getHeader(OUNAME).getIndex();
120
- const ouHeaderIndex = this.getHeader(OU).getIndex();
121
- let ouId;
122
- let ouName;
123
- this.rows.forEach(row => {
124
- ouId = row[ouHeaderIndex];
125
- if (items[ouId] === undefined) {
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 supportsEDI = `${serverVersion.major}.${serverVersion.minor}.${serverVersion.patch || 0}` >= '2.40.0';
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
- return /*#__PURE__*/React.createElement(ItemSelector, {
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
- supportsEDI: supportsEDI,
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 as dataTypes, DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM } from '../../modules/dataTypes.js';
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: i18n.t('Data Type'),
23
+ }, dataTypes.length === 1 ? /*#__PURE__*/React.createElement(SingleSelectField, {
24
+ label: label,
20
25
  dataTest: dataTest,
21
- selected: ((_dataTypes$currentDat = dataTypes[currentDataType]) === null || _dataTypes$currentDat === void 0 ? void 0 : _dataTypes$currentDat.id) || DIMENSION_TYPE_ALL,
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
- }), Object.values(dataTypes).filter(type => type.id !== DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM || includeCalculations).map(type => /*#__PURE__*/React.createElement(SingleSelectOption, {
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
- includeCalculations: PropTypes.bool
63
+ dataTypes: PropTypes.array
43
64
  };
44
65
  export default DataTypeSelector;